Skip to content

Commit b2d1ef3

Browse files
committed
Move jitterless scrolling logic
Moved the jitterless smooth scrolling logic out of JitterlessScrollPaneUI which wasn't the most logical place for it and into JitterlessScrollPane with the new PreciseMouseWheelListener inner class. JitterlessScrollPaneUI was then deleted and removed from themes/FlatLaf.properties
1 parent 70ce676 commit b2d1ef3

3 files changed

Lines changed: 97 additions & 140 deletions

File tree

src/main/java/edu/rpi/legup/ui/JitterlessScrollPane.java

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@
44
import java.awt.Graphics;
55
import java.awt.Graphics2D;
66
import java.awt.GraphicsEnvironment;
7+
import java.awt.Rectangle;
8+
import java.awt.event.MouseWheelEvent;
9+
import java.awt.event.MouseWheelListener;
710
import java.awt.geom.AffineTransform;
11+
import javax.swing.JScrollBar;
812
import javax.swing.JScrollPane;
913
import javax.swing.JViewport;
14+
import javax.swing.Scrollable;
15+
import javax.swing.SwingConstants;
16+
import javax.swing.UIManager;
1017

1118
/**
1219
* The {@code JitterlessScrollPane} class is a {@code JScrollPane} that combats jittering of
@@ -21,15 +28,12 @@ public class JitterlessScrollPane extends JScrollPane {
2128

2229
public JitterlessScrollPane() {
2330
super();
31+
addMouseWheelListener(new PreciseWheelListener());
2432
}
2533

2634
public JitterlessScrollPane(Component view) {
2735
super(view);
28-
}
29-
30-
@Override
31-
public String getUIClassID() {
32-
return "JitterlessScrollPaneUI";
36+
addMouseWheelListener(new PreciseWheelListener());
3337
}
3438

3539
public void setFracOffsetX(double fracOffsetX) {
@@ -83,7 +87,7 @@ protected void paintChildren(Graphics graphics) {
8387

8488
Graphics2D g = (Graphics2D) graphics.create();
8589

86-
// Get DPI scale from screen
90+
// Get graphics environment scale from screen
8791
AffineTransform scaleDPI = GraphicsEnvironment
8892
.getLocalGraphicsEnvironment()
8993
.getDefaultScreenDevice()
@@ -102,4 +106,91 @@ protected void paintChildren(Graphics graphics) {
102106
g.dispose();
103107
}
104108
}
109+
110+
/**
111+
* The {@code PreciseWheelListener} class is used by the {@code JitterlessScrollPane} class
112+
* to intercept scrolling events from precise inputs like trackpads and uses them to give
113+
* the {@code JitterlessScrollPane} sub-pixel scrolling accuracy and prevent "jumping".
114+
*/
115+
protected class PreciseWheelListener implements MouseWheelListener {
116+
117+
@Override
118+
public void mouseWheelMoved(MouseWheelEvent e) {
119+
120+
// Only intercept scroll wheel events from precise inputs and if enabled
121+
if (
122+
isWheelScrollingEnabled() &&
123+
UIManager.getBoolean("ScrollPane.smoothScrolling") &&
124+
e.getPreciseWheelRotation() != e.getWheelRotation() &&
125+
viewport != null
126+
) {
127+
// Modified code from com.formdev.flatlaf.ui.FlatScrollPaneUI#mouseWheelMovedSmooth
128+
JScrollBar scrollbar = verticalScrollBar;
129+
if (scrollbar == null || !scrollbar.isVisible() || e.isShiftDown()) {
130+
scrollbar = horizontalScrollBar;
131+
if (scrollbar == null || !scrollbar.isVisible()) {
132+
return;
133+
}
134+
}
135+
136+
e.consume();
137+
138+
double rotation = e.getPreciseWheelRotation();
139+
int unitIncrement;
140+
int orientation = scrollbar.getOrientation();
141+
Component view = viewport.getView();
142+
143+
if (view instanceof Scrollable scrollable) {
144+
145+
Rectangle visibleRect = new Rectangle(viewport.getExtentSize());
146+
unitIncrement = scrollable.getScrollableUnitIncrement(visibleRect, orientation, 1);
147+
148+
if (unitIncrement > 0) {
149+
150+
if (orientation == SwingConstants.VERTICAL) {
151+
visibleRect.y += unitIncrement;
152+
visibleRect.height -= unitIncrement;
153+
} else {
154+
visibleRect.x += unitIncrement;
155+
visibleRect.width -= unitIncrement;
156+
}
157+
158+
int unitIncrement2 = scrollable.getScrollableUnitIncrement(visibleRect, orientation, 1);
159+
if (unitIncrement2 > 0) {
160+
unitIncrement = Math.min(unitIncrement, unitIncrement2);
161+
}
162+
}
163+
} else {
164+
165+
int direction = rotation < 0 ? -1 : 1;
166+
unitIncrement = scrollbar.getUnitIncrement(direction);
167+
}
168+
169+
// Compute new position based on previous sub-pixel offset
170+
double delta = rotation * unitIncrement * e.getScrollAmount() +
171+
((orientation == SwingConstants.VERTICAL) ?
172+
getFracOffsetY() : getFracOffsetX());
173+
int idelta = (int) Math.round(delta);
174+
175+
int value = scrollbar.getValue();
176+
int minValue = scrollbar.getMinimum();
177+
int maxValue = scrollbar.getMaximum() - scrollbar.getModel().getExtent();
178+
int newValue = Math.max(minValue, Math.min(value + idelta, maxValue));
179+
180+
if (newValue != value) {
181+
scrollbar.setValue(newValue);
182+
}
183+
184+
// Set new sub-pixel offset
185+
double newFracOffset =
186+
(value + delta > minValue && value + delta < maxValue) ?
187+
delta - idelta : 0.0;
188+
if (orientation == SwingConstants.VERTICAL) {
189+
setFracOffsetY(newFracOffset);
190+
} else {
191+
setFracOffsetX(newFracOffset);
192+
}
193+
}
194+
}
195+
}
105196
}

src/main/java/edu/rpi/legup/ui/JitterlessScrollPaneUI.java

Lines changed: 0 additions & 129 deletions
This file was deleted.

src/main/resources/edu/rpi/legup/themes/FlatLaf.properties

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,6 @@ Expected.SvgIcon.borderColor = #232323
216216
[style]PopupMenu.dataSelection = background: #808080;
217217

218218

219-
# UI Delegate Mapping
220-
221-
JitterlessScrollPaneUI = edu.rpi.legup.ui.JitterlessScrollPaneUI
222-
223-
224219
# Overrides of default flatlaf\FlatLaf.properties
225220

226221
defaultFont = Roboto-Regular, 12

0 commit comments

Comments
 (0)