membrane

Ben Sless 2022-10-10T14:13:05.934099Z

I'm trying the rss example with the latest branch and it looks like I don't pack one of the items in a map it breaks scroll view (code in thread)

Ben Sless 2022-10-10T14:13:12.381279Z

(defui feed-view [{items :item name :title}]
  (ui/vertical-layout
   (ui/label name)
   (basic/scrollview
    {:scroll-bounds [800 800]
     :body
     (apply
      ui/vertical-layout
      (for [{:keys [title description]} items]
        (ui/vertical-layout
         (ui/label title title-font)
         (ui/label description))))})))

(defui op-view [{:keys [feed-uri feeds current-feed]}]
  (ui/on
   ::display-feed (fn [f]
                    (println f)
                    [[:set $current-feed f]])
   (ui/horizontal-layout
    (ui/vertical-layout
     (ui/vertical-layout
      (ui/wrap-on
       :key-press
       (fn [default-handler s]
         (let [effects (default-handler s)]
           (if (and (seq effects)
                    (= s :enter))
             [[::add-feed $feeds feed-uri]
              [:set $feed-uri ""]]
             effects)))
       (basic/textarea {:text feed-uri}))
      (basic/button {:text "Add Feed"
                     :on-click (fn []
                                 [[::add-feed $feeds feed-uri]
                                  [:set $feed-uri ""]])}))
     (apply ui/vertical-layout
            (for [feed feeds]
              (feed-item feed))))
    (when-let [feed (->> feeds
                         (filter (fn [{:keys [feed]}] (= current-feed (:uri feed))))
                         first
                         :feed)]

      (feed-view feed)))))

Ben Sless 2022-10-10T14:13:27.147479Z

The only difference is that feed-view isn't passed {:feed feed}

phronmophobic 2022-10-10T15:41:15.071869Z

ok, will take a look

👍 1
phronmophobic 2022-10-10T17:23:36.424419Z

Thanks for braving the new version! I found a couple issues. I guess that's why it's nice to have use cases in mind. • I doesn't matter that much in this example, but when-let isn't supported for tracking bindings. It would matter if feed-view wanted to manipulate feed. Adding support for when-let and if-let seems pretty easy. • When I was first thinking about supporting non-literal maps, the example I had in mind was something like a textarea. If you pass in a textarea with :text, you probably don't care too much if a bunch of extra keys like :cursor and :selection get added. However, I'm starting to think that your use case is actually much more common. You have a feed-view that displays a feed, and you probably would rather not the feed map get a bunch of extra keys dealing with hover states and scroll positions. Does that sound right? I'll think about it a bit more, but I'll try to have at least a snapshot version soon.

phronmophobic 2022-10-10T17:25:54.203789Z

Also, defui feed-view [{items :item name :title}]. While destructuring is supported in the body of defui, it's still not supported generically in the args for defui. It works without issue if you don't need to update items or name, but I thought I'd just point that out. More fodder for https://phronmophobic.github.io/membrane/membrane-topics.html#Defui-Limitations.

Ben Sless 2022-10-10T17:42:20.863319Z

you're right regarding the use cases. another example is collapsible list, where the expanded items are unrelated to the actual data in the list

👍 1
Ben Sless 2022-10-10T17:52:21.030889Z

naive question - why add support for particular forms instead of macroexpanding first?

phronmophobic 2022-10-10T17:56:45.593819Z

At some point, I think that's the right way to go. For now, the main reason is supporting either for or map is really important and

> (clojure.pprint/pprint
                          (macroexpand-1 '(for [x (range 3)]
                                            x)))
(clojure.core/let
 [iter__6373__auto__
  (clojure.core/fn
   iter__24408
   [s__24409]
   (clojure.core/lazy-seq
    (clojure.core/loop
     [s__24409 s__24409]
     (clojure.core/when-let
      [s__24409 (clojure.core/seq s__24409)]
      (if
       (clojure.core/chunked-seq? s__24409)
       (clojure.core/let
        [c__6371__auto__
         (clojure.core/chunk-first s__24409)
         size__6372__auto__
         (clojure.core/int (clojure.core/count c__6371__auto__))
         b__24411
         (clojure.core/chunk-buffer size__6372__auto__)]
        (if
         (clojure.core/loop
          [i__24410 (clojure.core/int 0)]
          (if
           (clojure.core/< i__24410 size__6372__auto__)
           (clojure.core/let
            [x (.nth c__6371__auto__ i__24410)]
            (do
             (clojure.core/chunk-append b__24411 x)
             (recur (clojure.core/unchecked-inc i__24410))))
           true))
         (clojure.core/chunk-cons
          (clojure.core/chunk b__24411)
          (iter__24408 (clojure.core/chunk-rest s__24409)))
         (clojure.core/chunk-cons (clojure.core/chunk b__24411) nil)))
       (clojure.core/let
        [x (clojure.core/first s__24409)]
        (clojure.core/cons
         x
         (iter__24408 (clojure.core/rest s__24409)))))))))]
 (iter__6373__auto__ (range 3)))

