flekschas / regl-scatterplot

Scalable WebGL-based scatter plot library build with Regl
https://flekschas.github.io/regl-scatterplot/
MIT License
192 stars 24 forks source link

Update cursors for different mouse modes #129

Open insertmike opened 1 year ago

insertmike commented 1 year ago

Abstact (Enhancement)

Currently, the library uses the same mouse cursor for all mouse modes (pan/zoom, lasso, rotate). This can be confusing for users who may not realize which mode they are in.

To improve the user experience, it would be helpful to have different cursors for different mouse modes. For example, the cursor could be a hand with a grabbing icon for pan/zoom mode, a crosshair for lasso mode, and an arrow for rotate mode.

I believe that this change would make the 'regl-scatter' library easier to use and more intuitive for users.

Implementation

I think that this could be also part of the dom-2d-camera repository. However, I can't see such parameters supported there. Shall we open the issue there?

Otherwise, we could easily manipulate the mouse mode via the canvas.style.cursor

I could potentially do this after work, but I will wait for your input first @flekschas

insertmike commented 1 year ago

For the meantime I am using this as workaround:

/**
 * Updates the mouse cursor on the given canvas element based on the mouse mode.
 * Note: When shift is active, mouse mode defaults to 'crosshair' (lasso)
 *
 * @param canvasRef - A reference to the canvas element.
 * @param mouseMode - The current mouse mode.
 * @param shiftKey - Whether the shift key is pressed.
 */
function updateCanvasMouseCursor(
  canvasRef: React.RefObject<HTMLCanvasElement>,
  mouseMode: MouseMode,
  shiftKey: boolean
) {
  if (!canvasRef.current) return;
  if (mouseMode === "lasso" || shiftKey) {
    canvasRef.current.style.cursor = "crosshair";
  } else if (mouseMode === "panZoom") {
    canvasRef.current.style.cursor = "grab";
  } else {
    canvasRef.current.style.cursor = "default";
  }
}

export function useMouseModeCursor(
  initialMode: MouseMode = "panZoom",
  canvasRef: React.RefObject<HTMLCanvasElement>
): [MouseMode, (mode: MouseMode) => void] {
  const [mouseMode, setMouseMode] = useState(initialMode);
  const isPressingShift = useIsPressingKey("Shift");

  useEffect(() => {
    updateCanvasMouseCursor(canvasRef, mouseMode, isPressingShift);
  }, [mouseMode, canvasRef, isPressingShift]);

  return [mouseMode, setMouseMode];
}
flekschas commented 1 year ago

I like the idea but I would refrain from hard-coding the cursors. Instead I would prefer to allow customizing the cursor via a map. By default I would say the following cursor settings are appropriate:

{
  default: 'default',
  pan: 'move',
  hover: 'pointer',
  lasso: 'crosshair'
}

I do not like the grab cursor by default because it suggests that the default interaction is panning. But depending on your setting, there are several possible actions: panning, zooming, long press to lasso, or click to trigger the lasso initiator. However, the map would allow you to set default: 'grab' and pan: 'grabbing' so everybody is happy :)

Apart from that, given that the lasso interaction does not cancel when one leaves the canvas element, I believe that the cursor would have to be assigned to the body. But that could mess other things up so maybe we also need to allow defining which element should receive the cursor styles.