1
votes

I am looking for a nice way of only responding to mouseover events but only when the mouse is down. I'm looking for a more core.async way of doing this.

I have two channels in an om component:

om/IDidMount
(did-mount [_]
  (let [canvas (q ".tutorial")
        _ref (js/Firebase. "https://conseal.firebaseio.com/scribble")
        ctx (.getContext canvas "2d")
        mouse-down (om/get-state owner :mouse-down)
        mouse-chan (om/get-state owner :mouse-chan)]

    (listen canvas EventType.MOUSEDOWN #(put! mouse-down [:down %]))
    (listen canvas EventType.MOUSEMOVE #(put! mouse-chan [:move %]))

I then have the following go-loop in IWillMount:

om/IWillMount
(will-mount [_]
  (let [mouse-down (om/get-state owner :mouse-down)
        mouse-chan (om/get-state owner :mouse-chan) ]
    (go-loop []
      (<! mouse-down)
      (let [[evt-type e] (<! mouse-chan)]
          (.log js/console (str evt-type " we are over " e)))
      (recur))))

The above does not work because mousedown events are not continually sent. I'm looking for a core.async idiomatic way of handling this.

If I was using rxjs I could do something like this:

var mouseDrags = mouseDowns.select(function (downEvent) {
    return mouseMoves.takeUntil(mouseUps).select(function (drag) {
        return functionToGetStuffWeWant(drag);
    });
});
1

1 Answers

2
votes

Keeping your current code structure, one way to do this is to add a mouse-up channel (or combine this with mouse-down into a mouse-buttons channel or something).

Then in your go loop, use the following logic:

  1. Read from mouse-down and mouse-channel (using alt). If mouse-channel was read, ignore the value and loop (to 1). If mouse-down was read, break out of the loop and continue with 2.
  2. Read from mouse-up and mouse-channel. If mousechannelwas read, process the current position and loop (to 2). Ifmouse-up` was read, break out of the inner loop, and loop back to 1.

To put this in concrete terms, you might do something like this (untested):

(go-loop []
  (loop []
    (alt! [mouse-down] down nil ;; stop looping when mouse-down is read
          [mouse-channel] event (recur))) ;; loop until mouse-down
  (loop []
    (alt! [mouse-up] up nil ;; stop looping when mouse-up is read
          [mouse-chan] event ;; process current position
            (do
              (.log js/console (str evt-type " we are over " event))
              (recur)))) ;; loop until mouse-up
  (recur)) ;; start the process all over again

For robustness, you may want to read from mouse-up in the first loop and mouse-down in the second loop, in case there are ever two consecutive mouse-up (or mouse-down) events. Otherwise, it's possible for them to get out of sync. In both cases, simply discard the value and recur.

On the other hand, you may prefer an approach similar to your rxjs example. In this case, note that several of the seq operations (map, take, etc) from core clojure have channel analogs in core.async. However, I don't think take-until is one of them. Still, it should be pretty easy to implement your own version of take-until that operates over channels.