Is this mess with the ref necessary here? (mouse handler callbacks)

Context: for a custom drag / drop, we have the following problem:

on mouse down:

  • register a handler

on mouse up/leave:

  • unregister everything

however, for unregister everything, we need access to the “listeners” to remove; so we have this weird loop ; and because these are primitive values, not functions, we can’t use let rec here.

Thus, we have this ugly ref. Is there a way to get rid of this ?



    let onMouseDown (_x : React.Event.Mouse.t) =
      let remove_all_ref = ref @@ fun _e -> () in
      let remove_all _e = !remove_all_ref _e in
      let mouse_move_listener =
        Brr.Ev.listen Brr.Ev.mousemove
          (fun _e -> Brr.Console.log [ "mousemove|" ])
          (Brr.Document.as_target Brr.G.document)
      in
      let mouse_leave_listener = Brr.Ev.listen Brr.Ev.mouseleave remove_all (Brr.Document.as_target Brr.G.document) in
      let mouse_up_listener = Brr.Ev.listen Brr.Ev.mouseup remove_all (Brr.Document.as_target Brr.G.document) in
      remove_all_ref :=
        fun _e ->
          Brr.Ev.unlisten mouse_move_listener;
          Brr.Ev.unlisten mouse_leave_listener;
          Brr.Ev.unlisten mouse_up_listener;
          Brr.Console.log [ "unregistered all" ]

The use of a reference or some analogous structure (eg lazy) seems unavoidable when dealing with cyclic data as in your example. But I think the code can be made a bit more readable, eg:

let onMouseDown (_x : React.Event.Mouse.t) =
  let to_be_removed : Brr.Ev.listener list ref = ref [] in
  let remove_all _ = List.iter Brr.Ev.unlisten !to_be_removed in
  let add listener = to_be_removed := listener :: !to_be_removed in
  add (Brr.Ev.listen Brr.Ev.mousemove (fun _e -> Brr.Console.log [ "mousemove|" ]) (Brr.Document.as_target Brr.G.document));
  add (Brr.Ev.listen Brr.Ev.mouseleave remove_all (Brr.Document.as_target Brr.G.document));
  add (Brr.Ev.listen Brr.Ev.mouseup remove_all (Brr.Document.as_target Brr.G.document))

Cheers,
Nicolas

1 Like

Wow. This rewrite is clever. I like it. Thanks!

Purely a drive-by comment, doesn’t this also need to reset: to_be_removed := []?

I’m not convinced it is necessary, as everything is “one use” . Here is the XY problem:

suppose we have a thin title bar and we want to (manually) implement drag & drop; what do we do ? Call the initial title-bar x.

The initial approach is to register mouse-down, mouse-up, mouse-move handlers on x. The problem is that if we move your mouse too fast, we might get outside x, then the drag/drop stops.

So instead re only register “mouse-down” on x – and in that handler, we globally register mouse-move and mouse-up on the global window object. Then, of course, we need to clean everything up on mouse-up@global_wnidow.

In this situation, every time when we click mouse-down, we are generating a new ref – and as a result, don’t need to reset it at the end.