Ben Sless 2022-10-10T17:57:55.593449Z

😄

Ben Sless 2022-10-10T17:58:06.094879Z

the really hard part about this is propagating bindings, right?

phronmophobic 2022-10-10T17:58:12.662489Z

right

phronmophobic 2022-10-10T17:58:28.851859Z

you have to figure out how x derives from (range 3)

Ben Sless 2022-10-10T17:59:01.061879Z

why "how"?

phronmophobic 2022-10-10T18:00:23.718379Z

if you have

(for [todo todos]
  (todo-item {:todo todo}))
and the todo-item view edits todo, then you need to figure out how to apply that edit within todos

phronmophobic 2022-10-10T18:01:12.557359Z

maybe the smart thing to do is write the deep walking macro and just provide m/for to use instead of for

phronmophobic 2022-10-10T18:02:06.992579Z

because for is already rewritten to do something slightly different within defui.

Ben Sless 2022-10-10T18:02:14.515699Z

ahhhh, I get it. that's what map-indexed is for

👍 1
Ben Sless 2022-10-10T18:03:47.499759Z

can you dynamically bind for inside defui to your own implementation? 🤔

phronmophobic 2022-10-10T18:04:41.864859Z

yea, there are probably several ways to support (dynamically bind, postwalk replace, then macroexpand)

Ben Sless 2022-10-10T18:05:53.242189Z

Besides being bad at writing UIs, I have been messing around with code rewriting and analysis 🙃

phronmophobic 2022-10-10T18:05:54.965109Z

I originally wrote defui as a weekend hack. Intuitively, the design feels not quite right, but using it feels pretty good. I still feel like there's some deeper idea that's more general and still covers all the same use cases.

Ben Sless 2022-10-10T18:06:27.802419Z

Turn the code into a DAG, perform flow analysis?

🙂 1
Ben Sless 2022-10-10T18:08:12.755379Z

Hyperfiddle guys did something similar. Code -> analysis -> EAV -> datascript, then analysis is just queries w/ rules

Ben Sless 2022-10-10T18:14:38.056659Z

I also have pretty much working implementation of beta reduce (and alpha reduce falls out of it), I would imagine you could do any sort of interesting things with it.

Ben Sless 2022-10-10T18:15:32.835589Z

There's also core.async's compiler which which you can borrow ideas from

phronmophobic 2022-10-10T18:15:53.040819Z

There's a bunch "compiler"-esque that would be cool with membrane. Right now, when you do [[:update $bool not]], there's a bunch of extra work that happens. However, it seems possible to determine, at compile time, how to convert that into a simple bounds check on the mouse position and pre-compile the code that makes that change. You could also precompile drawing routines that more efficiently draw and repaint based on model changes (which you may be able to determine at compile time)

Ben Sless 2022-10-10T18:16:14.609269Z

In essence, when calling a UI macro, you need to build up a stack of the path from the input argument to the function call?

👍 1
phronmophobic 2022-10-10T18:16:26.697339Z

Basically, there's a bunch of work you should be able to do at compile time to build more efficient draw, event, and view functions.

Ben Sless 2022-10-10T18:18:36.843969Z

I mean, there's a limited set of what can happen - indexed access, key access, iteration. Anything else to keep track of?

phronmophobic 2022-10-10T18:19:37.926099Z

these are the operations that are currently supported:

(defui my-component [{:keys [my-prop]}]
  (let [;; these work
        foo (:foo my-prop)
        foo (:foo my-prop :my-default)
        foo (nth my-prop 0)
        foo (nth my-prop 0 :my-default)
        foo (get-in my-prop [:foo :bar])
        foo (get-in my-prop [:foo :bar] :my-default)
        foo (take 42 my-prop)
        foo (drop 42 my-prop)
        foo (or my-prop 42)
        
        ;; destructuring also works
        {foo :foo
         bar :bar
         [a b & other-foos] :foos} my-prop]
    (my-other-component {:foo foo
                         :bar bar
                         :a a
                         :b b
                         :other-foos other-foos})))

phronmophobic 2022-10-10T18:19:57.796239Z

the or operation is important for defaults

phronmophobic 2022-10-10T18:20:36.435049Z

drop is required for supported destructuring [a b & other-foos]. if you modify other-foos, then that uses drop.

phronmophobic 2022-10-10T18:22:46.315639Z

any function that where operating on the transformed value can also be applied to the original value could be supported.

phronmophobic 2022-10-10T18:23:21.348129Z

which maps to https://github.com/redplanetlabs/specter/wiki/List-of-Navigators#parser in specter.

phronmophobic 2022-10-10T18:24:28.179869Z

for example, (filter f xs) currently works, but I'm not sure if it should be officially supported

