Fork me on GitHub

@smith.adriane I’m starting to understand just how wildly novel your approach is — kudos on building this out this far. It’s so different than anything I’ve ever seen, going all the way back to tcl/tk. So in that scrollview, how do I change the y-offset (e.g., scroll to top, set y offset to zero)? Thx! (After that, I’ll be tackling figuring out how not to hijack all the keyboard events… Currently, I added an on :key-press to the todo-app function, which works… but it seems to prevent any keyboard events from reaching the task-entry widget…. Is there a proper way to do this? The use case is: the keyboard is used to navigate between items: j/k goes to previous and next item. And the text field will be used to search.) That would complete my toy app, hacked on top of the todo example! I’ll work on posting what I have in a repo later today!

(defn todo-app
  (on :key-press
      (fn [s]
        (println "main: keypress event: " s)
        (println "type: " (type s))
        [[:keydown s]])
       10 10
        (ui/label (str "Hello from key8: " @(subscribe [:text])))
        (ui/spacer 0 20)
        (when (seq @(subscribe [:todos]))
        (ui/spacer 0 20)


PS: I really like how reloading the namespace updates the UI. It makes for a great development experience.


PS: after thinking this through a little bit, I’m now appreciating why so many windowing frameworks typically only allow “keyboard accelerators” to be handled this way…. I’ve noticed that by default, only “meta”, “command” + keystrokes are intercepted and mapped to events, and the rest are passed down to children… In ClojureScript apps, I’ve used things like mousetrap that understand what element has focus, and it makes determines whether to handle the event, or pass it on. I’m now dying to see what you think about this problem in membrane…. 🙂


there's a lot of flexibility for how to handle this in membrane since events and focus are effectively in userspace. you could even have multi-focus if you wanted to


I'm not super familiar with everything mousetrap has to offer, but i think most of it is covered by:

 (fn [handler s]
   (let [effects (handler s)]
     (if (seq effects)
       [[:keydown s]])))
which only dispatches the :keydown event if no child element is trying to dispatch an event. you could also do something based off focus depending on the use case


PS: This is a screenshot of my frankenstein app — there’s a list of items from a Feedly RSS feed; j/k shows next/prev item; ideally space/home will scroll down/up. Ideally, a left pane will show list of all subjects; a search box would filter matching items.

😄 3

adding a way to set the scroll position should be pretty easy. adding it now!


as far as the key events, I'm guessing you would like dispatch the :keydown event only if the key-press isn't supposed to be processed by a textbox?


the way to do that is with membrane.ui/wrap-on: for example:

 (fn [handler s]
   (let [effects (handler s)]
     (if (seq effects)
       [[:keydown s]])))


@genekim, there’s a new version of membrane available: [com.phronemophobic/membrane "0.9.13-beta"] with:!!


WOW! Trying it out now! Looks so great!!! Thx!!! 🙂 @smith.adriane


@smith.adriane Starting to understand the event flow better by printing out output of (handler s) everywhere. I can now see how text-boxes dispatch events based on their id… But if I want to capture the “j” and “k” outside of a text field, how do I “unfocus” all of the text-boxes? Ah, wait…. I think I’m starting to get it…. :focus is a property of each text-box, right? (Printing it out in REPL now. 🙂


        {:status :ready,
         :val {[:todo-input "new-todo"] {:text "dddddddddddddddjjjjjjjjkkjjj",
                                         :textarea-state {:cursor 20,
                                                          :mpos [133.556640625 10.80859375],
                                                          :down-pos nil,
                                                          :select-cursor nil},
                                         :extra {[[[(get [:todo-input "new-todo"]) :text]
                                                   [(get [:todo-input "new-todo"])
                                                    [(keypath :cursor) (nil->val 0)]]
                                                   [(fn-call (= focus (clojure.core/into arg-path-text-16264 [])))]
                                                   [(get [:todo-input "new-todo"]) :font]
                                                   [(get [:todo-input "new-todo"]) :textarea-state (keypath :down-pos)]
                                                   [(get [:todo-input "new-todo"]) :textarea-state (keypath :mpos)]
                                                   [(constant true)]
                                                   [(get [:todo-input "new-todo"])
                                                    (keypath :select-cursor)]]
                                                  :$last-click] [1598644212128 [133.556640625 10.80859375]]}},
      [(get [:todo-input "new-todo"]) :text],
               [:todo-input 1] {:text "jkkjddjkkkfffff∂∂∂∂jkkkkkkk",
                                :textarea-state {:cursor 27,
                                                 :mpos [145.359375 14.484375],
                                                 :down-pos nil,
                                                 :select-cursor nil},
                                :extra {[[[(get [:todo-input 1]) :text]
                                          [(get [:todo-input 1]) :textarea-state [(keypath :cursor) (nil->val 0)]]
                                          [(fn-call (= focus (clojure.core/into arg-path-text-16264 [])))]
                                          [(get [:todo-input 1]) :font]
                                          [(get [:todo-input 1]) :textarea-state (keypath :down-pos)]
                                          [(get [:todo-input 1]) :textarea-state (keypath :mpos)]
                                          [(constant true)]
                                          [(get [:todo-input 1]) :textarea-state (keypath :select-cursor)]]
                                         :$last-click] [1598644168468 [145.359375 14.484375]]}}}}]


i just stepped away from the keyboard


the state looks pretty gnarly, but i promise it’s not as crazy at it looks


Haha. No worries — we all have lives outside of Clojurians Slack!


This is wild…

(get @text-boxes ::focus)
=> [(get [:todo-input "new-todo"]) :text]


Okay, how do I clear focus from all element? (I feel like I’m so close to understanding this little piece of membrane!)


you should be able to set that to nil to remove all* focus


Ummm…. conceptually, I get it, but I have literally no idea what to type. 😂


does (swap! text-boxes assoc ::focus nil) work?


Ah! The namespaced keyword threw me for a loop…. Yes!


i’m now realizing how much documentation is missing.


Thanks!! Okay, gotta run to meeting, but it probably won’t surprise you that membrane is the most novel (and surreal) implementation of a UI I’ve ever seen. Either, I actually understand much less about UIs I’ve used over the last 30 years…. …or you’ve chosen to design membrane using a drastically different model than conventional UIs… Which I think is the case, from what I’m seeing, and your discussion of lenses, pure event handlers, etc…. Amiright? (Okay, I’ll be back in an hour. 🙂


PS: and by “surreal”, I mean “provocative” and “mind bending/blowing”, all in the best possible connotations!!!


PPS: This is all very exciting!


the attempt has been to apply the clojure philosophy to user interfaces. take everything apart and try to rebuild it with values and pure functions


most functional frameworks start building on top of some existing UI framework like UIKit, QT, html DOM, etc. and try to insulate you as much as possible from the non-functional parts. that's definitely the smart route since it's completely impractical in the short term to spend a bunch of time implementing text rendering, focus management, scrollviews, etc.


@smith.adriane ^^^ Your design goals are so super cool, and when evidenced in that text-boxes atom, it’s just so shocking and startling to see. it would be amazing to see you write even a couple of paragraphs on your goals, or build out some slides, or give a talk on it — and it helps us all appreciate what you’ve built! (and understand how it works under the hood.) Okay, I’m going to try to unset focus, using that magic you gave me. (Still amazed that all text-box states are in that atom.)


because of the lens part, the data can actually live anywhere, it’s the text-box-dispatch that ties it to the atom, but you could move the state to a ref, var, db, etc just by changing the dispatch function


some long form posts on how everything works are a work in progress


Wow, unfocusing works. Woot! UI framework via values… So incredibly unexpected…. Okay, now to work on side pane!


Got horizontal layout working… Creating subscription to populate it. BTW: issue confirmed… when you change a subscription and reload the subs namespace in REPL, the subscription is not updated. I’m finding that the only way to update the subscription function is to restart the REPL. @smith.adriane


:thumbsup: , I'll take a look at the subscription reloading issue


(expected behavior: in re-frame, when you reload the subs namespace, the subscription functions are updated without needing to restart the repl.)


I'm not familiar enough with the internals of re-frame subscriptions to figure out why reloading works in cljs, but not in clj, but I did find that calling (re-frame.subs/clear-subscription-cache!) after updating a subscription will update the subscription value


Huh! I’ve used re-frame for 3 years, and never knew that! Cool!


I wonder if there's a way to include in that example repos to save others the trouble


I’ll put that in my notes to add in the docs for my repo, or PR against your repo…. I’m compiling a list of notes/experiences as I’m using it…

🙏 3
thanks2 3

well, I'm going to file an issue against the example repos so I can figure out how to update the code/docs to make it easy to adopt a reasonable workflow


Got titles showing up on left — article on right. Woot! 1. Scrolling events seem to go to both windows, despite given each scrollview a unique id… 2. Hesitantly asking: is there a way to treat the scrollview on left as a selection widget? i.e., if I click on second or third line, I can get an event, so that I could jump to that article?


fyi, in [com.phronemophobic/membrane "0.9.14-beta"], I fixed: "Scrolling events seem to go to both windows, despite given each scrollview a unique id…" NOTE: if you did end up using fix-scroll, then you'll have to update it to the following version or you'll get an arity error

(defn fix-scroll [elem]
  (ui/on-scroll (fn [[sx sy] mpos]
                  (ui/scroll elem [(- sx) (- sy)] mpos))


That is fabulous!! Will try tonight or tomorrow, and will have repo up soon!!! Thank you!


#1 is a bug I haven't gotten to yet 😬


I have a disdain for scrollviews and always end up doing pagination . guess I need to bite the bullet and make scrollviews more usable ¯\(ツ)


Cool! Okay, if I implement search, that will wrap up my toy proof of concept. Maybe later tonight!


re #2, there's more than one way to do that, but I would probably opt for something like:

 (for [line lines]
    :mouse-down (fn [_]
                  [[:select-line line]])
    (ui/label line))))


if the lines don't change that often, I might also move to its own function and memoize the results:

(defn line-selector [lines]
   (for [line lines]
      :mouse-down (fn [_]
                    [[:select-line line]])
      (ui/label line)))))

(def line-selector-memo (memoize line-selector))


membrane.component does a lot of "caching under" where it will automatically cache views based on their arguments, but I'm not sure what the most idiomatic way to do that is in re-frame land


…wow? you can put all that into a scrollview? (scrollview contains a sequence of ui/labels, each with its own events?)


I think it will be fine with mouse-down events, but mouse-move events might cause an issue


…btw, this is such an interesting example of how different membrane is than typical UIs frameworks…. nothing mutating with those event handlers. all instantiated in a value, and processed on each event…. am I tracking that correctly?


I've been pretty surprised at the performance for most of the examples I've worked on. turns out browsers spend a bunch of time monkeying around


fyi, in [com.phronemophobic/membrane "0.9.14-beta"], I fixed: "Scrolling events seem to go to both windows, despite given each scrollview a unique id…" NOTE: if you did end up using fix-scroll, then you'll have to update it to the following version or you'll get an arity error

(defn fix-scroll [elem]
  (ui/on-scroll (fn [[sx sy] mpos]
                  (ui/scroll elem [(- sx) (- sy)] mpos))