diff --git a/clj/src/cljd/flutter.cljd b/clj/src/cljd/flutter.cljd index d6882240..d87e6eb8 100644 --- a/clj/src/cljd/flutter.cljd +++ b/clj/src/cljd/flutter.cljd @@ -1,5 +1,6 @@ (ns cljd.flutter (:require ["package:flutter/widgets.dart" :as widgets] + ["package:flutter/material.dart" :as material] ["package:flutter/foundation.dart" :as foundation] ["dart:async" :as dart:async] [cljd.string :as str]) @@ -609,13 +610,111 @@ .height ~height .child (-widget-cont ~(assoc env :key nil :closest-ctx false) ~@forms)))) +(defprotocol ITweenable + (-tween [end])) + +(extend-protocol ITweenable + fallback + (-tween [end] (widgets/Tween .end end)) + + widgets/AlignmentGeometry + (-tween [end] (widgets/AlignmentGeometryTween .end end)) + + widgets/Alignment + (-tween [end] (widgets/AlignmentTween .end end)) + + widgets/BorderRadius + (-tween [end] (widgets/BorderRadiusTween .end end)) + + widgets/Border + (-tween [end] (widgets/BorderTween .end end)) + + widgets/BoxConstraints + (-tween [end] (widgets/BoxConstraintsTween .end end)) + + widgets/Color + (-tween [end] (widgets/ColorTween .end end)) + + widgets/Decoration + (-tween [end] (widgets/DecorationTween .end end)) + + widgets/EdgeInsetsGeometry + (-tween [end] (widgets/EdgeInsetsGeometryTween .end end)) + + widgets/EdgeInsets + (-tween [end] (widgets/EdgeInsetsTween .end end)) + + widgets/FractionalOffset + (-tween [end] (widgets/FractionalOffsetTween .end end)) + + int + (-tween [end] (widgets/IntTween .end end)) + + widgets/Matrix4 + (-tween [end] (widgets/Matrix4Tween .end end)) + + widgets/Rect + (-tween [end] (widgets/RectTween .end end)) + + widgets/RelativeRect + (-tween [end] (widgets/RelativeRectTween .end end)) + + material/ShapeBorder + (-tween [end] (material/ShapeBorderTween .end end)) + + widgets/Size + (-tween [end] (widgets/SizeTween .end end)) + + widgets/TextStyle + (-tween [end] (widgets/TextStyleTween .end end)) + + material/ThemeData + (-tween [end] (material/ThemeDataTween .end end))) + +(deftype CustomTween [f ^:mutable lerpf] + :extends widgets/Tween + (begin [this v] (set! lerpf nil) (.-begin! ^super this v)) + (end [this v] (set! lerpf nil) (.-end! ^super this v)) + (lerp [this t] + (let [lerpf (or lerpf (set! lerpf (f (.-begin this) (.-end this))))] + (lerpf t)))) + +(defn ^widgets/Tween tween-with + "Create a tween ending at the provided value. Or return the value if it's already a tween. + When f is not provided, a tween is created using the ITweenable protocol. + When f is provided it must be a function of two args (begin and end values), + returning an interpolation function from t (between 0.0 and 1.0) to + interpolated value." + [f end] + (cond + f + (doto (CustomTween f nil) + (.-end! end)) + (instance? widgets/Tween end) end + :else + (-tween end))) + +(defn ^:macro-support expand-animate + [&env env binding expr {:keys [lerp on-end duration curve] :as opts + :or {duration `(Duration .milliseconds 500) + curve `widgets/Curves.linear}} forms] + `(widgets/TweenAnimationBuilder + .key ~(:key env) + .tween (tween-with ~lerp ~expr) + .duration ~duration + .curve ~curve + .onEnd ~(when on-end `(fn [] ~on-end nil)) + .builder + (fn [~closest-context ~binding _#] + (-widget-cont ~(assoc env :key nil :closest-ctx true) ~@forms)))) + (deftype SpyWidget [k child f] :extends (widgets/StatelessWidget .key k) (build [_ _] child) (debugFillProperties [this props-builder] - (.debugFillProperties ^super this props-builder) - (f props-builder) - nil)) + (.debugFillProperties ^super this props-builder) + (f props-builder) + nil)) (defn ^:macro-support expand-spy [env expr forms] `(SpyWidget ~(:key env) (-widget-cont ~(assoc env :key nil) ~@forms) @@ -777,7 +876,12 @@ :padding (expand-padding env v forms) :color (expand-color env v forms) (:height :width) (expand-height-width env (list* k v forms)) - :when (expand-visible env v forms)))) + :when (expand-visible env v forms) + :animate + (if-some [[binding expr & more] (seq v)] + (let [[opts & more] (collect-options more #{:duration :curve :on-end :lerp})] + (expand-animate &env env binding expr opts (list* :animate more forms))) + `(-widget-cont ~env ~@forms))))) (defmacro -widget-cont "PRIVATE DONT USE" diff --git a/samples/animated_string/deps.edn b/samples/animated_string/deps.edn new file mode 100644 index 00000000..50cae6cd --- /dev/null +++ b/samples/animated_string/deps.edn @@ -0,0 +1,6 @@ +{:paths ["src"] ; where your cljd files are + :deps {org.clojure/clojure {:mvn/version "1.10.1"} + tensegritics/clojuredart {:local/root "../../"}} + :aliases {:cljd {:main-opts ["-m" "cljd.build"]}} + :cljd/opts {:main sample.animated-string + :kind :flutter}} diff --git a/samples/animated_string/src/sample/animated_string.cljd b/samples/animated_string/src/sample/animated_string.cljd new file mode 100644 index 00000000..90d88cc8 --- /dev/null +++ b/samples/animated_string/src/sample/animated_string.cljd @@ -0,0 +1,41 @@ +(ns sample.animated-string + (:require + ["package:flutter/material.dart" :as m] + [cljd.flutter :as f])) + +(def animated-text + (f/widget + (m/Scaffold + .appBar (m/AppBar .title (m/Text ":animate demo"))) + .body + :watch [text (atom "") :as *text] + m/Column + .children + [(m/Text "Enter text below and press enter:") + (m/TextField + .onSubmitted (fn [x] (reset! *text x) nil)) + (m/Text "See text being slowly animated (interpolated) between actual value.") + (m/Text "(Try changing text again while the animation is running.)") + (f/widget + :animate [s text + :duration (Duration .seconds 3) + :lerp (fn [from to] + (if (= from to) + (constantly to) + (let [common (count (take-while true? (map = from to))) + nfrom (- (count from) common) + nto (- (count to) common) + n (+ nfrom nto) + threshold (/ nfrom (double n))] + (fn [t] + (if (< t threshold) + (subs from 0 (+ common (int (* n (- threshold t))))) + (subs to 0 (+ common (int (* n (- t threshold))))))))))] + (m/Text (str s "◼️") + .style (m/TextStyle .fontSize 36 .fontFamily "courier"))) + (m/Text "Animated length but with a shorter duration") + (f/widget + :animate [n (count text)] + (m/Text (str "n=" n)))])) + +(defn main [] (f/run m/MaterialApp .home animated-text))