Ben Sless 2022-10-10T18:46:15.331629Z

I would expect first/rest/next to work 🤔

phronmophobic 2022-10-10T18:49:00.549479Z

yea, those would be easy to implement

phronmophobic 2022-10-10T18:49:32.014079Z

I'm not sure how often they come up

phronmophobic 2022-10-10T18:52:33.718339Z

I guess the right answer is make it an open set and let people add whatever they want

phronmophobic 2022-10-10T18:57:15.154409Z

In addition to just trying to provide implementations for relevant clojure.core functions

phronmophobic 2022-10-11T04:04:35.873969Z

ok, I have a version that takes the extra state from the parent component's extra state. https://clojars.org/com.phronemophobic/membrane/versions/0.10.2-beta-SNAPSHOT

phronmophobic 2022-10-11T04:06:02.309269Z

for the feed-view, I think you still want to explicitly pass the extra properties so that each feed get it's own scroll state.

(when-let [feed (->> feeds
                         (filter (fn [feed] (= current-feed (:uri feed))))
                         first)]
      (let [feed-extra (get extra [:feed-view (:uri feed)])
            feed (assoc feed
                        :extra feed-extra
                        :$extra $feed-extra)]
        (feed-view feed)))

phronmophobic 2022-10-11T04:06:54.350269Z

Ben Sless 2022-10-11T04:23:39.834689Z

Would you like me to PR the RSS reader as an example to the repository? I think it's proving to be a good, interesting and self contained case

👍 1
phronmophobic 2022-10-11T04:25:18.863959Z

sounds good, I've started to put examples in https://github.com/phronmophobic/membrane/tree/master/examples rather than in the main source tree

Ben Sless 2022-10-11T04:25:38.200289Z

Makes sense

Ben Sless 2022-10-11T04:33:31.468129Z

Another thing I've been thinking about, related to collapsible list, is menus, specifically how to place them in relation to the rest of the window and elements. It needs to be dynamic, no? Is there a way to get the bounds of the parent components when placing a component?

phronmophobic 2022-10-11T04:34:06.397489Z

oh, I thought of some ideas for that when I was writing some of the docs

phronmophobic 2022-10-11T04:36:46.260029Z

One way to do it is have a contextual property, :container-size that is the [width height] of the current container. You can then have halign, valign, etc that position child components within the container and set the :container-size property in the contexts for all those child components

phronmophobic 2022-10-11T04:37:32.830869Z

I did a little work on the prototype, but before some of the other improvements were made. I think the new improvements should help.

phronmophobic 2022-10-11T04:38:16.195009Z

I was trying to implement some of the ideas from https://tonsky.me/blog/humble-layout/

phronmophobic 2022-10-11T04:39:10.071059Z

It seemed to work, but one gripe I have with the prototype is that it feels a little verbose:

(defui center-align [{:keys [body]}]
  (halign {:parent-pct 0.5
           :child-pct 0.5
           :body 
           (valign
            {:parent-pct 0.5
             :child-pct 0.5
             :body body})}))

phronmophobic 2022-10-11T04:39:38.677989Z

I would prefer syntax more like: (halign 0.5 0.5 child)

phronmophobic 2022-10-11T04:42:06.184719Z

to support that syntax, either: • halign, valign, etc would have to be macros 😕halign, valign, etc would have to be specially supported by defui 😕 • I would have to come up with a plan to support either multiple arguments or positional arguments for defui • something else?

phronmophobic 2022-10-11T04:44:04.372759Z

another example from the current prototype

(defui test-align [{:keys [state]}]
  [(ui/with-style ::ui/style-stroke
     (ui/rectangle 150 350)
     (ui/center (ui/path [0 0] [0 350])
                [150 350]))
   
   (row {:cols
         [(center-align {:body (ui/label "a")})
          (halign {:parent-pct 0.5
                   :child-pct 0.5
                   :body (ui/label "a")}
                  )
          (halign {:parent-pct 0.5
                   :child-pct 0.5
                   :body (ui/label "a")}
                  )
          (halign {:parent-pct 0.5
                   :child-pct 0.5
                   :body (ui/label "a")}
                  )]})]
  )

phronmophobic 2022-10-11T04:46:13.189089Z

the other change I would need to make is the function passed to backend/run doesn't receive any arguments. I need to decide how to make a version that accepts a map. The map could contain some common keys like :container-size and some backend specific keys like the JFrame container

Ben Sless 2022-10-11T04:46:13.754899Z

Why not make it positional?

phronmophobic 2022-10-11T04:47:20.972519Z

It relies on defui threading the context so it would have to be a defui component.

phronmophobic 2022-10-11T04:48:18.618149Z

obviously, you could pass the container size manually

Ben Sless 2022-10-11T05:05:46.061789Z

Let me know if you need a trained space monkey to take a wrench to the control panels once it's ready for a test flight 😄

👏 1