Skip to content

Instantly share code, notes, and snippets.

@martenjacobs
Last active June 27, 2022 11:52
Show Gist options
  • Save martenjacobs/75d6aeb04d577de9fdc1 to your computer and use it in GitHub Desktop.
Save martenjacobs/75d6aeb04d577de9fdc1 to your computer and use it in GitHub Desktop.
Simple class to enable kinetic scrolling through dragging to any JScrollPane
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
/**
* Simple class to 'touchify' JScrollPanes by enabling kinetic scrolling through dragging
* Usage: just create the JScrollPane as you normally would, but call TouchScreenScrollPane.install(JScrollPane) on it after
* all initialisation is done.
* @author martenjacobs
*/
public class TouchScreenScrollPane {
private TouchScreenScrollPane(){}
private static final HashMap<JScrollPane, MouseEventListener> listeners = new HashMap();
public static void install(JScrollPane p){
AWTEventListener awtevtlis = createAWTWindowListener();
Toolkit.getDefaultToolkit().addAWTEventListener(awtevtlis, AWTEvent.MOUSE_EVENT_MASK);
Toolkit.getDefaultToolkit().addAWTEventListener(awtevtlis, AWTEvent.MOUSE_MOTION_EVENT_MASK);
listeners.put(p, new MouseEventListener(p));
Component c = p.getViewport().getView();
if(c instanceof JTable){
JTable itm = (JTable) c;
itm.setRowHeight(36);
}
if(c instanceof JList){
JList itm = (JList) c;
itm.setFixedCellHeight(36);
}
if(c instanceof JComponent){
JComponent itm = (JComponent) c;
itm.setAutoscrolls(false);
Font f = itm.getFont();
Font nf = new Font(f.getFontName(), f.getStyle(), 16);
itm.setFont(nf);
}
}
private static void doDispatchEvent(MouseEvent awte, JScrollPane comp) {
MouseEventListener listener;
synchronized(listeners){
if(!listeners.containsKey(comp)) return;
listener = listeners.get(comp);
}
if (MouseEvent.MOUSE_CLICKED == awte.getID()) {
listener.mouseClicked(awte);
} else if (MouseEvent.MOUSE_PRESSED == awte.getID()) {
listener.mousePressed(awte);
} else if (MouseEvent.MOUSE_RELEASED == awte.getID()) {
listener.mouseReleased(awte);
} else if (MouseEvent.MOUSE_ENTERED == awte.getID()) {
listener.mouseEntered(awte);
} else if (MouseEvent.MOUSE_EXITED == awte.getID()) {
listener.mouseExited(awte);
} else if (MouseEvent.MOUSE_DRAGGED == awte.getID()) {
listener.mouseDragged(awte);
} else if (MouseEvent.MOUSE_MOVED == awte.getID()) {
listener.mouseMoved(awte);
}
}
private static AWTEventListener createAWTWindowListener() {
return new AWTEventListener() {
@Override
public void eventDispatched(AWTEvent awte) {
MouseEvent event = (MouseEvent) awte;
JScrollPane comp = checkForScrollPanel(event.getComponent());
if (comp!=null) {
doDispatchEvent(event, comp);
}
}
};
}
private static JScrollPane checkForScrollPanel(Component comp) {
if (comp instanceof JScrollPane) {
synchronized(listeners){
return (listeners.containsKey((JScrollPane) comp))?(JScrollPane) comp:null;
}
} else {
comp = comp.getParent();
if (comp != null) {
return checkForScrollPanel(comp);
}
}
return null;
}
private static class MouseEventListener implements MouseListener, MouseMotionListener {
private Point lastPos = null;
private Point relPos = null;
private ArrayList<MouseEvent> lastEvents = new ArrayList();
private Thread kinetic = new Thread();
private final JScrollPane pane;
public MouseEventListener(JScrollPane pane){
this.pane=pane;
}
@Override
public void mouseDragged(MouseEvent me) {
JViewport vp = pane.getViewport();
if(lastPos==null) lastPos=me.getLocationOnScreen();
moveBy(lastPos.x-me.getXOnScreen(), lastPos.y-me.getYOnScreen(), vp);
synchronized(lastEvents){
if(lastEvents.size()>5){
lastEvents.remove(0);
}
lastEvents.add(me);
}
}
@Override
public synchronized void mouseMoved(MouseEvent me) {
}
@Override
public synchronized void mouseClicked(MouseEvent me) {
}
@Override
public synchronized void mousePressed(MouseEvent me) {
JViewport vp = pane.getViewport();
lastPos=me.getLocationOnScreen();
relPos=vp.getViewPosition();
synchronized(lastEvents){
lastEvents.clear();
}
kinetic.interrupt();
}
@Override
public synchronized void mouseReleased(MouseEvent me) {
JViewport vp = pane.getViewport();
MouseEvent last;
MouseEvent first;
if(lastEvents.size()<2) return;
synchronized(lastEvents){
last = lastEvents.get(lastEvents.size()-1);
first = lastEvents.get(0);
lastEvents.clear();
}
Dimension vector = new Dimension(
first.getXOnScreen()-last.getXOnScreen(),
first.getYOnScreen()-last.getYOnScreen());
long time = last.getWhen()-first.getWhen();
Point2D p = new Point2D.Float(
((float) vector.width)/((float) time),
((float) vector.height)/((float) time));
kinetic.interrupt();
kinetic=new MoveThread(p, vp);
kinetic.start();
}
@Override
public synchronized void mouseEntered(MouseEvent me) {
}
@Override
public synchronized void mouseExited(MouseEvent me) {
}
public void moveBy(int x,int y,JViewport vp){
Point vis = relPos.getLocation();
vis.translate(x, y);
if(vis.x<0)vis.x=0;
if(vis.y<0)vis.y=0;
if(vis.x+vp.getWidth()>vp.getViewSize().width){
vis.x=vp.getViewSize().width-vp.getWidth();
}
if(vis.y+vp.getHeight()>vp.getViewSize().height){
vis.y=vp.getViewSize().height-vp.getHeight();
}
vp.setViewPosition(vis);
}
private class MoveThread extends Thread{
private Point2D speed;
private final JViewport vp;
public MoveThread(Point2D initialspeed, JViewport vp){
this.speed=initialspeed;
this.vp=vp;
}
private int ms = 1000/50;
private double modx = 0;
private double mody = 0;
@Override
public void run(){
try{
while(true){
double mx = speed.getX()*ms + modx;
double my = speed.getY()*ms + mody;
if(mx<=0.005 && my<=0.005
&& speed.getX()>=-0.005 && speed.getY()>=-0.005){
return;
}
relPos=vp.getViewPosition();
modx = mx % 1d;
mody = my % 1d;
moveBy((int) mx, (int) my, vp);
speed.setLocation(
speed.getX()/(Math.exp((double) ms/150d)),
speed.getY()/(Math.exp((double) ms/150d)));
Thread.sleep(ms);
}
}catch(Exception e){
}
}
}
}
}
@martenjacobs
Copy link
Author

ToDo:

  • disable passing through of press events when a user starts dragging
  • adding comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment