diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index cb8af2b1a3..0fec995f70 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -77,12 +77,12 @@ net.runelite.pushingpixels substance - 8.0.00-dev + 8.0.02 net.runelite.pushingpixels trident - 1.4 + 1.5.00 runtime diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java b/runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java index 6d840f754b..c8168b878c 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java @@ -41,6 +41,8 @@ public class RuneLiteProperties private static final String RUNESCAPE_VERSION = "runescape.version"; private static final String DISCORD_APP_ID = "runelite.discord.appid"; private static final String DISCORD_INVITE = "runelite.discord.invite"; + private static final String GITHUB_LINK = "runelite.github.link"; + private static final String PATREON_LINK = "runelite.patreon.link"; private final Properties properties = new Properties(); @@ -82,4 +84,14 @@ public class RuneLiteProperties { return properties.getProperty(DISCORD_INVITE); } -} + + public String getGithubLink() + { + return properties.getProperty(GITHUB_LINK); + } + + public String getPatreonLink() + { + return properties.getProperty(PATREON_LINK); + } +} \ No newline at end of file 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 61b1e8b105..0a0b91256e 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 @@ -37,7 +37,6 @@ 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.Comparator; import java.util.Map; @@ -61,6 +60,7 @@ import javax.swing.JTextField; import javax.swing.SpinnerModel; import javax.swing.SpinnerNumberModel; import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; @@ -75,16 +75,22 @@ import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.PluginInstantiationException; import net.runelite.client.plugins.PluginManager; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.DynamicGridLayout; import net.runelite.client.ui.PluginPanel; +import net.runelite.client.ui.components.ComboBoxListRenderer; +import net.runelite.client.ui.components.IconTextField; @Slf4j public class ConfigPanel extends PluginPanel { private static final int TEXT_FIELD_WIDTH = 7; private static final int SPINNER_FIELD_WIDTH = 6; - private static BufferedImage CONFIG_ICON; - private static BufferedImage UNCHECK_ICON; - private static BufferedImage CHECK_ICON; + + private static final ImageIcon CONFIG_ICON; + private static final ImageIcon ON_SWITCHER; + private static final ImageIcon OFF_SWITCHER; + private static final ImageIcon SEARCH; static { @@ -92,14 +98,15 @@ public class ConfigPanel extends PluginPanel { synchronized (ImageIO.class) { - CONFIG_ICON = ImageIO.read(ConfigPanel.class.getResourceAsStream("config_icon.png")); - UNCHECK_ICON = ImageIO.read(ConfigPanel.class.getResourceAsStream("disabled.png")); - CHECK_ICON = ImageIO.read(ConfigPanel.class.getResourceAsStream("enabled.png")); + CONFIG_ICON = new ImageIcon(ImageIO.read(ConfigPanel.class.getResourceAsStream("config_edit_icon.png"))); + ON_SWITCHER = new ImageIcon(ImageIO.read(ConfigPanel.class.getResourceAsStream("switchers/on.png"))); + OFF_SWITCHER = new ImageIcon(ImageIO.read(ConfigPanel.class.getResourceAsStream("switchers/off.png"))); + SEARCH = new ImageIcon(ImageIO.read(IconTextField.class.getResourceAsStream("search.png"))); } } catch (IOException e) { - log.warn("Failed to read icon", e); + throw new RuntimeException(e); } } @@ -107,7 +114,7 @@ public class ConfigPanel extends PluginPanel private final ConfigManager configManager; private final ScheduledExecutorService executorService; private final RuneLiteConfig runeLiteConfig; - private final JTextField searchBar = new JTextField(); + private final IconTextField searchBar = new IconTextField(); private Map children = new TreeMap<>(); private int scrollBarPosition = 0; @@ -119,6 +126,10 @@ public class ConfigPanel extends PluginPanel this.executorService = executorService; this.runeLiteConfig = runeLiteConfig; + searchBar.setIcon(SEARCH); + searchBar.setPreferredSize(new Dimension(100, 30)); + searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR); + searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); searchBar.getDocument().addDocumentListener(new DocumentListener() { @Override @@ -140,6 +151,10 @@ public class ConfigPanel extends PluginPanel } }); + setBorder(new EmptyBorder(10, 10, 10, 10)); + setLayout(new DynamicGridLayout(0, 1, 0, 5)); + setBackground(ColorScheme.DARK_GRAY_COLOR); + rebuildPluginList(); openConfigList(); } @@ -150,42 +165,54 @@ public class ConfigPanel extends PluginPanel Map newChildren = new TreeMap<>(); pluginManager.getPlugins().stream() - .filter(plugin -> !plugin.getClass().getAnnotation(PluginDescriptor.class).hidden()) - .sorted(Comparator.comparing(left -> left.getClass().getAnnotation(PluginDescriptor.class).name())) - .forEach(plugin -> - { - final Config pluginConfigProxy = pluginManager.getPluginConfigProxy(plugin); - final String pluginName = plugin.getClass().getAnnotation(PluginDescriptor.class).name(); + .filter(plugin -> !plugin.getClass().getAnnotation(PluginDescriptor.class).hidden()) + .sorted(Comparator.comparing(left -> left.getClass().getAnnotation(PluginDescriptor.class).name())) + .forEach(plugin -> + { + final Config pluginConfigProxy = pluginManager.getPluginConfigProxy(plugin); + final String pluginName = plugin.getClass().getAnnotation(PluginDescriptor.class).name(); - final JPanel groupPanel = buildGroupPanel(); - groupPanel.add(new JLabel(pluginName), BorderLayout.CENTER); + final JPanel groupPanel = buildGroupPanel(); - final JPanel buttonPanel = new JPanel(); - buttonPanel.setLayout(new GridLayout(1, 2, 3, 0)); - groupPanel.add(buttonPanel, BorderLayout.LINE_END); + JLabel name = new JLabel(pluginName); + name.setForeground(Color.WHITE); - final JButton editConfigButton = buildConfigButton(pluginConfigProxy); - buttonPanel.add(editConfigButton); + groupPanel.add(name, BorderLayout.CENTER); - final JButton toggleButton = buildToggleButton(plugin); - buttonPanel.add(toggleButton); + final JPanel buttonPanel = new JPanel(); + buttonPanel.setOpaque(false); + buttonPanel.setLayout(new GridLayout(1, 2)); + groupPanel.add(buttonPanel, BorderLayout.LINE_END); - newChildren.put(pluginName, groupPanel); - }); + final JLabel editConfigButton = buildConfigButton(pluginConfigProxy); + buttonPanel.add(editConfigButton); + final JLabel toggleButton = buildToggleButton(plugin); + toggleButton.setHorizontalAlignment(SwingConstants.RIGHT); + buttonPanel.add(toggleButton); + + newChildren.put(pluginName, groupPanel); + }); final JPanel groupPanel = buildGroupPanel(); - groupPanel.add(new JLabel("RuneLite"), BorderLayout.CENTER); + + JLabel name = new JLabel("RuneLite"); + name.setForeground(Color.WHITE); + + groupPanel.add(name, BorderLayout.CENTER); final JPanel buttonPanel = new JPanel(); - buttonPanel.setLayout(new GridLayout(1, 2, 3, 0)); + buttonPanel.setOpaque(false); + buttonPanel.setLayout(new GridLayout(1, 2)); groupPanel.add(buttonPanel, BorderLayout.LINE_END); - final JButton editConfigButton = buildConfigButton(runeLiteConfig); + final JLabel editConfigButton = buildConfigButton(runeLiteConfig); buttonPanel.add(editConfigButton); - final JButton toggleButton = buildToggleButton(null); + final JLabel toggleButton = buildToggleButton(null); + toggleButton.setVisible(false); buttonPanel.add(toggleButton); + newChildren.put("RuneLite", groupPanel); children = newChildren; @@ -197,15 +224,17 @@ public class ConfigPanel extends PluginPanel // Create base panel for the config button and enabled/disabled button final JPanel groupPanel = new JPanel(); groupPanel.setLayout(new BorderLayout(3, 0)); + groupPanel.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH, 20)); + groupPanel.setOpaque(false); return groupPanel; } - private JButton buildConfigButton(Config config) + private JLabel buildConfigButton(Config config) { // Create edit config button and disable it by default - final JButton editConfigButton = new JButton(new ImageIcon(CONFIG_ICON)); - editConfigButton.setPreferredSize(new Dimension(32, 0)); - editConfigButton.setEnabled(false); + final JLabel editConfigButton = new JLabel(CONFIG_ICON); + editConfigButton.setPreferredSize(new Dimension(25, 0)); + editConfigButton.setVisible(false); // If we have configuration proxy enable the button and add edit config listener if (config != null) @@ -215,8 +244,15 @@ public class ConfigPanel extends PluginPanel if (!configEmpty) { - editConfigButton.addActionListener(ae -> openGroupConfigPanel(config, configDescriptor, configManager)); - editConfigButton.setEnabled(true); + editConfigButton.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + openGroupConfigPanel(config, configDescriptor, configManager); + } + }); + editConfigButton.setVisible(true); editConfigButton.setToolTipText("Edit plugin configuration"); } } @@ -224,11 +260,11 @@ public class ConfigPanel extends PluginPanel return editConfigButton; } - private JButton buildToggleButton(Plugin plugin) + private JLabel buildToggleButton(Plugin plugin) { // Create enabling/disabling button - final JButton toggleButton = new JButton(new ImageIcon(CHECK_ICON)); - toggleButton.setPreferredSize(new Dimension(32, 0)); + final JLabel toggleButton = new JLabel(ON_SWITCHER); + toggleButton.setPreferredSize(new Dimension(25, 0)); if (plugin == null) { @@ -238,36 +274,43 @@ public class ConfigPanel extends PluginPanel highlightButton(toggleButton, pluginManager.isPluginEnabled(plugin)); - toggleButton.addActionListener(e -> executorService.submit(() -> + toggleButton.addMouseListener(new MouseAdapter() { - final boolean enabled = pluginManager.isPluginEnabled(plugin); - pluginManager.setPluginEnabled(plugin, !enabled); - - try + @Override + public void mousePressed(MouseEvent mouseEvent) { - if (enabled) + executorService.submit(() -> { - pluginManager.stopPlugin(plugin); - } - else - { - pluginManager.startPlugin(plugin); - } - } - catch (PluginInstantiationException ex) - { - log.warn("Error during starting/stopping plugin {}", plugin.getClass().getSimpleName(), ex); - } + final boolean enabled = pluginManager.isPluginEnabled(plugin); + pluginManager.setPluginEnabled(plugin, !enabled); - highlightButton(toggleButton, !enabled); - })); + try + { + if (enabled) + { + pluginManager.stopPlugin(plugin); + } + else + { + pluginManager.startPlugin(plugin); + } + } + catch (PluginInstantiationException ex) + { + log.warn("Error during starting/stopping plugin {}", plugin.getClass().getSimpleName(), ex); + } + + highlightButton(toggleButton, !enabled); + }); + } + }); return toggleButton; } - private void highlightButton(JButton button, boolean enabled) + private void highlightButton(JLabel button, boolean enabled) { - button.setIcon(enabled ? new ImageIcon(CHECK_ICON) : new ImageIcon(UNCHECK_ICON)); + button.setIcon(enabled ? ON_SWITCHER : OFF_SWITCHER); button.setToolTipText(enabled ? "Disable plugin" : "Enable plugin"); } @@ -301,7 +344,11 @@ public class ConfigPanel extends PluginPanel private void openConfigList() { removeAll(); - add(new JLabel("Plugin Configuration", SwingConstants.CENTER)); + + JLabel title = new JLabel("Configuration", SwingConstants.LEFT); + title.setForeground(Color.WHITE); + + add(title); add(searchBar); onSearchBarChanged(); @@ -331,6 +378,7 @@ public class ConfigPanel extends PluginPanel if (component instanceof JCheckBox) { JCheckBox checkbox = (JCheckBox) component; + checkbox.setOpaque(false); configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), "" + checkbox.isSelected()); } @@ -355,6 +403,8 @@ public class ConfigPanel extends PluginPanel if (component instanceof JComboBox) { JComboBox jComboBox = (JComboBox) component; + jComboBox.setRenderer(new ComboBoxListRenderer()); + jComboBox.setForeground(Color.WHITE); configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), ((Enum) jComboBox.getSelectedItem()).name()); } } @@ -376,6 +426,7 @@ public class ConfigPanel extends PluginPanel } JPanel item = new JPanel(); + item.setOpaque(false); item.setLayout(new BorderLayout()); name = cid.getItem().name(); JLabel configEntryName = new JLabel(name); @@ -385,6 +436,8 @@ public class ConfigPanel extends PluginPanel if (cid.getType() == boolean.class) { JCheckBox checkbox = new JCheckBox(); + checkbox.setOpaque(false); + checkbox.setBackground(ColorScheme.LIGHT_GRAY_COLOR); checkbox.setSelected(Boolean.parseBoolean(configManager.getConfiguration(cd.getGroup().keyName(), cid.getItem().keyName()))); checkbox.addActionListener(ae -> changeConfiguration(config, checkbox, cd, cid)); @@ -462,6 +515,7 @@ public class ConfigPanel extends PluginPanel if (cid.getType() == Dimension.class) { JPanel dimensionPanel = new JPanel(); + dimensionPanel.setOpaque(false); dimensionPanel.setLayout(new BorderLayout()); String str = configManager.getConfiguration(cd.getGroup().keyName(), cid.getItem().keyName()); @@ -482,7 +536,7 @@ public class ConfigPanel extends PluginPanel heightSpinnerTextField.setColumns(4); ChangeListener listener = e -> - configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), widthSpinner.getValue() + "x" + heightSpinner.getValue()); + configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), widthSpinner.getValue() + "x" + heightSpinner.getValue()); widthSpinner.addChangeListener(listener); heightSpinner.addChangeListener(listener); @@ -498,6 +552,9 @@ public class ConfigPanel extends PluginPanel { Class type = (Class) cid.getType(); JComboBox box = new JComboBox(type.getEnumConstants()); + box.setPreferredSize(new Dimension(box.getPreferredSize().width, 25)); + box.setRenderer(new ComboBoxListRenderer()); + box.setForeground(Color.WHITE); box.setFocusable(false); box.setPrototypeDisplayValue("XXXXXXXX"); //sorry but this is the way to keep the size of the combobox in check. try @@ -541,4 +598,4 @@ public class ConfigPanel extends PluginPanel revalidate(); getScrollPane().getVerticalScrollBar().setValue(0); } -} +} \ No newline at end of file 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 08a379875b..7a6cf9e7d8 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 @@ -78,6 +78,7 @@ public class ConfigPlugin extends Plugin navButton = NavigationButton.builder() .name("Configuration") .icon(icon) + .priority(0) .panel(configPanel) .build(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java index bbd126e943..e7aba556ea 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java @@ -32,6 +32,7 @@ import javax.swing.JButton; import javax.swing.JPanel; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.PluginPanel; @Slf4j @@ -52,6 +53,8 @@ public class DevToolsPanel extends PluginPanel this.plugin = plugin; this.widgetInspector = widgetInspector; + setBackground(ColorScheme.DARK_GRAY_COLOR); + varTracker = new VarTracker(client); add(createOptionsPanel()); } @@ -59,6 +62,7 @@ public class DevToolsPanel extends PluginPanel private JPanel createOptionsPanel() { final JPanel container = new JPanel(); + container.setBackground(ColorScheme.DARK_GRAY_COLOR); container.setLayout(new GridLayout(0, 2, 3, 3)); final JButton renderPlayersBtn = new JButton("Players"); 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 cebeccac04..9c0ebab009 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 @@ -122,6 +122,7 @@ public class DevToolsPlugin extends Plugin navButton = NavigationButton.builder() .name("Developer Tools") .icon(icon) + .priority(1) .panel(panel) .build(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/CropState.java b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/CropState.java index edfc15be1e..90bd66ff73 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/CropState.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/CropState.java @@ -27,15 +27,16 @@ package net.runelite.client.plugins.farmingtracker; import java.awt.Color; import lombok.Getter; import lombok.RequiredArgsConstructor; +import net.runelite.client.ui.ColorScheme; @RequiredArgsConstructor @Getter public enum CropState { - HARVESTABLE(Color.GREEN), - GROWING(Color.GREEN), - DISEASED(Color.ORANGE), - DEAD(Color.RED); + HARVESTABLE(ColorScheme.PROGRESS_COMPLETE_COLOR), + GROWING(ColorScheme.PROGRESS_COMPLETE_COLOR), + DISEASED(ColorScheme.PROGRESS_INPROGRESS_COLOR), + DEAD(ColorScheme.PROGRESS_ERROR_COLOR); private final Color color; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingPatchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingPatchPanel.java index c548236a6e..13d40259d0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingPatchPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingPatchPanel.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018 Abex + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,13 +25,19 @@ */ package net.runelite.client.plugins.farmingtracker; +import com.google.common.base.Strings; +import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Dimension; -import javax.swing.GroupLayout; +import java.awt.GridLayout; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JProgressBar; +import javax.swing.border.EmptyBorder; import lombok.Getter; +import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.components.ThinProgressBar; +import net.runelite.client.ui.components.shadowlabel.JShadowedLabel; @Getter class FarmingPatchPanel extends JPanel @@ -38,42 +45,43 @@ class FarmingPatchPanel extends JPanel private final FarmingPatch patch; private final JLabel icon = new JLabel(); private final JLabel estimate = new JLabel(); - private final JProgressBar progress = new JProgressBar(); + private final ThinProgressBar progress = new ThinProgressBar(); FarmingPatchPanel(FarmingPatch patch) { this.patch = patch; - GroupLayout layout = new GroupLayout(this); - this.setLayout(layout); + setLayout(new BorderLayout()); + setOpaque(false); + setBorder(new EmptyBorder(7, 0, 0, 0)); + + JPanel topContainer = new JPanel(); + topContainer.setBorder(new EmptyBorder(7, 7, 6, 0)); + topContainer.setLayout(new BorderLayout()); + topContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); - final JLabel location = new JLabel(patch.getRegion().getName() + " " + patch.getName()); - location.setFont(FontManager.getRunescapeSmallFont()); icon.setMinimumSize(new Dimension(36, 32)); - layout.setVerticalGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) - .addComponent(icon) - .addGroup(layout.createSequentialGroup() - .addGap(1) - .addComponent(location) - .addGap(1) - .addComponent(estimate) - ) - ) - .addComponent(progress, 8, 8, 8) - .addGap(4) - ); + JPanel infoPanel = new JPanel(); + infoPanel.setOpaque(false); + infoPanel.setLayout(new GridLayout(2, 1)); + infoPanel.setBorder(new EmptyBorder(4, 4, 4, 0)); - layout.setHorizontalGroup(layout.createParallelGroup() - .addGroup(layout.createSequentialGroup() - .addComponent(icon) - .addGroup(layout.createParallelGroup() - .addComponent(location) - .addComponent(estimate) - ) - ) - .addComponent(progress) - ); + final JLabel location = new JShadowedLabel(patch.getRegion().getName() + + (Strings.isNullOrEmpty(patch.getName()) ? "" : " (" + patch.getName() + ")")); + location.setFont(FontManager.getRunescapeSmallFont()); + location.setForeground(Color.WHITE); + + estimate.setFont(FontManager.getRunescapeSmallFont()); + estimate.setForeground(Color.GRAY); + + infoPanel.add(location); + infoPanel.add(estimate); + + topContainer.add(icon, BorderLayout.WEST); + topContainer.add(infoPanel, BorderLayout.CENTER); + + add(topContainer, BorderLayout.NORTH); + add(progress, BorderLayout.SOUTH); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPanel.java index 4968999ecb..0bd92d6838 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPanel.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018 Abex + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,11 +25,13 @@ */ package net.runelite.client.plugins.farmingtracker; +import com.google.common.base.Strings; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Image; +import java.awt.image.BufferedImage; import java.time.Instant; import java.time.LocalDateTime; import java.time.OffsetDateTime; @@ -37,9 +40,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; import javax.swing.ImageIcon; +import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; -import javax.swing.JTabbedPane; +import javax.swing.SwingConstants; import javax.swing.border.EmptyBorder; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; @@ -47,7 +51,11 @@ import net.runelite.api.vars.Autoweed; import net.runelite.client.config.ConfigManager; import net.runelite.client.game.AsyncBufferedImage; import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; import net.runelite.client.ui.PluginPanel; +import net.runelite.client.ui.components.materialtabs.MaterialTab; +import net.runelite.client.ui.components.materialtabs.MaterialTabGroup; @Slf4j class FarmingTrackerPanel extends PluginPanel @@ -61,6 +69,10 @@ class FarmingTrackerPanel extends PluginPanel private List patchPanels = new ArrayList<>(); + /* This is the panel the tabs' respective panels will be displayed on. */ + private final JPanel display = new JPanel(); + private final MaterialTabGroup tabGroup = new MaterialTabGroup(display); + FarmingTrackerPanel( Client client, ItemManager itemManager, @@ -77,11 +89,19 @@ class FarmingTrackerPanel extends PluginPanel this.config = config; setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); + + display.setOpaque(false); + display.setBorder(new EmptyBorder(10, 10, 8, 10)); + + tabGroup.setBorder(new EmptyBorder(10, 1, 0, 0)); + + add(tabGroup, BorderLayout.NORTH); + add(display, BorderLayout.CENTER); - JTabbedPane tabs = new JTabbedPane(); farmingWorld.getTabs().forEach((tab, patches) -> { - JPanel panel = new JPanel(new GridBagLayout()) + JPanel container = new JPanel(new GridBagLayout()) { @Override public Dimension getPreferredSize() @@ -89,7 +109,7 @@ class FarmingTrackerPanel extends PluginPanel return new Dimension(PluginPanel.PANEL_WIDTH, super.getPreferredSize().height); } }; - panel.setBorder(new EmptyBorder(2, 6, 6, 6)); + container.setBackground(ColorScheme.DARK_GRAY_COLOR); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.HORIZONTAL; @@ -97,43 +117,82 @@ class FarmingTrackerPanel extends PluginPanel c.gridx = 0; c.gridy = 0; + PatchImplementation lastImpl = null; + + boolean first = true; for (FarmingPatch patch : patches) { FarmingPatchPanel p = new FarmingPatchPanel(patch); + + /* Show labels to subdivide tabs into sections */ + if (patch.getImplementation() != lastImpl && !Strings.isNullOrEmpty(patch.getImplementation().getName())) + { + JLabel groupLabel = new JLabel(patch.getImplementation().getName()); + + if (first) + { + first = false; + groupLabel.setBorder(new EmptyBorder(4, 0, 0, 0)); + } + else + { + groupLabel.setBorder(new EmptyBorder(15, 0, 0, 0)); + } + + groupLabel.setFont(FontManager.getRunescapeSmallFont()); + + container.add(groupLabel, c); + c.gridy++; + lastImpl = patch.getImplementation(); + } + patchPanels.add(p); - panel.add(p, c); + container.add(p, c); c.gridy++; + + /* This is a weird hack to remove the top border on the first tracker of every tab */ + if (first) + { + first = false; + p.setBorder(null); + } } JPanel wrapped = new JPanel(new BorderLayout()); - wrapped.add(panel, BorderLayout.NORTH); + wrapped.add(container, BorderLayout.NORTH); + wrapped.setBackground(ColorScheme.DARK_GRAY_COLOR); + JScrollPane scroller = new JScrollPane(wrapped); scroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - scroller.getVerticalScrollBar().setUnitIncrement(16); + scroller.getVerticalScrollBar().setPreferredSize(new Dimension(16, 0)); + scroller.getVerticalScrollBar().setBorder(new EmptyBorder(0, 9, 0, 0)); + scroller.setBackground(ColorScheme.DARK_GRAY_COLOR); + + MaterialTab materialTab = new MaterialTab("", tabGroup, scroller); + materialTab.setName(tab.getName()); AsyncBufferedImage icon = itemManager.getImage(tab.getItemID()); - tabs.addTab(null, null, scroller, tab.getName()); - int idx = tabs.getTabCount() - 1; Runnable resize = () -> { - tabs.setIconAt(idx, new ImageIcon(icon.getScaledInstance(24, 21, Image.SCALE_SMOOTH))); + BufferedImage subIcon = icon.getSubimage(0, 0, 32, 32); + materialTab.setIcon(new ImageIcon(subIcon.getScaledInstance(24, 24, Image.SCALE_SMOOTH))); + materialTab.setHorizontalAlignment(SwingConstants.CENTER); + materialTab.setVerticalAlignment(SwingConstants.CENTER); + materialTab.setOpaque(true); + materialTab.setBackground(ColorScheme.DARKER_GRAY_COLOR); + materialTab.setPreferredSize(new Dimension(30, 27)); }; icon.onChanged(resize); resize.run(); + materialTab.setOnSelectEvent(() -> config.setPatch(tab)); + + tabGroup.addTab(materialTab); if (config.patch() == tab) { - tabs.setSelectedComponent(scroller); + tabGroup.select(materialTab); } - tabs.addChangeListener(e -> - { - if (tabs.getSelectedComponent() == scroller) - { - config.setPatch(tab); - } - }); }); - add(tabs, BorderLayout.CENTER); } void update() @@ -178,10 +237,11 @@ class FarmingTrackerPanel extends PluginPanel PatchState state = unixTime <= 0 ? null : patch.getImplementation().forVarbitValue(value); if (state == null) { - panel.getIcon().setIcon(null); + itemManager.getImage(Produce.WEEDS.getItemID()).addTo(panel.getIcon()); panel.getIcon().setToolTipText("Unknown state"); - panel.getProgress().setMaximum(0); + panel.getProgress().setMaximumValue(0); panel.getProgress().setValue(0); + panel.getProgress().setVisible(false); panel.getEstimate().setText("Unknown"); panel.getProgress().setBackground(null); } @@ -298,9 +358,20 @@ class FarmingTrackerPanel extends PluginPanel } } - panel.getProgress().setBackground(state.getCropState().getColor().darker()); - panel.getProgress().setMaximum(stages - 1); - panel.getProgress().setValue(stage); + /* Hide any fully grown weeds' progress bar. */ + if (state.getProduce() != Produce.WEEDS + || (state.getProduce() == Produce.WEEDS && !autoweed && stage < stages - 1)) + { + panel.getProgress().setVisible(true); + panel.getProgress().setForeground(state.getCropState().getColor().darker()); + panel.getProgress().setMaximumValue(stages - 1); + panel.getProgress().setValue(stage); + panel.getProgress().update(); + } + else + { + panel.getProgress().setVisible(false); + } } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPlugin.java index 5177308528..5af1974cf5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPlugin.java @@ -98,6 +98,7 @@ public class FarmingTrackerPlugin extends Plugin .name("Farming Tracker") .icon(icon) .panel(panel) + .priority(4) .build(); pluginToolbar.addNavigation(navButton); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingWorld.java b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingWorld.java index a79c3f746b..293b5826ef 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingWorld.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingWorld.java @@ -60,24 +60,24 @@ public class FarmingWorld )); add(new FarmingRegion("Ardougne", 10290, - new FarmingPatch("Bush", Varbits.FARMING_4771, PatchImplementation.BUSH) + new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.BUSH) )); add(new FarmingRegion("Ardougne", 10548, new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), - new FarmingPatch("Flower", Varbits.FARMING_4773, PatchImplementation.FLOWER), + new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), new FarmingPatch("", Varbits.FARMING_4774, PatchImplementation.HERB) )); add(new FarmingRegion("Brimhaven", 11058, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.FRUIT_TREE), - new FarmingPatch("Spirit Tree", Varbits.FARMING_4772, PatchImplementation.SPIRIT_TREE) + new FarmingPatch("", Varbits.FARMING_4772, PatchImplementation.SPIRIT_TREE) )); add(new FarmingRegion("Catherby", 11062, new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), - new FarmingPatch("Flower", Varbits.FARMING_4773, PatchImplementation.FLOWER), + new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), new FarmingPatch("", Varbits.FARMING_4774, PatchImplementation.HERB) )); add(new FarmingRegion("Catherby", 11317, @@ -85,20 +85,20 @@ public class FarmingWorld )); add(new FarmingRegion("Champion's Guild", 12596, - new FarmingPatch("Bush", Varbits.FARMING_4771, PatchImplementation.BUSH) + new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.BUSH) )); add(new FarmingRegion("Draynor Manor", 12340, - new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.BELLADONNA) + new FarmingPatch("Belladonna", Varbits.FARMING_4771, PatchImplementation.BELLADONNA) )); add(new FarmingRegion("Entrana", 11060, - new FarmingPatch("Hops", Varbits.FARMING_4771, PatchImplementation.HOPS) + new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.HOPS) )); add(new FarmingRegion("Etceteria", 10300, - new FarmingPatch("Bush", Varbits.FARMING_4771, PatchImplementation.BUSH), - new FarmingPatch("Spirit Tree", Varbits.FARMING_4772, PatchImplementation.SPIRIT_TREE) + new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.BUSH), + new FarmingPatch("", Varbits.FARMING_4772, PatchImplementation.SPIRIT_TREE) )); add(new FarmingRegion("Falador", 11828, @@ -107,7 +107,7 @@ public class FarmingWorld add(new FarmingRegion("Falador", 12083, new FarmingPatch("North West", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South East", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), - new FarmingPatch("Flower", Varbits.FARMING_4773, PatchImplementation.FLOWER), + new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), new FarmingPatch("", Varbits.FARMING_4774, PatchImplementation.HERB) ) { @@ -141,11 +141,11 @@ public class FarmingWorld add(new FarmingRegion("Kourend", 7222, new FarmingPatch("North East", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South West", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), - new FarmingPatch("Flower", Varbits.FARMING_4773, PatchImplementation.FLOWER), + new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), new FarmingPatch("", Varbits.FARMING_4774, PatchImplementation.HERB) )); add(new FarmingRegion("Kourend", 6711, - new FarmingPatch("Spirit Tree", Varbits.FARMING_4771, PatchImplementation.SPIRIT_TREE) + new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.SPIRIT_TREE) )); add(new FarmingRegion("Kourend", 7223, new FarmingPatch("West 1", Varbits.GRAPES_4953, PatchImplementation.GRAPES), @@ -167,7 +167,7 @@ public class FarmingWorld )); add(new FarmingRegion("Lumbridge", 12851, - new FarmingPatch("Hops", Varbits.FARMING_4771, PatchImplementation.HOPS) + new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.HOPS) )); add(new FarmingRegion("Lumbridge", 12594, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.TREE) @@ -179,17 +179,17 @@ public class FarmingWorld add(new FarmingRegion("Morytania", 14391, new FarmingPatch("North West", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South East", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), - new FarmingPatch("Flower", Varbits.FARMING_4773, PatchImplementation.FLOWER), + new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), new FarmingPatch("", Varbits.FARMING_4774, PatchImplementation.HERB) )); add(new FarmingRegion("Port Sarim", 12082, - new FarmingPatch("Spirit tree", Varbits.FARMING_4771, PatchImplementation.SPIRIT_TREE) + new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.SPIRIT_TREE) )); add(new FarmingRegion("Rimmington", 11570, - new FarmingPatch("Bush", Varbits.FARMING_4771, PatchImplementation.BUSH) + new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.BUSH) ), 11826); add(new FarmingRegion("Seers' Village", 10551, diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/PatchImplementation.java b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/PatchImplementation.java index 13ebe7c43c..468cafd7c9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/PatchImplementation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/PatchImplementation.java @@ -31,7 +31,7 @@ import lombok.RequiredArgsConstructor; @Getter public enum PatchImplementation { - CACTUS(Tab.SPECIAL) + CACTUS(Tab.SPECIAL, "") { @Override PatchState forVarbitValue(int value) @@ -84,7 +84,7 @@ public enum PatchImplementation return null; } }, - BELLADONNA(Tab.SPECIAL) + BELLADONNA(Tab.SPECIAL, "") { @Override PatchState forVarbitValue(int value) @@ -122,7 +122,7 @@ public enum PatchImplementation return null; } }, - MUSHROOM(Tab.SPECIAL) + MUSHROOM(Tab.SPECIAL, "") { @Override PatchState forVarbitValue(int value) @@ -160,7 +160,7 @@ public enum PatchImplementation return null; } }, - ALLOTMENT(Tab.ALLOTMENT) + ALLOTMENT(Tab.ALLOTMENT, "") { @Override PatchState forVarbitValue(int value) @@ -473,240 +473,7 @@ public enum PatchImplementation return null; } }, - FLOWER(Tab.SPECIAL) - { - @Override - PatchState forVarbitValue(int value) - { - if (value >= 0 && value <= 3) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843,7842,7841,7840 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3 - value); - } - if (value >= 4 && value <= 7) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843,7843,7843,7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 8 && value <= 11) - { - // Marigold[,Inspect,,Guide,] 7867,7868,7869,7870 - return new PatchState(Produce.MARIGOLD, CropState.GROWING, value - 8); - } - if (value == 12) - { - // Marigold[Pick,Inspect,,Guide,] 7871 - return new PatchState(Produce.MARIGOLD, CropState.HARVESTABLE, 12 - value); - } - if (value >= 13 && value <= 16) - { - // Rosemary[,Inspect,,Guide,] 7899,7900,7901,7902 - return new PatchState(Produce.ROSEMARY, CropState.GROWING, value - 13); - } - if (value == 17) - { - // Rosemary[Pick,Inspect,,Guide,] 7903 - return new PatchState(Produce.ROSEMARY, CropState.HARVESTABLE, 17 - value); - } - if (value >= 18 && value <= 21) - { - // Nasturtium[,Inspect,,Guide,] 7883,7884,7885,7886 - return new PatchState(Produce.NASTURTIUM, CropState.GROWING, value - 18); - } - if (value == 22) - { - // Nasturtium[Pick,Inspect,,Guide,] 7887 - return new PatchState(Produce.NASTURTIUM, CropState.HARVESTABLE, 22 - value); - } - if (value >= 23 && value <= 26) - { - // Woad[,Inspect,,Guide,] 7919,7920,7921,7922 - return new PatchState(Produce.WOAD, CropState.GROWING, value - 23); - } - if (value == 27) - { - // Woad[Pick,Inspect,,Guide,] 7923 - return new PatchState(Produce.WOAD, CropState.HARVESTABLE, 27 - value); - } - if (value >= 28 && value <= 31) - { - // Limpwurt[,Inspect,,Guide,] 7851,7852,7853,7854 - return new PatchState(Produce.LIMPWURT, CropState.GROWING, value - 28); - } - if (value == 32) - { - // Limpwurt[Pick,Inspect,,Guide,] 7855 - return new PatchState(Produce.LIMPWURT, CropState.HARVESTABLE, 32 - value); - } - if (value >= 33 && value <= 35) - { - // Scarecrow[Rake,Inspect,,Guide,Remove] 7918,7917,7916 - return new PatchState(Produce.SCARECROW, CropState.GROWING, 35 - value); - } - if (value == 36) - { - // Scarecrow[Remove,Inspect,,Guide,] 7915 - return new PatchState(Produce.SCARECROW, CropState.GROWING, 0); - } - if (value >= 37 && value <= 71) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 72 && value <= 75) - { - // Marigold[,Inspect,,Guide,] 7872,7873,7874,7875 - return new PatchState(Produce.MARIGOLD, CropState.GROWING, value - 72); - } - if (value == 76) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 77 && value <= 80) - { - // Rosemary[,Inspect,,Guide,] 7904,7905,7906,7907 - return new PatchState(Produce.ROSEMARY, CropState.GROWING, value - 77); - } - if (value == 81) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 82 && value <= 85) - { - // Nasturtium[,Inspect,,Guide,] 7888,7889,7890,7891 - return new PatchState(Produce.NASTURTIUM, CropState.GROWING, value - 82); - } - if (value == 86) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 87 && value <= 90) - { - // Woad[,Inspect,,Guide,] 7924,7925,7926,7927 - return new PatchState(Produce.WOAD, CropState.GROWING, value - 87); - } - if (value == 91) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 92 && value <= 95) - { - // Limpwurt[,Inspect,,Guide,] 7856,7857,7858,7859 - return new PatchState(Produce.LIMPWURT, CropState.GROWING, value - 92); - } - if (value >= 96 && value <= 136) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 137 && value <= 139) - { - // Diseased marigold[Cure,Inspect,,Guide,] 7876,7877,7878 - return new PatchState(Produce.MARIGOLD, CropState.DISEASED, value - 136); - } - if (value >= 140 && value <= 141) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843,7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 142 && value <= 144) - { - // Diseased rosemary[Cure,Inspect,,Guide,] 7908,7909,7910 - return new PatchState(Produce.ROSEMARY, CropState.DISEASED, value - 141); - } - if (value >= 145 && value <= 146) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843,7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 147 && value <= 149) - { - // Diseased nasturtium[Cure,Inspect,,Guide,] 7892,7893,7894 - return new PatchState(Produce.NASTURTIUM, CropState.DISEASED, value - 146); - } - if (value >= 150 && value <= 151) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843,7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 152 && value <= 154) - { - // Diseased woad[Cure,Inspect,,Guide,] 7928,7929,7930 - return new PatchState(Produce.WOAD, CropState.DISEASED, value - 151); - } - if (value >= 155 && value <= 156) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843,7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 157 && value <= 159) - { - // Diseased limpwurt[Cure,Inspect,,Guide,] 7860,7861,7862 - return new PatchState(Produce.LIMPWURT, CropState.DISEASED, value - 156); - } - if (value >= 160 && value <= 200) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 201 && value <= 203) - { - // Dead marigold[Clear,Inspect,,Guide,] 7879,7880,7881,7882 - return new PatchState(Produce.MARIGOLD, CropState.DEAD, value - 200); - } - if (value == 205) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 206 && value <= 208) - { - // Dead rosemary[Clear,Inspect,,Guide,] 7911,7912,7913,7914 - return new PatchState(Produce.ROSEMARY, CropState.DEAD, value - 205); - } - if (value == 210) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 211 && value <= 213) - { - // Dead nasturtium[Clear,Inspect,,Guide,] 7895,7896,7897,7898 - return new PatchState(Produce.NASTURTIUM, CropState.DEAD, value - 210); - } - if (value == 215) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 216 && value <= 218) - { - // Dead woad[Clear,Inspect,,Guide,] 7931,7932,7933,7934 - return new PatchState(Produce.WOAD, CropState.DEAD, value - 215); - } - if (value == 220) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - if (value >= 221 && value <= 224) - { - // Dead limpwurt[Clear,Inspect,,Guide,] 7863,7864,7865,7866 - return new PatchState(Produce.LIMPWURT, CropState.DEAD, value - 220); - } - if (value >= 225 && value <= 255) - { - // Flower Patch[Rake,Inspect,,Guide,] 7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843 - return new PatchState(Produce.WEEDS, CropState.GROWING, 3); - } - return null; - } - }, - HERB(Tab.HERB) + HERB(Tab.HERB, "") { @Override PatchState forVarbitValue(int value) @@ -970,7 +737,240 @@ public enum PatchImplementation return null; } }, - BUSH(Tab.SPECIAL) + FLOWER(Tab.HERB, "Flowers") + { + @Override + PatchState forVarbitValue(int value) + { + if (value >= 0 && value <= 3) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843,7842,7841,7840 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3 - value); + } + if (value >= 4 && value <= 7) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843,7843,7843,7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 8 && value <= 11) + { + // Marigold[,Inspect,,Guide,] 7867,7868,7869,7870 + return new PatchState(Produce.MARIGOLD, CropState.GROWING, value - 8); + } + if (value == 12) + { + // Marigold[Pick,Inspect,,Guide,] 7871 + return new PatchState(Produce.MARIGOLD, CropState.HARVESTABLE, 12 - value); + } + if (value >= 13 && value <= 16) + { + // Rosemary[,Inspect,,Guide,] 7899,7900,7901,7902 + return new PatchState(Produce.ROSEMARY, CropState.GROWING, value - 13); + } + if (value == 17) + { + // Rosemary[Pick,Inspect,,Guide,] 7903 + return new PatchState(Produce.ROSEMARY, CropState.HARVESTABLE, 17 - value); + } + if (value >= 18 && value <= 21) + { + // Nasturtium[,Inspect,,Guide,] 7883,7884,7885,7886 + return new PatchState(Produce.NASTURTIUM, CropState.GROWING, value - 18); + } + if (value == 22) + { + // Nasturtium[Pick,Inspect,,Guide,] 7887 + return new PatchState(Produce.NASTURTIUM, CropState.HARVESTABLE, 22 - value); + } + if (value >= 23 && value <= 26) + { + // Woad[,Inspect,,Guide,] 7919,7920,7921,7922 + return new PatchState(Produce.WOAD, CropState.GROWING, value - 23); + } + if (value == 27) + { + // Woad[Pick,Inspect,,Guide,] 7923 + return new PatchState(Produce.WOAD, CropState.HARVESTABLE, 27 - value); + } + if (value >= 28 && value <= 31) + { + // Limpwurt[,Inspect,,Guide,] 7851,7852,7853,7854 + return new PatchState(Produce.LIMPWURT, CropState.GROWING, value - 28); + } + if (value == 32) + { + // Limpwurt[Pick,Inspect,,Guide,] 7855 + return new PatchState(Produce.LIMPWURT, CropState.HARVESTABLE, 32 - value); + } + if (value >= 33 && value <= 35) + { + // Scarecrow[Rake,Inspect,,Guide,Remove] 7918,7917,7916 + return new PatchState(Produce.SCARECROW, CropState.GROWING, 35 - value); + } + if (value == 36) + { + // Scarecrow[Remove,Inspect,,Guide,] 7915 + return new PatchState(Produce.SCARECROW, CropState.GROWING, 0); + } + if (value >= 37 && value <= 71) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 72 && value <= 75) + { + // Marigold[,Inspect,,Guide,] 7872,7873,7874,7875 + return new PatchState(Produce.MARIGOLD, CropState.GROWING, value - 72); + } + if (value == 76) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 77 && value <= 80) + { + // Rosemary[,Inspect,,Guide,] 7904,7905,7906,7907 + return new PatchState(Produce.ROSEMARY, CropState.GROWING, value - 77); + } + if (value == 81) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 82 && value <= 85) + { + // Nasturtium[,Inspect,,Guide,] 7888,7889,7890,7891 + return new PatchState(Produce.NASTURTIUM, CropState.GROWING, value - 82); + } + if (value == 86) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 87 && value <= 90) + { + // Woad[,Inspect,,Guide,] 7924,7925,7926,7927 + return new PatchState(Produce.WOAD, CropState.GROWING, value - 87); + } + if (value == 91) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 92 && value <= 95) + { + // Limpwurt[,Inspect,,Guide,] 7856,7857,7858,7859 + return new PatchState(Produce.LIMPWURT, CropState.GROWING, value - 92); + } + if (value >= 96 && value <= 136) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 137 && value <= 139) + { + // Diseased marigold[Cure,Inspect,,Guide,] 7876,7877,7878 + return new PatchState(Produce.MARIGOLD, CropState.DISEASED, value - 136); + } + if (value >= 140 && value <= 141) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843,7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 142 && value <= 144) + { + // Diseased rosemary[Cure,Inspect,,Guide,] 7908,7909,7910 + return new PatchState(Produce.ROSEMARY, CropState.DISEASED, value - 141); + } + if (value >= 145 && value <= 146) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843,7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 147 && value <= 149) + { + // Diseased nasturtium[Cure,Inspect,,Guide,] 7892,7893,7894 + return new PatchState(Produce.NASTURTIUM, CropState.DISEASED, value - 146); + } + if (value >= 150 && value <= 151) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843,7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 152 && value <= 154) + { + // Diseased woad[Cure,Inspect,,Guide,] 7928,7929,7930 + return new PatchState(Produce.WOAD, CropState.DISEASED, value - 151); + } + if (value >= 155 && value <= 156) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843,7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 157 && value <= 159) + { + // Diseased limpwurt[Cure,Inspect,,Guide,] 7860,7861,7862 + return new PatchState(Produce.LIMPWURT, CropState.DISEASED, value - 156); + } + if (value >= 160 && value <= 200) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 201 && value <= 203) + { + // Dead marigold[Clear,Inspect,,Guide,] 7879,7880,7881,7882 + return new PatchState(Produce.MARIGOLD, CropState.DEAD, value - 200); + } + if (value == 205) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 206 && value <= 208) + { + // Dead rosemary[Clear,Inspect,,Guide,] 7911,7912,7913,7914 + return new PatchState(Produce.ROSEMARY, CropState.DEAD, value - 205); + } + if (value == 210) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 211 && value <= 213) + { + // Dead nasturtium[Clear,Inspect,,Guide,] 7895,7896,7897,7898 + return new PatchState(Produce.NASTURTIUM, CropState.DEAD, value - 210); + } + if (value == 215) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 216 && value <= 218) + { + // Dead woad[Clear,Inspect,,Guide,] 7931,7932,7933,7934 + return new PatchState(Produce.WOAD, CropState.DEAD, value - 215); + } + if (value == 220) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + if (value >= 221 && value <= 224) + { + // Dead limpwurt[Clear,Inspect,,Guide,] 7863,7864,7865,7866 + return new PatchState(Produce.LIMPWURT, CropState.DEAD, value - 220); + } + if (value >= 225 && value <= 255) + { + // Flower Patch[Rake,Inspect,,Guide,] 7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843,7843 + return new PatchState(Produce.WEEDS, CropState.GROWING, 3); + } + return null; + } + }, + BUSH(Tab.BUSH, "") { @Override PatchState forVarbitValue(int value) @@ -1209,7 +1209,7 @@ public enum PatchImplementation return null; } }, - FRUIT_TREE(Tab.FRUIT_TREE) + FRUIT_TREE(Tab.FRUIT_TREE, "Fruit trees") { @Override PatchState forVarbitValue(int value) @@ -1458,7 +1458,7 @@ public enum PatchImplementation return null; } }, - HOPS(Tab.SPECIAL) + HOPS(Tab.SPECIAL, "Hops") { @Override PatchState forVarbitValue(int value) @@ -1761,7 +1761,7 @@ public enum PatchImplementation return null; } }, - TREE(Tab.TREE) + TREE(Tab.TREE, "") { @Override PatchState forVarbitValue(int value) @@ -2049,7 +2049,7 @@ public enum PatchImplementation return null; } }, - HARDWOOD_TREE(Tab.TREE) + HARDWOOD_TREE(Tab.TREE, "Hardwood Trees") { @Override PatchState forVarbitValue(int value) @@ -2137,7 +2137,7 @@ public enum PatchImplementation return null; } }, - SPIRIT_TREE(Tab.TREE) + SPIRIT_TREE(Tab.TREE, "Spirit Trees") { @Override PatchState forVarbitValue(int value) @@ -2185,7 +2185,7 @@ public enum PatchImplementation return null; } }, - SEAWEED(Tab.SPECIAL) + SEAWEED(Tab.SPECIAL, "Seaweed") { @Override PatchState forVarbitValue(int value) @@ -2223,7 +2223,7 @@ public enum PatchImplementation return null; } }, - CALQUAT(Tab.FRUIT_TREE) + CALQUAT(Tab.FRUIT_TREE, "Calquat") { @Override PatchState forVarbitValue(int value) @@ -2271,7 +2271,7 @@ public enum PatchImplementation return null; } }, - GRAPES(Tab.SPECIAL) + GRAPES(Tab.BUSH, "Grapes") { @Override PatchState forVarbitValue(int value) @@ -2300,4 +2300,6 @@ public enum PatchImplementation abstract PatchState forVarbitValue(int value); private final Tab tab; + + private final String name; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/Tab.java b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/Tab.java index 4f1919d10b..50f50b2ed8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/Tab.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/Tab.java @@ -36,6 +36,7 @@ public enum Tab HERB("Herbs", ItemID.GRIMY_RANARR_WEED), TREE("Trees", ItemID.MAHOGANY_LOGS), FRUIT_TREE("Fruit Trees", ItemID.PINEAPPLE), + BUSH("Bushes", ItemID.REDBERRIES), SPECIAL("Special", ItemID.MUSHROOM); private final String name; 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 19e2e94c30..9b6d1e2229 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 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Lotto + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,6 +29,7 @@ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; +import java.awt.GridLayout; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.font.FontRenderContext; @@ -43,13 +45,16 @@ import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.EmptyBorder; import lombok.extern.slf4j.Slf4j; +import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; import net.runelite.client.ui.PluginPanel; import net.runelite.client.util.LinkBrowser; +import net.runelite.client.util.SwingUtil; import net.runelite.http.api.RuneLiteAPI; import net.runelite.http.api.feed.FeedItem; import net.runelite.http.api.feed.FeedItemType; @@ -63,8 +68,10 @@ import okhttp3.ResponseBody; @Slf4j class FeedPanel extends PluginPanel { - private static BufferedImage RUNELITE_ICON; - private static BufferedImage OSRS_ICON; + private static final ImageIcon RUNELITE_ICON; + private static final ImageIcon OSRS_ICON; + private static final ImageIcon RESET_ICON; + private static final ImageIcon RESET_ICON_CLICK; //used as a click effect (darker version of the reset icon) private static final Color TWEET_BACKGROUND = new Color(15, 15, 15); private static final Color OSRS_NEWS_BACKGROUND = new Color(36, 30, 19); @@ -74,6 +81,11 @@ class FeedPanel extends PluginPanel private static final int CONTENT_WIDTH = 148; private static final int TIME_WIDTH = 20; + /** + * Holds all feed items. + */ + private final JPanel feedContainer = new JPanel(); + private static final Comparator FEED_ITEM_COMPARATOR = (o1, o2) -> { if (o1.getType() != o2.getType()) @@ -97,24 +109,16 @@ class FeedPanel extends PluginPanel { synchronized (ImageIO.class) { - RUNELITE_ICON = ImageIO.read(FeedPanel.class.getResourceAsStream("runelite.png")); + BufferedImage reset = ImageIO.read(FeedPanel.class.getResourceAsStream("reset.png")); + RUNELITE_ICON = new ImageIcon(ImageIO.read(FeedPanel.class.getResourceAsStream("runelite.png"))); + OSRS_ICON = new ImageIcon(ImageIO.read(FeedPanel.class.getResourceAsStream("osrs.png"))); + RESET_ICON = new ImageIcon(reset); + RESET_ICON_CLICK = new ImageIcon(SwingUtil.grayscaleOffset(reset, -100)); } } catch (IOException e) { - log.warn("Client icon failed to load", e); - } - - try - { - synchronized (ImageIO.class) - { - OSRS_ICON = ImageIO.read(FeedPanel.class.getResourceAsStream("osrs.png")); - } - } - catch (IOException e) - { - log.warn("OSRS icon failed to load", e); + throw new RuntimeException(e); } } @@ -125,6 +129,56 @@ class FeedPanel extends PluginPanel { this.config = config; this.feedSupplier = feedSupplier; + + setBorder(new EmptyBorder(10, 10, 10, 10)); + setBackground(ColorScheme.DARK_GRAY_COLOR); + setLayout(new BorderLayout()); + + feedContainer.setLayout(new GridLayout(0, 1, 0, 4)); + feedContainer.setOpaque(false); + + /** + * This header contains the "News Feed" title and a refresh icon button. + */ + JPanel header = new JPanel(); + header.setOpaque(false); + header.setLayout(new BorderLayout()); + header.setBorder(new EmptyBorder(0, 0, 9, 0)); + + /** + * A refresh icon button, when clicked, it will swap icons for feedback effect and then call + * the rebuildFeed method. + */ + JLabel reset = new JLabel(); + reset.setIcon(RESET_ICON); + reset.setVerticalAlignment(SwingConstants.CENTER); + reset.setHorizontalAlignment(SwingConstants.CENTER); + reset.setToolTipText("Refresh"); + + reset.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + reset.setIcon(RESET_ICON_CLICK); + rebuildFeed(); + } + + @Override + public void mouseReleased(MouseEvent mouseEvent) + { + reset.setIcon(RESET_ICON); + } + }); + + JLabel title = new JLabel("News feed"); + title.setForeground(Color.WHITE); + + header.add(title, BorderLayout.WEST); + header.add(reset, BorderLayout.EAST); + + add(header, BorderLayout.NORTH); + add(feedContainer, BorderLayout.CENTER); } void rebuildFeed() @@ -138,7 +192,7 @@ class FeedPanel extends PluginPanel SwingUtilities.invokeLater(() -> { - removeAll(); + feedContainer.removeAll(); feed.getItems() .stream() @@ -207,14 +261,14 @@ class FeedPanel extends PluginPanel case OSRS_NEWS: if (OSRS_ICON != null) { - avatar.setIcon(new ImageIcon(OSRS_ICON)); + avatar.setIcon(OSRS_ICON); } avatarAndRight.setBackground(OSRS_NEWS_BACKGROUND); break; default: if (RUNELITE_ICON != null) { - avatar.setIcon(new ImageIcon(RUNELITE_ICON)); + avatar.setIcon(RUNELITE_ICON); } avatarAndRight.setBackground(BLOG_POST_BACKGROUND); break; @@ -296,7 +350,7 @@ class FeedPanel extends PluginPanel } }); - add(avatarAndRight); + feedContainer.add(avatarAndRight); } private String durationToString(Duration duration) 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 fa8bff265a..d1970191d1 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 @@ -93,6 +93,7 @@ public class FeedPlugin extends Plugin navButton = NavigationButton.builder() .name("News Feed") .icon(icon) + .priority(8) .panel(feedPanel) .build(); 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 24905da2c4..0a12675637 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 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Seth + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,14 +33,17 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; -import javax.swing.border.LineBorder; import lombok.extern.slf4j.Slf4j; +import net.runelite.client.ui.ColorScheme; import net.runelite.client.game.AsyncBufferedImage; import net.runelite.client.util.LinkBrowser; import net.runelite.client.util.StackFormatter; +/** + * This panel displays an individual item result in the + * Grand Exchange search plugin. + */ @Slf4j class GrandExchangeItemPanel extends JPanel { @@ -52,7 +56,8 @@ class GrandExchangeItemPanel extends JPanel layout.setHgap(5); setLayout(layout); setToolTipText(name); - + setBackground(ColorScheme.MEDIUM_GRAY_COLOR); + Color background = getBackground(); addMouseListener(new MouseAdapter() @@ -60,7 +65,7 @@ class GrandExchangeItemPanel extends JPanel @Override public void mouseEntered(MouseEvent e) { - setBackground(getBackground().darker().darker()); + setBackground(getBackground().brighter()); } @Override @@ -76,11 +81,7 @@ class GrandExchangeItemPanel extends JPanel } }); - setBorder(new CompoundBorder - ( - new LineBorder(getBackground().brighter(), 1), - new EmptyBorder(5, 5, 5, 5) - )); + setBorder(new EmptyBorder(5, 5, 5, 0)); // Icon JLabel itemIcon = new JLabel(); @@ -97,6 +98,9 @@ class GrandExchangeItemPanel extends JPanel // Item name JLabel itemName = new JLabel(); + itemName.setForeground(Color.WHITE); + itemName.setMaximumSize(new Dimension(0, 0)); // to limit the label's size for + itemName.setPreferredSize(new Dimension(0, 0)); // items with longer names itemName.setText(name); rightPanel.add(itemName); @@ -110,13 +114,13 @@ class GrandExchangeItemPanel extends JPanel { gePriceLabel.setText("N/A"); } - gePriceLabel.setForeground(Color.GREEN); + gePriceLabel.setForeground(ColorScheme.GRAND_EXCHANGE_PRICE); rightPanel.add(gePriceLabel); // Alch price JLabel haPriceLabel = new JLabel(); haPriceLabel.setText(StackFormatter.formatNumber(haPrice.intValue()) + " alch"); - haPriceLabel.setForeground(Color.orange); + haPriceLabel.setForeground(ColorScheme.GRAND_EXCHANGE_ALCH); rightPanel.add(haPriceLabel); add(rightPanel, BorderLayout.CENTER); @@ -131,4 +135,4 @@ class GrandExchangeItemPanel extends JPanel LinkBrowser.browse(url); } -} +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlot.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlot.java index 7e9409a26d..eab0023d68 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlot.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlot.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, SomeoneWithAnInternetConnection + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,38 +29,73 @@ package net.runelite.client.plugins.grandexchange; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; import java.awt.image.BufferedImage; +import java.io.IOException; import javax.annotation.Nullable; -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; +import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JProgressBar; -import javax.swing.border.TitledBorder; +import javax.swing.border.EmptyBorder; import lombok.extern.slf4j.Slf4j; import net.runelite.api.GrandExchangeOffer; import net.runelite.api.GrandExchangeOfferState; +import static net.runelite.api.GrandExchangeOfferState.CANCELLED_BUY; +import static net.runelite.api.GrandExchangeOfferState.CANCELLED_SELL; import static net.runelite.api.GrandExchangeOfferState.EMPTY; import net.runelite.api.ItemComposition; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.components.ThinProgressBar; import net.runelite.client.util.StackFormatter; +import net.runelite.client.util.SwingUtil; @Slf4j public class GrandExchangeOfferSlot extends JPanel { - private static final Color GE_INPROGRESS_ORANGE = new Color(0xd8, 0x80, 0x20).brighter(); - private static final Color GE_FINISHED_GREEN = new Color(0, 0x5f, 0); - private static final Color GE_CANCELLED_RED = new Color(0x8f, 0, 0); + private static final String FACE_CARD = "FACE_CARD"; + private static final String DETAILS_CARD = "DETAILS_CARD"; - private static final String INFO_CARD = "INFO_CARD"; - private static final String EMPTY_CARD = "EMPTY_CARD"; + private static final ImageIcon RIGHT_ARROW_ICON; + private static final ImageIcon LEFT_ARROW_ICON; + private final JPanel container = new JPanel(); private final CardLayout cardLayout = new CardLayout(); + private final JLabel itemIcon = new JLabel(); - private final TitledBorder itemName = BorderFactory.createTitledBorder("Nothing"); - private final JLabel offerState = new JLabel("Text so the label has height"); - private final JProgressBar progressBar = new JProgressBar(); + private final JLabel itemName = new JLabel(); + private final JLabel offerInfo = new JLabel(); + private final JLabel switchFaceViewIcon = new JLabel(); + + private final JLabel itemPrice = new JLabel(); + private final JLabel offerSpent = new JLabel(); + private final JLabel switchDetailsViewIcon = new JLabel(); + + private final ThinProgressBar progressBar = new ThinProgressBar(); + + private boolean showingFace = true; + + static + { + try + { + synchronized (ImageIO.class) + { + RIGHT_ARROW_ICON = new ImageIcon(ImageIO.read(GrandExchangeOfferSlot.class.getResourceAsStream("arrow_right.png"))); + LEFT_ARROW_ICON = new ImageIcon(ImageIO.read(GrandExchangeOfferSlot.class.getResourceAsStream("arrow_left.png"))); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } /** * This (sub)panel is used for each GE slot displayed @@ -72,123 +108,188 @@ public class GrandExchangeOfferSlot extends JPanel private void buildPanel() { - setBorder(BorderFactory.createCompoundBorder( - // Add a margin underneath each slot panel to space them out - BorderFactory.createEmptyBorder(0, 0, 3, 0), - itemName - )); + setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); + setBorder(new EmptyBorder(7, 0, 0, 0)); - // The default border color is kind of dark, so we change it to something lighter - itemName.setBorder(BorderFactory.createLineBorder(getBackground().brighter())); + final MouseListener ml = new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + super.mousePressed(mouseEvent); + switchPanel(); + } - progressBar.setStringPainted(true); + @Override + public void mouseEntered(MouseEvent mouseEvent) + { + super.mouseEntered(mouseEvent); + container.setBackground(ColorScheme.MEDIUM_GRAY_COLOR.brighter()); + } - setLayout(cardLayout); + @Override + public void mouseExited(MouseEvent mouseEvent) + { + super.mouseExited(mouseEvent); + container.setBackground(ColorScheme.MEDIUM_GRAY_COLOR); + } + }; - // Card for when the slot has an offer in it - JPanel infoCard = new JPanel(); - add(infoCard, INFO_CARD); - // Add padding to give the icon and progress bar room to breathe - infoCard.setBorder(BorderFactory.createEmptyBorder(0, 2, 2, 2)); + container.setLayout(cardLayout); + container.setBackground(ColorScheme.MEDIUM_GRAY_COLOR); - infoCard.setLayout(new BoxLayout(infoCard, BoxLayout.X_AXIS)); - // Icon on the left - infoCard.add(itemIcon); + JPanel faceCard = new JPanel(); + faceCard.setOpaque(false); + faceCard.setLayout(new BorderLayout()); + faceCard.addMouseListener(ml); - // Info on the right - JPanel offerStatePanel = new JPanel(); - offerStatePanel.setLayout(new BoxLayout(offerStatePanel, BoxLayout.Y_AXIS)); - offerStatePanel.add(offerState); - offerStatePanel.add(progressBar); - infoCard.add(offerStatePanel); + itemIcon.setVerticalAlignment(JLabel.CENTER); + itemIcon.setHorizontalAlignment(JLabel.CENTER); + itemIcon.setPreferredSize(new Dimension(45, 45)); - // Card for when the slot is empty - JPanel emptySlotCard = new JPanel(); - add(emptySlotCard, EMPTY_CARD); - // Counteract the height lost to the text at the top of the TitledBorder - int itemNameBorderHeight = itemName.getBorderInsets(this).top; - emptySlotCard.setBorder(BorderFactory.createEmptyBorder(0, 0, (itemNameBorderHeight - 1) / 2, 0)); - // Center the "Empty" label horizontally - emptySlotCard.setLayout( new BoxLayout(emptySlotCard, BoxLayout.X_AXIS)); - emptySlotCard.add(Box.createHorizontalGlue()); - emptySlotCard.add(new JLabel(getNameForState(EMPTY)), BorderLayout.CENTER); - emptySlotCard.add(Box.createHorizontalGlue()); + itemName.setForeground(Color.WHITE); + itemName.setVerticalAlignment(JLabel.BOTTOM); + itemName.setFont(FontManager.getRunescapeSmallFont()); - cardLayout.show(this, EMPTY_CARD); + offerInfo.setForeground(ColorScheme.LIGHT_GRAY_COLOR); + offerInfo.setVerticalAlignment(JLabel.TOP); + offerInfo.setFont(FontManager.getRunescapeSmallFont()); + + switchFaceViewIcon.setIcon(RIGHT_ARROW_ICON); + switchFaceViewIcon.setVerticalAlignment(JLabel.CENTER); + switchFaceViewIcon.setHorizontalAlignment(JLabel.CENTER); + switchFaceViewIcon.setPreferredSize(new Dimension(30, 45)); + + JPanel offerFaceDetails = new JPanel(); + offerFaceDetails.setOpaque(false); + offerFaceDetails.setLayout(new GridLayout(2, 1, 0, 2)); + + offerFaceDetails.add(itemName); + offerFaceDetails.add(offerInfo); + + faceCard.add(offerFaceDetails, BorderLayout.CENTER); + faceCard.add(itemIcon, BorderLayout.WEST); + faceCard.add(switchFaceViewIcon, BorderLayout.EAST); + + JPanel detailsCard = new JPanel(); + detailsCard.setOpaque(false); + detailsCard.setLayout(new BorderLayout()); + detailsCard.setBorder(new EmptyBorder(0, 15, 0, 0)); + detailsCard.addMouseListener(ml); + + itemPrice.setForeground(Color.WHITE); + itemPrice.setVerticalAlignment(JLabel.BOTTOM); + itemPrice.setFont(FontManager.getRunescapeSmallFont()); + + offerSpent.setForeground(Color.WHITE); + offerSpent.setVerticalAlignment(JLabel.TOP); + offerSpent.setFont(FontManager.getRunescapeSmallFont()); + + switchDetailsViewIcon.setIcon(LEFT_ARROW_ICON); + switchDetailsViewIcon.setVerticalAlignment(JLabel.CENTER); + switchDetailsViewIcon.setHorizontalAlignment(JLabel.CENTER); + switchDetailsViewIcon.setPreferredSize(new Dimension(30, 45)); + + JPanel offerDetails = new JPanel(); + offerDetails.setOpaque(false); + offerDetails.setLayout(new GridLayout(2, 1)); + + offerDetails.add(itemPrice); + offerDetails.add(offerSpent); + + detailsCard.add(offerDetails, BorderLayout.CENTER); + detailsCard.add(switchDetailsViewIcon, BorderLayout.EAST); + + container.add(faceCard, FACE_CARD); + container.add(detailsCard, DETAILS_CARD); + + cardLayout.show(container, FACE_CARD); + + add(container, BorderLayout.CENTER); + add(progressBar, BorderLayout.SOUTH); } - + void updateOffer(ItemComposition offerItem, BufferedImage itemImage, @Nullable GrandExchangeOffer newOffer) { if (newOffer == null || newOffer.getState() == EMPTY) { - cardLayout.show(this, EMPTY_CARD); - itemName.setTitle("Nothing"); + return; } else { - cardLayout.show(this, INFO_CARD); + cardLayout.show(container, FACE_CARD); - itemName.setTitle(offerItem.getName()); + itemName.setText(offerItem.getName()); + itemIcon.setIcon(new ImageIcon(itemImage)); - boolean shouldStack = offerItem.isStackable() || newOffer.getTotalQuantity() > 1; - ImageIcon newItemIcon = new ImageIcon(itemImage); - itemIcon.setIcon(newItemIcon); + boolean buying = newOffer.getState() == GrandExchangeOfferState.BOUGHT + || newOffer.getState() == GrandExchangeOfferState.BUYING + || newOffer.getState() == GrandExchangeOfferState.CANCELLED_BUY; - offerState.setText(getNameForState(newOffer.getState()) - + " at " - + StackFormatter.formatNumber(newOffer.getState() == GrandExchangeOfferState.BOUGHT ? (newOffer.getSpent() / newOffer.getTotalQuantity()) : newOffer.getPrice()) - + (newOffer.getTotalQuantity() > 1 ? " gp ea" : " gp")); + String offerState = (buying ? "Bought " : "Sold ") + + StackFormatter.quantityToRSDecimalStack(newOffer.getQuantitySold()) + " / " + + StackFormatter.quantityToRSDecimalStack(newOffer.getTotalQuantity()); - progressBar.setMaximum(newOffer.getTotalQuantity()); + offerInfo.setText(offerState); + + itemPrice.setText(htmlLabel("Price each: ", newOffer.getPrice() + "")); + + String action = buying ? "Spent: " : "Received: "; + + offerSpent.setText(htmlLabel(action, StackFormatter.formatNumber(newOffer.getSpent()) + " / " + + StackFormatter.formatNumber(newOffer.getPrice() * newOffer.getTotalQuantity()))); + + progressBar.setForeground(getProgressColor(newOffer)); + progressBar.setMaximumValue(newOffer.getTotalQuantity()); progressBar.setValue(newOffer.getQuantitySold()); - progressBar.setBackground(getColorForState(newOffer.getState())); - progressBar.setString(newOffer.getQuantitySold() + "/" + newOffer.getTotalQuantity()); + progressBar.update(); + + /* Couldn't set the tooltip for the container panel as the children override it, so I'm setting + * the tooltips on the children instead. */ + for (Component c : container.getComponents()) + { + if (c instanceof JPanel) + { + JPanel panel = (JPanel) c; + panel.setToolTipText(htmlTooltip(((int) progressBar.getPercentage()) + "%")); + } + } } + + revalidate(); repaint(); } - private String getNameForState(GrandExchangeOfferState state) + private String htmlTooltip(String value) { - switch (state) - { - case CANCELLED_BUY: - return "Buying cancelled"; - case CANCELLED_SELL: - return "Selling cancelled"; - case BUYING: - return "Buying"; - case BOUGHT: - return "Bought"; - case SELLING: - return "Selling"; - case SOLD: - return "Sold"; - case EMPTY: - default: - return "Empty"; - - } + return "Progress: " + value + ""; } - private Color getColorForState(GrandExchangeOfferState state) + private String htmlLabel(String key, String value) { - switch (state) - { - case CANCELLED_BUY: - case CANCELLED_SELL: - return GE_CANCELLED_RED; - case BUYING: - case SELLING: - return GE_INPROGRESS_ORANGE; - case BOUGHT: - case SOLD: - return GE_FINISHED_GREEN; - case EMPTY: - default: - return null; - } + return "" + key + "" + value + ""; } + private void switchPanel() + { + this.showingFace = !this.showingFace; + cardLayout.show(container, showingFace ? FACE_CARD : DETAILS_CARD); + } + + private Color getProgressColor(GrandExchangeOffer offer) + { + if (offer.getState() == CANCELLED_BUY || offer.getState() == CANCELLED_SELL) + { + return ColorScheme.PROGRESS_ERROR_COLOR; + } + + if (offer.getQuantitySold() == offer.getTotalQuantity()) + { + return ColorScheme.PROGRESS_COMPLETE_COLOR; + } + + return ColorScheme.PROGRESS_INPROGRESS_COLOR; + } } - diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOffersPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOffersPanel.java new file mode 100644 index 0000000000..fb13394a05 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOffersPanel.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grandexchange; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.image.BufferedImage; +import java.util.concurrent.ScheduledExecutorService; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; +import net.runelite.api.Client; +import net.runelite.api.GrandExchangeOffer; +import net.runelite.api.GrandExchangeOfferState; +import net.runelite.api.ItemComposition; +import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.components.PluginErrorPanel; + +public class GrandExchangeOffersPanel extends JPanel +{ + private static final String ERROR_PANEL = "ERROR_PANEL"; + private static final String OFFERS_PANEL = "OFFERS_PANEL"; + + private static final int MAX_OFFERS = 8; + + private final GridBagConstraints constraints = new GridBagConstraints(); + private final CardLayout cardLayout = new CardLayout(); + + /* The offers container, this will hold all the individual ge offers panels */ + private final JPanel offerPanel = new JPanel(); + + /* The error panel, this displays an error message */ + private final PluginErrorPanel errorPanel = new PluginErrorPanel(); + + /* The center panel, this holds either the error panel or the offers container */ + private final JPanel container = new JPanel(cardLayout); + + private final Client client; + private final ItemManager itemManager; + private final ScheduledExecutorService executor; + + private GrandExchangeOfferSlot[] offerSlotPanels = new GrandExchangeOfferSlot[MAX_OFFERS]; + + public GrandExchangeOffersPanel(Client client, ItemManager itemManager, ScheduledExecutorService executor) + { + this.client = client; + this.itemManager = itemManager; + this.executor = executor; + init(); + } + + void init() + { + setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); + + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.weightx = 1; + constraints.gridx = 0; + constraints.gridy = 0; + + /* This panel wraps the offers panel and limits its height */ + JPanel offersWrapper = new JPanel(new BorderLayout()); + offersWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR); + offersWrapper.add(offerPanel, BorderLayout.NORTH); + + offerPanel.setLayout(new GridBagLayout()); + offerPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + offerPanel.setBackground(ColorScheme.DARK_GRAY_COLOR); + + /* This panel wraps the error panel and limits its height */ + JPanel errorWrapper = new JPanel(new BorderLayout()); + errorWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR); + errorWrapper.add(errorPanel, BorderLayout.NORTH); + + errorPanel.setBorder(new EmptyBorder(50, 20, 20, 20)); + errorPanel.setContent("No offers detected", "No grand exchange offers were found on your account."); + + container.add(offersWrapper, OFFERS_PANEL); + container.add(errorWrapper, ERROR_PANEL); + + add(container, BorderLayout.CENTER); + + resetOffers(); + } + + void resetOffers() + { + offerPanel.removeAll(); + for (int i = 0; i < offerSlotPanels.length; i++) + { + offerSlotPanels[i] = null; + } + updateEmptyOffersPanel(); + } + + void updateOffer(ItemComposition item, BufferedImage itemImage, GrandExchangeOffer newOffer, int slot) + { + /* If slot was previously filled, and is now empty, remove it from the list */ + if (newOffer == null || newOffer.getState() == GrandExchangeOfferState.EMPTY) + { + if (offerSlotPanels[slot] != null) + { + offerPanel.remove(offerSlotPanels[slot]); + offerSlotPanels[slot] = null; + revalidate(); + repaint(); + } + + removeTopMargin(); + updateEmptyOffersPanel(); + return; + } + + /* If slot was empty, and is now filled, add it to the list */ + if (offerSlotPanels[slot] == null) + { + GrandExchangeOfferSlot newSlot = new GrandExchangeOfferSlot(); + offerSlotPanels[slot] = newSlot; + offerPanel.add(newSlot, constraints); + constraints.gridy++; + } + + offerSlotPanels[slot].updateOffer(item, itemImage, newOffer); + + removeTopMargin(); + + revalidate(); + repaint(); + + updateEmptyOffersPanel(); + } + + /** + * Reset the border for the first offer slot. + */ + private void removeTopMargin() + { + + if (offerPanel.getComponentCount() <= 0) + { + return; + } + + JPanel firstItem = (JPanel) offerPanel.getComponent(0); + firstItem.setBorder(null); + } + + /** + * This method calculates the amount of empty ge offer slots, if all slots are empty, + * it shows the error panel. + */ + private void updateEmptyOffersPanel() + { + int nullCount = 0; + for (GrandExchangeOfferSlot slot : offerSlotPanels) + { + if (slot == null) + { + nullCount++; + } + } + + if (nullCount == MAX_OFFERS) + { + offerPanel.removeAll(); + cardLayout.show(container, ERROR_PANEL); + } + else + { + cardLayout.show(container, OFFERS_PANEL); + } + + } + +} 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 3f84da8e01..b8bf685117 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 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, SomeoneWithAnInternetConnection + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,58 +27,58 @@ package net.runelite.client.plugins.grandexchange; import java.awt.BorderLayout; -import java.awt.image.BufferedImage; import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; -import javax.swing.BoxLayout; import javax.swing.JPanel; -import javax.swing.JTabbedPane; +import javax.swing.border.EmptyBorder; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; -import net.runelite.api.GrandExchangeOffer; -import net.runelite.api.ItemComposition; import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.PluginPanel; +import net.runelite.client.ui.components.materialtabs.MaterialTab; +import net.runelite.client.ui.components.materialtabs.MaterialTabGroup; @Slf4j class GrandExchangePanel extends PluginPanel { - private static final int MAX_OFFERS = 8; + + // this panel will hold either the ge search panel or the ge offers panel + private final JPanel display = new JPanel(); + + private final MaterialTabGroup tabGroup = new MaterialTabGroup(display); + private final MaterialTab searchTab; @Getter private GrandExchangeSearchPanel searchPanel; - - private GrandExchangeOfferSlot[] offerSlotPanels = new GrandExchangeOfferSlot[MAX_OFFERS]; - - private JPanel offerPanel = new JPanel(); - - private JTabbedPane tabbedPane = new JTabbedPane(); + @Getter + private GrandExchangeOffersPanel offersPanel; @Inject GrandExchangePanel(Client client, ItemManager itemManager, ScheduledExecutorService executor) { - setLayout(new BorderLayout()); - add(tabbedPane, BorderLayout.NORTH); + super(false); - // Offer Panel - offerPanel.setLayout(new BoxLayout(offerPanel, BoxLayout.Y_AXIS)); - for (int i = 0; i < offerSlotPanels.length; ++i) - { - offerSlotPanels[i] = new GrandExchangeOfferSlot(); - offerPanel.add(offerSlotPanels[i]); - } + setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); // Search Panel searchPanel = new GrandExchangeSearchPanel(client, itemManager, executor); - tabbedPane.addTab("Offers", offerPanel); - tabbedPane.addTab("Search", searchPanel); - } + //Offers Panel + offersPanel = new GrandExchangeOffersPanel(client, itemManager, executor); - void updateOffer(ItemComposition item, BufferedImage itemImage, GrandExchangeOffer newOffer, int slot) - { - offerSlotPanels[slot].updateOffer(item, itemImage, newOffer); + MaterialTab offersTab = new MaterialTab("Offers", tabGroup, offersPanel); + searchTab = new MaterialTab("Search", tabGroup, searchPanel); + + tabGroup.setBorder(new EmptyBorder(5, 0, 0, 0)); + tabGroup.addTab(offersTab); + tabGroup.addTab(searchTab); + tabGroup.select(offersTab); // selects the default selected tab + + add(tabGroup, BorderLayout.NORTH); + add(display, BorderLayout.CENTER); } void showSearch() @@ -87,7 +88,7 @@ class GrandExchangePanel extends PluginPanel return; } - tabbedPane.setSelectedComponent(searchPanel); + tabGroup.select(searchTab); revalidate(); } -} +} \ No newline at end of file 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 1c10fbcd7d..1a2788e0c7 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 @@ -44,6 +44,7 @@ import net.runelite.api.ItemComposition; import net.runelite.api.MenuEntry; import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.FocusChanged; +import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GrandExchangeOfferChanged; import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.widgets.WidgetID; @@ -113,6 +114,7 @@ public class GrandExchangePlugin extends Plugin button = NavigationButton.builder() .name("GE Offers") .icon(icon) + .priority(3) .panel(panel) .build(); @@ -161,7 +163,16 @@ public class GrandExchangePlugin extends Plugin ItemComposition offerItem = itemManager.getItemComposition(offer.getItemId()); boolean shouldStack = offerItem.isStackable() || offer.getTotalQuantity() > 1; BufferedImage itemImage = itemManager.getImage(offer.getItemId(), offer.getTotalQuantity(), shouldStack); - SwingUtilities.invokeLater(() -> panel.updateOffer(offerItem, itemImage, offerEvent.getOffer(), offerEvent.getSlot())); + SwingUtilities.invokeLater(() -> panel.getOffersPanel().updateOffer(offerItem, itemImage, offerEvent.getOffer(), offerEvent.getSlot())); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + if (gameStateChanged.getGameState() == GameState.LOGIN_SCREEN) + { + panel.getOffersPanel().resetOffers(); + } } @Subscribe 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 d3eb52044d..628658c179 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 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Seth + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,19 +27,19 @@ package net.runelite.client.plugins.grandexchange; import com.google.common.base.Strings; import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.GridLayout; -import java.awt.image.BufferedImage; +import java.awt.CardLayout; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledExecutorService; import javax.imageio.ImageIO; -import javax.swing.Icon; import javax.swing.ImageIcon; -import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import lombok.extern.slf4j.Slf4j; @@ -46,26 +47,69 @@ import net.runelite.api.Client; import net.runelite.api.ItemComposition; import net.runelite.client.game.AsyncBufferedImage; import net.runelite.client.game.ItemManager; -import net.runelite.client.plugins.hiscore.IconTextField; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.components.IconTextField; +import net.runelite.client.ui.components.PluginErrorPanel; import net.runelite.http.api.item.Item; import net.runelite.http.api.item.ItemPrice; import net.runelite.http.api.item.SearchResult; +/** + * This panel holds the search section of the Grand Exchange Plugin. + * It should display a search bar and either item results or a error panel. + */ @Slf4j class GrandExchangeSearchPanel extends JPanel { - private static final List ITEMS_LIST = new ArrayList<>(); + private static final String ERROR_PANEL = "ERROR_PANEL"; + private static final String RESULTS_PANEL = "RESULTS_PANEL"; + + private static final ImageIcon SEARCH_ICON; + private static final ImageIcon LOADING_ICON; + private static final ImageIcon ERROR_ICON; + + private final GridBagConstraints constraints = new GridBagConstraints(); + private final CardLayout cardLayout = new CardLayout(); private final Client client; private final ItemManager itemManager; private final ScheduledExecutorService executor; - private Icon search; + private final IconTextField searchBox = new IconTextField(); - private IconTextField searchBox = new IconTextField(); - private JPanel container = new JPanel(); - private JPanel searchItemsPanel = new JPanel(); - private JLabel searchingLabel = new JLabel(); + /* The main container, this holds the search bar and the center panel */ + private final JPanel container = new JPanel(); + + /* The results container, this will hold all the individual ge item panels */ + private final JPanel searchItemsPanel = new JPanel(); + + /* The center panel, this holds either the error panel or the results container */ + private final JPanel centerPanel = new JPanel(cardLayout); + + /* The error panel, this displays an error message */ + private final PluginErrorPanel errorPanel = new PluginErrorPanel(); + + /* The results wrapper, this scrolling panel wraps the results container */ + private JScrollPane resultsWrapper; + + private List itemsList = new ArrayList<>(); + + static + { + try + { + synchronized (ImageIO.class) + { + SEARCH_ICON = new ImageIcon(ImageIO.read(IconTextField.class.getResourceAsStream("search_darker.png"))); + LOADING_ICON = new ImageIcon(IconTextField.class.getResource("loading_spinner.gif")); + ERROR_ICON = new ImageIcon(ImageIO.read(IconTextField.class.getResourceAsStream("error.png"))); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } GrandExchangeSearchPanel(Client client, ItemManager itemManager, ScheduledExecutorService executor) { @@ -78,38 +122,54 @@ class GrandExchangeSearchPanel extends JPanel void init() { setLayout(new BorderLayout()); - container.setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); - // Search Box - try - { - BufferedImage icon; - synchronized (ImageIO.class) - { - icon = ImageIO.read(GrandExchangePlugin.class.getResourceAsStream("search.png")); - } - search = new ImageIcon(icon); - } - catch (IOException e) - { - log.warn("Failed to read icon", e); - } + container.setLayout(new BorderLayout(5, 5)); + container.setBorder(new EmptyBorder(10, 10, 10, 10)); + container.setBackground(ColorScheme.DARK_GRAY_COLOR); - searchBox.setIcon(search); + searchBox.setPreferredSize(new Dimension(100, 30)); + searchBox.setBackground(ColorScheme.MEDIUM_GRAY_COLOR); + searchBox.setHoverBackgroundColor(ColorScheme.MEDIUM_GRAY_COLOR.brighter()); + searchBox.setIcon(SEARCH_ICON); searchBox.addActionListener(e -> executor.execute(() -> priceLookup(false))); + searchItemsPanel.setLayout(new GridBagLayout()); + searchItemsPanel.setBackground(ColorScheme.DARK_GRAY_COLOR); + + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.weightx = 1; + constraints.gridx = 0; + constraints.gridy = 0; + + /* This panel wraps the results panel and guarantees the scrolling behaviour */ + JPanel wrapper = new JPanel(new BorderLayout()); + wrapper.setBackground(ColorScheme.DARK_GRAY_COLOR); + wrapper.add(searchItemsPanel, BorderLayout.NORTH); + + resultsWrapper = new JScrollPane(wrapper); + resultsWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR); + resultsWrapper.getVerticalScrollBar().setPreferredSize(new Dimension(12, 0)); + resultsWrapper.getVerticalScrollBar().setBorder(new EmptyBorder(0, 5, 0, 0)); + resultsWrapper.setVisible(false); + + /* This panel wraps the error panel and limits its height */ + JPanel errorWrapper = new JPanel(new BorderLayout()); + errorWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR); + errorWrapper.add(errorPanel, BorderLayout.NORTH); + + errorPanel.setContent("Grand Exchange Search", + "Here you can search for an item by its name to find price information."); + + centerPanel.add(resultsWrapper, RESULTS_PANEL); + centerPanel.add(errorWrapper, ERROR_PANEL); + + cardLayout.show(centerPanel, ERROR_PANEL); + container.add(searchBox, BorderLayout.NORTH); + container.add(centerPanel, BorderLayout.CENTER); - // Searching label - searchingLabel.setHorizontalAlignment(JLabel.CENTER); - searchingLabel.setForeground(Color.YELLOW); - - // Items Panel - searchItemsPanel.setLayout(new GridLayout(0, 1, 0, 3)); - searchItemsPanel.setBorder(new EmptyBorder(3, 0, 0, 0)); - - container.add(searchItemsPanel, BorderLayout.SOUTH); - add(container, BorderLayout.NORTH); + add(container, BorderLayout.CENTER); } void priceLookup(String item) @@ -130,7 +190,9 @@ class GrandExchangeSearchPanel extends JPanel // Input is not empty, add searching label searchItemsPanel.removeAll(); - showSearchString("Searching..."); + searchBox.setBackground(ColorScheme.MEDIUM_GRAY_COLOR); + searchBox.setEditable(false); + searchBox.setIcon(LOADING_ICON); SearchResult result; @@ -141,13 +203,19 @@ class GrandExchangeSearchPanel extends JPanel catch (ExecutionException ex) { log.warn("Unable to search for item {}", lookup, ex); - showSearchString("Error performing search"); + searchBox.setIcon(ERROR_ICON); + searchBox.setEditable(true); + errorPanel.setContent("Error fetching results", "An error occured why trying to fetch item data, please try again later."); + cardLayout.show(centerPanel, ERROR_PANEL); return; } + itemsList.clear(); if (result != null && !result.getItems().isEmpty()) { + cardLayout.show(centerPanel, RESULTS_PANEL); + for (Item item : result.getItems()) { int itemId = item.getId(); @@ -170,7 +238,7 @@ class GrandExchangeSearchPanel extends JPanel AsyncBufferedImage itemImage = itemManager.getImage(itemId); - ITEMS_LIST.add(new GrandExchangeItems(itemImage, item.getName(), itemId, itemPrice != null ? itemPrice.getPrice() : 0, itemComp.getPrice() * 0.6)); + itemsList.add(new GrandExchangeItems(itemImage, item.getName(), itemId, itemPrice != null ? itemPrice.getPrice() : 0, itemComp.getPrice() * 0.6)); // If using hotkey to lookup item, stop after finding match. if (exactMatch && item.getName().equalsIgnoreCase(lookup)) @@ -179,44 +247,51 @@ class GrandExchangeSearchPanel extends JPanel } } } + else + { + searchBox.setIcon(ERROR_ICON); + errorPanel.setContent("No results found.", "No items were found with that name, please try again."); + cardLayout.show(centerPanel, ERROR_PANEL); + } SwingUtilities.invokeLater(() -> { - if (ITEMS_LIST.isEmpty()) + int index = 0; + for (GrandExchangeItems item : itemsList) { - showSearchString("No results found."); - } - else - { - for (GrandExchangeItems item : ITEMS_LIST) + GrandExchangeItemPanel panel = new GrandExchangeItemPanel(item.getIcon(), item.getName(), + item.getItemId(), item.getGePrice(), item.getHaPrice()); + + /* + Add the first item directly, wrap the rest with margin. This margin hack is because + gridbaglayout does not support inter-element margins. + */ + if (index++ > 0) { - GrandExchangeItemPanel panel = new GrandExchangeItemPanel(item.getIcon(), item.getName(), - item.getItemId(), item.getGePrice(), item.getHaPrice()); - - searchItemsPanel.add(panel); + JPanel marginWrapper = new JPanel(new BorderLayout()); + marginWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR); + marginWrapper.setBorder(new EmptyBorder(5, 0, 0, 0)); + marginWrapper.add(panel, BorderLayout.NORTH); + searchItemsPanel.add(marginWrapper, constraints); } + else + { + searchItemsPanel.add(panel, constraints); + } + + constraints.gridy++; + } - // Remove searching label after search is complete - showSearchString(null); - ITEMS_LIST.clear(); + // remove focus from the search bar + searchItemsPanel.requestFocusInWindow(); + searchBox.setEditable(true); + + // Remove searching label after search is complete + if (!itemsList.isEmpty()) + { + searchBox.setIcon(SEARCH_ICON); } }); } - private void showSearchString(String str) - { - if (str != null) - { - remove(searchingLabel); - searchingLabel.setText(str); - add(searchingLabel, BorderLayout.CENTER); - } - else - { - remove(searchingLabel); - } - - revalidate(); - repaint(); - } -} +} \ No newline at end of file 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 948b2dd8ee..95ef5b8b41 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 @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Adam + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,10 +26,8 @@ package net.runelite.client.plugins.hiscore; import com.google.common.base.Strings; -import java.awt.BorderLayout; -import java.awt.Color; +import com.google.common.collect.ImmutableList; import java.awt.Dimension; -import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; @@ -39,31 +38,24 @@ import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import javax.annotation.Nullable; import javax.imageio.ImageIO; import javax.inject.Inject; -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JProgressBar; -import javax.swing.JTextArea; -import javax.swing.JToggleButton; -import javax.swing.UIManager; -import javax.swing.border.Border; -import javax.swing.event.MouseInputAdapter; +import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.Experience; import net.runelite.api.Player; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; import net.runelite.client.ui.PluginPanel; +import net.runelite.client.ui.components.IconTextField; import net.runelite.client.util.StackFormatter; import net.runelite.http.api.hiscore.HiscoreClient; import net.runelite.http.api.hiscore.HiscoreEndpoint; @@ -102,13 +94,17 @@ import net.runelite.http.api.hiscore.Skill; @Slf4j public class HiscorePanel extends PluginPanel { - private static final String SKILL_NAME = "SKILL_NAME"; - private static final String SKILL = "SKILL"; + /* The maximum allowed username length in runescape accounts */ + private static final int MAX_USERNAME_LENGTH = 12; + + private static final ImageIcon SEARCH_ICON; + private static final ImageIcon LOADING_ICON; + private static final ImageIcon ERROR_ICON; /** * Real skills, ordered in the way they should be displayed in the panel. */ - private static final Set SKILLS = new LinkedHashSet<>(Arrays.asList( + private static final List SKILLS = ImmutableList.of( ATTACK, HITPOINTS, MINING, STRENGTH, AGILITY, SMITHING, DEFENCE, HERBLORE, FISHING, @@ -117,7 +113,7 @@ public class HiscorePanel extends PluginPanel MAGIC, FLETCHING, WOODCUTTING, RUNECRAFT, SLAYER, FARMING, CONSTRUCTION, HUNTER - )); + ); @Inject ScheduledExecutorService executor; @@ -132,24 +128,45 @@ public class HiscorePanel extends PluginPanel private final List skillLabels = new ArrayList<>(); private final JPanel statsPanel = new JPanel(); - private final ButtonGroup endpointButtonGroup = new ButtonGroup(); - private final JTextArea details = new JTextArea(); - private final JProgressBar progressBar; - private List endpointButtons; + /* A list of all the selectable endpoints (ironman, deadman, etc) */ + private final List endPoints = new ArrayList<>(); private final HiscoreClient hiscoreClient = new HiscoreClient(); + private HiscoreResult result; + /* The currently selected endpoint */ + private HiscoreEndpoint selectedEndPoint; + + /* Used to prevent users from switching endpoint tabs while the results are loading */ + private boolean loading = false; + + static + { + try + { + synchronized (ImageIO.class) + { + SEARCH_ICON = new ImageIcon(ImageIO.read(IconTextField.class.getResourceAsStream("search.png"))); + LOADING_ICON = new ImageIcon(IconTextField.class.getResource("loading_spinner_darker.gif")); + ERROR_ICON = new ImageIcon(ImageIO.read(IconTextField.class.getResourceAsStream("error.png"))); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + @Inject public HiscorePanel(HiscoreConfig config) { super(); this.config = config; - // Panel "constants" - // This was an EtchedBorder, but the style would change when the window was maximized. - Border subPanelBorder = BorderFactory.createLineBorder(this.getBackground().brighter(), 2); + setBorder(new EmptyBorder(10, 10, 0, 10)); + setBackground(ColorScheme.DARK_GRAY_COLOR); // Create GBL to arrange sub items GridBagLayout gridBag = new GridBagLayout(); @@ -160,28 +177,11 @@ public class HiscorePanel extends PluginPanel c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.NORTH; - // Search box - JPanel inputPanel = new JPanel(); - inputPanel.setLayout(new BorderLayout(7, 7)); - inputPanel.setBorder(subPanelBorder); - - Icon search; - try - { - BufferedImage icon; - synchronized (ImageIO.class) - { - icon = ImageIO.read(HiscorePanel.class.getResourceAsStream("search.png")); - } - search = new ImageIcon(icon); - } - catch (IOException ex) - { - throw new RuntimeException(ex); - } - input = new IconTextField(); - input.setIcon(search); + input.setPreferredSize(new Dimension(100, 30)); + input.setBackground(ColorScheme.DARKER_GRAY_COLOR); + input.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); + input.setIcon(SEARCH_ICON); input.addActionListener(e -> executor.execute(this::lookup)); input.addMouseListener(new MouseAdapter() { @@ -192,7 +192,6 @@ public class HiscorePanel extends PluginPanel { return; } - if (client == null) { return; @@ -206,97 +205,20 @@ public class HiscorePanel extends PluginPanel } } }); - inputPanel.add(input, BorderLayout.CENTER); c.gridx = 0; c.gridy = 0; c.weightx = 1; c.weighty = 0; - c.insets = new Insets(0, 0, 3, 0); - gridBag.setConstraints(inputPanel, c); - add(inputPanel); - - // Panel that holds skill icons - GridLayout stats = new GridLayout(8, 3); - statsPanel.setLayout(stats); - statsPanel.setBorder(subPanelBorder); - - // For each skill on the ingame skill panel, create a Label and add it to the UI - for (HiscoreSkill skill : SKILLS) - { - JPanel panel = makeSkillPanel(skill.getName(), skill); - statsPanel.add(panel); - } - - c.gridx = 0; - c.gridy = 1; - gridBag.setConstraints(statsPanel, c); - add(statsPanel); - - JPanel totalPanel = new JPanel(); - totalPanel.setBorder(subPanelBorder); - totalPanel.setLayout(new GridLayout(1, 2)); - - totalPanel.add(makeSkillPanel(OVERALL.getName(), OVERALL)); - totalPanel.add(makeSkillPanel("Combat", null)); - - c.gridx = 0; - c.gridy = 2; - gridBag.setConstraints(totalPanel, c); - add(totalPanel); - - JPanel minigamePanel = new JPanel(); - minigamePanel.setBorder(subPanelBorder); - // These aren't all on one row because when there's a label with four or more digits it causes the details - // panel to change its size for some reason... - minigamePanel.setLayout(new GridLayout(2, 3)); - - minigamePanel.add(makeSkillPanel(CLUE_SCROLL_ALL.getName(), CLUE_SCROLL_ALL)); - minigamePanel.add(makeSkillPanel(LAST_MAN_STANDING.getName(), LAST_MAN_STANDING)); - minigamePanel.add(makeSkillPanel(BOUNTY_HUNTER_ROGUE.getName(), BOUNTY_HUNTER_ROGUE)); - minigamePanel.add(makeSkillPanel(BOUNTY_HUNTER_HUNTER.getName(), BOUNTY_HUNTER_HUNTER)); - - c.gridx = 0; - c.gridy = 3; - gridBag.setConstraints(minigamePanel, c); - add(minigamePanel); - - JPanel detailsPanel = new JPanel(); - detailsPanel.setBorder(subPanelBorder); - detailsPanel.setLayout(new BorderLayout()); - - // Rather than using one JLabel for each line, make a JTextArea look and act like a JLabel - details.setEditable(false); - details.setCursor(null); - details.setOpaque(false); - details.setFocusable(false); - details.setWrapStyleWord(true); - details.setLineWrap(true); - details.setMargin(new Insets(2, 4, 4, 4)); - details.setRows(6); - details.setText(""); - - detailsPanel.add(details, BorderLayout.CENTER); - - progressBar = new JProgressBar(); - progressBar.setStringPainted(true); - progressBar.setValue(0); - progressBar.setMinimum(0); - progressBar.setMaximum(100); - progressBar.setBackground(Color.RED); - progressBar.setVisible(false); - - detailsPanel.add(progressBar, BorderLayout.SOUTH); - - c.gridx = 0; - c.gridy = 4; - gridBag.setConstraints(detailsPanel, c); - add(detailsPanel); + c.insets = new Insets(0, 0, 10, 0); + gridBag.setConstraints(input, c); + add(input); + /* The container for all the endpoint selectors */ JPanel endpointPanel = new JPanel(); - endpointPanel.setBorder(subPanelBorder); + endpointPanel.setOpaque(false); + endpointPanel.setLayout(new GridLayout(1, 5, 7, 1)); - endpointButtons = new ArrayList<>(); for (HiscoreEndpoint endpoint : HiscoreEndpoint.values()) { try @@ -307,25 +229,32 @@ public class HiscorePanel extends PluginPanel iconImage = ImageIO.read(HiscorePanel.class.getResourceAsStream( endpoint.name().toLowerCase() + ".png")); } - JToggleButton button = new JToggleButton(); - button.setIcon(new ImageIcon(iconImage)); - button.setPreferredSize(new Dimension(24, 24)); - button.setBackground(Color.WHITE); - button.setFocusPainted(false); - button.setActionCommand(endpoint.name()); - button.setToolTipText(endpoint.getName() + " Hiscores"); - button.addActionListener((e -> executor.execute(this::lookup))); - button.addMouseListener(new MouseAdapter() + + JPanel panel = new JPanel(); + JLabel label = new JLabel(); + + label.setIcon(new ImageIcon(iconImage)); + + panel.add(label); + panel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + panel.setToolTipText(endpoint.getName() + " Hiscores"); + panel.addMouseListener(new MouseAdapter() { @Override - public void mouseReleased(MouseEvent e) + public void mouseClicked(MouseEvent e) { + if (loading) + { + return; + } + executor.execute(HiscorePanel.this::lookup); + selectedEndPoint = endpoint; updateButtons(); } }); - endpointButtons.add(button); - endpointButtonGroup.add(button); - endpointPanel.add(button); + + endPoints.add(panel); + endpointPanel.add(panel); } catch (IOException ex) { @@ -333,156 +262,61 @@ public class HiscorePanel extends PluginPanel } } - endpointButtons.get(0).setSelected(true); - endpointButtons.get(0).setBackground(Color.CYAN); + /* Default endpoint is the general (normal) endpoint */ + selectedEndPoint = HiscoreEndpoint.NORMAL; + updateButtons(); c.gridx = 0; - c.gridy = 5; - // Last item has a nonzero weighty so it will expand to fill vertical space - c.weighty = 1; + c.gridy = 1; gridBag.setConstraints(endpointPanel, c); add(endpointPanel); - } - void addInputKeyListener(KeyListener l) - { - this.input.addKeyListener(l); - } + // Panel that holds skill icons + GridLayout stats = new GridLayout(8, 3); + statsPanel.setLayout(stats); + statsPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + statsPanel.setBorder(new EmptyBorder(5, 0, 5, 0)); - void removeInputKeyListener(KeyListener l) - { - this.input.removeKeyListener(l); - } - - private void changeDetail(String skillName, HiscoreSkill skill) - { - if (result == null || result.getPlayer() == null) + // For each skill on the ingame skill panel, create a Label and add it to the UI + for (HiscoreSkill skill : SKILLS) { - return; + JPanel panel = makeSkillPanel(skill); + panel.setOpaque(false); + statsPanel.add(panel); } - String text; - int progress = -1; - switch (skillName) - { - case "Combat": - { - double combatLevel = Experience.getCombatLevelPrecise( - result.getAttack().getLevel(), - result.getStrength().getLevel(), - result.getDefence().getLevel(), - result.getHitpoints().getLevel(), - result.getMagic().getLevel(), - result.getRanged().getLevel(), - result.getPrayer().getLevel() - ); - text = "Skill: Combat" + System.lineSeparator() - + "Exact Combat Level: " + StackFormatter.formatNumber(combatLevel) + System.lineSeparator() - + "Experience: " + StackFormatter.formatNumber(result.getAttack().getExperience() - + result.getStrength().getExperience() + result.getDefence().getExperience() - + result.getHitpoints().getExperience() + result.getMagic().getExperience() - + result.getRanged().getExperience() + result.getPrayer().getExperience()); - break; - } - case "Clue Scrolls (all)": - { - String allRank = (result.getClueScrollAll().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollAll().getRank()); - String easyRank = (result.getClueScrollEasy().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollEasy().getRank()); - String mediumRank = (result.getClueScrollMedium().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollMedium().getRank()); - String hardRank = (result.getClueScrollHard().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollHard().getRank()); - String eliteRank = (result.getClueScrollElite().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollElite().getRank()); - String masterRank = (result.getClueScrollMaster().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollMaster().getRank()); - String all = (result.getClueScrollAll().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollAll().getLevel())); - String easy = (result.getClueScrollEasy().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollEasy().getLevel())); - String medium = (result.getClueScrollMedium().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollMedium().getLevel())); - String hard = (result.getClueScrollHard().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollHard().getLevel())); - String elite = (result.getClueScrollElite().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollElite().getLevel())); - String master = (result.getClueScrollMaster().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollMaster().getLevel())); - text = "All clues: " + all + " | Rank: " + allRank + System.lineSeparator() - + "Easy: " + easy + " | Rank: " + easyRank + System.lineSeparator() - + "Medium: " + medium + " | Rank: " + mediumRank + System.lineSeparator() - + "Hard: " + hard + " | Rank: " + hardRank + System.lineSeparator() - + "Elite: " + elite + " | Rank: " + eliteRank + System.lineSeparator() - + "Master: " + master + " | Rank: " + masterRank; - break; - } - case "Bounty Hunter - Rogue": - { - String rank = (result.getBountyHunterRogue().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getBountyHunterRogue().getRank()); - text = "Bounty Hunter - Rogue Kills" + System.lineSeparator() - + "Rank: " + rank; - break; - } - case "Bounty Hunter - Hunter": - { - String rank = (result.getBountyHunterHunter().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getBountyHunterHunter().getRank()); - text = "Bounty Hunter - Hunter Kills" + System.lineSeparator() - + "Rank: " + rank; - break; - } - case "Last Man Standing": - { - String rank = (result.getLastManStanding().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getLastManStanding().getRank()); - text = "Last Man Standing" + System.lineSeparator() - + "Rank: " + rank; - break; - } - case "Overall": - { - Skill requestedSkill = result.getSkill(skill); - String rank = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getRank()); - String exp = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getExperience()); - text = "Skill: " + skillName + System.lineSeparator() - + "Rank: " + rank + System.lineSeparator() - + "Experience: " + exp; - break; - } - default: - { - Skill requestedSkill = result.getSkill(skill); - String rank = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getRank()); - String exp = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getExperience()); - String remainingXp; - if (requestedSkill.getRank() == -1) - { - remainingXp = "Unranked"; - } - else - { - int currentLevel = Experience.getLevelForXp((int) requestedSkill.getExperience()); - int currentXp = (int) requestedSkill.getExperience(); - int xpForCurrentLevel = Experience.getXpForLevel(currentLevel); - int xpForNextLevel = currentLevel + 1 <= Experience.MAX_VIRT_LEVEL ? Experience.getXpForLevel(currentLevel + 1) : -1; + c.gridx = 0; + c.gridy = 2; + gridBag.setConstraints(statsPanel, c); + add(statsPanel); - remainingXp = xpForNextLevel != -1 ? StackFormatter.formatNumber(xpForNextLevel - currentXp) : "0"; + JPanel totalPanel = new JPanel(); + totalPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + totalPanel.setLayout(new GridLayout(1, 2)); - double xpGained = currentXp - xpForCurrentLevel; - double xpGoal = xpForNextLevel != -1 ? xpForNextLevel - xpForCurrentLevel : 100; - progress = (int) ((xpGained / xpGoal) * 100f); + totalPanel.add(makeSkillPanel(null)); //combat has no hiscore skill, refered to as null + totalPanel.add(makeSkillPanel(OVERALL)); - } - text = "Skill: " + skillName + System.lineSeparator() - + "Rank: " + rank + System.lineSeparator() - + "Experience: " + exp + System.lineSeparator() - + "Remaining XP: " + remainingXp; - break; - } - } + c.gridx = 0; + c.gridy = 3; + gridBag.setConstraints(totalPanel, c); + add(totalPanel); - details.setFont(UIManager.getFont("Label.font")); - details.setText(text); + JPanel minigamePanel = new JPanel(); + // These aren't all on one row because when there's a label with four or more digits it causes the details + // panel to change its size for some reason... + minigamePanel.setLayout(new GridLayout(2, 3)); + minigamePanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); - if (progress >= 0) - { - progressBar.setVisible(true); - progressBar.setValue(progress); - progressBar.setBackground(Color.getHSBColor((progress / 100.f) * (120.f / 360.f), 1, 1)); - } - else - { - progressBar.setVisible(false); - } + minigamePanel.add(makeSkillPanel(CLUE_SCROLL_ALL)); + minigamePanel.add(makeSkillPanel(LAST_MAN_STANDING)); + minigamePanel.add(makeSkillPanel(BOUNTY_HUNTER_ROGUE)); + minigamePanel.add(makeSkillPanel(BOUNTY_HUNTER_HUNTER)); + c.gridx = 0; + c.gridy = 4; + gridBag.setConstraints(minigamePanel, c); + add(minigamePanel); } @Override @@ -492,16 +326,14 @@ public class HiscorePanel extends PluginPanel input.requestFocusInWindow(); } - private JPanel makeSkillPanel(String skillName, HiscoreSkill skill) + /* Builds a JPanel displaying an icon and level/number associated with it */ + private JPanel makeSkillPanel(HiscoreSkill skill) { JLabel label = new JLabel(); + label.setFont(FontManager.getRunescapeSmallFont()); label.setText("--"); - // Store the skill that the label displays so we can tell them apart - label.putClientProperty(SKILL_NAME, skillName); - label.putClientProperty(SKILL, skill); - - String skillIcon = "skill_icons_small/" + skillName.toLowerCase() + ".png"; + String skillIcon = "skill_icons_small/" + (skill == null ? "combat" : skill.getName().toLowerCase()) + ".png"; log.debug("Loading skill icon from {}", skillIcon); try @@ -518,36 +350,31 @@ public class HiscorePanel extends PluginPanel log.warn(null, ex); } - // Show skill details on hover - label.addMouseListener(new MouseInputAdapter() - { - @Override - public void mouseEntered(MouseEvent e) - { - JLabel source = (JLabel) e.getSource(); - String skillName = (String) source.getClientProperty(SKILL_NAME); - HiscoreSkill skill = (HiscoreSkill) label.getClientProperty(SKILL); - changeDetail(skillName, skill); - } - }); - skillLabels.add(label); + boolean totalLabel = skill == HiscoreSkill.OVERALL || skill == null; //overall or combat + label.setIconTextGap(totalLabel ? 10 : 4); JPanel skillPanel = new JPanel(); + skillPanel.setOpaque(false); + skillPanel.setBorder(new EmptyBorder(2, 0, 2, 0)); + skillLabels.add(label); skillPanel.add(skillLabels.get(skillLabels.size() - 1)); + return skillPanel; } public void lookup(String username) { input.setText(username); + + selectedEndPoint = HiscoreEndpoint.NORMAL; //reset the endpoint to regular player + updateButtons(); + lookup(); } private void lookup() { String lookup = input.getText(); - details.setText("Loading..."); - progressBar.setVisible(false); lookup = sanitize(lookup); @@ -556,43 +383,77 @@ public class HiscorePanel extends PluginPanel return; } + /* Runescape usernames can't be longer than 12 characters long */ + if (lookup.length() > MAX_USERNAME_LENGTH) + { + input.setIcon(ERROR_ICON); + loading = false; + return; + } + + input.setEditable(false); + input.setIcon(LOADING_ICON); + loading = true; + for (JLabel label : skillLabels) { label.setText("--"); } + // if for some reason no endpoint was selected, default to normal + if (selectedEndPoint == null) + { + selectedEndPoint = HiscoreEndpoint.NORMAL; + } + try { - HiscoreEndpoint endpoint = HiscoreEndpoint.valueOf(endpointButtonGroup.getSelection().getActionCommand()); - log.debug("Hiscore endpoint " + endpoint.name() + " selected"); - - result = hiscoreClient.lookup(lookup, endpoint); + log.debug("Hiscore endpoint " + selectedEndPoint.name() + " selected"); + result = hiscoreClient.lookup(lookup, selectedEndPoint); } catch (IOException ex) { log.warn("Error fetching Hiscore data " + ex.getMessage()); - details.setText("Error fetching Hiscore data"); - progressBar.setVisible(false); + input.setIcon(ERROR_ICON); + input.setEditable(true); + loading = false; return; } + /* + For some reason, the fetch results would sometimes return a not null object + with all null attributes, to check for that, i'll just null check one of the attributes. + */ + if (result.getAttack() == null) + { + input.setIcon(ERROR_ICON); + input.setEditable(true); + loading = false; + return; + } + + //successful player search + input.setIcon(SEARCH_ICON); + input.setEditable(true); + loading = false; + + int index = 0; for (JLabel label : skillLabels) { - String skillName = (String) label.getClientProperty(SKILL_NAME); - HiscoreSkill skill = (HiscoreSkill) label.getClientProperty(SKILL); + HiscoreSkill skill = find(index); - if (skillName.equals("Combat")) + if (skill == null) { if (result.getPlayer() != null) { int combatLevel = Experience.getCombatLevel( - result.getAttack().getLevel(), - result.getStrength().getLevel(), - result.getDefence().getLevel(), - result.getHitpoints().getLevel(), - result.getMagic().getLevel(), - result.getRanged().getLevel(), - result.getPrayer().getLevel() + result.getAttack().getLevel(), + result.getStrength().getLevel(), + result.getDefence().getLevel(), + result.getHitpoints().getLevel(), + result.getMagic().getLevel(), + result.getRanged().getLevel(), + result.getPrayer().getLevel() ); label.setText(Integer.toString(combatLevel)); } @@ -600,7 +461,6 @@ public class HiscorePanel extends PluginPanel else if (result.getSkill(skill) != null && result.getSkill(skill).getRank() != -1) { Skill s = result.getSkill(skill); - int level; if (config.virtualLevels() && SKILLS.contains(skill)) { @@ -613,12 +473,191 @@ public class HiscorePanel extends PluginPanel label.setText(Integer.toString(level)); } + + label.setToolTipText(detailsHtml(skill)); + index++; + } + } + + void addInputKeyListener(KeyListener l) + { + this.input.addInputKeyListener(l); + } + + void removeInputKeyListener(KeyListener l) + { + this.input.removeInputKeyListener(l); + } + + /* + Returns a hiscore skill based on it's display order. + */ + private HiscoreSkill find(int index) + { + if (index < SKILLS.size()) + { + return SKILLS.get(index); } - // Clear details panel - details.setFont(UIManager.getFont("Label.font").deriveFont(Font.ITALIC)); - details.setText("Hover over a skill for details"); - progressBar.setVisible(false); + switch (index - SKILLS.size()) + { + case 0: + return null; + case 1: + return HiscoreSkill.OVERALL; + case 2: + return HiscoreSkill.CLUE_SCROLL_ALL; + case 3: + return HiscoreSkill.LAST_MAN_STANDING; + case 4: + return HiscoreSkill.BOUNTY_HUNTER_ROGUE; + case 5: + return HiscoreSkill.BOUNTY_HUNTER_HUNTER; + } + + return null; + } + + /* + Builds a html string to display on tooltip (when hovering a skill). + */ + private String detailsHtml(HiscoreSkill skill) + { + String openingTags = ""; + String closingTags = ""; + + String content = ""; + + if (result != null) + { + if (skill == null) + { + double combatLevel = Experience.getCombatLevelPrecise( + result.getAttack().getLevel(), + result.getStrength().getLevel(), + result.getDefence().getLevel(), + result.getHitpoints().getLevel(), + result.getMagic().getLevel(), + result.getRanged().getLevel(), + result.getPrayer().getLevel() + ); + + double combatExperience = result.getAttack().getExperience() + + result.getStrength().getExperience() + result.getDefence().getExperience() + + result.getHitpoints().getExperience() + result.getMagic().getExperience() + + result.getRanged().getExperience() + result.getPrayer().getExperience(); + + content += "

