This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-10-10
Channels
- # announcements (10)
- # aws (5)
- # babashka (81)
- # babashka-sci-dev (4)
- # beginners (100)
- # calva (60)
- # clj-kondo (34)
- # clojars (1)
- # clojure (30)
- # clojure-austin (12)
- # clojure-berlin (1)
- # clojure-europe (45)
- # clojure-italy (5)
- # clojure-losangeles (9)
- # clojure-nl (3)
- # clojure-norway (10)
- # clojure-spec (1)
- # clojurescript (8)
- # community-development (2)
- # conjure (5)
- # cursive (10)
- # data-science (2)
- # datalog (1)
- # emacs (4)
- # events (10)
- # fulcro (6)
- # jobs-discuss (8)
- # lambdaisland (3)
- # leiningen (2)
- # luminus (5)
- # membrane (61)
- # off-topic (8)
- # portal (5)
- # releases (20)
- # sci (25)
- # scittle (6)
- # shadow-cljs (25)
- # testing (6)
- # xtdb (11)
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)
(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)))))
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.
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.
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
naive question - why add support for particular forms instead of macroexpanding first?
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)))
you have to figure out how x
derives from (range 3)
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
maybe the smart thing to do is write the deep walking macro and just provide m/for
to use instead of for
because for
is already rewritten to do something slightly different within defui
.
can you dynamically bind for
inside defui
to your own implementation? :thinking_face:
yea, there are probably several ways to support (dynamically bind, postwalk replace, then macroexpand)
Besides being bad at writing UIs, I have been messing around with code rewriting and analysis 🙃
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.
Hyperfiddle guys did something similar. Code -> analysis -> EAV -> datascript, then analysis is just queries w/ rules
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.
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)
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?
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.
I mean, there's a limited set of what can happen - indexed access, key access, iteration. Anything else to keep track of?
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})))
the or
operation is important for defaults
drop
is required for supported destructuring [a b & other-foos]
. if you modify other-foos
, then that uses drop
.
any function that where operating on the transformed value can also be applied to the original value could be supported.
which maps to https://github.com/redplanetlabs/specter/wiki/List-of-Navigators#parser in specter.
for example, (filter f xs)
currently works, but I'm not sure if it should be officially supported
yea, those would be easy to implement
I'm not sure how often they come up
I guess the right answer is make it an open set and let people add whatever they want
In addition to just trying to provide implementations for relevant clojure.core functions
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
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)))
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
sounds good, I've started to put examples in https://github.com/phronmophobic/membrane/tree/master/examples rather than in the main source tree
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?
oh, I thought of some ideas for that when I was writing some of the docs
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
I did a little work on the prototype, but before some of the other improvements were made. I think the new improvements should help.
I was trying to implement some of the ideas from https://tonsky.me/blog/humble-layout/
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})}))
I would prefer syntax more like: (halign 0.5 0.5 child)
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?
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")}
)]})]
)
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
It relies on defui
threading the context so it would have to be a defui
component.
obviously, you could pass the container size manually