Would it be trival to bind ctx to me in some way in f/mx?
Imaginary usage:
(-> m/MediaQuery (.of (fx/my-ctx me) .-size .-width)Research required! ðĪĢ Specifically, is context sth that can reliably be cached and then used at some arbitrarily later time? Or is it a dynamic value without object identity? Over here, research has been deferred until I see a use case where ctc needs to be pulled from some cache.
defs evaluate just once, FYI. So the only difference is the laziness.
Haha, I thought the latest was not working until I saw the values for the text widgets -- it's an MX inspector!
Aside: I changed ~(symbol ".builder") to .builder and it seems fine.
So is this solution too specific in what it does to be called with-layout? I am thinking different usages will want to do different things with the box constraints. Can this be generalized with a new :builder-watch property that will be invoked on each builder call? Or are you thinking you just want to make f/mx generally extensible by folks who want to author their own factory macros? That would be fine for power users, but I like the idea of a generic :builder-watch for an in-between solution that keeps the built child as kid-1 but supports build-watching. ðĪ
I am seeing over here -- and I may have screwed sth up, or not have the latest -- I am seeing that rotating the sim loses track of the :max-w. btw, did you mean the two magicals to be nested one under the other? The make-app I have has them both with the column as the parent. I recall you mentioning nesting.
I saw that early on and my reaction was, well, MX models never change parent, ie they never change location, so maybe we can grab the context and cache it. Indeed, we did for a while, in a meta property fx$ctx or sth.
But then I saw we could always get the required context with a suitably tagged callback that says in effect, "I will tell you the value for this property if you call me back with the build context."
So I decided to play it safe and heed the "do not cache" warning, even tho it did not seem to apply.
That said, perhaps there is a Flutter use case that requires a widget to be un-/re-mounted, because it has to be some place different but be the identical widget.
I see no harm in stashing the context, so I think we can make that change and see if it breaks.
Your thoughts, @zenflowapp?
ps. Yes, this is definitely what I call a speed bump. Good metaphor, because we literally must slow down and think at a crawl, very carefully picking the right spot and right callback wrapper. We might have too many of the latter, btw. ð My thinking is that, as we code more and more app stuff in anger, we might settle on just a few, and then give them better names. ð
Btw, nice confluence: quiesce is still broken, and I keep wondering if it is because an old Dart widget is lurking about somewhere swallowing mouse events. The mention of "unmount" has me thinking if md-quiesce needs to do more work to properly snuff a widget. ðĪ
Hmm, I'd be happy to experiment with it! Seems my app has plenty of things to trigger weird flutter edge-cases, might be a good testing ground :P
I'd imagine as long as our cached context is updated on the new builds, the only remaining danger would be the case where you try to use the context and the widget is unmounted. So if we can handle that, I imagine we're golden.
I think you nailed it on the head: we get the ctx with every build, so the only problem will be if we need the ctx before then. I have a ctx cacher working OK, just need to test on more examples then away we go. (In a few hours if all goes well.)
> so the only problem will be if we need the ctx before then Before build - would that include during build? IE. in a layout-builder?
Worst case scenario we revert to in-my-context when needed :)
"Before build - would that include during build? IE. in a layout-builder?"
Dunno, just speculating. We are passed the ctx in the build call, and I am caching it first thing, so if we do not see a :fx$ctx we must be "before" any build. ðĪ·
Possible use case: Not sure if it is still there, but at one point I tried using one of my ctx macros in a formula for a custom state cell -- which ran before we had ctx. I think. ð It might have been the where a standard widget factory did not use fx-resolve, and those have been converted. My original thinking was that only cells for Dart/Flutter, the first map, would ever need context, but I was goofing around trying to grab some Flutter info in a custom temp cell. The values for the d/F constructors would only be sought during builds, so that seemed safe. Let's see where the new approach comes up short and address it if/then.
Gonna test some more and maybe push sth in a few.
Btw, the builders I see in my examples all get passed a ctx -- did you have one that did not? Flutter certainly has a wide variety of patterns for things.
Oh, right.. No I haven't yet.
I'm still tinkering with trying to get a custom layout-builder widget that I can use first-class with mx (fxx/magic-layout (fx/some-child-widget {:height (/ (:fl$max-h @me) 2)}))
Not sure I follow, but often this MX stuff works only because things are in formulas that will not run straight away, and all these with-ctx-whatever macros double down on that by yielding functions that will be called from within build functions, where ctx is avaliable. Maybe we need a new macro? ð
> Not sure I follow Your probably not missing too much, this is me imagining I can somehow or another bypass all my flutter-not-wanting-to-be-responsive-the-way-I-want-it-to woes (read: probably wishful thinking, or if I'm lucky, a new macro) :P.
OK, keep me posted, I will dive in early if needed, no need to go crazy on anything before asking. Still lots of polishing ot go, and I have not even looked at performance yet. ctx realease candidate is in SHA e789cd4f4bb0447f840a02f9d5543787a84b5158 on the "todo" branch, which has been pushed. The examples in f/mx work, gonna try the sampler next. Relevant code:
;;; --- ctx tracking ----------------------
(defn record-fx$ctx [me ctx]
(mx/rmap-meta-set! [:fx$ctx me] ctx))
(defn ctx-check [fx-class me ctx]
(cond
(nil? (fx-ctx me))
(do
;(dp :ctx-check-NIL (str "<<" fx-class ">> NIL so adopting build ctx: <" ctx))
(record-fx$ctx me ctx))
(= ctx (fx-ctx me))
(dpx :ctx-check-OK! (str "<<" fx-class ">> both ctx: <" ctx ">"))
:else (do
(dp :ctx-match-fail-being-corrected (str "<<" fx-class ">> adopting build ctx: <" ctx "> not= cache: <" (fx-ctx me) ">"))
(record-fx$ctx me ctx))))
(defn fx-ctx [me]
(:fx$ctx (meta me)))
ctx-check gets called just inside the build calls. Needs a better name actually -- it now caches the ctx, but yells if it already knows a ctx and is passed a different one.
Call fx-ctx to retrieve the ctx where otherwise not available (and be ready for nil ð in case we missed sth).
If you have your own build, the ones I did look like this:
(~'build [_# ctx]
(tiltontec.flutter-mx.factory/ctx-check (quote ~fx-class) me ctx)
(with-flutter-ref...
Testing the sandbox examples next.Sweet, thanks!
I'll try to organize list my scattered thoughts:
âĒ flutter provides through layout builder maxH/W properties of parent that I want to use to size the child
âĒ this is only available during layout (part of build) time
âĒ because of infinite constraints of things like scrollviews and others, I want to "inherit" the last maxH/W that isn't infinite
âĒ my "dream" api would be something like (fx/parent-capture-max-size-and-attach-to-me (fx/a-child-that-technically-is-wrapped-in--as-dart-callback--and-passed-to-bulider {:height (/ (mget?-like-that-returns-first-found-prop-asc :bc$max-h) 2)}))
Last bit made more readable:
(defn first-fm-up-with-prop [me prop]
(mx/fm-navig #(not (nil? (mx/mget? % prop))) me :me? false? :up? true :inside? false))
(defn get-prop-up [me prop]
(when-let [first-parent-with-prop (first-fm-up-with-prop me prop)]
(mget first-parent-with-prop prop)))
(fx/parent-capture-max-size-from-layoutBuilder-and-attach-to-me
(fx/a-child-that-technically-is-wrapped-in--as-dart-callback--and-passed-to-bulider
{:height (/ (get-prop-up :bc$max-h) 2)}))"...first-found-prop-asc"!!! I have been contemplating such a beast!! Sure would simplify the coding, and further scandalize the string static crowd!
What if an ascendant fas the prop :bc$max-h but the value is nil? Do you want to give up there, or keep ascending until a second ascendant has the prop and a value?
Oh, sounds like a good variant/flag to have!
In the layout use-case I'm after the first "something" but in general, is well, general :)
To test if me has prop, just a simple clj contains? is what mget? uses:
(contains? @me prop)
Prolly should be encapsulated in md-has-prop? or sth.
As far as this bit goes:
(fx/parent-capture-max-size-from-layoutBuilder-and-attach-to-me
(fx/a-child-that-technically-is-wrapped-in--as-dart-callback--and-passed-to-bulider
What do you think? Am I dreaming something unrealistic?(not necessarily wrapped in as-dart-callback I suppose, what I mean is that it's treated as any other mx widget in the sense of kids/me/parent being handled)
Sorry, I did not grasp that. But one good thing about having MX widgets wrapping dart widgets is we can get away with a lot.
Can we take an existing example with a builder and make things more concrete?
I've been trying, but not being all that versed in matrix intenals I get a little lost :P Here is a commented excerpt:
Not seeing the excerpt. What was the example we just did where I used as-dart-callback to make the buillding widget visible to the built?
defn make-app []
(let [title "Layout Builder Dynamic Text Size"]
(fx/material-app {:title title}
(fx/scaffold
{:appBar (fx/app-bar {:title (fx/text title)})}
{:name :scaffo
:scaff-color m.Colors/red}
(fx/center
(fx/layout-builder
{:builder (as-dart-callback [ctx box-constraint]
(dp :layo-builder-sees-me (minfo me))
(fx/fx-render ctx
; now we can play in f/mx world....
(with-par me
(fx/text
{:style (cF (p/TextStyle
.color (mget me :font-color)
.fontSize (mget me :font-size)))}
{:font-size (* 0.1 (.-maxHeight ^m/BoxConstraints box-constraint))
:font-color (cF (cond
(> (mget me :font-size) 50)
(mget (fasc :scaffo) :scaff-color) ;; m.Colors/red ;; nb <=======================
:else m.Colors/cyan))}
(str "Fontsize " (/ (int (* 10 (mget me :font-size))) 10))))))}))))))Ah, ok, it was x029-layout-builder. I never copied that to the sandbox.
Sorry, modding excerpt, almost ready
np I am working thru all the sandbox examples, looking good so far.
Alright, untested, probably all kinds of wrong and broken, maybe-close code:
(defmacro kid->layout-builder [fx-class fx-props mx-props & child]
`(tiltontec.flutter-mx.factory/make-fx (new tiltontec.flutter-mx.factory/FXDartWidget)
~fx-props
(assoc ~mx-props
:fx-class (quote ~fx-class)
:fx-gen (fn [me ctx]
(reify :extends (m/StatelessWidget)
(~'build [_# ctx]
(with-flutter-ref
(~fx-class
~(symbol ".builder")
(fx/-as-dart-callback [_# constraints]
(fx/fx-render ctx (tiltontec.matrix.api/cFkids ~@child)))
~@(let [kvs (for [[k# _#] fx-props]
[(symbol (str "." (name k#)))
`(tiltontec.flutter-mx.core/fx-resolve ~k# me ctx
(tiltontec.matrix.api/mget me ~k#))])]
(apply concat kvs))))))))))
Add in a bit from my old macro, (can be be simplifed a lot, sorry for the noise), and attach to me's meta instead of dircetly into the matrix, and I think it might work
`(fx/as-dart-callback [~(symbol "ctx") ^m/BoxConstraints ~(symbol "box-constraint")]
(mx/with-par ~(symbol "me")
(let [^m/BoxConstraints ~(symbol "w-box-constraints") ^m/BoxConstraints
(if (> 9999 (.-maxWidth ^m/BoxConstraints ~(symbol "box-constraint")))
^m/BoxConstraints ~(symbol "box-constraint")
^m/BoxConstraints (pbvm.utils.fmx/get-prop-up ~(symbol "me") :w-box-constraints))
^m/BoxConstraints ~(symbol "h-box-constraints") ^m/BoxConstraints
(if (> 9999 (.-maxHeight ^m/BoxConstraints ~(symbol "box-constraint")))
~(symbol "box-constraint")
(pbvm.utils.fmx/get-prop-up ~(symbol "me") :w-box-constraints))]
(let [^m/BoxConstraint ~(symbol "min-h") (.-minHeight ~(symbol "h-box-constraints"))
^m/BoxConstraint ~(symbol "max-h") (.-maxHeight ~(symbol "h-box-constraints"))
^m/BoxConstraint ~(symbol "min-w") (.-minWidth ~(symbol "w-box-constraints"))
^m/BoxConstraint ~(symbol "max-w") (.-maxWidth ~(symbol "w-box-constraints"))]
(fx/fx-render ~(symbol "ctx")
(fx/container
~args
~(merge matrix
`{:h-box-constraints ^m/BoxConstraints ~(symbol "h-box-constraints")
:w-box-constraints ^m/BoxConstraints ~(symbol "w-box-constraints")
:min-h ^m/BoxConstraint ~(symbol "min-h")
:max-h ^m/BoxConstraint ~(symbol "max-h")
:min-w ^m/BoxConstraint ~(symbol "min-w")
:max-w ^m/BoxConstraint ~(symbol "max-w")})
~body))))))Okay, definitely broken, just a moment.... Oh nvm, back to just probably :P
I'm really not so great with decoding flutter errors, but I think this means I have to type something different?
ââ⥠EXCEPTION CAUGHT BY WIDGETS LIBRARY ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
The following NoSuchMethodError was thrown building make_appReifyirmorq$2(dirty, state:
make_appReify2we0cb$2#2ed24):
Class 'Symbol' has no instance method '$_invoke$4' with matching arguments.
Receiver: Instance of 'Symbol'
Tried calling: $_invoke$4(LayoutBuilder, null, null, Instance of 'Atom')
Found: $_invoke$4(dynamic, dynamic, dynamic, dynamic) => dynamicOr probably more likely, I need to quote/unquote differently ðĪ·ðŧââïļ
Actually, I was wondering about your use of symbol
Ah, yep, that can change methinks
eg, cannot ~(symbol "me") just be me?
Oh, your looking at the second part -- yeah, you can ignore the all the symbol bits.
Did I start that? ð Lemme look around....
Part of the clutter that I plan to clean
For now I'm just trying to get the first part working, without actually doing anything with layoutbuilders constraints
Hmm, looking at kids->prop-stateless as an example, maybe we don't need the reify bit
btw, are you working in a clone of f/mx proper? Or are you working your macrology into your own sources? I am thinking it would help if I could pull in the code you are working on. This stuff is so dense I really need to compile/run to think about it.
So going back to your original "scattered thoughts", do I understand to want the parent of the built widgets to track the max values passed as constraints, such that they are available to the built widgets?
The latter, but should be copy/pastable into f/mx: this into factory:
(defmacro kid->layout-builder [fx-class fx-props mx-props & child]
`(tiltontec.flutter-mx.factory/make-fx (new tiltontec.flutter-mx.factory/FXDartWidget)
~fx-props
(assoc ~mx-props
:fx-class (quote ~fx-class)
:fx-gen (fn [me ctx]
(with-flutter-ref
(~fx-class
~(symbol ".builder")
(fx/-as-dart-callback [_# constraints]
(fx/fx-render ctx (tiltontec.matrix.api/cFkids ~@child)))
~@(let [kvs (for [[k# _#] fx-props]
[(symbol (str "." (name k#)))
`(tiltontec.flutter-mx.core/fx-resolve ~k# me ctx
(tiltontec.matrix.api/mget me ~k#))])]
(apply concat kvs))))))))
and this into core:
(fx/deftag `kid->prop-layout-builder magical-layout m/LayoutBuilder) ;; <-- different name of course :P
Wait, why "diff name of course"? I like magical-layout! ð
And then the actual make-app?
> do I understand to want the parent of the built widgets to track the max values passed as constraints, such that they are available to the built widgets? Yes, and if the max value is infinite, use that of the next magical-layout up. Haha, well, I guess there may not really be a short name that describes it well :P, (yet at least).
Oh right, just a sec.
Nested layout builders? Yer having fun, aren't you? ð I'll start a new branch and start adding your code.
Haha yep, and on certain pages, everywhere and in great multitude. Thanks!
make app - step 1:
(defn make-app []
(let [title "Magic layout - Dynamic Text Size - wip"]
(fx/material-app {:title title}
(fx/scaffold
{:appBar (fx/app-bar {:title (fx/text title)})}
(fx/center
(fx/magical-layout ; step 1: actually compile
(m/Text "hello" .style (m/TextStyle .fontSize 200))))))))Your deftag ^^^ has an interesting backquote: (fx/deftag `kid->prop-layout-builder..... Whassat?
Oh, the first problem! :P not sure how that happened..
And you provided above the source for kid->layout-builder, but your deftag has kid->prop-layout-builder.
Yep! Sorry, trying to move to quickly
Ignore my deftag and start from scratch :P
np! Take your time. I will put on the golf.
In mx, would be like this: (fx/deftag tiltontec.flutter-mx.factory/kid->layout-builder magical-layout m/LayoutBuilder)
Acidental extra hyphen prefix on with-dart-callback
Ah, and another problem, I moved the kids into the builder, but now make-fx is missing it's last arg.
Right, use nil for the last arg.
Ah cool didn't know I could send nil, and I'm guessing this would have other problems:
(defmacro kid->layout-builder [fx-class fx-props mx-props & child]
`(let [mx-kids (tiltontec.matrix.api/cFkids ~@child)]
(tiltontec.flutter-mx.factory/make-fx (new tiltontec.flutter-mx.factory/FXDartWidget)
~fx-props
(assoc ~mx-props
:fx-class (quote ~fx-class)
:fx-gen (fn [me ctx]
(~fx-class
~(symbol ".builder")
(fx/as-dart-callback [_# constraints]
(fx/fx-render ctx (tiltontec.matrix.api/cFkids ~@child)))
~@(let [kvs (for [[k# _#] fx-props]
[(symbol (str "." (name k#)))
`(tiltontec.flutter-mx.core/fx-resolve ~k# me ctx
(tiltontec.matrix.api/mget me ~k#))])]
(apply concat kvs)))))
mx-kids)))Is this a new version we should work on? ^^^
I think so, after nixing the let
like so:
(defmacro kid->layout-builder [fx-class fx-props mx-props & child]
`(tiltontec.flutter-mx.factory/make-fx (new tiltontec.flutter-mx.factory/FXDartWidget)
~fx-props
(assoc ~mx-props
:fx-class (quote ~fx-class)
:fx-gen (fn [me ctx]
(~fx-class
~(symbol ".builder")
(fx/as-dart-callback [_# constraints]
(fx/fx-render ctx (tiltontec.matrix.api/cFkids ~@child)))
~@(let [kvs (for [[k# _#] fx-props]
[(symbol (str "." (name k#)))
`(tiltontec.flutter-mx.core/fx-resolve ~k# me ctx
(tiltontec.matrix.api/mget me ~k#))])]
(apply concat kvs)))))
nil))The flutter ref can prob. be put back in as well, removed to simplify earlier
Ok, but now we can lose the trailing nil?
I am over here getting back up to speed on this builder pattern.
with nil, :kids will be nil. If we instead use the let approach instead kids will be set, but I'm not sure if we will have problems or not because of the fx-render that happens in .builder arg?
I am looking at the todo app:
(defn todo-items []
(fx/expanded
;; ^^^ sizer is required to join column, which demands children know their size
(fx/list-view+builder
{:padding (m.EdgeInsets/all 0.0)
:itemCount (cF (count (mkids me)))
:itemBuilder (cF (fx/as-is
(fn [ctx i]
;; we must yield a native Flutter widget ready for Flutter, so we must "render" here,
(fx/fx-render ctx
(nth (mkids me) i)))))}
{:name :lv-builder}
(map todo-list-item
(sort-by todo/td-created-at
(todo/app-todos (my-app)))))))
list-view+builder uses:
(defmacro kids-for-builder [fx-class fx-props mx-props & children]
(let []
`(tiltontec.flutter-mx.factory/make-fx (new tiltontec.flutter-mx.factory/FXDartWidget)
~fx-props
(assoc ~mx-props
:fx-class (quote ~fx-class)
:fx-gen (fn [me ctx]
(with-flutter-ref
(~fx-class
~@(let [kvs (for [[k# _#] fx-props]
[(symbol (str "." (name k#)))
`(tiltontec.flutter-mx.core/fx-resolve ~k# me ctx
(tiltontec.matrix.api/mget me ~k#))])]
(apply concat kvs))))))
(tiltontec.matrix.api/cFkids ~@children))))
Lemme remind myself why this does not fx-render the kids. brbAha. fx-render grabs the :fx-gen property to get the code that actually renders, and we can see ^^^ it just calls the constructor (of the parent) and sets the properties, ignoring children. One of the properties is sth like :itemBuilder which pulls in the kids.
I cannot believe I wrote all this. It was exhausting, I remember. ðĪŊ
Ah, okie dokie. Haha yaah when I first started perusing the fx part of f/mx I was just like ðĩâðŦ
OK, compile is good, ready to give it a run.
OK, the usual: "Exception: No extension of protocol IFn found for type CBToResolve." So I need to figure out where to call fx-resolve on what.
This might help?: I found if I pass in a m/Text instead of an fx/text
(xu/magical-layout ; step 1: actually compile
(m/Text "hello")))))))
I get Error: The method '$_invoke$0' isn't defined for the class 'Text'.using this as the factory:
(defmacro kid->layout-builder [fx-class fx-props mx-props & child]
`(tiltontec.flutter-mx.factory/make-fx (new tiltontec.flutter-mx.factory/FXDartWidget)
~fx-props
(assoc ~mx-props
:fx-class (quote ~fx-class)
:fx-gen (fn [me ctx]
(~fx-class
~(symbol ".builder")
(fx/as-dart-callback [_# constraints]
(fx/fx-render ctx ~child))
~@(let [kvs (for [[k# _#] fx-props]
[(symbol (str "." (name k#)))
`(tiltontec.flutter-mx.core/fx-resolve ~k# me ctx
(tiltontec.matrix.api/mget me ~k#))])]
(apply concat kvs)))))
nil))Actually, I am seeing the other builder examples use standard factories, but have properties like :builder or :itemBuilder . I think that is the way to go.
I am about to crash, but this example seems closest in that the widget has kids and an :itemBuilder:
(defn todo-items []
(fx/expanded
;; ^^^ sizer is required to join column, which demands children know their size
(fx/list-view+builder
{:padding (m.EdgeInsets/all 0.0)
:itemCount (cF (count (mkids me)))
:itemBuilder (cF (fx/as-is
(fn [ctx i]
;; we must yield a native Flutter widget ready for Flutter, so we must "render" here,
(fx/fx-render ctx
(nth (mkids me) i)))))}
{:name :lv-builder}
(map todo-list-item
(sort-by todo/td-created-at
(todo/app-todos (my-app)))))))
With just one kid you will not get the i parameter.Maybe even closer:
(defn expanding-action-button [me anime & {:keys [action direction-degrees max-distance progress-key]}]
(fx/animated-builder
{:animation anime ;; (mget (mx/fasc :fab) :expand-animation)
:builder (as-dart-callback [ctx child]
(let [progress ^m/CurvedAnimation anime #_(mget me :animation) #_(mget (mx/fasc :fab) :expand-animation)
offset (m.Offset/fromDirection
(* direction-degrees (/ math/pi 180.00))
(* (.-value progress) max-distance))]
;; N.B! alpha widgets are welcome at leaves of the MX tree...
(f/widget
(f/nest
(m/Positioned
.right (+ 4.0 (.-dx offset))
.bottom (+ 4.0 (.-dy offset)))
(m.Transform/rotate
.angle (* (- 1.0 (.-value progress))
math/pi 0.5))
child))))}
{:name :ani-builder}
(fx/fade-transition
{:opacity anime #_(mget (mx/fasc :fab) :expand-animation)}
(action-button
:icon (:icon action)
:onPressed (fx/in-my-context [me ctx]
(fx/->CBAsIs
#(show-action ctx action)))))))
The deftag:
(deftag tiltontec.flutter-mx.factory/k1-child-stateful animated-builder m/AnimatedBuilder)
Hmm, so maybe this basic idea (it it actually can be made to work) could be generalized to other builder widgets as well thinking-face
Hang on. That ^^^ fx-gen makes the first kid into a child. Lemme chech sth.
Oh, the apply convat kvs maybe trying to call the builder prop?
Eh, nope
forgot not passing that anymore anyway
Note to self: I really should run code before I send chats :P
No, that is fine, it will just convert it to the function expected by .builder. I have to go watch the Preakness, but I am looking at what we have for fx/layout-builder . Did that not work for this use case?
Aside from the wierd markNeedsBuild called during build stuff, it works :) Sorry, didn't mean to make it sound otherwise, this is just me trying to achieve aa more inline syntax.
Hmm, off the top of my head, would it be feasible to make CBToResolve extend IFn and resolve + call?
"just me trying to achieve aa more inline syntax."
Ah, so perhaps a new macro cobbled together from the todo items example, but does not expect i and just grabs the first kid? Gotta ð !
For when you return:
This renders: ð
(defmacro kid->layout-builder [fx-class fx-props mx-props & [child]]
`(tiltontec.flutter-mx.factory/make-fx
(new tiltontec.flutter-mx.factory/FXDartWidget)
~fx-props
(assoc ~mx-props
:fx-class (quote ~fx-class)
:fx-gen (fn [me ctx]
(~fx-class
~(symbol ".builder")
(fn [_# constraints]
(fx/fx-render ctx ~child)))))
nil))
Expanding back to fuller factory:
(defmacro kid->layout-builder [fx-class fx-props mx-props child]
`(let [mx-kids (tiltontec.matrix.api/cFkids ~child)]
(tiltontec.flutter-mx.factory/make-fx (new tiltontec.flutter-mx.factory/FXDartWidget)
~fx-props
(assoc ~mx-props
:fx-class (quote ~fx-class)
:fx-gen (fn [me ctx]
(tiltontec.flutter-mx.factory/ctx-check (quote ~fx-class) me ctx)
(~fx-class
~(symbol ".builder")
(fn [_# constraints]
(fx/fx-render ctx (mx/with-par me ~child)))
~@(let [kvs (for [[k# _#] fx-props]
[(symbol (str "." (name k#)))
`(tiltontec.flutter-mx.core/fx-resolve ~k# me ctx
(tiltontec.matrix.api/mget me ~k#))])]
(apply concat kvs)))))
mx-kids)))
Now onto step 2.Ain't macros grand? And we have only scratched the surface.
There comes a time where nothing else comes close, and Flutter definately needs a lot of them to have some sanity. :P
Yippy-yi-yay! Still have yet to polish/test thoroughly, but may I present to you: Magic Layout (alpha) âĒïļ ! ðĪŠ
(defmacro kid->layout-builder [fx-class fx-props mx-props child]
`(let [mx-kids (tiltontec.matrix.api/cFkids ~child)]
(tiltontec.flutter-mx.factory/make-fx
(new tiltontec.flutter-mx.factory/FXDartWidget)
~fx-props
(assoc ~mx-props
:fx-class (quote ~fx-class)
:fx-gen (fn [me ctx]
(tiltontec.flutter-mx.factory/ctx-check (quote ~fx-class) me ctx)
(~fx-class
~(symbol ".builder")
(fn [_# ^m/BoxConstraints box-constraints#]
(let [min-h# (.-minHeight box-constraints#)
min-w# (.-minWidth box-constraints#)
ancestor-max-h# (or (get-prop-up me :max-h)
(-> m/MediaQuery (.of ctx) .-size .-height))
ancestor-max-w# (or (get-prop-up me :min-h)
(-> m/MediaQuery (.of ctx) .-size .-width))
max-h# (min (.-maxHeight box-constraints#) ancestor-max-h#)
max-w# (min (.-maxWidth box-constraints#) ancestor-max-w#)]
(swap! me assoc
:min-h min-h# :max-h max-h#
:min-w min-w# :max-w max-w#)
(fx/fx-render ctx (mx/with-par me ~child))))
~@(let [kvs (for [[k# _#] fx-props]
[(symbol (str "." (name k#)))
`(tiltontec.flutter-mx.core/fx-resolve ~k# me ctx
(tiltontec.matrix.api/mget me ~k#))])]
(apply concat kvs)))))
mx-kids)))Since this is during build, I don't want to trigger any matrix reactivity, thus the swap! on me.
Grassshopper ready to leave MX temple! I was thinking swap!.
I feel armed and dangerous now that I have a little handle on custom f/mx widgets -- bye bye flutter clutter :P Just need the careful, this was more addictive than hacking my emacs config.
Sorry, my memory is fading. Had you done Flutter from Dart before CLJD? Anyway, I do look at the clutter and marvel that it was tolerated at all. I guess it is one part "we are used to being abused" and one part "Web, mobile, and desktop in one? OMG!". Glad to see the first convert confirming the CLJD opportunity.
Haha, yeah I think your spot on with that recipe.
No, I haven't used flutter before cljd, I looked at it with my last project, and decided I preferred the pain of react-native (albeit buffered a little by using clojurescript, it's still a mess).
As far I know the only flutter -> cljd convert so far is @windlejacob12
Hmm, seems I didn't get the :kids quite right...
ââ⥠EXCEPTION CAUGHT BY WIDGETS LIBRARY ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
The following _Exception was thrown building LayoutBuilder:
Exception: MXAPI_COMPUTE_CYCLE_DETECTED> cyclic dependency detected while computing prop ' :kids '
of model ' anon '.
...> formula for :kids :Only happens when I add the mget
Or rather the (get-prop-up ,,,) from before.
For now I've worked around it my passing nil instead of mx-kids, but I'd definitely be interested in a real solution.
Something I got wrong in the fmnavig, perchance?
(defn first-fm-up-with-prop [me prop]
(mx/fm-navig #(not (nil? (mx/mget? % prop))) me :me? false? :up? true :inside? false))
(defn get-prop-up [me prop]
(when-let [first-parent-with-prop (first-fm-up-with-prop me prop)]
(mget first-parent-with-prop prop)))What is the new code? A change to make-app?
Oops sorry, forgot to paste example: just a sec.
The navig looks OK at first blush.
Heh, repro works fine, time to get more elaborate :P
Oh, I think I found it... its not the kids, it's the me , methinks:
(defn make-app []
(let [title "Magic layout - Dynamic Text Size - wip"]
(fx/material-app {:title title}
(fx/scaffold
{:appBar (fx/app-bar {:title (fx/text title)})}
{:name :scaffo}
(fx/column
(fx/magical-layout
{} {:name :magic?-1}
(fx/container {:decoration (m/BoxDecoration .color m/Colors.blue)
:width (or (xu/get-prop-up me :max-w) 200)}
(fx/text (str [:container (deref (:parent @me))]))
))
(fx/magical-layout
{} {:name :magic?-2}
(fx/container {:decoration (m/BoxDecoration .color m/Colors.blue)
:width (or (xu/get-prop-up me :max-w) 200)}
(fx/text (str [:magical-layout (deref (:parent (deref (:parent @me))))]))
)))))))(edited to change fxx/ to fx/)
Ya gotta watch out for me! https://github.com/kennytilton/web-mx/wiki/Who%3F-Me%3F
Do we need a cF around (or (xu/get-prop-up me :max-w) 200)?
Ohh, not sure, I may have been missing those in my whole app!
I'll give that make-app a try.
I think I mistakenly thought they were implicit same as child/children
A too-high asc-wise me is OK if what we seek is higher than the me we take by mistake.
Yes, that was why my first repro failed to repro
I have to scroll back ang get your get-prop-up. Is that inclusive of the me param?
not in the navig iiuc
(defn first-fm-up-with-prop [me prop] (mx/fm-navig #(not (nil? (mx/mget? % prop))) me :me? false? :up? true :inside? false)) (defn get-prop-up [me prop] (when-let [first-parent-with-prop (first-fm-up-with-prop me prop)] (mget first-parent-with-prop prop)))
Oh, yes it is: typo in there
false? ; => truthy :P
Hmm. I see :me? false:
(defn first-fm-up-with-prop [me prop]
(mx/fm-navig #(not (nil? (mx/mget? % prop))) me :me? false? :up? true :inside? false))
Oh, Ok. So now we have?:
(defn first-fm-up-with-prop [me prop]
(mx/fm-navig #(not (nil? (mx/mget? % prop))) me :me? false :up? true :inside? false))
That would not be inclusive.Now, yes, so far same error.
I see the two magicals as siblings under the column. Intended? That will run afoul of fm-navig "up".
Ooh, yes, was intended, didn't know that would mess with up. Also was the first repro of the cycle error.
Recall that "up" is really "outside" the tree with me as the root. So my sibling is outside me [hence the cycle, I think]. fm-navig does not have an "ascendant-only" capability. I am zonked, but suggest you do an fasc-prop hack.
Oh, me is the root! Now I get it.
Alright, thank you so much for all your help ð
heh-heh, you are helping me to improve MX! Thank you!
Monkey-patch to get by:
(defn get-prop-up
([me prop]
(get-prop-up me prop nil))
([me prop maybe-found]
(if (or maybe-found (not (:parent @me)))
maybe-found
(recur (:parent @me) prop (mx/mget? me prop)))))Also improved magical-layout that does't "shrink" because of history:
as "magical" as it feels esp. compared the the headache I had prior, I feel like with-layoutmight be a better fitting name.
Ah!!!!!!!!!! So instead of (container....) being evaluated when the def w-in-def is evaluated, it runs when (w-in-def ...) is called, and the special *parent* is bound to The Right Thing(tm)!
Perfect, Now what about that naked (/ (xu/get-prop-up me :max-w) 3) . Can we get a cF around that?
btw, this is not the first time this has happened, but I am greatly encouraged by folks showing up withworking MX projects who I never knew about until they showed up! Makes me think MX is not too hard to pick up. But this can lead to some non-idiomatic code, and my apologies for not picking up that def sooner. Do you follow now why the defn works so much differently? In brief, MX works dynamically, and f/mx or web/mx macros are especially run-time beasts, so when code runs matters.
Good luck with the deadline! Have you figured out how to deploy?
Also, have we looked at resource leaks? I added the quiesce API to support clean-ups, but have not documented it.
I am also concerned about the final image size. eg, Does all that macrology expand into bloat? I imagine a few macro-helping functions can help, but at POC-time I was just trying to get the thing working.
Caveat Shippers!
> Perfect, Now what about that naked (/ (xu/get-prop-up me :max-w) 3) . Can we get a cF around that?
Sure thing. :) Seems with layout builder, most of the time it's not needed, because it's forcing a rebuild, nothings out-of-date. However there is a certain point where enough layers away and certain abstracted children need the cF, presumably because some flutter mech. recognizes it could be more efficient and not rebuild that child.
> Makes me think MX is not too hard to pick up.
Yep! Originally, I was a bit skeptical as it was "smallstream" (relative to clojure's already "smallstream"), but it's proved to be so useful I want it everywhere, even though it probably wouldn't fit on the 8kb ram micro controllers we're using :P.
> my apologies for not picking up that def sooner.
There was no sooner, this was the first time I realized it was relevant to put in the repro as I had done that bit a long time ago. :)
> Do you follow now why the defn works so much differently?
Yep! Makes a lot of sense, it was a newbie mistake on my part. (Newbie to programming, not to MX).
> Good luck with the deadline! Have you figured out how to deploy?
Thanks! - Yes, one thing I do appreciate about flutter is that the docs are nicely thorough.
> Also, have we looked at resource leaks? I added the quiesce API to support clean-ups, but have not documented it.
Not really. Flutter has a profiler build option, I'll give that a shot at some point.
> Does all that macrology expand into bloat?
Not sure, but it's not too much for this app, at least. About 27mb for a release build. (or 47 for a bundle that includes each abi).
Oh, is the quiesce api user-facing? ð
I assumed it was also magical. :P
Oh, is the quiesce api user-facing?
I assumed it was also magical. :P
I suppose it could be. Here was the first time I thought to worry about resource leaks:
:msg-rcvd (cF+n [:ephemeral? true
:on-quiesce (fn [cell]
(when-let [sub (:subscription @cell)]
(.cancel ^#/(async/StreamSubscription void) sub)))
:watch (fn [_ me new-source old-source cell]
(when (= old-source mx/unbound)
(let [sub (.listen (.-stream ^async/StreamController new-source)
(fn [^String msg]
(mset! me :msg-rcvd msg)))]
(rmap-set! [:subscription cell] sub))))]
(mget (fm* :msg-sender) :msg-stream))
(Later it occurred to me that the formula itself could have subscribed, then I would not need to stash the subscription in a cell property :subscription.)
Anyway, the Flutter doc says it is wise to cancel subscriptions when no longer needed, so I thought I would handle quiescing.
As for automating, I think we can think about having cells/models have a meta property :resources where a widget could keep those. Then, yeah, at least the cleanup would be more automatic. Version 2!> Flutter doc says it is wise So maybe I can get by without it right at first ð
I guess the thing to look out for is "After X hours it gets pretty slow, but we turn it off and restart and it flies again."
Ah, now I remember why I was using defs -- seems to make hot reload work a lot better. It's surprising to me just how well it worked, given that it seems it shouldn't ðĪ·ðŧââïļ
Hmm, maybe there is a window for improvement. Can you describe what seemed better?
Faster. My guess is, with a def it only re-evals if it changes. When a fn call, re-evals every time period.
I suppose we could offer as a performance tip: 'If we have a self-contained tree of f/mx widgets -- no formulas searching outside the tree -- and we need to add it to our app only once, define it in a def.' ðĪ
Wait a minute, Try this:
(def w-in-def
(fx/container
{:decoration (m/BoxDecoration .color m/Colors.blue)
:width (cF (/ (xu/get-prop-up me :max-w) 3))}
(fx/text (str "If this renders, surprise! :scaffo 's :isolated value: " (xu/get-prop-up me :isolated)))))
(defn make-app []
(let [title "Magic layout - Dynamic Text Size - wip"]
(fx/material-app
{:title title}
(fx/scaffold
{:appBar (fx/app-bar {:title (fx/text title)})}
{:name :scaffo
:max-w 900
:isolated "Hello, can you see me, w-in-def?"}
(fx/center
(fx/column
(fx/text "always visible")
w-in-def))))))^ outside the tree, no?
To make sure it's not "stuck in time"
(def w-in-def
(fx/container
{:decoration (m/BoxDecoration .color m/Colors.blue)
:width (cF (/ (xu/get-prop-up me :max-w) 3))}
(fx/text (str "If this renders, surprise! :scaffo 's :isolated value: " (xu/get-prop-up me :isolated)))))
(defn make-app []
(let [title "Magic layout - Dynamic Text Size - wip"]
(fx/material-app
{:title title}
(fx/scaffold
{:appBar (fx/app-bar {:title (fx/text title)})}
{:name :scaffo
:max-w 900
:isolated (cI "Hello, can you see me, w-in-def? .... this message overriden")}
(fx/center
(fx/column
(fx/text "always visible")
w-in-def
(do (with-cc :test (mset! (fasc :scaffo) :isolated "Now all you see is this, because the matrix somehow works with def iso'ed trees, no?"))
(fx/text "the end"))))))))BTW, Probably not a finished product perf boost, just a hot-reload perf. boost. Perhaps only really noticeable an my wimpy phone :P
Maybe it works because fx-resolve/fx-gen doesn't render the defed code until it's mounted or something like that?
This allows for direct referening of matrixs, instead of tree searching fwiw:
(def w-in-def
(fx/container
{:decoration (m/BoxDecoration .color m/Colors.blue)
:width (cF (/ (xu/get-prop-up me :max-w) 3))}
(do (dc/print scaff)
;; Here we access the fx/scaffold matrix directly
(with-cc :test (mset! scaff :isolated "Now all you see is this, because the matrix somehow works with def iso'ed trees, no?"))
(fx/text (str "If this renders, surprise! :scaffo 's :isolated value: " (xu/get-prop-up me :isolated))))))
(def scaff
(fx/scaffold
{:appBar (fx/app-bar {:title (fx/text "Why Hello")})}
{:name :scaffo
:max-w 900
:isolated (cI "Hello, can you see me, w-in-def? .... this message overriden")}
(fx/center
(fx/column
(fx/text "always visible")
w-in-def
(fx/text "the end")))))
(defn make-app [] (let [title "Magic layout - Dynamic Text Size - wip"]
(fx/material-app
{:title title}
scaff)))Yikes! def is lazy! In CLJD, not CLJ or Common Lisp. CLJS has not been checked. Asking the lads in a sec...
Heads up, CLJD fans, def is lazy! https://clojurians.slack.com/archives/C03A6GE8D32/p1684910461277039
Anyway, assuming def stays lazy because of the nature of compilation to Dart...well, I will explore some more. I am curious if the expression is evaluated every time the deffed term is accessed. If not, using w-in-def twice would fail.
btw, @zenflowapp, I am curious now about what difference you saw moving from def to defn. Maybe this gets back to hot restart/reload?
I think no matter what we learn, I would stay away from (def xyz (fx/any-widget...))!! ð
thinking-face Good question! I've think I've read somewhere that capturing the context was a common newbie mistake among darters, but the reason given was that you could never be sure where your widget was in the tree. That the context is a handle to the location of a widget in the widget tree. Aha, in the docs: > Avoid storing instances of BuildContexts because they may become invalid if the widget they are associated with is unmounted from the widget tree. So presumably, so long as we handle discarding the context on unmount, we'd be okay?
Perhaps worth mentioning:
The main reason this comes to mind again and again, is how many tries it takes me to figure out where I need to place an (fx/in-my-context [me ctx] ,,,) where the CBToResolve will actually resolve :P Sometimes I just give up and try a different approach.
Seems where it finally works is a fair bit up from where I want to use it, and it pulls me out of flow state a bit. But it is a pet-peeve/convenience thing mostly, not a show-stopper.
Hmm, :builder-watch sounds like a good idea!
> loses track of the :max-w
I've had a situation like that here too. Perhaps because we're using swap! the cF doesn't think anything's changed and uses a memoized value?
(Seems to only happen in a cF if I'm not mistaken.)
Edit: Oh nvm, there must be another factor here.
Latest factory:
Thx, I will give this a try. I need a break from deep cells internals occasioned by the novel support for :async? cells. Almost there, but a break will help.
I gotta think we are getting a re-gen of a widget along the way, so the cached max gets lost.
I have improved the diagnostics available, let me see what I can see.
Did the make-app change?
Oh, and what about this?
btw, did you mean the two magicals to be nested one under the other? The make-app I have has them both with the column as the parent. I recall you mentioning nesting.
I am concerned about interacting issues, tho I grok it should work either way.
I will carry on with the two as siblings.Yes, should âĒïļ work either way :P
I'll have a new make-app example soon I think
OK, I'll play with the one I have in the meantime.
Might just be a logic error. Where do we take the max of two values? Reformatted:
(let [...
max-w# (if (= dart:core/double.infinity current-max-w#)
ancestor-max-w#
current-max-w#))
....This seems OK:
(if (= dart:core/double.infinity current-max-w#)
ancestor-max-w#
(if ancestor-max-w#
(max ancestor-max-w# current-max-w#)
current-max-w#))So, were not actually after the max of two values. The only reason ancestor is there at all is because fx/column and row and several others have double.infinite as the max, which is not helpful for using as a value for sizing text or anything else layout-wise. So ancestor is there to work around that. I propbably should put the media query back in as a default for the ancestor, in case one forgets to add one above an double.infinite max-w/h
OK, well the original code is correctly toggling between alternating current-max's, not tracking any max reached during the widget lifetime.
Oh, I missed your comment "this seems OK", let me take a look...
Oh, maybe it means that get-prop-up is pulling up a value from previous build on the current widget...
or even old value on parent widget would cause it.
My assumption was parent would build first
maybe not in some cases
I guess I a lost. Is this a use case? My sim is vertical, the Max-w is 430 or so. I rotate the sim. The max-w shows as 930. Now I rotate back to vertical. What should the max-w be?
430
We're not after max-w of all time :P
OK, that is what I was seeing before these patches I sent. I thought we wanted to track the max ever seen. Anyway, are we OK then with this solution?
Should be fine as a native-app btw, that way you can resize the window and see live update
Oh! Sorry, I really need to improve my communiction skills/code-comments
np! Natural language is a terrible medium. There is a reason the military developed that jargon.
Should be mostly okay then. I do have a weird thing going on where in one case I have a thing where it works on first layout, but not on resize, and I think that is something to do with mx not detecting that :max-w changed due to using swap, so it gives the old value.
Come to think of it, this has me puzzled:
(defn get-prop-up
([me prop]
(get-prop-up me prop nil))
([me prop maybe-found]
(if (or maybe-found (not (:parent @me)))
maybe-found
(recur (:parent @me) prop (mx/mget? me prop)))))
If I suppy a non-nil maybe-found, that is what gets returned. It is not behaving as a default.Okay right about now I'm really missing not having a repl :P I'll take a look
Wait, it's returning the fn itself?
I was expecting:
(defn get-prop-up
([me prop]
(get-prop-up me prop nil))
([me prop default-value]
(or (mx/mget? me prop)
(if (:parent @me)
(recur (:parent @me) prop default-value)
default-value))))Yeah, I just throw code in the main function and do a hot restart when I need a REPL. It's OK.
Oh, that reads much more clearly, I like it!
I'm not smart like you guys, I have to code like a five year-old or my head explodes.
Haha, mine isn't smart coding, it's coding-after-my-brain-is-far-spent-and-i-know-it-should-work-because-ive-used-structual-editing-to-modify-the-simple-use-case--to-its-now-unreadable-form---so-it-should-work :P
Ok, I am headed out for some R&R, I'll check when I get back for any updates. Have fun!
I'm suspicious that using a direct fn instead of as-dart-callback is causing funny things to happen to me. Or, more accurately, what me is mein the build phase.
Aha, repro:
(def w-in-def
(fx/container
{:decoration (m/BoxDecoration .color m/Colors.blue)
:width (cF (/ (xu/get-prop-up me :max-w) 3))}
(fx/text (str (xu/get-prop-up me :max-w)))))
(defn make-app []
(let [title "Magic layout - Dynamic Text Size - wip"]
(fx/material-app
{:title title}
(fx/scaffold
{:appBar (fx/app-bar {:title (fx/text title)})}
{:name :scaffo}
(fxx/magical-layout
(fx/column
(fx/container
{:decoration (m/BoxDecoration .color m/Colors.blue)
:width (/ (xu/get-prop-up me :max-w) 3)}
(fx/text (str (xu/get-prop-up me :max-w))))
w-in-def))))))
max-w/his stuck to initial layout when you use a widget in a def.
Putting it in a defn instead works as expected.For now I'll just use defns, since the deadline is soon and it works âĒïļ
You know, I have been puzzling over this:
(def w-in-def
(fx/container
{:decoration (m/BoxDecoration .color m/Colors.blue)
:width (cF (/ (xu/get-prop-up me :max-w) 3))}
(fx/text (str (xu/get-prop-up me :max-w)))))
...followed by using w-in-def in the child/kids position.
When did we come up with that? I ask because in the example, the w-in-def container will be parentless, because parent gets established when the widget factory make-fx runs, calling model.core/make, which starts out like this:
(defn make [& arg-list]
;;(prn :make-entry (count arg-list) (first arg-list))
(cond
(odd? (count arg-list)) (apply make :mx-type arg-list)
:else
(do
(let [iargs (apply hash-map arg-list)
me (atom
(let [mdmap (merge {:parent *md-parent* ;; <===================
:host *md-host*}
....
I will look around to see where that pattern originated. It would be fine (I think) if we were not now searching up the ascendant tree looking for stuff.
Also, in:
(fx/container
{:decoration (m/BoxDecoration .color m/Colors.blue)
:width (/ (xu/get-prop-up me :max-w) 3)}
(fx/text (str (xu/get-prop-up me :max-w))))
...I am concerned about the :width value not being wrapped in (cF ....). That means the nearest cell is the dependent, whch would be the hidden :kids formula for the next guy up, the column. It is OK, btw, that in (fx/text (str (xu/get-prop-up me :max-w)) the str form is not wrapped in a cF, because it is in the :kids position of the fx/text, and kids are all wrapped in cF by my GUI element macros, HTML or Flutter.
I know you have a deadline, but if you get me your latest repro I will play with it. It might be wise to sort this out if it is just a couple of no-brainer adjustments. Your call.> When did we come up with that? Back when I first started experimenting with f/mx and saw that it seemed to work :P Clearly a bad idea on my part ðĪŠ .
Now that I'm using defn s it's working great :)
@logbot has joined the channel
@zulip-mirror-bot has joined the channel
point_up::skin-tone-2 That will ensure future content here in #matrix is mirrored to the ClojureVerse logs and to Zulip. It would be a shame for all this knowledge to be lost if we lose Pro status later this year.