package com.example.essentialsplus.currency.ui; import com.example.essentialsplus.currency.Currency; import com.example.essentialsplus.currency.CurrencyHistory; import com.example.essentialsplus.currency.CurrencyTracker; import com.example.essentialsplus.ui.TimeFilter; import com.example.essentialsplus.ui.Ui; import java.awt.BorderLayout; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.util.ArrayList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import net.runelite.client.game.ItemManager; import net.runelite.client.ui.DynamicGridLayout; import net.runelite.client.ui.FontManager; import net.runelite.client.ui.PluginPanel; /** Points & Marks tab: every tracked minigame point, mark and token with the amount earned in the range. */ public class CurrencyPanel extends JPanel { private final CurrencyTracker tracker; private final TimeFilter filter; private final CurrencyIcons icons; private final JPanel list = new JPanel(new DynamicGridLayout(0, 1, 0, 4)); private final CurrencyCard hero; private CurrencyCard overviewCard; /** currency always shown on the overview card, null = what is being played */ private Currency overviewPin; private boolean overviewShowTracked = true; public CurrencyPanel(CurrencyTracker tracker, ItemManager itemManager, TimeFilter filter, Runnable onBack) { this.tracker = tracker; this.filter = filter; this.icons = new CurrencyIcons(itemManager); this.hero = new CurrencyCard(icons, () -> { }); hero.setCursor(Cursor.getDefaultCursor()); setLayout(new BorderLayout(0, 6)); setBackground(Ui.BG); setBorder(BorderFactory.createEmptyBorder(10, 8, 8, 8)); JPanel backRow = new JPanel(new BorderLayout(6, 0)); backRow.setOpaque(false); JButton backBtn = new JButton("← Overview"); backBtn.setFont(FontManager.getRunescapeSmallFont()); backBtn.setForeground(Ui.ACCENT); backBtn.setFocusPainted(false); backBtn.setMargin(new java.awt.Insets(2, 2, 2, 2)); backBtn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); backBtn.addActionListener(e -> onBack.run()); backRow.add(backBtn, BorderLayout.WEST); JLabel title = new JLabel("Points & Marks"); title.setFont(FontManager.getRunescapeBoldFont()); title.setForeground(Ui.ACCENT); title.setHorizontalAlignment(JLabel.RIGHT); backRow.add(title, BorderLayout.CENTER); add(backRow, BorderLayout.NORTH); list.setOpaque(false); JPanel listWrap = new JPanel(new BorderLayout()); listWrap.setOpaque(false); listWrap.add(list, BorderLayout.NORTH); JScrollPane scroll = new JScrollPane(listWrap); scroll.setBorder(null); scroll.setOpaque(false); scroll.getViewport().setOpaque(false); scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); scroll.getVerticalScrollBar().setUnitIncrement(16); scroll.getVerticalScrollBar().setPreferredSize(new Dimension(8, 0)); add(scroll, BorderLayout.CENTER); refresh(); } public void setOverviewOptions(Currency pin, boolean showTracked) { overviewPin = pin; overviewShowTracked = showTracked; refresh(); } /** Currencies with data for the logged-in character, in enum order. */ public List tracked() { List out = new ArrayList<>(); CurrencyHistory h = tracker.history(); if (h != null) { for (Currency c : Currency.values()) { if (h.has(c)) { out.add(c); } } } return out; } /** Card for the overview, kept up to date by {@link #refresh}. */ public CurrencyCard createOverviewCard(Runnable onClick) { overviewCard = new CurrencyCard(icons, onClick); refresh(); return overviewCard; } /** Swing thread. */ public void refresh() { CurrencyHistory h = tracker.history(); long now = System.currentTimeMillis(); String range = filter.inLabel(); List shown = new ArrayList<>(); if (h != null) { for (Currency c : Currency.values()) { if (h.has(c)) { shown.add(c); } } } Currency active = tracker.active(); Currency featured = active; if (featured == null && h != null) { featured = Currency.byName(h.lastActive); if (featured == null) { long best = -1; for (Currency c : shown) { List s = h.samples(c); long t = s.get(s.size() - 1).t; if (t > best) { best = t; featured = c; } } } } for (CurrencyCard card : new CurrencyCard[]{hero, overviewCard}) { if (card == null) { continue; } Currency c = featured; boolean pinned = card == overviewCard && overviewPin != null && h != null && h.has(overviewPin); if (pinned) { c = overviewPin; } card.setOptions(card != overviewCard || overviewShowTracked, pinned && c != active); if (c == null || h == null) { card.update(null, 0, 0, range, false, 0, 0); } else { card.update(c, Math.max(0, h.amount(c)), h.change(c, filter, now)[0], range, c == active, h.lastGain(c), shown.size()); } } list.removeAll(); list.add(hero); if (h == null || shown.isEmpty()) { list.add(hint(h == null ? "Log in to start tracking." : "Nothing tracked yet. Points show up once they change or are read in-game.")); } else { // earned in the range first, then most recently earned shown.sort((a, b) -> { int byActive = Boolean.compare(b == active, a == active); if (byActive != 0) { return byActive; } return Long.compare(h.lastGain(b), h.lastGain(a)); }); addSection("POINTS", shown, h, active, now, false); addSection("MARKS & TOKENS", shown, h, active, now, true); } list.add(hint("Pest Control points are read while the lander or reward shop is open. " + "Items count bank, inventory, gear, looting bag and seed vault.")); list.revalidate(); list.repaint(); } private void addSection(String name, List shown, CurrencyHistory h, Currency active, long now, boolean items) { boolean header = false; for (Currency c : shown) { if ((c.source == Currency.Source.ITEM) != items) { continue; } if (!header) { list.add(header(name)); header = true; } long[] change = h.change(c, filter, now); list.add(new Row(c, h.amount(c), change[0], change[1], h.lastGain(c), c == active)); } } private static JLabel header(String text) { JLabel l = new JLabel(text); l.setFont(FontManager.getRunescapeSmallFont()); l.setForeground(Ui.MUTED); l.setBorder(BorderFactory.createEmptyBorder(6, 2, 0, 2)); return l; } private static JLabel hint(String text) { JLabel l = new JLabel("" + text + ""); l.setFont(FontManager.getRunescapeSmallFont()); l.setForeground(Ui.FAINT); l.setBorder(BorderFactory.createEmptyBorder(6, 4, 6, 4)); return l; } /** One currency: icon, name, amount, earned / spent in the range. */ private final class Row extends JComponent { private static final int ICON = 28; private final Currency currency; private final long amount; private final long earned; private final long spent; private final long lastGain; private final boolean active; Row(Currency currency, long amount, long earned, long spent, long lastGain, boolean active) { this.currency = currency; this.amount = amount; this.earned = earned; this.spent = spent; this.lastGain = lastGain; this.active = active; setToolTipText(currency.label + ": " + CurrencyCard.number(amount) + (lastGain > 0 ? " - last earned " + CurrencyCard.ago(lastGain) : "")); } @Override public Dimension getPreferredSize() { return new Dimension(PluginPanel.PANEL_WIDTH - 24, 44); } @Override protected void paintComponent(Graphics g0) { Graphics2D g = Ui.prepare(g0); int w = getWidth(); int h = getHeight(); g.setColor(active ? Ui.CARD_SELECTED : Ui.CARD); g.fillRoundRect(0, 0, w, h, 6, 6); if (active) { g.setColor(Ui.UP); g.fillRoundRect(0, 6, 3, h - 12, 3, 3); } int pad = 8; icons.paint(g, currency, pad - 2, (h - ICON) / 2, ICON, this); int tx = pad + ICON + 6; Font bold = FontManager.getRunescapeBoldFont(); Font small = FontManager.getRunescapeSmallFont(); g.setFont(bold); FontMetrics bfm = g.getFontMetrics(); String value = CurrencyCard.number(Math.max(0, amount)); int line1 = 5 + bfm.getAscent(); g.setColor(Ui.GOLD); Ui.drawRight(g, value, w - pad, line1); g.setFont(FontManager.getRunescapeFont()); FontMetrics nfm = g.getFontMetrics(); g.setColor(Ui.TEXT); int nameMax = w - pad - bfm.stringWidth(value) - 8 - tx; g.drawString(clip(nfm, currency.label, nameMax), tx, line1); g.setFont(small); FontMetrics sfm = g.getFontMetrics(); int line2 = line1 + sfm.getAscent() + 6; String plus = "+" + CurrencyCard.number(earned); g.setColor(earned > 0 ? Ui.UP : Ui.FAINT); g.drawString(plus, tx, line2); int x = tx + sfm.stringWidth(plus); if (spent < 0) { String minus = " -" + CurrencyCard.number(-spent); g.setColor(Ui.DOWN); g.drawString(minus, x, line2); x += sfm.stringWidth(minus); } g.setColor(Ui.FAINT); g.drawString(" " + filter.getLabel(), x, line2); g.setColor(active ? Ui.UP : Ui.FAINT); Ui.drawRight(g, active ? "now" : lastGain > 0 ? CurrencyCard.ago(lastGain) : "", w - pad, line2); g.dispose(); } } private static String clip(FontMetrics fm, String s, int max) { if (fm.stringWidth(s) <= max) { return s; } for (int i = s.length() - 1; i > 0; i--) { String t = s.substring(0, i) + ".."; if (fm.stringWidth(t) <= max) { return t; } } return ""; } /** Coalesced refresh from any thread. */ public void refreshLater() { SwingUtilities.invokeLater(this::refresh); } }