Skill: Combat

"; + content += "

Exact Combat Level: " + StackFormatter.formatNumber(combatLevel) + "

"; + content += "

Experience: " + StackFormatter.formatNumber(combatExperience) + "

"; + } + else + { + switch (skill) + { + case CLUE_SCROLL_ALL: + { + String rank = (result.getClueScrollAll().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollAll().getRank()); + String allRank = (result.getClueScrollAll().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollAll().getRank()); + String easyRank = (result.getClueScrollEasy().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollEasy().getRank()); + String mediumRank = (result.getClueScrollMedium().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollMedium().getRank()); + String hardRank = (result.getClueScrollHard().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollHard().getRank()); + String eliteRank = (result.getClueScrollElite().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollElite().getRank()); + String masterRank = (result.getClueScrollMaster().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollMaster().getRank()); + String all = (result.getClueScrollAll().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollAll().getLevel())); + String easy = (result.getClueScrollEasy().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollEasy().getLevel())); + String medium = (result.getClueScrollMedium().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollMedium().getLevel())); + String hard = (result.getClueScrollHard().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollHard().getLevel())); + String elite = (result.getClueScrollElite().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollElite().getLevel())); + String master = (result.getClueScrollMaster().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollMaster().getLevel())); + content += "

