Created
June 2, 2022 20:34
-
-
Save alanwhite/42502f20390baf879d093691ebb72066 to your computer and use it in GitHub Desktop.
Example Mac OSX Magnify and Rotate gestures in a Swing app that will compile on all platform but only work on Mac (help with other platforms welcome)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xyz.arwhite.swing; | |
import java.awt.Dimension; | |
import java.awt.event.MouseAdapter; | |
import java.awt.event.MouseEvent; | |
import java.awt.event.MouseWheelEvent; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.InvocationHandler; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Proxy; | |
import javax.swing.JFrame; | |
import javax.swing.JPanel; | |
import javax.swing.SwingUtilities; | |
import javax.swing.WindowConstants; | |
/* | |
* To make this work ensure on jdk17.02+2 onwards | |
* Compile with -XDignore.symbol.file --add-exports java.desktop/com.apple.eawt.event=ALL-UNNAMED | |
* Run with --add-opens java.desktop/com.apple.eawt.event=ALL-UNNAMED | |
* | |
* This will compile and run on platforms other than osx as it uses reflection to reach the | |
* osx specific classes. | |
*/ | |
@SuppressWarnings("serial") | |
public class Indigesturing extends JFrame { | |
public class MagnifyHandler implements InvocationHandler { | |
public Object invoke(Object proxy, Method method, Object[] args) { | |
// method.getName() should always return "magnify" as that's all we subscribed to | |
// production code may wish to double check this. | |
try { | |
for(Object o: args) { | |
Object mag = o.getClass() | |
.getMethod("getMagnification") | |
.invoke(o); | |
// mag.getClass() should always return java.lang.Double | |
// production code may wish to check double this (no pun intended) | |
System.out.println("Magnify: "+mag); | |
} | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
return null; | |
} | |
} | |
public class RotateHandler implements InvocationHandler { | |
public Object invoke(Object proxy, Method method, Object[] args) { | |
// method.getName() should always return "rotate" as that's all we subscribed to | |
// production code may wish to double check this. | |
try { | |
for(Object o: args) { | |
Object rot = o.getClass() | |
.getMethod("getRotation") | |
.invoke(o); | |
// mag.getClass() should always return java.lang.Double | |
// production code may wish to check double this (no pun intended) | |
System.out.println("Rotate: "+rot); | |
} | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
return null; | |
} | |
} | |
public Indigesturing() { | |
super(); | |
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); | |
this.setPreferredSize(new Dimension(600,400)); | |
JPanel p = new JPanel(); | |
getContentPane().add(p); | |
/* | |
GestureUtilities.addGestureListenerTo(p, (GestureListener) new MagnificationListener() { | |
@Override | |
public void magnify(MagnificationEvent magnificationEvent) { | |
System.out.println("Magnify "+magnificationEvent.getMagnification()); | |
} | |
@Override | |
public void rotate(RotationEvent rotationEvent) { | |
System.out.println("Rotate: "+rotationEvent.getrotation()); | |
} | |
}); | |
*/ | |
// 40 odd lines of code replace 10 lines to make it cross-platform friendly | |
if (System.getProperty("os.name").contains("Mac")) { | |
try { | |
// Run with --add-opens java.desktop/com.apple.eawt.event=ALL-UNNAMED | |
Constructor[] constructors = Class.forName("com.apple.eawt.event.GestureUtilities") | |
.getDeclaredConstructors(); | |
Object gu=null; | |
for (Constructor constructor : constructors) | |
{ | |
constructor.setAccessible(true); | |
gu = constructor.newInstance(); | |
break; | |
} | |
Object mh = Proxy.newProxyInstance( | |
Class.forName("com.apple.eawt.event.MagnificationListener").getClassLoader(), | |
new Class[]{Class.forName("com.apple.eawt.event.MagnificationListener")}, | |
new MagnifyHandler() | |
); | |
gu.getClass() | |
.getMethod("addGestureListenerTo", | |
Class.forName("javax.swing.JComponent"), | |
Class.forName("com.apple.eawt.event.GestureListener")) | |
.invoke(gu, p, mh); | |
Object rh = Proxy.newProxyInstance( | |
Class.forName("com.apple.eawt.event.RotationListener").getClassLoader(), | |
new Class[]{Class.forName("com.apple.eawt.event.RotationListener")}, | |
new RotateHandler() | |
); | |
gu.getClass() | |
.getMethod("addGestureListenerTo", | |
Class.forName("javax.swing.JComponent"), | |
Class.forName("com.apple.eawt.event.GestureListener")) | |
.invoke(gu, p, rh); | |
} | |
catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
var ma = new MouseAdapter() { | |
/* | |
* 2-finger swipe is a mouse wheel scroll | |
* modifier/ext-modifier of shift is supplied if horizontal, otherwise vertical | |
* wheelRotation is positive for up/left swipes, negative for down/right, preciseWheelRotation follows, almost always updates, even if wheelRotation 0 | |
* using the touchpad appears to use some momentum algo under the covers as events can keep coming after fingers have left contact with the pad | |
* not evident how to tell when contact has been broken, rotation reduces as moment reduces | |
* | |
* This bug is the only place I've seen that documents the shift behaviour https://bugs.openjdk.java.net/browse/JDK-8203048 | |
*/ | |
@Override | |
public void mouseWheelMoved(MouseWheelEvent e) { | |
System.out.println(e); | |
} | |
/* | |
* 3-finger swipe is a drag event, reported as Button1 being pressed, even if no click down has been made | |
* There is no difference between a 3-finger drag whilst clicked/pressed into the pad, and not | |
* | |
* 2-finger click and drag, is reported as a drag event with cmd+Button3 modifiers | |
* 2-finger swipe, ie no click, is a mouse scroll event, see above | |
* | |
* 1-finger click and drag, is a drag and reports as Button1 pressed, exactly the same as a 3-finger swipe, just no button down event before (I imagine) | |
* | |
* Direction is always determined by the co-ordinate move. There is no concept of momentum as this event relies on the co-ordinates of the pointer. | |
*/ | |
@Override | |
public void mouseDragged(MouseEvent e) { System.out.println(e); } | |
}; | |
p.addMouseListener(ma); | |
p.addMouseMotionListener(ma); | |
p.addMouseWheelListener(ma); | |
pack(); | |
setVisible(true); | |
} | |
public static void main(String[] args) { | |
SwingUtilities.invokeLater(new Runnable() { | |
public void run() { | |
new Indigesturing(); | |
} | |
}); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment