changeset 179:e73174356504

Use defrecord for Views for easier printing and introspection.
author Mikhail Kryshen <mikhail@kryshen.net>
date Sun, 20 Sep 2015 02:00:53 +0300
parents 8ba774291bac
children dcb941086063
files src/indyvon/core.clj src/indyvon/demo.clj src/indyvon/viewport.clj src/indyvon/views.clj
diffstat 4 files changed, 306 insertions(+), 248 deletions(-) [+]
line wrap: on
line diff
--- a/src/indyvon/core.clj	Fri Sep 18 17:59:50 2015 +0300
+++ b/src/indyvon/core.clj	Sun Sep 20 02:00:53 2015 +0300
@@ -643,17 +643,20 @@
                                      *event-dispatcher* handle handlers)]
     (apply f args)))
 
+(defmacro handlers [& specs]
+  (reduce (fn [m spec]
+            (assoc m (first spec)
+                   `(fn [~(second spec)]
+                      ~@(nnext spec)))) {}
+                      specs))
+
 (defmacro with-handlers
   "specs => (:event-id name & handler-body)*
 
   Execute form with the specified event handlers."
   [handle form & specs]
   `(with-handlers* ~handle
-     ~(reduce (fn [m spec]
-                (assoc m (first spec)
-                       `(fn [~(second spec)]
-                          ~@(nnext spec)))) {}
-                          specs)
+     (handlers ~@specs)
      (fn [] ~form)))
 
 (defn picked? [handle]
--- a/src/indyvon/demo.clj	Fri Sep 18 17:59:50 2015 +0300
+++ b/src/indyvon/demo.clj	Sun Sep 20 02:00:53 2015 +0300
@@ -201,14 +201,15 @@
 
 ;; Main scene
 (def scene
-  (fps-view
-   (decorate-view vp [_]
-     (draw! vp)
-     (draw-aligned!
-      (label (str "Drag mouse to pan," \newline
-                  "use mouse wheel to zoom."))
-      :left :bottom 5 (- *height* 5))
-     (draw! vp-miniature (- *width* 105) 5))))
+  (->> vp
+       (decorator (fn [_ content]
+                    (draw! content)
+                    (draw-aligned!
+                     (label (str "Drag mouse to pan," \newline
+                                 "use mouse wheel to zoom."))
+                     :left :bottom 5 (- *height* 5))
+                    (draw! vp-miniature (- *width* 105) 5)))
+       fps-view))
 
 (defn show-frame [view]
   (doto (make-jframe "Test" view)
--- a/src/indyvon/viewport.clj	Fri Sep 18 17:59:50 2015 +0300
+++ b/src/indyvon/viewport.clj	Sun Sep 20 02:00:53 2015 +0300
@@ -174,65 +174,67 @@
   (min (/ max-width width)
        (/ max-height height)))
 
+(def ^:dynamic *miniature-thread-priority* 2)
+
 (defn miniature
   "Creates a view that asynchronously renders the content view scaled to
   the specified size."
   [mw mh content]
-  (async-view
-   mw mh *miniature-thread-priority*
-   (reify
-    View
-    (render! [this]
-      (let [geom (geometry content)
-            cw (width geom)
-            ch (height geom)
-            s (scaling cw ch mw mh)]
-        (.scale *graphics* s s)
-        (draw! content
-               (align-x :center cw (/ mw s))
-               (align-y :center ch (/ mh s))
-               cw ch)))
-    (geometry [_]
-      (->Size mw mh)))))
+  (->> content
+       (decorator (fn [_ content]
+                    (let [geom (geometry content)
+                          cw (width geom)
+                          ch (height geom)
+                          s (scaling cw ch mw mh)]
+                      (.scale *graphics* s s)
+                      (draw! content
+                             (align-x :center cw (/ mw s))
+                             (align-y :center ch (/ mh s))
+                             cw ch)))
+                  (fn [_ _]
+                    (->Size mw mh)))  
+       (async-view mw mh *miniature-thread-priority*)))
 
 (defn viewport-miniature
   "Creates miniature view of the viewport's contents."
   [m-width m-height viewport]
-  (let [miniature (miniature m-width m-height (:content viewport))]
-    (decorate-view miniature [l]
-      (repaint-on-update! viewport)
-      (let [geom (geometry (:content viewport))
-            s (scaling (width geom) (height geom) m-width m-height)
-            vp-state @(:state viewport)
-            {:keys [transform last-width last-height]} @(:state viewport)
-            ox (align-x :center (width geom) (/ m-width s))
-            oy (align-y :center (height geom) (/ m-height s))
-            inverse (.createInverse ^AffineTransform transform)
-            transform (doto (AffineTransform.)
-                        (.scale s s)
-                        (.translate ox oy)
-                        (.concatenate inverse))
-            move-vp (fn [state x y]
-                      (let [x (- (/ x s) ox)
-                            y (- (/ y s) oy)
-                            tr (:transform state)
-                            [x y] (transform-point tr x y)
-                            x (- x (/ (:last-width state) 2))
-                            y (- y (/ (:last-height state) 2))]
-                        (assoc state
-                          :transform (pre-translate tr (- x) (- y)))))
-            move-vp! (fn [x y]
-                       (swap! (:state viewport) move-vp x y)
-                       (notify! viewport))]
-        (with-color :alt-back-color
-          (.fillRect *graphics* 0 0 *width* *height*))
-        (with-transform transform
-          (with-color :back-color
-            (.fillRect *graphics* 0 0 last-width last-height)))
-        (with-handlers l
-          (draw! miniature)
-          [:mouse-pressed e (move-vp! (:x e) (:y e))]
-          [:mouse-dragged e (move-vp! (:x e) (:y e))])
-        (with-transform transform
-          (with-color :border-color
-            (.drawRect *graphics* 0 0 last-width last-height)))))))
+  (->> (:content viewport)
+       (miniature m-width m-height)
+       (decorator
+        (fn [v m]
+          (repaint-on-update! viewport)
+          (let [geom (geometry (:content viewport))
+                s (scaling (width geom) (height geom) m-width m-height)
+                vp-state @(:state viewport)
+                {:keys [transform last-width last-height]} @(:state viewport)
+                ox (align-x :center (width geom) (/ m-width s))
+                oy (align-y :center (height geom) (/ m-height s))
+                inverse (.createInverse ^AffineTransform transform)
+                transform (doto (AffineTransform.)
+                            (.scale s s)
+                            (.translate ox oy)
+                            (.concatenate inverse))
+                move-vp (fn [state x y]
+                          (let [x (- (/ x s) ox)
+                                y (- (/ y s) oy)
+                                tr (:transform state)
+                                [x y] (transform-point tr x y)
+                                x (- x (/ (:last-width state) 2))
+                                y (- y (/ (:last-height state) 2))]
+                            (assoc state
+                                   :transform (pre-translate tr (- x) (- y)))))
+                move-vp! (fn [x y]
+                           (swap! (:state viewport) move-vp x y)
+                           (notify! viewport))]
+            (with-color :alt-back-color
+              (.fillRect *graphics* 0 0 *width* *height*))
+            (with-transform transform
+              (with-color :back-color
+                (.fillRect *graphics* 0 0 last-width last-height)))
+            (with-handlers v
+              (draw! m)
+              [:mouse-pressed e (move-vp! (:x e) (:y e))]
+              [:mouse-dragged e (move-vp! (:x e) (:y e))])
+            (with-transform transform
+              (with-color :border-color
+                (.drawRect *graphics* 0 0 last-width last-height))))))))
--- a/src/indyvon/views.clj	Fri Sep 18 17:59:50 2015 +0300
+++ b/src/indyvon/views.clj	Sun Sep 20 02:00:53 2015 +0300
@@ -30,14 +30,26 @@
    java.util.concurrent.TimeUnit
    (com.google.common.cache Cache CacheBuilder CacheLoader)))
 
-(defmacro decorate-view
-  "Decorate the view replacing render! implementation."
-  [view & render-tail]
-  `(let [view# ~view]
-     (reify
-       View
-       (render! ~@render-tail)
-       (geometry [t#] (geometry view#)))))
+(defrecord Decorator [render-fn geometry-fn content]
+  View
+  (render! [decorator]
+    (render-fn decorator content))
+  (geometry [decorator]
+    (geometry-fn decorator content)))
+
+(defn- decorator-content-geometry [_ content]
+  (geometry content))
+
+(defn decorator
+  "Creates a decorator view. Functions render-fn and geometry-fn will
+  be called with the decorator and content as arguments. Use this in
+  preference to (reify View) as it creates a defrecord instance
+  instead of an opaque reify object. Prefer to pass render-fn and
+  geometry-fn as Vars to allow the view to be printed and read back."
+  ([render-fn content]
+   (decorator render-fn #'decorator-content-geometry content))
+  ([render-fn geometry-fn content]
+   (->Decorator render-fn geometry-fn content)))
 
 (defrecord Empty []
   View
@@ -47,23 +59,39 @@
 
 (def empty-view (->Empty))
 
+(defrecord Padding [top left bottom right content]
+  View
+  (render! [l]
+    (draw! content
+           left top
+           (- *width* left right)
+           (- *height* top bottom)
+           false))
+  (geometry [l]
+    (->NestedGeometry (geometry content) top left bottom right)))
+
 (defn padding
   "Adds padding to the content view."
   ([distance content]
-     (padding distance distance distance distance content))
+   (padding distance distance distance distance content))
   ([top left bottom right content]
-     (if (== 0 top left bottom right)
-       content
-       (reify
-        View
-        (render! [l]
-           (draw! content
-                  left top
-                  (- *width* left right)
-                  (- *height* top bottom)
-                  false))
-        (geometry [l]
-          (->NestedGeometry (geometry content) top left bottom right))))))
+   (if (== 0 top left bottom right)
+     content
+     (->Padding top left bottom right content))))
+
+(defrecord Border [^double thickness content]
+  View
+  (render! [_]
+    (render! content)
+    (with-color :border-color
+      (let [w (double *width*)
+            h (double *height*)
+            t thickness
+            outer (Area. (Rectangle2D$Double. 0.0 0.0 w h))
+            inner (Area. (Rectangle2D$Double. t t (- w t t) (- h t t)))]
+        (.subtract outer inner)
+        (.fill *graphics* outer))))
+  (geometry [_] (geometry content)))
 
 (defn border
   "Adds a border to the content view."
@@ -72,52 +100,45 @@
   ([thickness content]
      (border thickness 0 content))
   ([thickness gap content]
-     (let [view (padding (+ thickness gap) content)
-           t (double thickness)]
-       (decorate-view view [_]
-         (render! view)
-         (with-color :border-color
-           (let [w (double *width*)
-                 h (double *height*)
-                 outer (Area. (Rectangle2D$Double. 0.0 0.0 w h))
-                 inner (Area. (Rectangle2D$Double. t t (- w t t) (- h t t)))]
-             (.subtract outer inner)
-             (.fill *graphics* outer)))))))
+   (->Border thickness (padding (+ thickness gap) content))))
 
 ;; TODO: opacity and blur.
+(defrecord Shadow [^double x ^double y content]
+  View
+  (render! [_]
+    (let [w (- *width* (Math/abs x))
+          h (- *height* (Math/abs y))]
+      (with-color :shadow-color
+        (.fillRect *graphics* (max x 0.0) (max y 0.0) w h))
+      (draw! content (max (- x) 0.0) (max (- y) 0.0) w h)))
+  (geometry [_]
+    (->NestedGeometry (geometry content)
+                      (max (- y) 0.0) ;; top
+                      (max (- x) 0.0) ;; left
+                      (max y 0.0)     ;; bottom
+                      (max x 0.0))))  ;; right
 (defn shadow
   "Adds a shadow to the content view."
   ([content]
-     (shadow 1 1 content))
+   (shadow 1 1 content))
   ([x-offset y-offset content]
-     (let [x (if (neg? x-offset) (- x-offset) 0)
-           y (if (neg? y-offset) (- y-offset) 0)
-           abs-x (if (neg? x-offset) (- x-offset) x-offset)
-           abs-y (if (neg? y-offset) (- y-offset) y-offset)
-           shadow-x (+ x-offset x)
-           shadow-y (+ y-offset y)]
-       (reify
-        View
-        (render! [_]
-          (let [w (- *width* abs-x)
-                h (- *height* abs-y)]
-            (with-color :shadow-color
-              (.fillRect *graphics* shadow-x shadow-y w h))
-            (draw! content x y w h)))
-        (geometry [_]
-          (->NestedGeometry (geometry content)
-                            y x shadow-y shadow-x))))))
+   (->Shadow x-offset y-offset content)))
+
+(defrecord Panel [back-color content]
+  View
+  (render! [_]
+    (with-color back-color
+      (.fill *graphics* (Rectangle2D$Double. 0.0 0.0 *width* *height*)))
+    (render! content))
+  (geometry [_] (geometry content)))
 
 (defn panel
   "An opaque view using theme's alt-back-color or a custom background
   color."
   ([content]
-     (panel :alt-back-color content))
+   (panel :alt-back-color content))
   ([back-color content]
-     (decorate-view content [_]
-       (with-color back-color
-         (.fill *graphics* (Rectangle2D$Double. 0.0 0.0 *width* *height*)))
-       (render! content))))
+   (->Panel back-color content)))
 
 (defrecord Box [proportional? translate-geometry contents]
   View
@@ -242,40 +263,53 @@
                (.getLeading tl)))
           0 layouts))
 
+(defrecord Label [h-align v-align lines]
+  View
+  (render! [view]
+    (let [w *width*
+          h *height*
+          font (.getFont *graphics*)
+          layouts (layout-text lines font (font-context))
+          y (align-y v-align (text-height layouts) h)]
+      (loop [layouts layouts, y y]
+        (when-first [^TextLayout layout layouts]
+          (let [ascent (.getAscent layout)
+                lh (+ ascent (.getDescent layout) (.getLeading layout))
+                x (align-x h-align (.getAdvance layout) w)]
+            (.draw layout *graphics* x (+ y ascent))
+            (recur (next layouts) (+ y lh)))))))
+  (geometry [view]
+    (let [layouts (layout-text lines (theme-get :font) (font-context))
+          w (text-width layouts)
+          h (text-height layouts)]
+      (->Size w h))))
+
 (defn label
   "Creates a view to display multiline text."
   ([text]
-     (label :left :top text))
+   (label :left :top text))
   ([h-align v-align text]
-     (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" (str text))]
-       (reify View
-        (render! [view]
-          (let [w *width*
-                h *height*
-                font (.getFont *graphics*)
-                layouts (layout-text lines font (font-context))
-                y (align-y v-align (text-height layouts) h)]
-            (loop [layouts layouts, y y]
-              (when-first [^TextLayout layout layouts]
-                (let [ascent (.getAscent layout)
-                      lh (+ ascent (.getDescent layout) (.getLeading layout))
-                      x (align-x h-align (.getAdvance layout) w)]
-                  (.draw layout *graphics* x (+ y ascent))
-                  (recur (next layouts) (+ y lh)))))))
-        (geometry [view]
-          (let [layouts (layout-text lines (theme-get :font) (font-context))
-                w (text-width layouts)
-                h (text-height layouts)]
-            (->Size w h)))))))
+   (->Label h-align
+            v-align
+            (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" (str text)))))
 
-(defn- ^ImageObserver image-observer [view]
-  (reify
-   ImageObserver
-   (imageUpdate [this img infoflags x y width height]
-     (notify! view)
-     (zero? (bit-and infoflags
-                     (bit-or ImageObserver/ALLBITS
-                             ImageObserver/ABORT))))))
+(defrecord ImageView [^Image image]
+  View
+  (render! [view]
+    (repaint-on-update! view)
+    (.drawImage *graphics* image 0 0 view))
+  (geometry [view]
+    (let [width (.getWidth image view)
+          height (.getHeight image view)
+          width (if (pos? width) width 1)
+          height (if (pos? height) height 1)]
+      (->Size width height)))
+  ImageObserver
+  (imageUpdate [view img infoflags x y width height]
+    (notify! view)
+    (zero? (bit-and infoflags
+                    (bit-or ImageObserver/ALLBITS
+                            ImageObserver/ABORT)))))
 
 (defn image-view
   [image-or-uri]
@@ -284,93 +318,97 @@
                        (.getImage (Toolkit/getDefaultToolkit)
                                   ^java.net.URL image-or-uri))]
     (.prepareImage (Toolkit/getDefaultToolkit) image -1 -1 nil)
-    (reify
-     View
-     (render! [view]
-       (repaint-on-update! view)
-       (.drawImage *graphics* image 0 0 (image-observer view)))
-     (geometry [view]
-       (let [observer (image-observer view)
-             width (.getWidth image observer)
-             height (.getHeight image observer)
-             width (if (pos? width) width 1)
-             height (if (pos? height) height 1)]
-         (->Size width height))))))
+    (->ImageView image)))
 
-(def ^:dynamic *miniature-thread-priority* 2)
+(defrecord RefView [reference view-delay-atom]
+  View
+  (render! [v]
+    (repaint-on-update! v)
+    (when-let [view @@view-delay-atom]
+      (render! view)))
+  (geometry [v]
+    (if-let [view @@view-delay-atom]
+      (geometry view)
+      (->Size 1 1))))
 
 (defn ref-view
-  ([view-ref]
-   (ref-view view-ref identity))
-  ([view-ref view-fn]
-   (let [v (reify
-             View
-             (render! [v]
-               (repaint-on-update! v)
-               (when-let [view (view-fn @view-ref)]
-                 (render! view)))
-             (geometry [_]
-               (if-let [view (view-fn @view-ref)]
-                 (geometry view)
-                 (->Size 1 1))))]
-     (add-watch view-ref v (fn [_ _ _ _] (notify! v)))
+  ([reference]
+   (ref-view reference #'identity))
+  ([reference view-fn]
+   ;; The reference may update multiple times before repaint happens,
+   ;; delay is used to avoid unnecessary invocations of view-fn.
+   (let [view-delay-atom (atom (delay (view-fn @reference)))
+         v (->RefView reference view-delay-atom)]
+     (add-watch reference v (fn [_ _ _ n]
+                              (reset! view-delay-atom (delay (view-fn n)))
+                              (notify! v)))
      v)))
 
 ;;
 ;; View context decorators
 ;;
 
-(defmacro add-handlers [view & handlers]
+(defrecord Active [handlers content]
+  View
+  (render! [v]
+    (with-handlers* v handlers render! content))
+  (geometry [_]
+    (geometry content)))
+
+(defmacro add-handlers [view & event-handlers]
   "Adds event handling to the view."
-  `(let [view# ~view]
-     (decorate-view view# [t#]
-       (with-handlers t#
-         (render! view#)
-         ~@handlers))))
+  `(->Active (handlers ~@event-handlers) ~view))
+
+(defrecord Themed [theme view]
+  View
+  (render! [_]
+    (with-theme theme
+      (apply-theme)
+      (render! view)))
+  (geometry [_]
+    (with-theme* theme geometry view)))
 
 (defn themed [theme view]
-  (reify
-    View
-    (render! [_]
-      (with-theme theme
-        (apply-theme)
-        (render! view)))
-    (geometry [_]
-      (with-theme* theme geometry view))))
+  (->Themed theme view))
+
+(defrecord Hinted [hints view]
+  View
+  (render! [_]
+    (with-hints* hints render! view))
+  (geometry [_]
+    (if (bound? #'*graphics*)
+      (with-hints* hints geometry view)
+      (geometry view))))
 
 (defn hinted [hints view]
-  (reify
-    View
-    (render! [_]
-      (with-hints* hints render! view))
-    (geometry [_]
-      (if (bound? #'*graphics*)
-        (with-hints* hints geometry view)
-        (geometry view)))))
+  (->Hinted hints view))
 
 (defn- transform-bounds
   ^Rectangle2D [^AffineTransform tr ^double w ^double h]
   (.getBounds2D
    (.createTransformedShape tr (Rectangle2D$Double. 0 0 w h))))
 
+(defrecord Transform [transform view]
+  View
+  (render! [v]
+    (let [g (geometry view)
+          w (double (width g))
+          h (double (height g))
+          ^Rectangle2D bounds (transform-bounds transform w h)
+          g *graphics*]
+      (.translate g (- (.getX bounds)) (- (.getY bounds)))
+      (.transform g transform)
+      ;; TODO: scale w and h to fit *width* and *height*.
+      (draw! view 0 0 w h)))
+  (geometry [_]
+    (let [g (geometry view)
+          w (double (width g))
+          h (double (height g))
+          ^Rectangle2D bounds (transform-bounds transform w h)]
+      (->Size (.getWidth bounds) (.getHeight bounds)))))
+
 (defn transform [tr view]
-  (reify View
-    (render! [v]
-      (let [g (geometry view)
-            w (double (width g))
-            h (double (height g))
-            ^Rectangle2D bounds (transform-bounds tr w h)
-            g *graphics*]
-        (.translate g (- (.getX bounds)) (- (.getY bounds)))
-        (.transform g tr)
-        ;; TODO: scale w and h to fit *width* and *height*.
-        (draw! view 0 0 w h)))
-    (geometry [_]
-      (let [g (geometry view)
-            w (double (width g))
-            h (double (height g))
-            ^Rectangle2D bounds (transform-bounds tr w h)]
-        (->Size (.getWidth bounds) (.getHeight bounds))))))
+  (->Transform tr view))
 
 (defn rotate [^double degrees view]
   (transform
@@ -383,44 +421,51 @@
 
 (def ^:dynamic *interval*)
 
+(defrecord IntervalView [last-time content]
+  View
+  (render! [_]
+    (compare-and-set! last-time nil *time*)
+    (let [lt @last-time]
+      (binding [*interval* (if (compare-and-set! last-time lt *time*)
+                             (- *time* lt)
+                             0)] ; already measured on parallel thread
+        (render! content))))
+  (geometry [_]
+    (geometry content)))
+
 (defn interval-view
-  "Creates a view that measures time between repaints ant draws it's
+  "Creates a view that measures time between repaints and draws it's
   content with the *interval* var bound to the measured time."
   [content]
-  (let [last-time (atom nil)]
-    (decorate-view content [_]
-      (compare-and-set! last-time nil *time*)
-      (let [lt @last-time]
-        (binding [*interval* (if (compare-and-set! last-time lt *time*)
-                               (- *time* lt)
-                               0)] ; already measured on parallel thread
-          (render! content))))))
+  (->IntervalView (atom nil) content))
 
 (defn- fps-label [text]
   (padding 5 (label :right :bottom text)))
 
+(defrecord FPSView [update-interval frames prev-time display content]
+  View
+  (render! [_]
+    (draw! content)
+    (draw!
+     (dosync
+      (alter frames inc)
+      (if @prev-time
+        (let [elapsed (- *time* @prev-time)]
+          (when (> elapsed update-interval)
+            (let [fps (/ @frames (/ elapsed 1E9))]
+              (ref-set display (fps-label (format "%.1f" fps)))
+              (ref-set frames 0)
+              (ref-set prev-time *time*))))
+        (ref-set prev-time *time*))
+      @display)))
+  (geometry [_] (geometry content)))
+
 (defn fps-view
   "Creates a view that draws content and displays the
   frames per second rate."
   [content]
-  (let [update-interval 2E8 ; 0.2 s in nanoseconds
-        frames (ref 0)
-        prev-time (ref nil)
-        display (ref (fps-label "fps n/a"))]
-    (decorate-view content [_]
-      (draw! content)
-      (draw!
-       (dosync
-        (alter frames inc)
-        (if @prev-time
-          (let [elapsed (- *time* @prev-time)]
-            (when (> elapsed update-interval)
-              (let [fps (/ @frames (/ elapsed 1E9))]
-                (ref-set display (fps-label (format "%.1f" fps)))
-                (ref-set frames 0)
-                (ref-set prev-time *time*))))
-          (ref-set prev-time *time*))
-        @display)))))
+  ;; 2E8 ns = 0.2 s.
+  (->FPSView 2E8 (ref 0) (ref nil) (ref (fps-label "fps n/a")) content))
 
 ;;
 ;; Overlays
@@ -457,7 +502,7 @@
     (draw! view x y w h)))
 
 (defn overlay!
-  "Draws view in the overlay context above the other views."
+  "Draws view in the overlay context above the current layer."
   ([view]
      (overlay* draw-relative! view (.getTransform *graphics*) 0 0))
   ([view x y]
@@ -488,9 +533,16 @@
 (defmacro with-overlays [recursive? & body]
   `(with-overlays* ~recursive? (fn [] ~@body)))
 
+(defrecord Layered [recursive? content]
+  View
+  (render! [_]
+    (with-overlays* recursive? render! content))
+  (geometry [_ ]
+    (geometry content)))
+
 (defn layered
+  "Allows content view to display pop-ups on top of itself."
   ([content]
-     (layered true content))
+   (layered true content))
   ([recursive? content]
-     (decorate-view content [_]
-       (with-overlays* recursive? render! content))))
+   (->Layered recursive? content)))