All: " + all + " Rank: " + allRank + "

"; + content += "

Easy: " + easy + " Rank: " + easyRank + "

"; + content += "

Medium: " + medium + " Rank: " + mediumRank + "

"; + content += "

Hard: " + hard + " Rank: " + hardRank + "

"; + content += "

Elite: " + elite + " Rank: " + eliteRank + "

"; + content += "

Master: " + master + " Rank: " + masterRank + "

"; + break; + } + case BOUNTY_HUNTER_ROGUE: + { + String rank = (result.getBountyHunterRogue().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getBountyHunterRogue().getRank()); + content += "

Rank: " + rank + "

"; + break; + } + case BOUNTY_HUNTER_HUNTER: + { + String rank = (result.getBountyHunterHunter().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getBountyHunterHunter().getRank()); + content += "

Rank: " + rank + "

"; + break; + } + case LAST_MAN_STANDING: + { + String rank = (result.getLastManStanding().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getLastManStanding().getRank()); + content += "

Rank: " + rank + "

"; + break; + } + case OVERALL: + { + Skill requestedSkill = result.getSkill(skill); + String rank = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getRank()); + String exp = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getExperience()); + content += "

Skill: " + skill.getName() + "

"; + content += "

Rank: " + rank + "

"; + content += "

Experience: " + exp + "

