diff --git a/README.md b/README.md index 03951c6..f556d10 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Tracked Stats: Purpose: To track afk metrics to compare between activies and methods. Similar to tracking DPS and Kills/hr. -Session history shows your most recent sessions first. Click a session name to rename it. Sessions can be copied to clipboard or deleted (with confirmation). +Session history shows your most recent sessions first. Click a session name to rename it. Sessions can be copied to clipboard or deleted (with confirmation). The Start/Stop buttons and live stats stay pinned in place; only the history list scrolls when it grows long. ## Wiki Guide diff --git a/panel-preview.png b/panel-preview.png index 04b6505..8b267c5 100644 Binary files a/panel-preview.png and b/panel-preview.png differ diff --git a/src/main/java/com/afkstatstracker/AfkStatsTrackerPanel.java b/src/main/java/com/afkstatstracker/AfkStatsTrackerPanel.java index 75c1c57..ba8527c 100644 --- a/src/main/java/com/afkstatstracker/AfkStatsTrackerPanel.java +++ b/src/main/java/com/afkstatstracker/AfkStatsTrackerPanel.java @@ -9,6 +9,7 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; +import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; @@ -30,6 +31,7 @@ import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.ImageIcon; +import javax.swing.Scrollable; import javax.swing.SwingConstants; import javax.swing.Timer; import net.runelite.client.ui.ColorScheme; @@ -55,10 +57,14 @@ public class AfkStatsTrackerPanel extends PluginPanel private DistanceIndicator distanceIndicator; private JPanel historyContainer; + private JScrollPane historyScrollPane; private boolean historyExpanded = true; public AfkStatsTrackerPanel(AfkStatsTrackerPlugin plugin, SessionHistoryManager sessionHistoryManager) { + // wrap=false: we manage our own scroll so the buttons/stats stay pinned and only history data scrolls + super(false); + this.plugin = plugin; this.sessionHistoryManager = sessionHistoryManager; @@ -67,9 +73,9 @@ public AfkStatsTrackerPanel(AfkStatsTrackerPlugin plugin, SessionHistoryManager timer = new Timer(1000, e -> updateStats()); timer.setRepeats(true); - // Main content panel - JPanel contentPanel = new JPanel(); - contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); + // Pinned top panel (buttons + stats + history header) — never scrolls + JPanel topPanel = new JPanel(); + topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS)); // Button panel JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 5, 0)); @@ -128,14 +134,22 @@ public AfkStatsTrackerPanel(AfkStatsTrackerPlugin plugin, SessionHistoryManager statsPanel.add(distanceIndicator); statsPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, statsPanel.getPreferredSize().height)); - // History section - JPanel historySection = createHistorySection(); + // History header (pinned) + scrollable data area + JPanel historyHeader = createHistoryHeader(); + + historyContainer = new ScrollablePanel(); - contentPanel.add(buttonPanel); - contentPanel.add(statsPanel); - contentPanel.add(historySection); + historyScrollPane = new JScrollPane(historyContainer); + historyScrollPane.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); + historyScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + refreshHistoryPanel(); - add(contentPanel, BorderLayout.NORTH); + topPanel.add(buttonPanel); + topPanel.add(statsPanel); + topPanel.add(historyHeader); + + add(topPanel, BorderLayout.NORTH); + add(historyScrollPane, BorderLayout.CENTER); } private JPanel createStatCard(String title, String tooltip) @@ -192,13 +206,11 @@ private JPanel createSessionCard() return card; } - private JPanel createHistorySection() + private JPanel createHistoryHeader() { - JPanel section = new JPanel(new BorderLayout()); - section.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); - - // Header with toggle JPanel header = new JPanel(new BorderLayout()); + header.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); + JLabel headerLabel = new JLabel("▼ Session History"); headerLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); headerLabel.addMouseListener(new MouseAdapter() @@ -208,25 +220,12 @@ public void mouseClicked(MouseEvent e) { historyExpanded = !historyExpanded; headerLabel.setText((historyExpanded ? "▼" : "▶") + " Session History"); - historyContainer.setVisible(historyExpanded); + historyScrollPane.setVisible(historyExpanded); revalidate(); } }); header.add(headerLabel, BorderLayout.WEST); - - // History container - historyContainer = new JPanel(); - historyContainer.setLayout(new BoxLayout(historyContainer, BoxLayout.Y_AXIS)); - - JScrollPane scrollPane = new JScrollPane(historyContainer); - scrollPane.setPreferredSize(new Dimension(0, 300)); - scrollPane.setBorder(null); - - section.add(header, BorderLayout.NORTH); - section.add(scrollPane, BorderLayout.CENTER); - - refreshHistoryPanel(); - return section; + return header; } public void refreshHistoryPanel() @@ -464,6 +463,45 @@ public void stopTimer() } } + // Vertical list whose width follows the viewport, so rows shrink/truncate instead of forcing a horizontal scrollbar + private static class ScrollablePanel extends JPanel implements Scrollable + { + ScrollablePanel() + { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + } + + @Override + public Dimension getPreferredScrollableViewportSize() + { + return getPreferredSize(); + } + + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) + { + return 16; + } + + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) + { + return visibleRect.height; + } + + @Override + public boolean getScrollableTracksViewportWidth() + { + return true; + } + + @Override + public boolean getScrollableTracksViewportHeight() + { + return false; + } + } + private static class ConsistencyIndicator extends JPanel { private static final int MARKER_SIZE = 8;