Files
Runelite-EssentialsPlus/src/main/java/com/example/essentialsplus/currency/ui/CurrencyPanel.java
T
2026-09-18 17:18:57 +02:00

354 lines
9.8 KiB
Java

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<Currency> tracked()
{
List<Currency> 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<Currency> 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<CurrencyHistory.Sample> 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<Currency> 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("<html>" + text + "</html>");
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);
}
}