Fork me on GitHub
#membrane
<
2022-03-15
>
Richie14:03:31

It's really common button behavior that I can left click and hold on a button but the button isn't "clicked" until I release the left click. After the initial mouse-down, the button indicates that it's depressed and I can move the mouse around freely without changing the depressed appearance of the button. If I let up on the mouse not on the button then the button stops looking depressed. Alternatively, if I let up on the mouse on the button then the button is clicked. How would I do that in membrane? The root component would need to participate, right? I think the mouse-move events need to get forwarded to the button when the button is pressed and the mouse is outside the button bounds.

Richie14:03:30

I really mean to ask, what's the best way to do that? Or, how do I do that without making the code more difficult to understand? I think the problem I'm expecting to have is that the button is a leaf node but the behavior would go on the root and I don't think I'm supposed to separate the thing from the behavior.

Richie14:03:28

Do you mind this kind of question? I'm assuming you don't mind as long as it's related to functional immediate mode desktop clojure ui.

👍 1
Richie14:03:15

I think I'll try nesting the root in a new node that does event handling for this case.

phronmophobic04:03:51

> Do you mind this kind of question? I'm assuming you don't mind as long as it's related to functional immediate mode desktop clojure ui. Absolutely! I really appreciate the questions. As I've mentioned, the feedback is really helpful.

phronmophobic04:03:34

Currently, the support for this type of feature could be better. Given that, I'll respond to 3 different aspects of the question. 1. How could this feature be implemented without any updates to membrane? 2. Should membrane have more direct, builtin support for this type of interaction? 3. If so, what kind of changes/additions should be added to support this interaction?

phronmophobic04:03:40

1. How could this feature be implemented without any updates to membrane? I would normally spend a little more time thinking through this, but hopefully this gives you a rough sketch of how this might look in membrane.

(defui wrap-depressed [{:keys [body
                               ^:membrane.component/contextual
                               depressed]}]
  (ui/wrap-on

   ;; It's unclear whether you want mouse move events to be ignored while
   ;; a button is depressed. If you do, just uncomment the :mouse-move wrapper
   ;; here.
   #_#_
   :mouse-move
   (fn [handler pos]
     (when (not depressed)
       (handler pos)))

   :mouse-up
   (fn [handler pos]
     (concat (handler pos)
             [[:set $depressed nil]]))
   ;; The fixed bounds is a workaround for not being able to catch top level
   ;; mouse events outside the size of the app.
   ;; eg. If the user releases the mouse-up event outside the relatively small size of
   ;;     test-app ui, then depressed won't get reset correctly.
   (ui/fixed-bounds [4000 4000]
                    body)))

(defui normal-button [{:keys [text
                              private
                              ^:membrane.component/contextual
                              depressed]}]
  (let [is-depressed (= depressed $private)]
    (ui/on
     :mouse-down
     (fn [_]
       [[:set $depressed $private]])
     :mouse-up
     (fn [_]
       (when is-depressed
         [[:do-something]]))
     (ui/button text nil is-depressed))))

(defui test-app [{:keys []}]
  (wrap-depressed
   {:body
    (ui/vertical-layout
     (basic/textarea {:text (:text extra)})
     (basic/button {:text "no depression"})
     (ui/on
      :do-something
      (fn []
        [[::e1]])
      (normal-button {:text "with depression"}))
     (ui/on
      :do-something
      (fn []
        [[::e2]])
      (normal-button {:text "with depression 2"}))

     (apply
      ui/horizontal-layout
      (for [text ["a" "b" "c"]]
        (ui/on
         :do-something
         (fn []
           [[::e3 text]])
         (normal-button {:text text})))))}))

(def app (membrane.component/make-app #'test-app {}) )

(comment
  (backend/run app)
  ,)

phronmophobic05:03:13

> The root component would need to participate, right? Usually, I just make a top level component that is specific to my app. In the future, I'd like to simplify and break apart the top level component that membrane.component/make-app creates so that there's a more idiomatic approach to customizing how your app works while also being able to easily adopt sane defaults.

phronmophobic05:03:30

> I think the mouse-move events need to get forwarded to the button when the button is pressed and the mouse is outside the button bounds. In this particular case, no state is actually changing when the mouse moves. Based on your description, I think it only matters if the mouse down and mouse up events occur within the same element.

phronmophobic05:03:47

2. Should membrane have more direct, builtin support for this type of interaction? 3. If so, what kind of changes/additions should be added to support this interaction? Yes, I think membrane should do a better job supporting this interaction as well as other "composite" events (eg. drag and drop). I don't think any changes to membrane's event model or state management are required. Off the top of my head, the steps required are something like: • simplify and break apart membrane.component/top-level-ui • package support for different interactions like mouse-down-and-up, drag-and-drop, double-click, context menus (eg. right click), copy/cut/paste, modals, etc into reusable pieces • provide sane defaults

phronmophobic05:03:14

Just created an issue which is mostly just a copy and paste of the above, https://github.com/phronmophobic/membrane/issues/38

Richie20:03:02

I appreciate this immensely. I will eventually digest this and respond to it. Sorry to leave you hanging.