membrane

https://github.com/phronmophobic/membrane
genekim 2020-08-28T15:49:55.078500Z

@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]])
      (ui/translate
       10 10
       (vertical-layout
        (ui/label (str "Hello from key8: " @(subscribe [:text])))
        (task-entry)
        (ui/spacer 0 20)
        (when (seq @(subscribe [:todos]))
          (task-list))
        (ui/spacer 0 20)
        (footer-controls)
        (stories)))))

genekim 2020-08-28T15:51:11.079100Z

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

genekim 2020-08-28T15:55:19.079400Z

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. https://craig.is/killing/mice I’m now dying to see what you think about this problem in membrane…. 🙂

genekim 2020-08-28T16:00:52.081900Z

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.

😄 1
phronmophobic 2020-08-28T16:54:22.082700Z

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

phronmophobic 2020-08-28T16:55:37.083900Z

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?

phronmophobic 2020-08-28T16:59:33.084400Z

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

(ui/wrap-on
 :key-press
 (fn [handler s]
   (let [effects (handler s)]
     (if (seq effects)
       effects
       [[:keydown s]])))
 ...child-elements...)

phronmophobic 2020-08-28T17:03:12.084600Z

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

phronmophobic 2020-08-28T17:05:39.084800Z

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

(ui/wrap-on
 :key-press
 (fn [handler s]
   (let [effects (handler s)]
     (if (seq effects)
       effects
       [[:keydown s]])))
 ...child-elements...)
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

phronmophobic 2020-08-28T17:28:03.085800Z

@genekim, there’s a new version of membrane available: [com.phronemophobic/membrane "0.9.13-beta"] with: membrane.re-frame/scroll-to-top! membrane.re-frame/set-scroll-offset!

genekim 2020-08-28T19:05:10.086700Z

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

genekim 2020-08-28T19:48:30.089400Z

@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. 🙂

genekim 2020-08-28T19:50:51.089800Z

#object[clojure.lang.Atom
        0x2f0a3cc8
        {: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"])
                                                    :textarea-state
                                                    [(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"])
                                                    :textarea-state
                                                    (keypath :select-cursor)]]
                                                  :$last-click] [1598644212128 [133.556640625 10.80859375]]}},
               :membrane.re-frame/focus [(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]]}}}}]

genekim 2020-08-28T19:51:40.090100Z

Wow. 🤯

phronmophobic 2020-08-28T19:59:00.090900Z

i just stepped away from the keyboard

phronmophobic 2020-08-28T19:59:28.091800Z

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

genekim 2020-08-28T19:59:35.092Z

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

genekim 2020-08-28T19:59:48.092800Z

This is wild…

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

phronmophobic 2020-08-28T20:00:00.093200Z

you found it!

genekim 2020-08-28T20:00:14.093900Z

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

phronmophobic 2020-08-28T20:00:18.094100Z

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

genekim 2020-08-28T20:01:23.094600Z

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

phronmophobic 2020-08-28T20:02:30.095800Z

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

genekim 2020-08-28T20:02:34.096100Z

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

phronmophobic 2020-08-28T20:04:52.099200Z

i’m now realizing how much documentation is missing.

genekim 2020-08-28T20:06:08.100500Z

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. 🙂

genekim 2020-08-28T20:07:08.101600Z

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

genekim 2020-08-28T20:13:34.102300Z

PPS: This is all very exciting!

phronmophobic 2020-08-28T20:14:23.103Z

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

phronmophobic 2020-08-28T20:33:16.105900Z

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.

genekim 2020-08-28T21:53:12.109800Z

@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.)

phronmophobic 2020-08-28T21:56:44.112300Z

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

phronmophobic 2020-08-28T21:58:07.113600Z

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

genekim 2020-08-28T22:00:30.114600Z

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

genekim 2020-08-28T23:25:24.116500Z

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

phronmophobic 2020-08-28T23:26:29.117700Z

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

genekim 2020-08-28T23:26:36.117900Z

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

phronmophobic 2020-08-28T23:33:45.119400Z

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

phronmophobic 2020-08-28T23:45:33.120600Z

based on the re-frame docs, it seems like this behavior matches cljs as well: • http://day8.github.io/re-frame/api-re-frame.core/#clear-subscription-cachehttp://day8.github.io/re-frame/FAQs/Why-Clear-Sub-Cache/#question

genekim 2020-08-28T23:46:23.121800Z

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

phronmophobic 2020-08-28T23:46:35.122100Z

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

genekim 2020-08-28T23:47:25.123500Z

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…

🙏 1
1
phronmophobic 2020-08-28T23:47:40.124Z

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

genekim 2020-08-28T23:49:40.126Z

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?

genekim 2020-09-05T15:44:29.193500Z

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

phronmophobic 2020-08-28T23:50:35.126700Z

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

phronmophobic 2020-08-28T23:51:52.127400Z

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

genekim 2020-08-28T23:52:41.128300Z

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

phronmophobic 2020-08-28T23:53:26.128500Z

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

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

phronmophobic 2020-08-28T23:55:08.129300Z

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

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

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

phronmophobic 2020-08-28T23:57:06.131Z

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

genekim 2020-08-28T23:57:32.131600Z

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

phronmophobic 2020-08-28T23:58:46.132500Z

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

genekim 2020-08-28T23:59:50.134200Z

…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?

phronmophobic 2020-08-28T23:59:51.134300Z

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