From a5f5fe69407aed7422d4f2b48377e469efd56f56 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 13 Jan 2020 17:59:43 -0700 Subject: [PATCH 1/2] loottracker: Optimize panel rebuild Removing components in swing can be incredibly slow under serveral circumstances. This adds a special removeAll that can take less time in some cases --- .../plugins/loottracker/LootTrackerPanel.java | 3 +- .../loottracker/LootTrackerPlugin.java | 2 +- .../net/runelite/client/util/SwingUtil.java | 56 +++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java index 22ce4561fa..37b4d8e9f9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java @@ -54,6 +54,7 @@ import net.runelite.client.ui.components.PluginErrorPanel; import net.runelite.client.util.ColorUtil; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.QuantityFormatter; +import net.runelite.client.util.SwingUtil; import net.runelite.http.api.loottracker.LootTrackerClient; class LootTrackerPanel extends PluginPanel @@ -488,7 +489,7 @@ class LootTrackerPanel extends PluginPanel */ private void rebuild() { - logsContainer.removeAll(); + SwingUtil.fastRemoveAll(logsContainer); boxes.clear(); if (groupLoot) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index adb5217a4e..0864776476 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -612,7 +612,7 @@ public class LootTrackerPlugin extends Plugin } config.setIgnoredItems(Text.toCSV(ignoredItemSet)); - panel.updateIgnoredRecords(); + // the config changed will update the panel } boolean isIgnored(String name) 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 f4e12c9020..5eb7cef4af 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 @@ -26,11 +26,16 @@ package net.runelite.client.util; import java.awt.AWTException; import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.EventQueue; import java.awt.Font; import java.awt.Frame; import java.awt.Image; import java.awt.Insets; +import java.awt.SecondaryLoop; import java.awt.SystemTray; +import java.awt.Toolkit; import java.awt.TrayIcon; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -50,6 +55,7 @@ import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; @@ -293,4 +299,54 @@ public class SwingUtil { button.addItemListener(l -> button.setToolTipText(button.isSelected() ? on : off)); } + + /** + * Removes all of a component's children faster than calling removeAll() on it in many cases + */ + public static void fastRemoveAll(Container c) + { + // If we are not on the EDT this will deadlock, in addition to being totally unsafe + assert SwingUtilities.isEventDispatchThread(); + + // when a component is removed it has to be resized for some reason, but only if it's valid + // so we make sure to invalidate everything before removing it + c.invalidate(); + for (int i = 0; i < c.getComponentCount(); i++) + { + Component ic = c.getComponent(i); + + // removeAll and removeNotify are both recursive, so we have to recurse before them + if (ic instanceof Container) + { + fastRemoveAll((Container) ic); + } + + // each removeNotify needs to remove anything from the event queue that is for that widget + // this however requires taking a lock, and is moderately slow, so we just execute all of + // those events with a secondary event loop + pumpPendingEvents(); + + // call removeNotify early; this is most of the work in removeAll, and generates events that + // the next secondaryLoop will pickup + ic.removeNotify(); + } + + // Actually remove anything + c.removeAll(); + } + + /** + * Run any events currently in the event queue + */ + public static void pumpPendingEvents() + { + EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); + + if (eq.peekEvent() != null) + { + SecondaryLoop l = eq.createSecondaryLoop(); + SwingUtilities.invokeLater(l::exit); + l.enter(); + } + } } From aa85eb6a6e350f1fed4267b0edda045f00080616 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 13 Jan 2020 18:35:34 -0700 Subject: [PATCH 2/2] loottracker: Handle rollover/select icons correctly --- .../plugins/loottracker/LootTrackerPanel.java | 175 ++++++------------ 1 file changed, 57 insertions(+), 118 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java index 37b4d8e9f9..5f1d4d4867 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java @@ -39,13 +39,19 @@ import java.util.List; import java.util.function.Predicate; import javax.swing.BorderFactory; import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; import javax.swing.ImageIcon; +import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; +import javax.swing.JRadioButton; +import javax.swing.JToggleButton; import javax.swing.border.EmptyBorder; +import javax.swing.plaf.basic.BasicButtonUI; +import javax.swing.plaf.basic.BasicToggleButtonUI; import net.runelite.client.game.ItemManager; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; @@ -98,11 +104,11 @@ class LootTrackerPanel extends PluginPanel // Details and navigation private final JPanel actionsContainer = new JPanel(); private final JLabel detailsTitle = new JLabel(); - private final JLabel backBtn = new JLabel(); - private final JLabel viewHiddenBtn = new JLabel(); - private final JLabel singleLootBtn = new JLabel(); - private final JLabel groupedLootBtn = new JLabel(); - private final JLabel collapseBtn = new JLabel(); + private final JButton backBtn = new JButton(); + private final JToggleButton viewHiddenBtn = new JToggleButton(); + private final JRadioButton singleLootBtn = new JRadioButton(); + private final JRadioButton groupedLootBtn = new JRadioButton(); + private final JButton collapseBtn = new JButton(); // Aggregate of all kills private final List aggregateRecords = new ArrayList<>(); @@ -174,121 +180,64 @@ class LootTrackerPanel extends PluginPanel final JPanel viewControls = new JPanel(new GridLayout(1, 3, 10, 0)); viewControls.setBackground(ColorScheme.DARKER_GRAY_COLOR); + SwingUtil.removeButtonDecorations(collapseBtn); collapseBtn.setIcon(EXPAND_ICON); - collapseBtn.addMouseListener(new MouseAdapter() - { - @Override - public void mousePressed(MouseEvent e) - { - changeCollapse(); - } - }); - - singleLootBtn.setIcon(SINGLE_LOOT_VIEW); - singleLootBtn.setToolTipText("Show each kill separately"); - singleLootBtn.addMouseListener(new MouseAdapter() - { - @Override - public void mousePressed(MouseEvent mouseEvent) - { - changeGrouping(false); - } - - @Override - public void mouseExited(MouseEvent mouseEvent) - { - singleLootBtn.setIcon(groupLoot ? SINGLE_LOOT_VIEW_FADED : SINGLE_LOOT_VIEW); - } - - @Override - public void mouseEntered(MouseEvent mouseEvent) - { - singleLootBtn.setIcon(groupLoot ? SINGLE_LOOT_VIEW_HOVER : SINGLE_LOOT_VIEW); - } - }); - - groupedLootBtn.setIcon(GROUPED_LOOT_VIEW); - groupedLootBtn.setToolTipText("Group loot by source"); - groupedLootBtn.addMouseListener(new MouseAdapter() - { - @Override - public void mousePressed(MouseEvent mouseEvent) - { - changeGrouping(true); - } - - @Override - public void mouseExited(MouseEvent mouseEvent) - { - groupedLootBtn.setIcon(groupLoot ? GROUPED_LOOT_VIEW : GROUPED_LOOT_VIEW_FADED); - } - - @Override - public void mouseEntered(MouseEvent mouseEvent) - { - groupedLootBtn.setIcon(groupLoot ? GROUPED_LOOT_VIEW : GROUPED_LOOT_VIEW_HOVER); - } - }); - - viewHiddenBtn.setIcon(VISIBLE_ICON); - viewHiddenBtn.setToolTipText("Show ignored items"); - viewHiddenBtn.addMouseListener(new MouseAdapter() - { - @Override - public void mousePressed(MouseEvent mouseEvent) - { - changeItemHiding(!hideIgnoredItems); - } - - @Override - public void mouseExited(MouseEvent mouseEvent) - { - viewHiddenBtn.setIcon(hideIgnoredItems ? INVISIBLE_ICON : VISIBLE_ICON); - } - - @Override - public void mouseEntered(MouseEvent mouseEvent) - { - viewHiddenBtn.setIcon(hideIgnoredItems ? INVISIBLE_ICON_HOVER : VISIBLE_ICON_HOVER); - } - }); - + collapseBtn.setSelectedIcon(COLLAPSE_ICON); + SwingUtil.addModalTooltip(collapseBtn, "Collapse All", "Un-Collapse All"); + collapseBtn.setBackground(ColorScheme.DARKER_GRAY_COLOR); + collapseBtn.setUI(new BasicButtonUI()); // substance breaks the layout + collapseBtn.addActionListener(ev -> changeCollapse()); viewControls.add(collapseBtn); + + SwingUtil.removeButtonDecorations(singleLootBtn); + singleLootBtn.setIcon(SINGLE_LOOT_VIEW_FADED); + singleLootBtn.setRolloverIcon(SINGLE_LOOT_VIEW_HOVER); + singleLootBtn.setSelectedIcon(SINGLE_LOOT_VIEW); + singleLootBtn.setToolTipText("Show each kill separately"); + singleLootBtn.addActionListener(e -> changeGrouping(false)); + + SwingUtil.removeButtonDecorations(groupedLootBtn); + groupedLootBtn.setIcon(GROUPED_LOOT_VIEW_FADED); + groupedLootBtn.setRolloverIcon(GROUPED_LOOT_VIEW_HOVER); + groupedLootBtn.setSelectedIcon(GROUPED_LOOT_VIEW); + groupedLootBtn.setToolTipText("Group loot by source"); + groupedLootBtn.addActionListener(e -> changeGrouping(true)); + + ButtonGroup groupSingleGroup = new ButtonGroup(); + groupSingleGroup.add(singleLootBtn); + groupSingleGroup.add(groupedLootBtn); + viewControls.add(groupedLootBtn); viewControls.add(singleLootBtn); - viewControls.add(viewHiddenBtn); changeGrouping(true); + + SwingUtil.removeButtonDecorations(viewHiddenBtn); + viewHiddenBtn.setIconTextGap(0); + viewHiddenBtn.setIcon(VISIBLE_ICON); + viewHiddenBtn.setRolloverIcon(INVISIBLE_ICON_HOVER); + viewHiddenBtn.setSelectedIcon(INVISIBLE_ICON); + viewHiddenBtn.setRolloverSelectedIcon(VISIBLE_ICON_HOVER); + viewHiddenBtn.setBackground(ColorScheme.DARKER_GRAY_COLOR); + viewHiddenBtn.setUI(new BasicToggleButtonUI()); // substance breaks the layout and the pressed icon + SwingUtil.addModalTooltip(viewHiddenBtn, "Show ignored items", "Hide ignored items"); changeItemHiding(true); + viewControls.add(viewHiddenBtn); final JPanel leftTitleContainer = new JPanel(new BorderLayout(5, 0)); leftTitleContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); detailsTitle.setForeground(Color.WHITE); + SwingUtil.removeButtonDecorations(backBtn); backBtn.setIcon(BACK_ARROW_ICON); + backBtn.setRolloverIcon(BACK_ARROW_ICON_HOVER); backBtn.setVisible(false); - backBtn.addMouseListener(new MouseAdapter() + backBtn.addActionListener(ev -> { - @Override - public void mousePressed(MouseEvent mouseEvent) - { - currentView = null; - backBtn.setVisible(false); - detailsTitle.setText(""); - rebuild(); - } - - @Override - public void mouseExited(MouseEvent mouseEvent) - { - backBtn.setIcon(BACK_ARROW_ICON); - } - - @Override - public void mouseEntered(MouseEvent mouseEvent) - { - backBtn.setIcon(BACK_ARROW_ICON_HOVER); - } + currentView = null; + backBtn.setVisible(false); + detailsTitle.setText(""); + rebuild(); }); leftTitleContainer.add(backBtn, BorderLayout.WEST); @@ -368,16 +317,7 @@ class LootTrackerPanel extends PluginPanel void updateCollapseText() { - if (isAllCollapsed()) - { - collapseBtn.setToolTipText("Un-Collapse All"); - collapseBtn.setIcon(COLLAPSE_ICON); - } - else - { - collapseBtn.setToolTipText("Collapse All"); - collapseBtn.setIcon(EXPAND_ICON); - } + collapseBtn.setSelected(isAllCollapsed()); } private boolean isAllCollapsed() @@ -428,9 +368,8 @@ class LootTrackerPanel extends PluginPanel private void changeGrouping(boolean group) { groupLoot = group; + (group ? groupedLootBtn : singleLootBtn).setSelected(true); rebuild(); - groupedLootBtn.setIcon(group ? GROUPED_LOOT_VIEW : GROUPED_LOOT_VIEW_FADED); - singleLootBtn.setIcon(group ? SINGLE_LOOT_VIEW_FADED : SINGLE_LOOT_VIEW); } /** @@ -441,8 +380,8 @@ class LootTrackerPanel extends PluginPanel private void changeItemHiding(boolean hide) { hideIgnoredItems = hide; + viewHiddenBtn.setSelected(hide); rebuild(); - viewHiddenBtn.setIcon(hideIgnoredItems ? VISIBLE_ICON : INVISIBLE_ICON); } /**