Opened 2 years ago
Last modified 2 years ago
#23102 new enhancement
Poor map navigation on macbooks — at Initial Version
| Reported by: | delusional | Owned by: | team |
|---|---|---|---|
| Priority: | normal | Milestone: | |
| Component: | Core | Version: | latest |
| Keywords: | macosx | Cc: |
Description
I'm using an M2 macbook for on-the-go mapping, and I've found that moving around the map is cumbersome.
From a user perspective I see two problems. The first is a mismatch between the platform native gestures and JOSM. In macos two finger dragging is generally panning, but because it's still presented to the application as scrolling, JOSM interprets it as zooming. This forces panning to be a two finger click-drag which doesn't feel good (pushing down and dragging at the same time creates too much friction to feel good). The second problem is around integer input handling. Zooming, currently scrolling but ideally a multitouch gesture, feels clunky and, overly sensitive, and doesn't react well to the magnitude of the movement. Looking at the code this seems to be caused by zooming being an integer operation, and macos generally relying on many subinteger (fractional) events to smoothly scroll.
I've been looking through the code, and it looks like we can get closer to something good with some changes to src/org/openstreetmap/josm/gui/MapMover.java. Scrolling can be turned into (very nice and smooth) panning by changing mouseWheelMoved:
@@ -252,8 +317,19 @@
*/
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
- int rotation = PROP_ZOOM_REVERSE_WHEEL.get() ? -e.getWheelRotation() : e.getWheelRotation();
- nc.zoomManyTimes(e.getX(), e.getY(), rotation);
+
+ float fracx = 0.0f;
+ float fracy = 0.0f;
+
+ if((e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) == 0) {
+ fracy -= e.getPreciseWheelRotation() * 8; // 8 feels good
+ } else {
+ fracx += e.getPreciseWheelRotation() * 8;
+ }
+
+ nc.zoomTo(nc.getCenter().add(fracx * nc.getScale(), fracy * nc.getScale()));
}
This leaves us without a zoom gesture. On macos the multitouch pinch gesture is accessed through the apple java specific GestureUtilities, We can use reflection to dynamically link against it (this was taken from https://gist.github.com/alanwhite/42502f20390baf879d093691ebb72066). Note that I'm manually keeping track of the fractional part of the zoom to make it feel a little better. Ideally the mapview should support fractional zooming internally, but I like this first change being single file:
@@ -106,6 +114,33 @@
}
}
+ private double scalef = 0.0;
+
+ public final 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);
+
+ scalef -= ((double)mag);
+
+ Point mouse = Optional.ofNullable(nc.getMousePosition()).orElseGet(
+ () -> new Point((int) nc.getBounds().getCenterX(), (int) nc.getBounds().getCenterY()));
+ nc.zoomManyTimes(mouse.x, mouse.y, (int)scalef);
+
+ scalef -= (int)scalef;
+ }
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ throw new AssertionError(e);
+ }
+ return null;
+ }
+ }
+
/**
* The point in the map that was the under the mouse point
* when moving around started.
@@ -150,6 +185,36 @@
registerActionShortcut(new ZoomerAction(".", "MapMover.Zoomer.out"),
Shortcut.registerShortcut("view:zoomoutalternate", tr("Map: {0}", tr("Zoom Out")), KeyEvent.VK_PERIOD, Shortcut.CTRL));
+ } else {
+ 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, nc, mh);
+ }
+ catch (Exception e) {
+ throw new AssertionError(e);
+ }
}
}
I'm aware that this isn't currently production quality. Right now I'm focused on opening up a discussion around how the maintainers think about this problem, and my proposed solution. The proper patch will have error handling, more specific exception handling, and possibly include configuration knobs for which codepath the user wants.