"; + break; + } + default: + { + Skill requestedSkill = result.getSkill(skill); + + String rank = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getRank()); + String exp = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getExperience()); + String remainingXp; + if (requestedSkill.getRank() == -1) + { + remainingXp = "Unranked"; + } + else + { + int currentLevel = Experience.getLevelForXp((int) requestedSkill.getExperience()); + remainingXp = (currentLevel + 1 <= Experience.MAX_VIRT_LEVEL) ? StackFormatter.formatNumber(Experience.getXpForLevel(currentLevel + 1) - requestedSkill.getExperience()) : "0"; + } + + content += "

Skill: " + skill.getName() + "

"; + content += "

Rank: " + rank + "

"; + content += "

Experience: " + exp + "

"; + content += "

Remaining XP: " + remainingXp + "

"; + + break; + } + } + } + } + + /** + * Adds a html progress bar to the hover information + */ + if (SKILLS.contains(skill)) + { + int currentLevel = Experience.getLevelForXp((int) result.getSkill(skill).getExperience()); + int currentXp = (int) result.getSkill(skill).getExperience(); + int xpForCurrentLevel = Experience.getXpForLevel(currentLevel); + int xpForNextLevel = currentLevel + 1 <= Experience.MAX_VIRT_LEVEL ? Experience.getXpForLevel(currentLevel + 1) : -1; + + double xpGained = currentXp - xpForCurrentLevel; + double xpGoal = xpForNextLevel != -1 ? xpForNextLevel - xpForCurrentLevel : 100; + int progress = (int) ((xpGained / xpGoal) * 100f); + + // had to wrap the bar with an empty div, if i added the margin directly to the bar, it would mess up + content += "
" + + "
" + + "
" + + "
" + + "
" + + "
"; + } + + return openingTags + content + closingTags; } private static String sanitize(String lookup) @@ -626,20 +665,18 @@ public class HiscorePanel extends PluginPanel return lookup.replace('\u00A0', ' '); } + /* + When an endpoint gets selected, this method will correctly display the selected one + with an orange underline. + */ private void updateButtons() { - for (JToggleButton button : endpointButtons) + for (JPanel panel : endPoints) { - Color color; - if (button.isSelected()) - { - color = Color.CYAN; - } - else - { - color = Color.WHITE; - } - button.setBackground(color); + panel.setBorder(new EmptyBorder(0, 0, 1, 0)); } + + int selectedIndex = selectedEndPoint.ordinal(); + endPoints.get(selectedIndex).setBorder(new MatteBorder(0, 0, 1, 0, ColorScheme.BRAND_ORANGE)); } -} +} \ No newline at end of file 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 499a8777e6..5ad19a9f55 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 @@ -98,12 +98,13 @@ public class HiscorePlugin extends Plugin BufferedImage icon; synchronized (ImageIO.class) { - icon = ImageIO.read(getClass().getResourceAsStream("hiscore.gif")); + icon = ImageIO.read(getClass().getResourceAsStream("normal.png")); } navButton = NavigationButton.builder() .name("Hiscore") .icon(icon) + .priority(5) .panel(hiscorePanel) .build(); 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 42de837fa5..778c3e262b 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 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018 Abex + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -27,14 +28,22 @@ package net.runelite.client.plugins.info; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import com.google.inject.Inject; +import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Font; +import java.awt.GridLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; import java.util.concurrent.ScheduledExecutorService; import javax.annotation.Nullable; +import javax.imageio.ImageIO; import javax.inject.Singleton; -import javax.swing.BorderFactory; -import javax.swing.GroupLayout; +import javax.swing.Box; +import javax.swing.ImageIcon; import javax.swing.JLabel; -import javax.swing.LayoutStyle; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; import javax.swing.event.HyperlinkEvent; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; @@ -42,8 +51,10 @@ import net.runelite.api.events.SessionClose; import net.runelite.api.events.SessionOpen; import net.runelite.client.RuneLiteProperties; import net.runelite.client.account.SessionManager; +import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; import net.runelite.client.ui.PluginPanel; +import net.runelite.client.util.LinkBrowser; import net.runelite.client.util.RunnableExceptionLogger; @Slf4j @@ -52,6 +63,14 @@ public class InfoPanel extends PluginPanel { private static final String RUNELITE_LOGIN = "https://runelite_login/"; + private static final ImageIcon ARROW_RIGHT_ICON; + private static final ImageIcon GITHUB_ICON; + private static final ImageIcon DISCORD_ICON; + private static final ImageIcon PATREON_ICON; + + private final JLabel loggedLabel = new JLabel(); + private final JRichTextPane emailLabel = new JRichTextPane(); + @Inject @Nullable private Client client; @@ -68,33 +87,58 @@ public class InfoPanel extends PluginPanel @Inject private ScheduledExecutorService executor; - private final GroupLayout layout = new GroupLayout(this); - private final JLabel usernameHeader = new JLabel(); - private final JRichTextPane username = new JRichTextPane(); + static + { + try + { + synchronized (ImageIO.class) + { + ARROW_RIGHT_ICON = new ImageIcon(ImageIO.read(InfoPanel.class.getResourceAsStream("arrow_right.png"))); + GITHUB_ICON = new ImageIcon(ImageIO.read(InfoPanel.class.getResourceAsStream("github_icon.png"))); + DISCORD_ICON = new ImageIcon(ImageIO.read(InfoPanel.class.getResourceAsStream("discord_icon.png"))); + PATREON_ICON = new ImageIcon(ImageIO.read(InfoPanel.class.getResourceAsStream("patreon_icon.png"))); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } void init() { - setLayout(layout); + setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); + setBorder(new EmptyBorder(10, 10, 10, 10)); + + JPanel versionPanel = new JPanel(); + versionPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + versionPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + versionPanel.setLayout(new GridLayout(0, 1)); final Font smallFont = FontManager.getRunescapeSmallFont(); - final JLabel runeliteVersionHeader = new JLabel("RuneLite version"); - runeliteVersionHeader.setFont(smallFont); - final JLabel runeliteVersion = new JLabel(runeLiteProperties.getVersion()); + JLabel version = new JLabel(htmlLabel("RuneLite version: ", runeLiteProperties.getVersion())); + version.setFont(smallFont); - final JLabel runescapeVersionHeader = new JLabel("OldSchool Engine"); - runescapeVersionHeader.setFont(smallFont); + JLabel revision = new JLabel(); + revision.setFont(smallFont); String engineVer = "Unknown"; if (client != null) { engineVer = String.format("Rev %s", runeLiteProperties.getRunescapeVersion()); } - final JLabel runescapeVersion = new JLabel(engineVer); - usernameHeader.setFont(smallFont); - username.enableAutoLinkHandler(false); - username.addHyperlinkListener(e -> + revision.setText(htmlLabel("Oldschool revision: ", engineVer)); + + loggedLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR); + loggedLabel.setFont(smallFont); + + emailLabel.setForeground(Color.WHITE); + emailLabel.setFont(smallFont); + emailLabel.enableAutoLinkHandler(false); + emailLabel.addHyperlinkListener(e -> { if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType()) && e.getURL() != null) { @@ -105,51 +149,74 @@ public class InfoPanel extends PluginPanel } }); + versionPanel.add(version); + versionPanel.add(revision); + versionPanel.add(Box.createGlue()); + versionPanel.add(loggedLabel); + versionPanel.add(emailLabel); + updateLoggedIn(); - final JRichTextPane issueLink = new JRichTextPane("text/html", - "RuneLite is open source!
" - + "Found an issue? Want a feature?
" - + "" - + "Open an issue on GitHub!" - + "" - ); + JPanel actionsContainer = new JPanel(); + actionsContainer.setBorder(new EmptyBorder(10, 0, 0, 0)); + actionsContainer.setOpaque(false); + actionsContainer.setLayout(new GridLayout(3, 1, 0, 10)); - setBorder(BorderFactory.createEmptyBorder(2, 6, 6, 6)); + actionsContainer.add(buildLinkPanel(GITHUB_ICON, "Report an issue or", "make a suggestion", runeLiteProperties.getGithubLink())); + actionsContainer.add(buildLinkPanel(DISCORD_ICON, "Talk to us on our", "discord server", runeLiteProperties.getDiscordInvite())); + actionsContainer.add(buildLinkPanel(PATREON_ICON, "Become a patron to", "help support RuneLite", runeLiteProperties.getPatreonLink())); - layout.setVerticalGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup() - .addComponent(runeliteVersionHeader) - .addComponent(runescapeVersionHeader) - ).addGroup(layout.createParallelGroup() - .addComponent(runeliteVersion) - .addComponent(runescapeVersion) - ).addGap(6) - .addComponent(usernameHeader) - .addGroup(layout.createParallelGroup() - .addComponent(username) - ) - .addGap(12) - .addComponent(issueLink) - ); - - layout.setHorizontalGroup(layout.createParallelGroup() - .addGroup(layout.createSequentialGroup() - .addComponent(runeliteVersionHeader) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) - .addComponent(runescapeVersionHeader)) - .addGroup(layout.createSequentialGroup() - .addComponent(runeliteVersion) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) - .addComponent(runescapeVersion)) - .addComponent(usernameHeader) - .addComponent(username) - .addComponent(issueLink) - ); + add(versionPanel, BorderLayout.NORTH); + add(actionsContainer, BorderLayout.CENTER); eventBus.register(this); } + /** + * Builds a link panel with a given icon, text and url to redirect to. + */ + private static JPanel buildLinkPanel(ImageIcon icon, String topText, String bottomText, String url) + { + JPanel container = new JPanel(); + container.setBackground(ColorScheme.DARKER_GRAY_COLOR); + container.setLayout(new BorderLayout()); + container.setBorder(new EmptyBorder(10, 10, 10, 10)); + container.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + LinkBrowser.browse(url); + } + }); + + JLabel iconLabel = new JLabel(icon); + container.add(iconLabel, BorderLayout.WEST); + + JPanel textContainer = new JPanel(); + textContainer.setOpaque(false); + textContainer.setLayout(new GridLayout(2, 1)); + textContainer.setBorder(new EmptyBorder(5, 10, 5, 10)); + + JLabel topLine = new JLabel(topText); + topLine.setForeground(Color.WHITE); + topLine.setFont(FontManager.getRunescapeSmallFont()); + + JLabel bottomLine = new JLabel(bottomText); + bottomLine.setForeground(Color.WHITE); + bottomLine.setFont(FontManager.getRunescapeSmallFont()); + + textContainer.add(topLine); + textContainer.add(bottomLine); + + container.add(textContainer, BorderLayout.CENTER); + + JLabel arrowLabel = new JLabel(ARROW_RIGHT_ICON); + container.add(arrowLabel, BorderLayout.EAST); + + return container; + } + private void updateLoggedIn() { final String name = sessionManager.getAccountSession() != null @@ -158,18 +225,23 @@ public class InfoPanel extends PluginPanel if (name != null) { - username.setContentType("text/plain"); - username.setText(name); - usernameHeader.setText("Logged in as"); + emailLabel.setContentType("text/plain"); + emailLabel.setText(name); + loggedLabel.setText("Logged in as"); } else { - username.setContentType("text/html"); - username.setText("Login to sync settings to the cloud."); - usernameHeader.setText("Not logged in"); + emailLabel.setContentType("text/html"); + emailLabel.setText("Login to sync settings to the cloud."); + loggedLabel.setText("Not logged in"); } } + private static String htmlLabel(String key, String value) + { + return "" + key + "" + value + ""; + } + @Subscribe public void onSessionOpen(SessionOpen sessionOpen) { 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 bb14b4ea3e..0b1905ff98 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 @@ -58,6 +58,7 @@ public class InfoPlugin extends Plugin navButton = NavigationButton.builder() .name("Info") .icon(icon) + .priority(9) .panel(panel) .build(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/BookPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/BookPanel.java index b819731a03..5c169aab11 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/BookPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/BookPanel.java @@ -28,6 +28,7 @@ import java.awt.Color; import javax.swing.GroupLayout; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; import net.runelite.client.ui.FontManager; class BookPanel extends JPanel @@ -36,6 +37,9 @@ class BookPanel extends JPanel BookPanel(Book b) { + setBorder(new EmptyBorder(3, 3, 3, 3)); + setOpaque(false); + GroupLayout layout = new GroupLayout(this); this.setLayout(layout); @@ -74,4 +78,4 @@ class BookPanel extends JPanel { location.setForeground(target ? Color.GREEN : Color.WHITE); } -} +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPanel.java index fb894ee2a8..1afaf27961 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPanel.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018 Abex + * Copyright (c) 2018 Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,35 +26,71 @@ package net.runelite.client.plugins.kourendlibrary; import com.google.inject.Inject; +import java.awt.BorderLayout; +import java.awt.Color; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.io.IOException; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.imageio.ImageIO; import javax.inject.Singleton; +import javax.swing.BorderFactory; import javax.swing.GroupLayout; -import javax.swing.JButton; +import javax.swing.ImageIcon; +import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.PluginPanel; +import net.runelite.client.util.SwingUtil; @Singleton public class KourendLibraryPanel extends PluginPanel { + private static final ImageIcon RESET_ICON; + private static final ImageIcon RESET_CLICK_ICON; + @Inject private Library library; private final HashMap bookPanels = new HashMap<>(); + static + { + try + { + synchronized (ImageIO.class) + { + BufferedImage resetIcon = ImageIO.read(KourendLibraryPanel.class.getResourceAsStream("reset.png")); + RESET_ICON = new ImageIcon(resetIcon); + RESET_CLICK_ICON = new ImageIcon(SwingUtil.grayscaleOffset(resetIcon, -100)); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + void init() { GroupLayout layout = new GroupLayout(this); setLayout(layout); + setBorder(new EmptyBorder(10, 10, 10, 10)); + setBackground(ColorScheme.DARK_GRAY_COLOR); JPanel books = new JPanel(new GridBagLayout()); + books.setOpaque(false); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1; @@ -70,21 +107,45 @@ public class KourendLibraryPanel extends PluginPanel c.gridy++; }); - JButton reset = new JButton("Reset"); - reset.addActionListener(e -> + JLabel reset = new JLabel(RESET_ICON); + reset.addMouseListener(new MouseAdapter() { - library.reset(); - update(); + @Override + public void mousePressed(MouseEvent mouseEvent) + { + reset.setIcon(RESET_CLICK_ICON); + library.reset(); + update(); + } + + @Override + public void mouseReleased(MouseEvent mouseEvent) + { + reset.setIcon(RESET_ICON); + } }); + JPanel header = new JPanel(); + header.setOpaque(false); + header.setLayout(new BorderLayout()); + header.setBorder(new CompoundBorder( + BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(58, 58, 58)), + BorderFactory.createEmptyBorder(0, 0, 10, 0))); + + JLabel pluginName = new JLabel("Kourend Library Plugin"); + pluginName.setForeground(Color.WHITE); + + header.add(reset, BorderLayout.EAST); + header.add(pluginName, BorderLayout.CENTER); + layout.setHorizontalGroup(layout.createParallelGroup() .addComponent(books) - .addComponent(reset) + .addComponent(header) ); layout.setVerticalGroup(layout.createSequentialGroup() + .addComponent(header) + .addGap(10) .addComponent(books) - .addGap(4) - .addComponent(reset) ); update(); @@ -134,4 +195,4 @@ public class KourendLibraryPanel extends PluginPanel } }); } -} +} \ No newline at end of file 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 154abbcb9e..83406e0f2d 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 @@ -94,6 +94,7 @@ public class KourendLibraryPlugin extends Plugin navButton = NavigationButton.builder() .name("Kourend Library") + .priority(6) .icon(icon) .panel(panel) .build(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/notes/NotesPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/notes/NotesPanel.java index d0cd509e4b..b0d6039b30 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/notes/NotesPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/notes/NotesPanel.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018 Charlie Waters + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,13 +25,17 @@ */ package net.runelite.client.plugins.notes; +import java.awt.Color; import javax.swing.BorderFactory; import javax.swing.JTextArea; import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import lombok.extern.slf4j.Slf4j; +import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.PluginPanel; import java.awt.BorderLayout; @@ -50,14 +55,24 @@ public class NotesPanel extends PluginPanel getParent().add(this, BorderLayout.CENTER); setLayout(new BorderLayout()); - setBorder(BorderFactory.createEmptyBorder(2, 6, 6, 6)); + setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + setBackground(ColorScheme.DARK_GRAY_COLOR); final JLabel notesHeader = new JLabel("Notes"); + notesHeader.setForeground(Color.WHITE); + notesHeader.setBorder(new EmptyBorder(1, 0, 10, 0)); + add(notesHeader, BorderLayout.NORTH); notesEditor.setLineWrap(true); notesEditor.setWrapStyleWord(true); + JPanel notesContainer = new JPanel(); + notesContainer.setLayout(new BorderLayout()); + notesContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + notesEditor.setOpaque(false); + // load note text String data = config.notesData(); notesEditor.setText(data); @@ -90,7 +105,10 @@ public class NotesPanel extends PluginPanel } } }); - add(notesEditor, BorderLayout.CENTER); + notesContainer.add(notesEditor, BorderLayout.CENTER); + notesContainer.setBorder(new EmptyBorder(10, 10, 10, 10)); + + add(notesContainer, BorderLayout.CENTER); } void setNotes(String data) 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 0cd89cfa66..a6fac2de52 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 @@ -74,6 +74,7 @@ public class NotesPlugin extends Plugin navButton = NavigationButton.builder() .name("Notes") .icon(icon) + .priority(7) .panel(panel) .build(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculatorPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculatorPanel.java index 25b2797c3d..b0e520a33e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculatorPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculatorPanel.java @@ -38,6 +38,7 @@ import javax.swing.JButton; import javax.swing.JPanel; import net.runelite.api.Client; import net.runelite.client.game.SkillIconManager; +import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.PluginPanel; class SkillCalculatorPanel extends PluginPanel @@ -64,10 +65,13 @@ class SkillCalculatorPanel extends PluginPanel uiButtonGridConstraints.insets = new Insets(2, 2, 2, 2); uiButtonGrid.setLayout(uiButtonGridLayout); + uiButtonGrid.setBackground(ColorScheme.DARK_GRAY_COLOR); uiButtonGrid.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); addCalculatorButtons(); final UICalculatorInputArea uiInput = new UICalculatorInputArea(); + uiInput.setBackground(ColorScheme.DARK_GRAY_COLOR); + uiCalculator = new SkillCalculator(client, uiInput); add(uiButtonGrid); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculatorPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculatorPlugin.java index 66bfbc70c0..df124975a6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculatorPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculatorPlugin.java @@ -75,7 +75,12 @@ public class SkillCalculatorPlugin extends Plugin SkillCalculator.itemManager = itemManager; uiPanel = new SkillCalculatorPanel(skillIconManager, client); - uiNavigationButton = NavigationButton.builder().name("Skill Calculator").icon(icon).panel(uiPanel).build(); + uiNavigationButton = NavigationButton.builder() + .name("Skill Calculator") + .icon(icon) + .priority(6) + .panel(uiPanel) + .build(); pluginToolbar.addNavigation(uiNavigationButton); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/UIActionSlot.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/UIActionSlot.java index 74c3bfe16f..dcc6cba345 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/UIActionSlot.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/UIActionSlot.java @@ -34,7 +34,7 @@ import javax.swing.JLabel; import javax.swing.JPanel; import net.runelite.client.plugins.skillcalculator.beans.SkillDataEntry; import net.runelite.client.ui.FontManager; -import net.runelite.client.ui.JShadowedLabel; +import net.runelite.client.ui.components.shadowlabel.JShadowedLabel; class UIActionSlot extends JPanel { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/UICombinedActionSlot.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/UICombinedActionSlot.java index acdd05b7de..d981e9d97f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/UICombinedActionSlot.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/UICombinedActionSlot.java @@ -33,7 +33,7 @@ import javax.swing.BorderFactory; import javax.swing.JLabel; import javax.swing.JPanel; import net.runelite.client.ui.FontManager; -import net.runelite.client.ui.JShadowedLabel; +import net.runelite.client.ui.components.shadowlabel.JShadowedLabel; class UICombinedActionSlot extends JPanel { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/SkillColor.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/SkillColor.java new file mode 100644 index 0000000000..fe2a808e91 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/SkillColor.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.xptracker; + +import java.awt.Color; +import lombok.Getter; + +public enum SkillColor +{ + ATTACK(105, 32, 7), + DEFENCE(98, 119, 190), + STRENGTH(4, 149, 90), + HITPOINTS(131, 126, 126), + RANGED(109, 144, 23), + PRAYER(159, 147, 35), + MAGIC(50, 80, 193), + COOKING(112, 35, 134), + WOODCUTTING(52, 140, 37), + FLETCHING(3, 141, 125), + FISHING(106, 132, 164), + FIREMAKING(189, 120, 25), + CRAFTING(151, 110, 77), + SMITHING(108, 107, 82), + MINING(93, 143, 167), + HERBLORE(7, 133, 9), + AGILITY(58, 60, 137), + THIEVING(108, 52, 87), + SLAYER(100, 100, 100), + FARMING(101, 152, 63), + RUNECRAFT(170, 141, 26), + HUNTER(92, 89, 65), + CONSTRUCTION(130, 116, 95); + + @Getter + private final Color color; + + SkillColor(int red, int green, int blue) + { + this.color = new Color(red, green, blue); + } + +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpInfoBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpInfoBox.java index 01bcdcc0bd..547671a335 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpInfoBox.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpInfoBox.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Adam + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,54 +27,54 @@ package net.runelite.client.plugins.xptracker; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Dimension; import java.awt.GridLayout; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.image.BufferedImage; -import java.awt.image.LookupOp; -import java.awt.image.LookupTable; import java.io.IOException; import javax.swing.ImageIcon; -import javax.swing.JButton; import javax.swing.JLabel; -import javax.swing.JLayeredPane; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; -import javax.swing.JProgressBar; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; -import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; -import javax.swing.border.LineBorder; +import lombok.AccessLevel; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.Skill; import net.runelite.client.game.SkillIconManager; -import net.runelite.client.ui.JShadowedLabel; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.components.ProgressBar; import net.runelite.client.util.LinkBrowser; -import org.pushingpixels.substance.internal.SubstanceSynapse; +import net.runelite.client.util.StackFormatter; +import net.runelite.client.util.SwingUtil; @Slf4j class XpInfoBox extends JPanel { - private static final Rectangle ICON_BOUNDS = new Rectangle(0, 0, 26, 26); - private final Client client; private final JPanel panel; + + @Getter(AccessLevel.PACKAGE) private final Skill skill; + /* The tracker's wrapping container */ private final JPanel container = new JPanel(); + + /* Contains the skill icon and the stats panel */ + private final JPanel headerPanel = new JPanel(); + + /* Contains all the skill information (exp gained, per hour, etc) */ private final JPanel statsPanel = new JPanel(); - private final JProgressBar progressBar = new JProgressBar(); - private final JLabel xpHr = new JLabel(); - private final JLabel xpGained = new JLabel(); - private final JLabel xpLeft = new JLabel(); + + private final ProgressBar progressBar = new ProgressBar(); + + private final JLabel expGained = new JLabel(); + private final JLabel expHour = new JLabel(); + private final JLabel expLeft = new JLabel(); private final JLabel actionsLeft = new JLabel(); - private final JLabel levelLabel = new JShadowedLabel(); XpInfoBox(XpTrackerPlugin xpTrackerPlugin, Client client, JPanel panel, Skill skill, SkillIconManager iconManager) throws IOException { @@ -82,121 +83,81 @@ class XpInfoBox extends JPanel this.skill = skill; setLayout(new BorderLayout()); - setBorder(new CompoundBorder - ( - new EmptyBorder(2, 0, 2, 0), - new LineBorder(getBackground().brighter(), 1) - )); - - // Expand stats panel on click - Color backgroundColor = getBackground(); - - MouseListener panelMouseListener = new MouseAdapter() - { - @Override - public void mouseEntered(MouseEvent e) - { - container.setBackground(backgroundColor.darker().darker()); - } - @Override - public void mouseExited(MouseEvent e) - { - container.setBackground(backgroundColor); - } - @Override - public void mouseReleased(MouseEvent e) - { - if (SwingUtilities.isLeftMouseButton(e)) - { - showStatsPanel(); - } - } - }; + setBorder(new EmptyBorder(10, 0, 0, 0)); + setOpaque(false); container.setLayout(new BorderLayout()); - container.setBorder(new EmptyBorder(5, 5, 5, 5)); - container.addMouseListener(panelMouseListener); + container.setOpaque(true); + container.setBackground(ColorScheme.DARKER_GRAY_COLOR); // Create open xp tracker menu - final JMenuItem openXpTracker = new JMenuItem("Open XP tracker"); + final JMenuItem openXpTracker = new JMenuItem("Open online tracker"); openXpTracker.addActionListener(e -> LinkBrowser.browse(XpPanel.buildXpTrackerUrl(client.getLocalPlayer(), skill))); + // Create reset menu + final JMenuItem reset = new JMenuItem("Reset"); + reset.addActionListener(e -> reset()); + // Create popup menu final JPopupMenu popupMenu = new JPopupMenu(); + popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5)); popupMenu.add(openXpTracker); + popupMenu.add(reset); container.setComponentPopupMenu(popupMenu); - // Create icon panel - final JPanel iconBarPanel = new JPanel(); - iconBarPanel.setLayout(new BorderLayout(5, 0)); - iconBarPanel.setOpaque(false); + JLabel skillIcon = new JLabel(new ImageIcon(iconManager.getSkillImage(skill))); + skillIcon.setHorizontalAlignment(SwingConstants.CENTER); + skillIcon.setVerticalAlignment(SwingConstants.CENTER); + skillIcon.setPreferredSize(new Dimension(35, 35)); - // Create skill/reset icon - final BufferedImage skillImage = iconManager.getSkillImage(skill); - final JButton skillIcon = new JButton(); + headerPanel.setOpaque(false); + headerPanel.setLayout(new BorderLayout()); - skillIcon.putClientProperty(SubstanceSynapse.FLAT_LOOK, Boolean.TRUE); - skillIcon.putClientProperty(SubstanceSynapse.BUTTON_NEVER_PAINT_BACKGROUND, Boolean.TRUE); - skillIcon.setIcon(new ImageIcon(skillImage)); - skillIcon.setRolloverIcon(new ImageIcon(createHoverImage(skillImage))); - - skillIcon.setToolTipText("Reset " + skill.getName() + " tracker"); - skillIcon.addActionListener(e -> xpTrackerPlugin.resetSkillState(skill)); - skillIcon.setBounds(ICON_BOUNDS); - skillIcon.setOpaque(false); - skillIcon.setFocusPainted(false); - - // Create level label - levelLabel.setHorizontalAlignment(JLabel.CENTER); - levelLabel.setForeground(Color.YELLOW); - levelLabel.setBounds(ICON_BOUNDS); - levelLabel.setOpaque(false); - - // Create pane for grouping skill icon and level label - final JLayeredPane layeredPane = new JLayeredPane(); - layeredPane.add(skillIcon, new Integer(1)); - layeredPane.add(levelLabel, new Integer(2)); - layeredPane.setPreferredSize(ICON_BOUNDS.getSize()); - iconBarPanel.add(layeredPane, BorderLayout.LINE_START); - - // Create progress bar - progressBar.setStringPainted(true); - progressBar.setValue(0); - progressBar.setMinimum(0); - progressBar.setMaximum(100); - progressBar.setBackground(Color.RED); - progressBar.addMouseListener(panelMouseListener); - iconBarPanel.add(progressBar, BorderLayout.CENTER); - - container.add(iconBarPanel, BorderLayout.NORTH); - - // Stats panel - statsPanel.setLayout(new GridLayout(2, 2)); - statsPanel.setBorder(new EmptyBorder(3, 0, 0, 0)); + statsPanel.setLayout(new BorderLayout()); + statsPanel.setBorder(new EmptyBorder(9, 5, 9, 10)); statsPanel.setOpaque(false); - statsPanel.add(xpGained); - xpHr.setHorizontalAlignment(SwingConstants.RIGHT); - statsPanel.add(xpHr); - statsPanel.add(xpLeft); - actionsLeft.setHorizontalAlignment(SwingConstants.RIGHT); - statsPanel.add(actionsLeft); + JPanel leftPanel = new JPanel(); + leftPanel.setOpaque(false); + leftPanel.setLayout(new GridLayout(2, 1)); - add(container, BorderLayout.CENTER); - } + expGained.setFont(FontManager.getRunescapeSmallFont()); + expHour.setFont(FontManager.getRunescapeSmallFont()); - private void showStatsPanel() - { - if (statsPanel.isShowing()) - { - container.remove(statsPanel); - revalidate(); - } - else - { - container.add(statsPanel, BorderLayout.SOUTH); - revalidate(); - } + leftPanel.add(expGained); + leftPanel.add(expHour); + + JPanel rightPanel = new JPanel(); + rightPanel.setOpaque(false); + rightPanel.setLayout(new GridLayout(2, 1)); + + expLeft.setFont(FontManager.getRunescapeSmallFont()); + actionsLeft.setFont(FontManager.getRunescapeSmallFont()); + + rightPanel.add(expLeft); + rightPanel.add(actionsLeft); + + statsPanel.add(leftPanel, BorderLayout.WEST); + statsPanel.add(rightPanel, BorderLayout.EAST); + + headerPanel.add(skillIcon, BorderLayout.WEST); + headerPanel.add(statsPanel, BorderLayout.CENTER); + + JPanel progressWrapper = new JPanel(); + progressWrapper.setOpaque(false); + progressWrapper.setLayout(new BorderLayout()); + progressWrapper.setBorder(new EmptyBorder(0, 7, 7, 7)); + + progressBar.setMaximumValue(100); + progressBar.setBackground(new Color(61, 56, 49)); + progressBar.setForeground(SkillColor.values()[skill.ordinal()].getColor()); + + progressWrapper.add(progressBar, BorderLayout.NORTH); + + container.add(headerPanel, BorderLayout.NORTH); + container.add(progressWrapper, BorderLayout.SOUTH); + + add(container, BorderLayout.NORTH); } void reset() @@ -221,46 +182,46 @@ class XpInfoBox extends JPanel panel.revalidate(); } - levelLabel.setText(String.valueOf(xpSnapshotSingle.getCurrentLevel())); - xpGained.setText(XpPanel.formatLine(xpSnapshotSingle.getXpGainedInSession(), "xp gained")); - xpLeft.setText(XpPanel.formatLine(xpSnapshotSingle.getXpRemainingToGoal(), "xp left")); - actionsLeft.setText(XpPanel.formatLine(xpSnapshotSingle.getActionsRemainingToGoal(), "actions left")); + // Update information labels + expGained.setText(htmlLabel("XP Gained: ", xpSnapshotSingle.getXpGainedInSession())); + expLeft.setText(htmlLabel("XP Left: ", xpSnapshotSingle.getXpRemainingToGoal())); + actionsLeft.setText(htmlLabel("Actions: ", xpSnapshotSingle.getActionsRemainingToGoal())); - final int progress = xpSnapshotSingle.getSkillProgressToGoal(); - - progressBar.setValue(progress); - progressBar.setBackground(Color.getHSBColor((progress / 100.f) * (120.f / 360.f), 1, 1)); + // Update progress bar + progressBar.setValue(xpSnapshotSingle.getSkillProgressToGoal()); + progressBar.setCenterLabel(xpSnapshotSingle.getSkillProgressToGoal() + "%"); + progressBar.setLeftLabel("Lvl. " + xpSnapshotSingle.getCurrentLevel()); + progressBar.setRightLabel("Lvl. " + (xpSnapshotSingle.getCurrentLevel() + 1)); progressBar.setToolTipText("" - + XpPanel.formatLine(xpSnapshotSingle.getActionsInSession(), "actions") + + xpSnapshotSingle.getActionsInSession() + " actions done" + "
" - + XpPanel.formatLine(xpSnapshotSingle.getActionsPerHour(), "actions/hr") + + xpSnapshotSingle.getActionsPerHour() + " actions/hr" + "
" + xpSnapshotSingle.getTimeTillGoal() + " till next lvl" + ""); + + progressBar.repaint(); } - // Always update xp/hr as time always changes - xpHr.setText(XpPanel.formatLine(xpSnapshotSingle.getXpPerHour(), "xp/hr")); + // Update exp per hour seperately, everytime (not only when there's an update) + expHour.setText(htmlLabel("XP/Hour: ", xpSnapshotSingle.getXpPerHour())); } - private static BufferedImage createHoverImage(BufferedImage image) + public static String htmlLabel(String key, int value) { - LookupTable lookup = new LookupTable(0, 4) + String valueStr = value + ""; + + if (value > 9999999 || value < -9999999) { - @Override - public int[] lookupPixel(int[] src, int[] dest) - { - if (dest[3] != 0) - { - dest[3] = 60; - } + valueStr = "Lots!"; + } + else + { + valueStr = StackFormatter.quantityToRSDecimalStack(value); + } - return dest; - } - }; - - LookupOp op = new LookupOp(lookup, new RenderingHints(null)); - return op.filter(image, null); + return "" + key + "" + valueStr + ""; } + } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpPanel.java index 1782caba72..602ab9a3d2 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpPanel.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Cameron + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,64 +30,93 @@ import java.awt.GridLayout; import java.io.IOException; import java.util.HashMap; import java.util.Map; -import javax.swing.BorderFactory; import javax.swing.BoxLayout; -import javax.swing.JButton; +import javax.swing.ImageIcon; import javax.swing.JLabel; +import javax.swing.JMenuItem; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Actor; import net.runelite.api.Client; import net.runelite.api.Skill; import net.runelite.client.game.SkillIconManager; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; import net.runelite.client.ui.PluginPanel; +import net.runelite.client.ui.components.PluginErrorPanel; import net.runelite.client.util.LinkBrowser; -import net.runelite.client.util.StackFormatter; import okhttp3.HttpUrl; @Slf4j class XpPanel extends PluginPanel { private final Map infoBoxes = new HashMap<>(); - private final JLabel totalXpGained = new JLabel(); - private final JLabel totalXpHr = new JLabel(); + + private final JLabel overallExpGained = new JLabel(XpInfoBox.htmlLabel("Gained: ", 0)); + private final JLabel overallExpHour = new JLabel(XpInfoBox.htmlLabel("Per hour: ", 0)); + + private final JPanel overallPanel = new JPanel(); + + /* This displays the "No exp gained" text */ + private final PluginErrorPanel errorPanel = new PluginErrorPanel(); XpPanel(XpTrackerPlugin xpTrackerPlugin, Client client, SkillIconManager iconManager) { super(); + setBorder(new EmptyBorder(10, 10, 10, 10)); + setBackground(ColorScheme.DARK_GRAY_COLOR); + final JPanel layoutPanel = new JPanel(); - layoutPanel.setLayout(new BorderLayout(0, 3)); + layoutPanel.setOpaque(false); + layoutPanel.setLayout(new BorderLayout()); add(layoutPanel); - final JPanel totalPanel = new JPanel(); - totalPanel.setLayout(new BorderLayout()); - totalPanel.setBorder(BorderFactory.createLineBorder(getBackground().brighter(), 1, true)); + overallPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + overallPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + overallPanel.setLayout(new BorderLayout()); + overallPanel.setVisible(false); // this will only become visible when the player gets exp - final JPanel infoPanel = new JPanel(); - infoPanel.setLayout(new GridLayout(4, 1)); - infoPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + // Create open xp tracker menu + final JMenuItem openXpTracker = new JMenuItem("Open online tracker"); + openXpTracker.addActionListener(e -> LinkBrowser.browse(XpPanel.buildXpTrackerUrl(client.getLocalPlayer(), Skill.OVERALL))); - final JButton resetButton = new JButton("Reset All"); - resetButton.addActionListener(e -> xpTrackerPlugin.resetAndInitState()); + // Create reset all menu + final JMenuItem reset = new JMenuItem("Reset All"); + reset.addActionListener(e -> xpTrackerPlugin.resetAndInitState()); - final JButton openTrackerButton = new JButton("Open XP tracker"); - openTrackerButton.addActionListener(e -> LinkBrowser.browse(buildXpTrackerUrl(client.getLocalPlayer(), Skill.OVERALL))); + // Create popup menu + final JPopupMenu popupMenu = new JPopupMenu(); + popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5)); + popupMenu.add(openXpTracker); + popupMenu.add(reset); + overallPanel.setComponentPopupMenu(popupMenu); - totalXpGained.setText(formatLine(0, "total xp gained")); - totalXpHr.setText(formatLine(0, "total xp/hr")); + final JLabel overallIcon = new JLabel(new ImageIcon(iconManager.getSkillImage(Skill.OVERALL))); + + final JPanel overallInfo = new JPanel(); + overallInfo.setOpaque(false); + overallInfo.setLayout(new GridLayout(2, 1)); + overallInfo.setBorder(new EmptyBorder(0, 10, 0, 0)); + + overallExpGained.setFont(FontManager.getRunescapeSmallFont()); + overallExpHour.setFont(FontManager.getRunescapeSmallFont()); + + overallInfo.add(overallExpGained); + overallInfo.add(overallExpHour); + + overallPanel.add(overallIcon, BorderLayout.WEST); + overallPanel.add(overallInfo, BorderLayout.CENTER); - infoPanel.add(totalXpGained); - infoPanel.add(totalXpHr); - infoPanel.add(resetButton); - infoPanel.add(openTrackerButton); - totalPanel.add(infoPanel, BorderLayout.CENTER); - layoutPanel.add(totalPanel, BorderLayout.NORTH); final JPanel infoBoxPanel = new JPanel(); + infoBoxPanel.setOpaque(false); infoBoxPanel.setLayout(new BoxLayout(infoBoxPanel, BoxLayout.Y_AXIS)); layoutPanel.add(infoBoxPanel, BorderLayout.CENTER); + layoutPanel.add(overallPanel, BorderLayout.NORTH); try { @@ -96,7 +126,6 @@ class XpPanel extends PluginPanel { break; } - infoBoxes.put(skill, new XpInfoBox(xpTrackerPlugin, client, infoBoxPanel, skill, iconManager)); } } @@ -104,6 +133,10 @@ class XpPanel extends PluginPanel { log.warn(null, e); } + + errorPanel.setContent("Exp trackers", "You have not gained experience yet."); + errorPanel.setBorder(new EmptyBorder(0, 0, 0, 0)); + add(errorPanel); } static String buildXpTrackerUrl(final Actor player, final Skill skill) @@ -150,30 +183,23 @@ class XpPanel extends PluginPanel } } - void updateTotal(XpSnapshotTotal xpSnapshotTotal) + + public void updateTotal(XpSnapshotTotal xpSnapshotTotal) { + // if player has gained exp and hasn't switched displays yet, hide error panel and show overall info + if (xpSnapshotTotal.getXpGainedInSession() > 0 && !overallPanel.isVisible()) + { + overallPanel.setVisible(true); + remove(errorPanel); + } + SwingUtilities.invokeLater(() -> rebuildAsync(xpSnapshotTotal)); } private void rebuildAsync(XpSnapshotTotal xpSnapshotTotal) { - totalXpGained.setText(formatLine(xpSnapshotTotal.getXpGainedInSession(), "total xp gained")); - totalXpHr.setText(formatLine(xpSnapshotTotal.getXpPerHour(), "total xp/hr")); + overallExpGained.setText(XpInfoBox.htmlLabel("Gained: ", xpSnapshotTotal.getXpGainedInSession())); + overallExpHour.setText(XpInfoBox.htmlLabel("Per hour: ", xpSnapshotTotal.getXpPerHour())); } - static String formatLine(double number, String description) - { - String numberStr; - if (number < 100000) - { - numberStr = StackFormatter.formatNumber(number); - } - else - { - int num = (int) (Math.log(number) / Math.log(1000)); - numberStr = String.format("%.1f%c", number / Math.pow(1000, num), "KMB".charAt(num - 1)); - } - - return numberStr + " " + description; - } } 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 1a97034673..e99e3d16bd 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 @@ -122,6 +122,7 @@ public class XpTrackerPlugin extends Plugin navButton = NavigationButton.builder() .name("XP Tracker") .icon(icon) + .priority(2) .panel(xpPanel) .build(); 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 16c55e4bfc..e67f79adc0 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 @@ -67,11 +67,12 @@ 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.ui.skin.SubstanceRuneLiteLookAndFeel; import net.runelite.client.input.KeyManager; 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.SubstanceSynapse; import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities; import org.pushingpixels.substance.internal.utils.SubstanceTitlePaneUtilities; @@ -327,7 +328,7 @@ public class ClientUI SwingUtil.setupDefaults(); // Use substance look and feel - SwingUtil.setTheme(new SubstanceGraphiteLookAndFeel()); + SwingUtil.setTheme(new SubstanceRuneLiteLookAndFeel()); // Use custom UI font SwingUtil.setFont(FontManager.getRunescapeFont()); @@ -362,6 +363,8 @@ public class ClientUI navContainer.setLayout(new BorderLayout(0, 0)); navContainer.setMinimumSize(new Dimension(0, 0)); navContainer.setMaximumSize(new Dimension(0, Integer.MAX_VALUE)); + // To reduce substance's colorization (tinting) + navContainer.putClientProperty(SubstanceSynapse.COLORIZATION_FACTOR, 1.0); container.add(navContainer); pluginToolbar = new ClientPluginToolbar(); @@ -565,6 +568,7 @@ public class ClientUI /** * Get offset of game canvas in game window + * * @return game canvas offset */ public Point getCanvasOffset() diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ColorScheme.java b/runelite-client/src/main/java/net/runelite/client/ui/ColorScheme.java new file mode 100644 index 0000000000..bec0c4f052 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/ColorScheme.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui; + +import java.awt.Color; + +/** + * This class serves to hold commonly used UI colors. + */ +public class ColorScheme +{ + /* The orange color used for the branding's accents */ + public static final Color BRAND_ORANGE = new Color(220, 138, 0); + + /* The orange color used for the branding's accents, with lowered opacity */ + public static final Color BRAND_ORANGE_TRANSPARENT = new Color(220, 138, 0, 120); + + public static final Color DARKER_GRAY_COLOR = new Color(30, 30, 30); + public static final Color DARK_GRAY_COLOR = new Color(40, 40, 40); + public static final Color MEDIUM_GRAY_COLOR = new Color(77, 77, 77); + public static final Color LIGHT_GRAY_COLOR = new Color(165, 165, 165); + public static final Color DARK_GRAY_HOVER_COLOR = new Color(35, 35, 35); + + /* The color for the green progress bar (used in ge offers, farming tracker, etc)*/ + public static final Color PROGRESS_COMPLETE_COLOR = new Color(55, 240, 70); + + /* The color for the red progress bar (used in ge offers, farming tracker, etc)*/ + public static final Color PROGRESS_ERROR_COLOR = new Color(230, 30, 30); + + /* The color for the orange progress bar (used in ge offers, farming tracker, etc)*/ + public static final Color PROGRESS_INPROGRESS_COLOR = new Color(230, 150, 30); + + /* The color for the price indicator in the ge search results */ + public static final Color GRAND_EXCHANGE_PRICE = new Color(110, 225, 110); + + /* The color for the high alch indicator in the ge search results */ + public static final Color GRAND_EXCHANGE_ALCH = new Color(240, 207, 123); + + /* The background color of the scrollbar's track */ + public static final Color SCROLL_TRACK_COLOR = new Color(25, 25, 25); +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/DynamicGridLayout.java b/runelite-client/src/main/java/net/runelite/client/ui/DynamicGridLayout.java new file mode 100644 index 0000000000..8be06a706c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/DynamicGridLayout.java @@ -0,0 +1,215 @@ +/* + * 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.Container; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.Insets; +import java.util.function.Function; + +/** + * Grid layout implementation with support for cells with unequal size. + * + * See https://www.javaworld.com/article/2077486/core-java/java-tip-121--flex-your-grid-layout.html + */ +public class DynamicGridLayout extends GridLayout +{ + public DynamicGridLayout() + { + this(1, 0, 0, 0); + } + + public DynamicGridLayout(int rows, int cols) + { + this(rows, cols, 0, 0); + } + + public DynamicGridLayout(int rows, int cols, int hgap, int vgap) + { + super(rows, cols, hgap, vgap); + } + + @Override + public Dimension preferredLayoutSize(Container parent) + { + synchronized (parent.getTreeLock()) + { + return calculateSize(parent, Component::getPreferredSize); + } + } + + @Override + public Dimension minimumLayoutSize(Container parent) + { + synchronized (parent.getTreeLock()) + { + return calculateSize(parent, Component::getMinimumSize); + } + } + + @Override + public void layoutContainer(Container parent) + { + synchronized (parent.getTreeLock()) + { + final Insets insets = parent.getInsets(); + final int ncomponents = parent.getComponentCount(); + int nrows = getRows(); + int ncols = getColumns(); + + if (ncomponents == 0) + { + return; + } + + if (nrows > 0) + { + ncols = (ncomponents + nrows - 1) / nrows; + } + else + { + nrows = (ncomponents + ncols - 1) / ncols; + } + + final int hgap = getHgap(); + final int vgap = getVgap(); + + // scaling factors + final Dimension pd = preferredLayoutSize(parent); + final double sw = (1.0 * parent.getWidth()) / pd.width; + final double sh = (1.0 * parent.getHeight()) / pd.height; + + final int[] w = new int[ncols]; + final int[] h = new int[nrows]; + + // calculate dimensions for all components + apply scaling + for (int i = 0; i < ncomponents; i++) + { + final int r = i / ncols; + final int c = i % ncols; + final Component comp = parent.getComponent(i); + final Dimension d = comp.getPreferredSize(); + d.width = (int) (sw * d.width); + d.height = (int) (sh * d.height); + + if (w[c] < d.width) + { + w[c] = d.width; + } + + if (h[r] < d.height) + { + h[r] = d.height; + } + } + + // Apply new bounds to all child components + for (int c = 0, x = insets.left; c < ncols; c++) + { + for (int r = 0, y = insets.top; r < nrows; r++) + { + int i = r * ncols + c; + + if (i < ncomponents) + { + parent.getComponent(i).setBounds(x, y, w[c], h[r]); + } + + y += h[r] + vgap; + } + + x += w[c] + hgap; + } + } + } + + /** + * Calculate outer size of the layout based on it's children and sizer + * @param parent parent component + * @param sizer functioning returning dimension of the child component + * @return outer size + */ + private Dimension calculateSize(final Container parent, final Function sizer) + { + final int ncomponents = parent.getComponentCount(); + int nrows = getRows(); + int ncols = getColumns(); + + if (nrows > 0) + { + ncols = (ncomponents + nrows - 1) / nrows; + } + else + { + nrows = (ncomponents + ncols - 1) / ncols; + } + + final int[] w = new int[ncols]; + final int[] h = new int[nrows]; + + // Calculate dimensions for all components + for (int i = 0; i < ncomponents; i++) + { + final int r = i / ncols; + final int c = i % ncols; + final Component comp = parent.getComponent(i); + final Dimension d = sizer.apply(comp); + + if (w[c] < d.width) + { + w[c] = d.width; + } + + if (h[r] < d.height) + { + h[r] = d.height; + } + } + + // Calculate total width and height of the layout + int nw = 0; + + for (int j = 0; j < ncols; j++) + { + nw += w[j]; + } + + int nh = 0; + + for (int i = 0; i < nrows; i++) + { + nh += h[i]; + } + + final Insets insets = parent.getInsets(); + + // Apply insets and horizontal and vertical gap to layout + return new Dimension( + insets.left + insets.right + nw + (ncols - 1) * getHgap(), + insets.top + insets.bottom + nh + (nrows - 1) * getVgap()); + } +} \ No newline at end of file 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 9da89f2185..9695c765fe 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 @@ -74,6 +74,12 @@ public class NavigationButton */ private PluginPanel panel; + + /** + * The order in which the button should be displayed in the side bar. (from lower to higher) + */ + private int priority; + /** * Map of key-value pairs for setting the popup menu */ 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 36dd220fcc..44835b771e 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 @@ -60,13 +60,14 @@ public abstract class PluginPanel extends JPanel { setBorder(BORDER_PADDING); setLayout(new GridLayout(0, 1, 0, 3)); + setBackground(ColorScheme.DARK_GRAY_COLOR); final JPanel northPanel = new JPanel(); northPanel.setLayout(new BorderLayout()); northPanel.add(this, BorderLayout.NORTH); + northPanel.setBackground(ColorScheme.DARK_GRAY_COLOR); scrollPane = new JScrollPane(northPanel); - scrollPane.getVerticalScrollBar().setUnitIncrement(16); //Otherwise scrollspeed is really slow scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); wrappedPanel = new JPanel(); 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 4ef441f7a6..5979a133d7 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,8 +24,8 @@ */ package net.runelite.client.ui; +import com.google.common.collect.ComparisonChain; import com.google.common.eventbus.EventBus; -import java.util.Comparator; import java.util.TreeSet; import javax.inject.Inject; import javax.inject.Singleton; @@ -39,7 +39,11 @@ import net.runelite.client.events.PluginToolbarButtonRemoved; public class PluginToolbar { private final EventBus eventBus; - private final TreeSet buttons = new TreeSet<>(Comparator.comparing(NavigationButton::getName)); + private final TreeSet buttons = new TreeSet<>((a, b) -> + ComparisonChain.start() + .compare(a.getPriority(), b.getPriority()) + .compare(a.getName(), b.getName()) + .result()); @Inject private PluginToolbar(final EventBus eventBus) diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/ComboBoxListRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/components/ComboBoxListRenderer.java new file mode 100644 index 0000000000..c97e128374 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/ComboBoxListRenderer.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui.components; + +import java.awt.Color; +import java.awt.Component; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.border.EmptyBorder; +import net.runelite.client.ui.ColorScheme; + +/** + * A custom list renderer to avoid substance's weird coloring. + * Substance was making selected items' foreground color black, this + * was very hard to see in the dark gray background, this makes the selected + * item white and adds some padding to the elements for more readable list. + */ +public final class ComboBoxListRenderer extends JLabel implements ListCellRenderer +{ + + @Override + public Component getListCellRendererComponent(JList list, Object o, int index, boolean isSelected, boolean cellHasFocus) + { + if (isSelected) + { + setBackground(ColorScheme.DARK_GRAY_COLOR); + setForeground(Color.WHITE); + } + else + { + setBackground(list.getBackground()); + setForeground(ColorScheme.LIGHT_GRAY_COLOR); + } + + setBorder(new EmptyBorder(5, 5, 5, 0)); + + String text = (String) o.toString(); + setText(text); + + return this; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/CustomScrollBarUI.java b/runelite-client/src/main/java/net/runelite/client/ui/components/CustomScrollBarUI.java new file mode 100644 index 0000000000..128c88197a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/CustomScrollBarUI.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui.components; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JScrollBar; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.basic.BasicScrollBarUI; +import lombok.Setter; +import net.runelite.client.ui.ColorScheme; + +/** + * This scroll bar UI is to be used for the "RuneLite Obsidian" client theme. + * It is a part of the client's redesign as detailed on issue #1342 + */ +public class CustomScrollBarUI extends BasicScrollBarUI +{ + + /* The background color of the bar's thumb */ + @Setter + private Color thumbColor = ColorScheme.MEDIUM_GRAY_COLOR; + + /* The background color of the bar's track */ + @Setter + private Color trackColor = ColorScheme.SCROLL_TRACK_COLOR; + + /** + * Overrides the painting of the bar's track (the darker part underneath that extends + * the full page length). + */ + @Override + protected void paintTrack(Graphics graphics, JComponent jComponent, Rectangle rectangle) + { + graphics.setColor(trackColor); + graphics.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height); + } + + /** + * Overrides the painting of the bar's thumb (the lighter part on top that users + * use to slide up and down the page). + */ + @Override + protected void paintThumb(Graphics graphics, JComponent jComponent, Rectangle rectangle) + { + graphics.setColor(thumbColor); + graphics.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height); + } + + /** + * Creates an empty JButton to be used as the scroll bar's arrows (to disable the arrows). + */ + protected JButton createEmptyButton() + { + JButton button = new JButton(); + Dimension zeroDim = new Dimension(0, 0); + button.setPreferredSize(zeroDim); + button.setMinimumSize(zeroDim); + button.setMaximumSize(zeroDim); + return button; + } + + public static ComponentUI createUI(JComponent c) + { + JScrollBar bar = (JScrollBar) c; + bar.setUnitIncrement(16); + bar.setPreferredSize(new Dimension(7, 0)); + return new CustomScrollBarUI(); + } + + /** + * Applies an empty button to the decrease (down arrow) button. + */ + @Override + protected JButton createDecreaseButton(int orientation) + { + return createEmptyButton(); + } + + /** + * Applies an empty button to the increase (up arrow) button. + */ + @Override + protected JButton createIncreaseButton(int orientation) + { + return createEmptyButton(); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java b/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java new file mode 100644 index 0000000000..0734851fb0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2017, Adam + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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 HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui.components; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionListener; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.text.Document; +import net.runelite.client.ui.ColorScheme; + +/** + * This component is a JTextField with an icon on its left side. + */ +public class IconTextField extends JPanel +{ + private final JTextField textField; + + //to support gifs, the icon needs to be wrapped in a JLabel + private final JLabel iconWrapperLabel; + + //the default background color, this needs to be stored for hover effects + private Color backgroundColor = ColorScheme.DARKER_GRAY_COLOR; + //the default hover background color, this needs to be stored for hover effects + private Color hoverBackgroundColor; + + // the input can be blocked (no clicking, no editing, no hover effects) + private boolean blocked; + + public IconTextField() + { + setLayout(new BorderLayout()); + + this.iconWrapperLabel = new JLabel(); + this.iconWrapperLabel.setPreferredSize(new Dimension(30, 0)); + this.iconWrapperLabel.setVerticalAlignment(JLabel.CENTER); + this.iconWrapperLabel.setHorizontalAlignment(JLabel.CENTER); + + this.textField = new JTextField(); + this.textField.setBorder(null); + this.textField.setOpaque(false); + this.textField.setSelectedTextColor(Color.WHITE); + this.textField.setSelectionColor(ColorScheme.BRAND_ORANGE_TRANSPARENT); + + add(iconWrapperLabel, BorderLayout.WEST); + add(textField, BorderLayout.CENTER); + + textField.addMouseListener(new MouseAdapter() + { + @Override + public void mouseEntered(MouseEvent mouseEvent) + { + if (blocked) + { + return; + } + + if (hoverBackgroundColor != null) + { + IconTextField.super.setBackground(hoverBackgroundColor); + } + } + + @Override + public void mouseExited(MouseEvent mouseEvent) + { + IconTextField.super.setBackground(backgroundColor); + } + }); + } + + public void addActionListener(ActionListener actionListener) + { + textField.addActionListener(actionListener); + } + + public void setIcon(ImageIcon icon) + { + iconWrapperLabel.setIcon(icon); + } + + public String getText() + { + return textField.getText(); + } + + public void setText(String text) + { + textField.setText(text); + } + + @Override + public void setBackground(Color color) + { + if (color == null) + { + return; + } + super.setBackground(color); + this.backgroundColor = color; + } + + public void addInputKeyListener(KeyListener l) + { + textField.addKeyListener(l); + } + + public void removeInputKeyListener(KeyListener l) + { + textField.removeKeyListener(l); + } + + public void setHoverBackgroundColor(Color hoverBackgroundColor) + { + if (hoverBackgroundColor == null) + { + return; + } + this.hoverBackgroundColor = hoverBackgroundColor; + } + + public void setEditable(boolean editable) + { + this.blocked = !editable; + textField.setEditable(editable); + textField.setFocusable(editable); + if (!editable) + { + super.setBackground(backgroundColor); + } + } + + public Document getDocument() + { + return textField.getDocument(); + } + +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/PluginErrorPanel.java b/runelite-client/src/main/java/net/runelite/client/ui/components/PluginErrorPanel.java new file mode 100644 index 0000000000..e7b2fad971 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/PluginErrorPanel.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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 HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui.components; + +import java.awt.BorderLayout; +import java.awt.Color; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.components.shadowlabel.JShadowedLabel; + +/** + * A component to display an error/info message (to be used on a plugin panel) + * Example uses are: no ge search results found, no ge offers found. + */ +public class PluginErrorPanel extends JPanel +{ + private final JLabel noResultsTitle = new JShadowedLabel(); + private final JLabel noResultsDescription = new JShadowedLabel(); + + public PluginErrorPanel() + { + setOpaque(false); + setBorder(new EmptyBorder(50, 10, 0, 10)); + setLayout(new BorderLayout()); + + noResultsTitle.setForeground(Color.WHITE); + noResultsTitle.setHorizontalAlignment(SwingConstants.CENTER); + + noResultsDescription.setFont(FontManager.getRunescapeSmallFont()); + noResultsDescription.setForeground(Color.GRAY); + noResultsDescription.setHorizontalAlignment(SwingConstants.CENTER); + + add(noResultsTitle, BorderLayout.NORTH); + add(noResultsDescription, BorderLayout.CENTER); + + setVisible(false); + } + + /** + * Changes the content of the panel to the given parameters. + * The description has to be wrapped in html so that its text can be wrapped. + */ + public void setContent(String title, String description) + { + noResultsTitle.setText(title); + noResultsDescription.setText("" + description + ""); + setVisible(true); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/ProgressBar.java b/runelite-client/src/main/java/net/runelite/client/ui/components/ProgressBar.java new file mode 100644 index 0000000000..c94845ee1e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/ProgressBar.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui.components; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; +import lombok.Setter; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.components.shadowlabel.JShadowedLabel; + +/** + * A progress bar to be displayed underneath the GE offer item panels + */ +public class ProgressBar extends JPanel +{ + @Setter + private int maximumValue; + + @Setter + private int value; + + private final JLabel leftLabel = new JShadowedLabel(); + private final JLabel rightLabel = new JShadowedLabel(); + private final JLabel centerLabel = new JShadowedLabel(); + + public ProgressBar() + { + setLayout(new BorderLayout()); + setBackground(Color.GREEN.darker()); + setPreferredSize(new Dimension(100, 16)); + + leftLabel.setFont(FontManager.getRunescapeSmallFont()); + leftLabel.setForeground(Color.WHITE); + leftLabel.setBorder(new EmptyBorder(2, 5, 0, 0)); + + rightLabel.setFont(FontManager.getRunescapeSmallFont()); + rightLabel.setForeground(Color.WHITE); + rightLabel.setBorder(new EmptyBorder(2, 0, 0, 5)); + + centerLabel.setFont(FontManager.getRunescapeSmallFont()); + centerLabel.setForeground(Color.WHITE); + centerLabel.setHorizontalAlignment(SwingConstants.CENTER); + centerLabel.setBorder(new EmptyBorder(2, 0, 0, 0)); + + add(leftLabel, BorderLayout.WEST); + add(centerLabel, BorderLayout.CENTER); + add(rightLabel, BorderLayout.EAST); + + } + + @Override + public void paint(Graphics g) + { + double percentage = getPercentage(); + int topWidth = (int) (getSize().width * (percentage / 100)); + + super.paint(g); + g.setColor(getForeground()); + g.fillRect(0, 0, topWidth, 16); + super.paintComponents(g); + } + + + public void setLeftLabel(String txt) + { + this.leftLabel.setText(txt); + } + + public void setRightLabel(String txt) + { + this.rightLabel.setText(txt); + } + + public void setCenterLabel(String txt) + { + this.centerLabel.setText(txt); + } + + public double getPercentage() + { + if (value == 0) + { + return 0; + } + + return (value * 100) / maximumValue; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/IconTextField.java b/runelite-client/src/main/java/net/runelite/client/ui/components/ThinProgressBar.java similarity index 52% rename from runelite-client/src/main/java/net/runelite/client/plugins/hiscore/IconTextField.java rename to runelite-client/src/main/java/net/runelite/client/ui/components/ThinProgressBar.java index 055640de17..424e4ebebb 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/IconTextField.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/ThinProgressBar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Adam + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,54 +23,71 @@ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.client.plugins.hiscore; +package net.runelite.client.ui.components; -import java.awt.Graphics; -import java.awt.Insets; -import javax.swing.BorderFactory; -import javax.swing.Icon; -import javax.swing.JTextField; -import javax.swing.border.Border; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import javax.swing.JPanel; +import lombok.Setter; +import net.runelite.client.ui.ColorScheme; -public class IconTextField extends JTextField +/** + * A progress bar to be displayed underneath the GE offer item panels + */ +public class ThinProgressBar extends JPanel { - private Border border; - private Icon icon; + @Setter + private int maximumValue; - @Override - public void setBorder(Border border) + @Setter + private int value; + + private final JPanel topBar = new JPanel(); + + public ThinProgressBar() { - this.border = border; + setLayout(new BorderLayout()); + setBackground(Color.GREEN.darker()); - if (icon == null) + topBar.setPreferredSize(new Dimension(100, 4)); + topBar.setBackground(ColorScheme.PROGRESS_COMPLETE_COLOR); + + add(topBar, BorderLayout.WEST); + } + + /** + * Updates the UI based on the percentage progress + */ + public void update() + { + double percentage = getPercentage(); + int topWidth = (int) (getSize().width * (percentage / 100)); + + topBar.setPreferredSize(new Dimension(topWidth, 4)); + topBar.repaint(); + + revalidate(); + repaint(); + } + + public double getPercentage() + { + if (value == 0) { - super.setBorder(border); - } - else - { - Border margin = BorderFactory.createEmptyBorder(0, icon.getIconWidth() + 4, 0, 0); - Border compound = BorderFactory.createCompoundBorder(border, margin); - super.setBorder(compound); + return 0; } + + return (value * 100) / maximumValue; } @Override - public void paintComponent(Graphics graphics) + public void setForeground(Color color) { - super.paintComponent(graphics); - - Insets iconInsets = border.getBorderInsets(this); - icon.paintIcon(this, graphics, iconInsets.left, iconInsets.top); + if (topBar != null) + { + topBar.setBackground(color); + } + setBackground(color.darker()); } - - public void setIcon(Icon icon) - { - this.icon = icon; - resetBorder(); - } - - private void resetBorder() - { - setBorder(border); - } -} +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/materialtabs/MaterialTab.java b/runelite-client/src/main/java/net/runelite/client/ui/components/materialtabs/MaterialTab.java new file mode 100644 index 0000000000..3d84cb483d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/materialtabs/MaterialTab.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui.components.materialtabs; + +import java.awt.Color; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import lombok.Getter; +import lombok.Setter; +import net.runelite.client.ui.ColorScheme; + +/** + * This class represents a Material Design inspired tab. + *

+ * Each tab will communicate with it's containing group when it's clicked + * and that group will display the tab's content on it's own display. + * + * @author Psikoi + */ +public class MaterialTab extends JLabel +{ + private static final Border SELECTED_BORDER = new CompoundBorder( + BorderFactory.createMatteBorder(0, 0, 1, 0, ColorScheme.BRAND_ORANGE), + BorderFactory.createEmptyBorder(5, 10, 5, 10)); + + private static final Border UNSELECTED_BORDER = BorderFactory + .createEmptyBorder(5, 10, 5, 10); + + /* The tab's containing group */ + private final MaterialTabGroup group; + + /* The tab's associated content display */ + @Getter + private final JComponent content; + + /* To be execuded when the tab is selected */ + @Setter + private Runnable onSelectEvent; + + @Getter + private boolean selected; + + public MaterialTab(String string, MaterialTabGroup group, JComponent content) + { + super(string); + + this.group = group; + this.content = content; + + if (selected) + { + select(); + } + else + { + unselect(); + } + + addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + group.select(MaterialTab.this); + } + }); + } + + public void select() + { + setBorder(SELECTED_BORDER); + setForeground(Color.WHITE); + selected = true; + if (onSelectEvent != null) + { + onSelectEvent.run(); + } + } + + public void unselect() + { + setBorder(UNSELECTED_BORDER); + setForeground(Color.GRAY); + selected = false; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/materialtabs/MaterialTabGroup.java b/runelite-client/src/main/java/net/runelite/client/ui/components/materialtabs/MaterialTabGroup.java new file mode 100644 index 0000000000..67978efd37 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/materialtabs/MaterialTabGroup.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui.components.materialtabs; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.util.ArrayList; +import java.util.List; +import javax.swing.JPanel; + +/** + * This class will be a container (group) for the new Material Tabs. It will + * contain a list of tabs and a display (JPanel). When a tab is selected, the + * JPanel "display" will display the content associated with that tab. + *

+ * How to use these tabs: + *

    + *
  1. 1 - Create displays (JPanels) for each tab
  2. + *
  3. 2 - Create an empty JPanel to serve as the group's display
  4. + *
  5. 3 - Create a new MaterialGroup, passing the panel in step 2 as a param
  6. + *
  7. 4 - Create new tabs, passing the group in step 3 and one of the panels in + * step 1 as params
  8. + *
  9. 5 - Add the tabs to the group using the MaterialTabGroup#addTab method
  10. + *
  11. 6 - Select one of the tabs using the MaterialTab#select method
  12. + *
+ * + * @author Psikoi + */ +public class MaterialTabGroup extends JPanel +{ + /* The panel on which the content tab's content will be displayed on. */ + private final JPanel display; + /* A list of all the tabs contained in this group. */ + private final List tabs = new ArrayList<>(); + + public MaterialTabGroup(JPanel display) + { + this.display = display; + this.display.setLayout(new BorderLayout()); + setLayout(new FlowLayout(FlowLayout.CENTER, 8, 0)); + setOpaque(false); + } + + /* Returns the tab on a certain index. */ + public MaterialTab getTab(int index) + { + + if (tabs == null || tabs.isEmpty()) + { + return null; + } + + return tabs.get(index); + } + + public void addTab(MaterialTab tab) + { + tabs.add(tab); + add(tab, BorderLayout.NORTH); + } + + /*** + * Selects a tab from the group, and sets the display's content to the + * tab's associated content. + * @param selectedTab - The tab to select + */ + public boolean select(MaterialTab selectedTab) + { + if (!tabs.contains(selectedTab)) + { + return false; + } + + display.removeAll(); + + for (MaterialTab tab : tabs) + { + if (tab.equals(selectedTab)) + { + tab.select(); + display.add(tab.getContent()); + display.revalidate(); + display.repaint(); + } + else + { + tab.unselect(); + } + } + + return true; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/JShadowedLabel.java b/runelite-client/src/main/java/net/runelite/client/ui/components/shadowlabel/JShadowedLabel.java similarity index 97% rename from runelite-client/src/main/java/net/runelite/client/ui/JShadowedLabel.java rename to runelite-client/src/main/java/net/runelite/client/ui/components/shadowlabel/JShadowedLabel.java index 37182fb762..35ccf12374 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/JShadowedLabel.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/shadowlabel/JShadowedLabel.java @@ -22,7 +22,7 @@ * (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; +package net.runelite.client.ui.components.shadowlabel; import java.awt.Color; import java.awt.Point; diff --git a/runelite-client/src/main/java/net/runelite/client/ui/JShadowedLabelUI.java b/runelite-client/src/main/java/net/runelite/client/ui/components/shadowlabel/JShadowedLabelUI.java similarity index 97% rename from runelite-client/src/main/java/net/runelite/client/ui/JShadowedLabelUI.java rename to runelite-client/src/main/java/net/runelite/client/ui/components/shadowlabel/JShadowedLabelUI.java index 0494b66d1f..32e59f4993 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/JShadowedLabelUI.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/shadowlabel/JShadowedLabelUI.java @@ -22,7 +22,7 @@ * (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; +package net.runelite.client.ui.components.shadowlabel; import java.awt.Graphics; import javax.swing.JLabel; diff --git a/runelite-client/src/main/java/net/runelite/client/ui/skin/ObsidianSkin.java b/runelite-client/src/main/java/net/runelite/client/ui/skin/ObsidianSkin.java new file mode 100644 index 0000000000..6cd6e140d4 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/skin/ObsidianSkin.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Psikoi + * 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.skin; + +import javax.swing.AbstractButton; +import org.pushingpixels.substance.api.ComponentState; +import org.pushingpixels.substance.api.SubstanceColorSchemeBundle; +import org.pushingpixels.substance.api.SubstanceSkin; +import org.pushingpixels.substance.api.SubstanceSlices.ColorSchemeAssociationKind; +import org.pushingpixels.substance.api.SubstanceSlices.DecorationAreaType; +import org.pushingpixels.substance.api.colorscheme.ColorSchemeSingleColorQuery; +import org.pushingpixels.substance.api.colorscheme.SubstanceColorScheme; +import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter; +import org.pushingpixels.substance.api.painter.border.CompositeBorderPainter; +import org.pushingpixels.substance.api.painter.border.DelegateBorderPainter; +import org.pushingpixels.substance.api.painter.decoration.MatteDecorationPainter; +import org.pushingpixels.substance.api.painter.fill.FractionBasedFillPainter; +import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter; +import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter; +import org.pushingpixels.substance.api.painter.overlay.BottomShadowOverlayPainter; +import org.pushingpixels.substance.api.painter.overlay.TopBezelOverlayPainter; +import org.pushingpixels.substance.api.painter.overlay.TopLineOverlayPainter; +import org.pushingpixels.substance.api.shaper.ClassicButtonShaper; +import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities; + +public class ObsidianSkin extends SubstanceSkin +{ + /** + * Display name for this skin. + */ + private static final String NAME = "RuneLite"; + + /** + * Creates a new RuneLite skin. + */ + ObsidianSkin() + { + final SubstanceSkin.ColorSchemes schemes = SubstanceSkin + .getColorSchemes(getClass().getResource(NAME + ".colorschemes")); + final SubstanceColorScheme activeScheme = schemes.get("RuneLite Active"); + final SubstanceColorScheme enabledScheme = schemes.get("RuneLite Enabled"); + + final SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle( + activeScheme, enabledScheme, enabledScheme); + defaultSchemeBundle.registerColorScheme(enabledScheme, 0.6f, + ComponentState.DISABLED_UNSELECTED); + defaultSchemeBundle.registerColorScheme(activeScheme, 0.6f, + ComponentState.DISABLED_SELECTED); + + // borders + final SubstanceColorScheme borderDisabledSelectedScheme = schemes + .get("RuneLite Selected Disabled Border"); + final SubstanceColorScheme borderScheme = schemes.get("RuneLite Border"); + defaultSchemeBundle.registerColorScheme(borderDisabledSelectedScheme, + ColorSchemeAssociationKind.BORDER, ComponentState.DISABLED_SELECTED); + defaultSchemeBundle.registerColorScheme(borderScheme, ColorSchemeAssociationKind.BORDER); + + // marks + final SubstanceColorScheme markActiveScheme = schemes.get("RuneLite Mark Active"); + defaultSchemeBundle.registerColorScheme(markActiveScheme, ColorSchemeAssociationKind.MARK, + ComponentState.getActiveStates()); + defaultSchemeBundle.registerColorScheme(markActiveScheme, 0.6f, + ColorSchemeAssociationKind.MARK, ComponentState.DISABLED_SELECTED, + ComponentState.DISABLED_UNSELECTED); + + // separators + final SubstanceColorScheme separatorScheme = schemes.get("RuneLite Separator"); + defaultSchemeBundle.registerColorScheme(separatorScheme, + ColorSchemeAssociationKind.SEPARATOR); + + // tab borders + defaultSchemeBundle.registerColorScheme(schemes.get("RuneLite Tab Border"), + ColorSchemeAssociationKind.TAB_BORDER, ComponentState.getActiveStates()); + + final SubstanceColorScheme watermarkScheme = schemes.get("RuneLite Watermark"); + + this.registerDecorationAreaSchemeBundle(defaultSchemeBundle, watermarkScheme, + DecorationAreaType.NONE); + + final SubstanceColorSchemeBundle decorationsSchemeBundle = new SubstanceColorSchemeBundle( + activeScheme, enabledScheme, enabledScheme); + decorationsSchemeBundle.registerColorScheme(enabledScheme, 0.5f, + ComponentState.DISABLED_UNSELECTED); + + // borders + decorationsSchemeBundle.registerColorScheme(borderDisabledSelectedScheme, + ColorSchemeAssociationKind.BORDER, ComponentState.DISABLED_SELECTED); + decorationsSchemeBundle.registerColorScheme(borderScheme, + ColorSchemeAssociationKind.BORDER); + + // marks + decorationsSchemeBundle.registerColorScheme(markActiveScheme, + ColorSchemeAssociationKind.MARK, ComponentState.getActiveStates()); + + // separators + final SubstanceColorScheme separatorDecorationsScheme = schemes + .get("RuneLite Decorations Separator"); + decorationsSchemeBundle.registerColorScheme(separatorDecorationsScheme, + ColorSchemeAssociationKind.SEPARATOR); + + final SubstanceColorScheme decorationsWatermarkScheme = schemes + .get("RuneLite Decorations Watermark"); + + this.registerDecorationAreaSchemeBundle(decorationsSchemeBundle, decorationsWatermarkScheme, + DecorationAreaType.TOOLBAR, DecorationAreaType.GENERAL, DecorationAreaType.FOOTER); + + final SubstanceColorSchemeBundle headerSchemeBundle = new SubstanceColorSchemeBundle(activeScheme, + enabledScheme, enabledScheme); + headerSchemeBundle.registerColorScheme(enabledScheme, 0.5f, + ComponentState.DISABLED_UNSELECTED); + + // borders + final SubstanceColorScheme headerBorderScheme = schemes.get("RuneLite Header Border"); + headerSchemeBundle.registerColorScheme(borderDisabledSelectedScheme, + ColorSchemeAssociationKind.BORDER, ComponentState.DISABLED_SELECTED); + headerSchemeBundle.registerColorScheme(headerBorderScheme, + ColorSchemeAssociationKind.BORDER); + // marks + headerSchemeBundle.registerColorScheme(markActiveScheme, ColorSchemeAssociationKind.MARK, + ComponentState.getActiveStates()); + + headerSchemeBundle.registerHighlightColorScheme(activeScheme, 0.7f, + ComponentState.ROLLOVER_UNSELECTED, ComponentState.ROLLOVER_ARMED, + ComponentState.ARMED); + headerSchemeBundle.registerHighlightColorScheme(activeScheme, 0.8f, + ComponentState.SELECTED); + headerSchemeBundle.registerHighlightColorScheme(activeScheme, 1.0f, + ComponentState.ROLLOVER_SELECTED); + + final SubstanceColorScheme headerWatermarkScheme = schemes.get("RuneLite Header Watermark"); + + this.registerDecorationAreaSchemeBundle(headerSchemeBundle, headerWatermarkScheme, + DecorationAreaType.PRIMARY_TITLE_PANE, DecorationAreaType.SECONDARY_TITLE_PANE, + DecorationAreaType.HEADER); + + setTabFadeStart(0.2); + setTabFadeEnd(0.9); + + // Add overlay painters to paint drop shadows along the bottom + // edges of toolbars and footers + this.addOverlayPainter(BottomShadowOverlayPainter.getInstance(), + DecorationAreaType.TOOLBAR); + this.addOverlayPainter(BottomShadowOverlayPainter.getInstance(), DecorationAreaType.FOOTER); + + // add an overlay painter to paint a dark line along the bottom + // edge of toolbars + final BottomLineOverlayPainter toolbarBottomLineOverlayPainter = new BottomLineOverlayPainter( + (SubstanceColorScheme scheme) -> scheme.getUltraDarkColor().darker()); + this.addOverlayPainter(toolbarBottomLineOverlayPainter, DecorationAreaType.TOOLBAR); + + // add an overlay painter to paint a dark line along the bottom + // edge of toolbars + final TopLineOverlayPainter toolbarTopLineOverlayPainter = new TopLineOverlayPainter( + (SubstanceColorScheme scheme) -> SubstanceColorUtilities + .getAlphaColor(scheme.getForegroundColor(), 32)); + this.addOverlayPainter(toolbarTopLineOverlayPainter, DecorationAreaType.TOOLBAR); + + // add an overlay painter to paint a bezel line along the top + // edge of footer + final TopBezelOverlayPainter footerTopBezelOverlayPainter = new TopBezelOverlayPainter( + (SubstanceColorScheme scheme) -> scheme.getUltraDarkColor().darker(), + (SubstanceColorScheme scheme) -> SubstanceColorUtilities + .getAlphaColor(scheme.getForegroundColor(), 32)); + this.addOverlayPainter(footerTopBezelOverlayPainter, DecorationAreaType.FOOTER); + + this.setTabFadeStart(0.18); + this.setTabFadeEnd(0.18); + + // Set button shaper to use "flat" design + this.buttonShaper = new ClassicButtonShaper() + { + @Override + public float getCornerRadius(AbstractButton button, float insets) + { + return 0; + } + }; + + this.watermark = null; + this.fillPainter = new FractionBasedFillPainter("RuneLite", + new float[]{0.0f, 0.5f, 1.0f}, + new ColorSchemeSingleColorQuery[]{ColorSchemeSingleColorQuery.ULTRALIGHT, + ColorSchemeSingleColorQuery.LIGHT, ColorSchemeSingleColorQuery.LIGHT}); + this.decorationPainter = new MatteDecorationPainter(); + this.highlightPainter = new ClassicHighlightPainter(); + this.borderPainter = new CompositeBorderPainter("RuneLite", new ClassicBorderPainter(), + new DelegateBorderPainter("RuneLite Inner", new ClassicBorderPainter(), 0x40FFFFFF, + 0x20FFFFFF, 0x00FFFFFF, + (SubstanceColorScheme scheme) -> scheme.tint(0.2f))); + } + + @Override + public String getDisplayName() + { + return NAME; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/skin/SubstanceRuneLiteLookAndFeel.java b/runelite-client/src/main/java/net/runelite/client/ui/skin/SubstanceRuneLiteLookAndFeel.java new file mode 100644 index 0000000000..86afea00a8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/skin/SubstanceRuneLiteLookAndFeel.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Psikoi + * 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.skin; + +import org.pushingpixels.substance.api.SubstanceLookAndFeel; + +public class SubstanceRuneLiteLookAndFeel extends SubstanceLookAndFeel +{ + public SubstanceRuneLiteLookAndFeel() + { + super(new ObsidianSkin()); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/util/StackFormatter.java b/runelite-client/src/main/java/net/runelite/client/util/StackFormatter.java index 607da65d91..777688f84a 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/StackFormatter.java +++ b/runelite-client/src/main/java/net/runelite/client/util/StackFormatter.java @@ -128,6 +128,61 @@ public class StackFormatter } } + /** + * Convert a quantity to stack size as it would + * appear in RuneScape. (with decimals) + *

+ * This differs from quantityToRSStack in that it displays + * decimals. Ex: 27100 is 27,1k (not 27k) + *

+ * This uses the NumberFormat singleton instead of the + * NUMBER_FORMATTER variable to ensure the UK locale. + * + * @param quantity The quantity to convert. + * @return The stack size as it would appear in RS, with decimals, + * with K after 100,000 and M after 10,000,000 + */ + public static String quantityToRSDecimalStack(int quantity) + { + + if (quantity < 10_000) + { + return Integer.toString(quantity); + } + else if (quantity < 1_000_000) + { + if (quantity % 1000 == 0) + { + return quantity / 1000 + "K"; + } + return NUMBER_FORMATTER.format(quantity).substring(0, Integer.toString(quantity).length() - 1) + "K"; + } + else if (quantity < 10_000_000) + { + if (quantity % 1_000_000 == 0) + { + return quantity / 1_000_000 + "M"; + } + return NUMBER_FORMATTER.format(quantity).substring(0, Integer.toString(quantity).length() - 4) + "M"; + } + else if (quantity < 1_000_000_000) + { + if (quantity % 1_000_000 == 0) + { + return quantity / 1_000_000 + "M"; + } + return NUMBER_FORMATTER.format(quantity).substring(0, Integer.toString(quantity).length() - 4) + "M"; + } + else + { + if (quantity % 1_000_000_000 == 0) + { + return quantity / 1_000_000_000 + "B"; + } + return NUMBER_FORMATTER.format(quantity).substring(0, Integer.toString(quantity).length() - 7) + "B"; + } + } + /** * Converts a string representation of a stack * back to (close to) it's original value. @@ -147,8 +202,8 @@ public class StackFormatter * * @param number the long number to format * @return the formatted String - * @exception ArithmeticException if rounding is needed with rounding - * mode being set to RoundingMode.UNNECESSARY + * @throws ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY * @see java.text.Format#format */ public static String formatNumber(final long number) @@ -161,8 +216,8 @@ public class StackFormatter * * @param number the double number to format * @return the formatted String - * @exception ArithmeticException if rounding is needed with rounding - * mode being set to RoundingMode.UNNECESSARY + * @throws ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY * @see java.text.Format#format */ public static String formatNumber(double number) diff --git a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java index ff5015917e..82e7ec726d 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java @@ -25,12 +25,14 @@ package net.runelite.client.util; import java.awt.AWTException; +import java.awt.Color; 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.RenderingHints; import java.awt.SystemTray; import java.awt.Toolkit; import java.awt.TrayIcon; @@ -40,6 +42,8 @@ import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; +import java.awt.image.LookupOp; +import java.awt.image.LookupTable; import java.util.Enumeration; import java.util.concurrent.Callable; import java.util.function.BiConsumer; @@ -59,6 +63,7 @@ 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 net.runelite.client.ui.components.CustomScrollBarUI; import org.pushingpixels.substance.internal.SubstanceSynapse; import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities; @@ -77,8 +82,13 @@ public class SwingUtil // Force heavy-weight popups/tooltips. // Prevents them from being obscured by the game applet. ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false); + ToolTipManager.sharedInstance().setInitialDelay(300); JPopupMenu.setDefaultLightWeightPopupEnabled(false); + UIManager.put("Button.foreground", Color.WHITE); + UIManager.put("MenuItem.foreground", Color.WHITE); + UIManager.put("ScrollBarUI", CustomScrollBarUI.class.getName()); + // Do not render shadows under popups/tooltips. // Fixes black boxes under popups that are above the game applet. System.setProperty("jgoodies.popupDropShadowEnabled", "false"); @@ -88,6 +98,48 @@ public class SwingUtil System.setProperty("sun.awt.noerasebackground", "true"); } + /** + * Offsets an image in the grayscale (darkens/brightens) by an offset + */ + public static BufferedImage grayscaleOffset(BufferedImage image, int offset) + { + int numComponents = image.getColorModel().getNumComponents(); + int index = numComponents - 1; + + LookupTable lookup = new LookupTable(0, numComponents) + { + @Override + public int[] lookupPixel(int[] src, int[] dest) + { + if (dest[index] != 0) + { + dest[index] = dest[index] + offset; + if (dest[index] < 0) + { + dest[index] = 0; + } + else if (dest[index] > 255) + { + dest[index] = 255; + } + } + + return dest; + } + }; + + LookupOp op = new LookupOp(lookup, new RenderingHints(null)); + return op.filter(image, null); + } + + /** + * Converts a given color to it's hexidecimal equivalent. + */ + public static String toHexColor(Color color) + { + return "#" + Integer.toHexString(color.getRGB()).substring(2); + } + /** * Safely sets Swing theme * @@ -210,7 +262,7 @@ public class SwingUtil result = JOptionPane.showConfirmDialog( frame, "Are you sure you want to exit?", "Exit", - JOptionPane .OK_CANCEL_OPTION, + JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); } } @@ -258,6 +310,7 @@ public class SwingUtil : navigationButton.getIcon(); final JButton button = new JButton(); + button.setMaximumSize(new Dimension(30, 30)); button.setName(navigationButton.getName()); button.setToolTipText(navigationButton.getTooltip()); button.setIcon(new ImageIcon(scaledImage)); diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/account/login_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/account/login_icon.png index eed416cfd1..1ba7ac1fae 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/account/login_icon.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/account/login_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/account/logout_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/account/logout_icon.png index 8d8ee8c565..b1bfcd1115 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/account/logout_icon.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/account/logout_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/config/config_edit_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/config/config_edit_icon.png new file mode 100644 index 0000000000..d88ec51f71 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/config/config_edit_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/config/disabled.png b/runelite-client/src/main/resources/net/runelite/client/plugins/config/disabled.png deleted file mode 100644 index 160d431a95..0000000000 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/config/disabled.png and /dev/null differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/config/enabled.png b/runelite-client/src/main/resources/net/runelite/client/plugins/config/enabled.png deleted file mode 100644 index a48cfcd5cc..0000000000 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/config/enabled.png and /dev/null differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/config/switchers/off.png b/runelite-client/src/main/resources/net/runelite/client/plugins/config/switchers/off.png new file mode 100644 index 0000000000..ff25cc611a Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/config/switchers/off.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/config/switchers/on.png b/runelite-client/src/main/resources/net/runelite/client/plugins/config/switchers/on.png new file mode 100644 index 0000000000..43028e1716 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/config/switchers/on.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/feed/icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/feed/icon.png index 95b048b153..20ef822883 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/feed/icon.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/feed/icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/feed/reset.png b/runelite-client/src/main/resources/net/runelite/client/plugins/feed/reset.png new file mode 100644 index 0000000000..e94f6102a5 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/feed/reset.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/arrow_left.png b/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/arrow_left.png new file mode 100644 index 0000000000..480358e6a1 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/arrow_left.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/arrow_right.png b/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/arrow_right.png new file mode 100644 index 0000000000..63ab23f273 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/arrow_right.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/ge_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/ge_icon.png index 0783e0cd32..6455733f8f 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/ge_icon.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/ge_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/search.png b/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/search.png deleted file mode 100644 index 23bf66b759..0000000000 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/search.png and /dev/null differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/hardcore_ironman.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/hardcore_ironman.png index 2f4631d36e..bf70440802 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/hardcore_ironman.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/hardcore_ironman.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ironman.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ironman.png index a00b92b1f8..453e0326de 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ironman.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ironman.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/normal.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/normal.png index c00f4ec040..375dd4e906 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/normal.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/normal.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/search.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/search.png deleted file mode 100644 index 23bf66b759..0000000000 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/search.png and /dev/null differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ultimate_ironman.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ultimate_ironman.png index efa7643d97..07eb519971 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ultimate_ironman.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ultimate_ironman.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/info/arrow_right.png b/runelite-client/src/main/resources/net/runelite/client/plugins/info/arrow_right.png new file mode 100644 index 0000000000..92048f0c78 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/info/arrow_right.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/info/discord_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/info/discord_icon.png new file mode 100644 index 0000000000..a5d1d5722e Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/info/discord_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/info/github_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/info/github_icon.png new file mode 100644 index 0000000000..92222740b7 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/info/github_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/info/info_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/info/info_icon.png index 565f2a48b2..f4d052870a 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/info/info_icon.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/info/info_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/info/patreon_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/info/patreon_icon.png new file mode 100644 index 0000000000..0a52c8d262 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/info/patreon_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/kourendlibrary/panel_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/kourendlibrary/panel_icon.png index cb569cd1be..3b116835d6 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/kourendlibrary/panel_icon.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/kourendlibrary/panel_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/kourendlibrary/reset.png b/runelite-client/src/main/resources/net/runelite/client/plugins/kourendlibrary/reset.png new file mode 100644 index 0000000000..e94f6102a5 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/kourendlibrary/reset.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/notes/notes_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/notes/notes_icon.png index e94292baf1..31379ce441 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/notes/notes_icon.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/notes/notes_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/screenshot.png b/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/screenshot.png index 9a4f892bd7..cae8ee2341 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/screenshot.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/screenshot.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/xptracker/reset.png b/runelite-client/src/main/resources/net/runelite/client/plugins/xptracker/reset.png deleted file mode 100644 index 4d9d111037..0000000000 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/xptracker/reset.png and /dev/null differ diff --git a/runelite-client/src/main/resources/net/runelite/client/runelite.properties b/runelite-client/src/main/resources/net/runelite/client/runelite.properties index 4860325109..fa6ece9990 100644 --- a/runelite-client/src/main/resources/net/runelite/client/runelite.properties +++ b/runelite-client/src/main/resources/net/runelite/client/runelite.properties @@ -2,4 +2,6 @@ runelite.title=RuneLite runelite.version=${project.version} runescape.version=${rs.version} runelite.discord.appid=409416265891971072 -runelite.discord.invite=https://discord.gg/R4BQ8tU \ No newline at end of file +runelite.discord.invite=https://discord.gg/R4BQ8tU +runelite.github.link=https://github.com/runelite +runelite.patreon.link=https://www.patreon.com/runelite \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/ui/components/error.png b/runelite-client/src/main/resources/net/runelite/client/ui/components/error.png new file mode 100644 index 0000000000..11048a6501 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/ui/components/error.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/ui/components/loading_spinner.gif b/runelite-client/src/main/resources/net/runelite/client/ui/components/loading_spinner.gif new file mode 100644 index 0000000000..81e7c2845d Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/ui/components/loading_spinner.gif differ diff --git a/runelite-client/src/main/resources/net/runelite/client/ui/components/loading_spinner_darker.gif b/runelite-client/src/main/resources/net/runelite/client/ui/components/loading_spinner_darker.gif new file mode 100644 index 0000000000..e44fcdab65 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/ui/components/loading_spinner_darker.gif differ diff --git a/runelite-client/src/main/resources/net/runelite/client/ui/components/search.png b/runelite-client/src/main/resources/net/runelite/client/ui/components/search.png new file mode 100644 index 0000000000..e68566fe4d Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/ui/components/search.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/ui/components/search_darker.png b/runelite-client/src/main/resources/net/runelite/client/ui/components/search_darker.png new file mode 100644 index 0000000000..0d5d4608a6 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/ui/components/search_darker.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/ui/skin/RuneLite.colorschemes b/runelite-client/src/main/resources/net/runelite/client/ui/skin/RuneLite.colorschemes new file mode 100644 index 0000000000..1d9ffee41b --- /dev/null +++ b/runelite-client/src/main/resources/net/runelite/client/ui/skin/RuneLite.colorschemes @@ -0,0 +1,166 @@ +# Copyright (c) 2018, Tomas Slusny +# Copyright (c) 2018, Psikoi +# 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. + +RuneLite Enabled { + kind=Dark + colorUltraLight=#232323 + colorExtraLight=#232323 + colorLight=#232323 + colorMid=#232323 + colorDark=#232323 + colorUltraDark=#232323 + colorForeground=#C6C6C6 +} + +RuneLite Active { + kind=Light + colorUltraLight=#4e4e4e + colorExtraLight=#4e4e4e + colorLight=#4e4e4e + colorMid=#232323 + colorDark=#232323 + colorUltraDark=#232323 + colorForeground=#000000 +} + +RuneLite Selected Disabled Border { + kind=Dark + colorUltraLight=#191919 + colorExtraLight=#191919 + colorLight=#191919 + colorMid=#191919 + colorDark=#191919 + colorUltraDark=#191919 + colorForeground=#C6C6C6 +} + +RuneLite Border { + kind=Dark + colorUltraLight=#191919 + colorExtraLight=#191919 + colorLight=#191919 + colorMid=#191919 + colorDark=#191919 + colorUltraDark=#191919 + colorForeground=#C6C6C6 +} + +RuneLite Tab Border { + kind=Light + colorUltraLight=#232323 + colorExtraLight=#232323 + colorLight=#232323 + colorMid=#232323 + colorDark=#232323 + colorUltraDark=#232323 + colorForeground=#232323 +} + +RuneLite Mark Active { + kind=Dark + colorUltraLight=#191919 + colorExtraLight=#191919 + colorLight=#191919 + colorMid=#191919 + colorDark=#191919 + colorUltraDark=#191919 + colorForeground=#191919 +} + +RuneLite Highlight { + kind=Light + colorUltraLight=#C6C6C6 + colorExtraLight=#C6C6C6 + colorLight=#C6C6C6 + colorMid=#C6C6C6 + colorDark=#C6C6C6 + colorUltraDark=#C6C6C6 + colorForeground=#191919 +} + +RuneLite Watermark { + kind=Light + colorUltraLight=#313131 + colorExtraLight=#313131 + colorLight=#313131 + colorMid=#313131 + colorDark=#313131 + colorUltraDark=#313131 + colorForeground=#C6C6C6 +} + +RuneLite Decorations Watermark { + kind=Light + colorUltraLight=#1e1e1e + colorExtraLight=#1e1e1e + colorLight=#1e1e1e + colorMid=#1e1e1e + colorDark=#1e1e1e + colorUltraDark=#1e1e1e + colorForeground=#1e1e1e +} + +RuneLite Separator { + kind=Dark + colorUltraLight=#232323 + colorExtraLight=#232323 + colorLight=#232323 + colorMid=#232323 + colorDark=#232323 + colorUltraDark=#232323 + colorForeground=#232323 +} + +RuneLite Decorations Separator { + kind=Dark + colorUltraLight=#232323 + colorExtraLight=#232323 + colorLight=#232323 + colorMid=#232323 + colorDark=#232323 + colorUltraDark=#232323 + colorForeground=#232323 +} + +RuneLite Header Watermark { + kind=Dark + colorUltraLight=#1e1e1e + colorExtraLight=#1e1e1e + colorLight=#1e1e1e + colorMid=#1e1e1e + colorDark=#1e1e1e + colorUltraDark=#1e1e1e + colorForeground=#C6C6C6 +} + +RuneLite Header Border { + kind=Dark + colorUltraLight=#1e1e1e + colorExtraLight=#1e1e1e + colorLight=#1e1e1e + colorMid=#1e1e1e + colorDark=#1e1e1e + colorUltraDark=#1e1e1e + colorForeground=#C6C6C6 +} \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/util/StackFormatterTest.java b/runelite-client/src/test/java/net/runelite/client/util/StackFormatterTest.java index 9c344a909f..c36abcf8b3 100644 --- a/runelite-client/src/test/java/net/runelite/client/util/StackFormatterTest.java +++ b/runelite-client/src/test/java/net/runelite/client/util/StackFormatterTest.java @@ -26,12 +26,39 @@ package net.runelite.client.util; import java.text.NumberFormat; import java.text.ParseException; +import java.util.Locale; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import org.junit.Before; import org.junit.Test; public class StackFormatterTest { + @Before + public void setUp() + { + Locale.setDefault(Locale.ENGLISH); + } + + @Test + public void quantityToRSDecimalStackSize() + { + assertEquals("0", StackFormatter.quantityToRSDecimalStack(0)); + assertEquals("8500", StackFormatter.quantityToRSDecimalStack(8_500)); + assertEquals("10K", StackFormatter.quantityToRSDecimalStack(10_000)); + assertEquals("21,7K", StackFormatter.quantityToRSDecimalStack(21_700)); + assertEquals("100K", StackFormatter.quantityToRSDecimalStack(100_000)); + assertEquals("100,3K", StackFormatter.quantityToRSDecimalStack(100_300)); + assertEquals("1M", StackFormatter.quantityToRSDecimalStack(1_000_000)); + assertEquals("8,4M", StackFormatter.quantityToRSDecimalStack(8_450_000)); + assertEquals("10M", StackFormatter.quantityToRSDecimalStack(10_000_000)); + assertEquals("12,8M", StackFormatter.quantityToRSDecimalStack(12_800_000)); + assertEquals("100M", StackFormatter.quantityToRSDecimalStack(100_000_000)); + assertEquals("250,1M", StackFormatter.quantityToRSDecimalStack(250_100_000)); + assertEquals("1B", StackFormatter.quantityToRSDecimalStack(1_000_000_000)); + assertEquals("1,5B", StackFormatter.quantityToRSDecimalStack(1500_000_000)); + assertEquals("2,1B", StackFormatter.quantityToRSDecimalStack(Integer.MAX_VALUE)); + } @Test public void quantityToRSStackSize()