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 diff
     1.1 --- a/src/indyvon/core.clj	Fri Sep 18 17:59:50 2015 +0300
     1.2 +++ b/src/indyvon/core.clj	Sun Sep 20 02:00:53 2015 +0300
     1.3 @@ -643,17 +643,20 @@
     1.4                                       *event-dispatcher* handle handlers)]
     1.5      (apply f args)))
     1.6  
     1.7 +(defmacro handlers [& specs]
     1.8 +  (reduce (fn [m spec]
     1.9 +            (assoc m (first spec)
    1.10 +                   `(fn [~(second spec)]
    1.11 +                      ~@(nnext spec)))) {}
    1.12 +                      specs))
    1.13 +
    1.14  (defmacro with-handlers
    1.15    "specs => (:event-id name & handler-body)*
    1.16  
    1.17    Execute form with the specified event handlers."
    1.18    [handle form & specs]
    1.19    `(with-handlers* ~handle
    1.20 -     ~(reduce (fn [m spec]
    1.21 -                (assoc m (first spec)
    1.22 -                       `(fn [~(second spec)]
    1.23 -                          ~@(nnext spec)))) {}
    1.24 -                          specs)
    1.25 +     (handlers ~@specs)
    1.26       (fn [] ~form)))
    1.27  
    1.28  (defn picked? [handle]
     2.1 --- a/src/indyvon/demo.clj	Fri Sep 18 17:59:50 2015 +0300
     2.2 +++ b/src/indyvon/demo.clj	Sun Sep 20 02:00:53 2015 +0300
     2.3 @@ -201,14 +201,15 @@
     2.4  
     2.5  ;; Main scene
     2.6  (def scene
     2.7 -  (fps-view
     2.8 -   (decorate-view vp [_]
     2.9 -     (draw! vp)
    2.10 -     (draw-aligned!
    2.11 -      (label (str "Drag mouse to pan," \newline
    2.12 -                  "use mouse wheel to zoom."))
    2.13 -      :left :bottom 5 (- *height* 5))
    2.14 -     (draw! vp-miniature (- *width* 105) 5))))
    2.15 +  (->> vp
    2.16 +       (decorator (fn [_ content]
    2.17 +                    (draw! content)
    2.18 +                    (draw-aligned!
    2.19 +                     (label (str "Drag mouse to pan," \newline
    2.20 +                                 "use mouse wheel to zoom."))
    2.21 +                     :left :bottom 5 (- *height* 5))
    2.22 +                    (draw! vp-miniature (- *width* 105) 5)))
    2.23 +       fps-view))
    2.24  
    2.25  (defn show-frame [view]
    2.26    (doto (make-jframe "Test" view)
     3.1 --- a/src/indyvon/viewport.clj	Fri Sep 18 17:59:50 2015 +0300
     3.2 +++ b/src/indyvon/viewport.clj	Sun Sep 20 02:00:53 2015 +0300
     3.3 @@ -174,65 +174,67 @@
     3.4    (min (/ max-width width)
     3.5         (/ max-height height)))
     3.6  
     3.7 +(def ^:dynamic *miniature-thread-priority* 2)
     3.8 +
     3.9  (defn miniature
    3.10    "Creates a view that asynchronously renders the content view scaled to
    3.11    the specified size."
    3.12    [mw mh content]
    3.13 -  (async-view
    3.14 -   mw mh *miniature-thread-priority*
    3.15 -   (reify
    3.16 -    View
    3.17 -    (render! [this]
    3.18 -      (let [geom (geometry content)
    3.19 -            cw (width geom)
    3.20 -            ch (height geom)
    3.21 -            s (scaling cw ch mw mh)]
    3.22 -        (.scale *graphics* s s)
    3.23 -        (draw! content
    3.24 -               (align-x :center cw (/ mw s))
    3.25 -               (align-y :center ch (/ mh s))
    3.26 -               cw ch)))
    3.27 -    (geometry [_]
    3.28 -      (->Size mw mh)))))
    3.29 +  (->> content
    3.30 +       (decorator (fn [_ content]
    3.31 +                    (let [geom (geometry content)
    3.32 +                          cw (width geom)
    3.33 +                          ch (height geom)
    3.34 +                          s (scaling cw ch mw mh)]
    3.35 +                      (.scale *graphics* s s)
    3.36 +                      (draw! content
    3.37 +                             (align-x :center cw (/ mw s))
    3.38 +                             (align-y :center ch (/ mh s))
    3.39 +                             cw ch)))
    3.40 +                  (fn [_ _]
    3.41 +                    (->Size mw mh)))  
    3.42 +       (async-view mw mh *miniature-thread-priority*)))
    3.43  
    3.44  (defn viewport-miniature
    3.45    "Creates miniature view of the viewport's contents."
    3.46    [m-width m-height viewport]
    3.47 -  (let [miniature (miniature m-width m-height (:content viewport))]
    3.48 -    (decorate-view miniature [l]
    3.49 -      (repaint-on-update! viewport)
    3.50 -      (let [geom (geometry (:content viewport))
    3.51 -            s (scaling (width geom) (height geom) m-width m-height)
    3.52 -            vp-state @(:state viewport)
    3.53 -            {:keys [transform last-width last-height]} @(:state viewport)
    3.54 -            ox (align-x :center (width geom) (/ m-width s))
    3.55 -            oy (align-y :center (height geom) (/ m-height s))
    3.56 -            inverse (.createInverse ^AffineTransform transform)
    3.57 -            transform (doto (AffineTransform.)
    3.58 -                        (.scale s s)
    3.59 -                        (.translate ox oy)
    3.60 -                        (.concatenate inverse))
    3.61 -            move-vp (fn [state x y]
    3.62 -                      (let [x (- (/ x s) ox)
    3.63 -                            y (- (/ y s) oy)
    3.64 -                            tr (:transform state)
    3.65 -                            [x y] (transform-point tr x y)
    3.66 -                            x (- x (/ (:last-width state) 2))
    3.67 -                            y (- y (/ (:last-height state) 2))]
    3.68 -                        (assoc state
    3.69 -                          :transform (pre-translate tr (- x) (- y)))))
    3.70 -            move-vp! (fn [x y]
    3.71 -                       (swap! (:state viewport) move-vp x y)
    3.72 -                       (notify! viewport))]
    3.73 -        (with-color :alt-back-color
    3.74 -          (.fillRect *graphics* 0 0 *width* *height*))
    3.75 -        (with-transform transform
    3.76 -          (with-color :back-color
    3.77 -            (.fillRect *graphics* 0 0 last-width last-height)))
    3.78 -        (with-handlers l
    3.79 -          (draw! miniature)
    3.80 -          [:mouse-pressed e (move-vp! (:x e) (:y e))]
    3.81 -          [:mouse-dragged e (move-vp! (:x e) (:y e))])
    3.82 -        (with-transform transform
    3.83 -          (with-color :border-color
    3.84 -            (.drawRect *graphics* 0 0 last-width last-height)))))))
    3.85 +  (->> (:content viewport)
    3.86 +       (miniature m-width m-height)
    3.87 +       (decorator
    3.88 +        (fn [v m]
    3.89 +          (repaint-on-update! viewport)
    3.90 +          (let [geom (geometry (:content viewport))
    3.91 +                s (scaling (width geom) (height geom) m-width m-height)
    3.92 +                vp-state @(:state viewport)
    3.93 +                {:keys [transform last-width last-height]} @(:state viewport)
    3.94 +                ox (align-x :center (width geom) (/ m-width s))
    3.95 +                oy (align-y :center (height geom) (/ m-height s))
    3.96 +                inverse (.createInverse ^AffineTransform transform)
    3.97 +                transform (doto (AffineTransform.)
    3.98 +                            (.scale s s)
    3.99 +                            (.translate ox oy)
   3.100 +                            (.concatenate inverse))
   3.101 +                move-vp (fn [state x y]
   3.102 +                          (let [x (- (/ x s) ox)
   3.103 +                                y (- (/ y s) oy)
   3.104 +                                tr (:transform state)
   3.105 +                                [x y] (transform-point tr x y)
   3.106 +                                x (- x (/ (:last-width state) 2))
   3.107 +                                y (- y (/ (:last-height state) 2))]
   3.108 +                            (assoc state
   3.109 +                                   :transform (pre-translate tr (- x) (- y)))))
   3.110 +                move-vp! (fn [x y]
   3.111 +                           (swap! (:state viewport) move-vp x y)
   3.112 +                           (notify! viewport))]
   3.113 +            (with-color :alt-back-color
   3.114 +              (.fillRect *graphics* 0 0 *width* *height*))
   3.115 +            (with-transform transform
   3.116 +              (with-color :back-color
   3.117 +                (.fillRect *graphics* 0 0 last-width last-height)))
   3.118 +            (with-handlers v
   3.119 +              (draw! m)
   3.120 +              [:mouse-pressed e (move-vp! (:x e) (:y e))]
   3.121 +              [:mouse-dragged e (move-vp! (:x e) (:y e))])
   3.122 +            (with-transform transform
   3.123 +              (with-color :border-color
   3.124 +                (.drawRect *graphics* 0 0 last-width last-height))))))))
     4.1 --- a/src/indyvon/views.clj	Fri Sep 18 17:59:50 2015 +0300
     4.2 +++ b/src/indyvon/views.clj	Sun Sep 20 02:00:53 2015 +0300
     4.3 @@ -30,14 +30,26 @@
     4.4     java.util.concurrent.TimeUnit
     4.5     (com.google.common.cache Cache CacheBuilder CacheLoader)))
     4.6  
     4.7 -(defmacro decorate-view
     4.8 -  "Decorate the view replacing render! implementation."
     4.9 -  [view & render-tail]
    4.10 -  `(let [view# ~view]
    4.11 -     (reify
    4.12 -       View
    4.13 -       (render! ~@render-tail)
    4.14 -       (geometry [t#] (geometry view#)))))
    4.15 +(defrecord Decorator [render-fn geometry-fn content]
    4.16 +  View
    4.17 +  (render! [decorator]
    4.18 +    (render-fn decorator content))
    4.19 +  (geometry [decorator]
    4.20 +    (geometry-fn decorator content)))
    4.21 +
    4.22 +(defn- decorator-content-geometry [_ content]
    4.23 +  (geometry content))
    4.24 +
    4.25 +(defn decorator
    4.26 +  "Creates a decorator view. Functions render-fn and geometry-fn will
    4.27 +  be called with the decorator and content as arguments. Use this in
    4.28 +  preference to (reify View) as it creates a defrecord instance
    4.29 +  instead of an opaque reify object. Prefer to pass render-fn and
    4.30 +  geometry-fn as Vars to allow the view to be printed and read back."
    4.31 +  ([render-fn content]
    4.32 +   (decorator render-fn #'decorator-content-geometry content))
    4.33 +  ([render-fn geometry-fn content]
    4.34 +   (->Decorator render-fn geometry-fn content)))
    4.35  
    4.36  (defrecord Empty []
    4.37    View
    4.38 @@ -47,23 +59,39 @@
    4.39  
    4.40  (def empty-view (->Empty))
    4.41  
    4.42 +(defrecord Padding [top left bottom right content]
    4.43 +  View
    4.44 +  (render! [l]
    4.45 +    (draw! content
    4.46 +           left top
    4.47 +           (- *width* left right)
    4.48 +           (- *height* top bottom)
    4.49 +           false))
    4.50 +  (geometry [l]
    4.51 +    (->NestedGeometry (geometry content) top left bottom right)))
    4.52 +
    4.53  (defn padding
    4.54    "Adds padding to the content view."
    4.55    ([distance content]
    4.56 -     (padding distance distance distance distance content))
    4.57 +   (padding distance distance distance distance content))
    4.58    ([top left bottom right content]
    4.59 -     (if (== 0 top left bottom right)
    4.60 -       content
    4.61 -       (reify
    4.62 -        View
    4.63 -        (render! [l]
    4.64 -           (draw! content
    4.65 -                  left top
    4.66 -                  (- *width* left right)
    4.67 -                  (- *height* top bottom)
    4.68 -                  false))
    4.69 -        (geometry [l]
    4.70 -          (->NestedGeometry (geometry content) top left bottom right))))))
    4.71 +   (if (== 0 top left bottom right)
    4.72 +     content
    4.73 +     (->Padding top left bottom right content))))
    4.74 +
    4.75 +(defrecord Border [^double thickness content]
    4.76 +  View
    4.77 +  (render! [_]
    4.78 +    (render! content)
    4.79 +    (with-color :border-color
    4.80 +      (let [w (double *width*)
    4.81 +            h (double *height*)
    4.82 +            t thickness
    4.83 +            outer (Area. (Rectangle2D$Double. 0.0 0.0 w h))
    4.84 +            inner (Area. (Rectangle2D$Double. t t (- w t t) (- h t t)))]
    4.85 +        (.subtract outer inner)
    4.86 +        (.fill *graphics* outer))))
    4.87 +  (geometry [_] (geometry content)))
    4.88  
    4.89  (defn border
    4.90    "Adds a border to the content view."
    4.91 @@ -72,52 +100,45 @@
    4.92    ([thickness content]
    4.93       (border thickness 0 content))
    4.94    ([thickness gap content]
    4.95 -     (let [view (padding (+ thickness gap) content)
    4.96 -           t (double thickness)]
    4.97 -       (decorate-view view [_]
    4.98 -         (render! view)
    4.99 -         (with-color :border-color
   4.100 -           (let [w (double *width*)
   4.101 -                 h (double *height*)
   4.102 -                 outer (Area. (Rectangle2D$Double. 0.0 0.0 w h))
   4.103 -                 inner (Area. (Rectangle2D$Double. t t (- w t t) (- h t t)))]
   4.104 -             (.subtract outer inner)
   4.105 -             (.fill *graphics* outer)))))))
   4.106 +   (->Border thickness (padding (+ thickness gap) content))))
   4.107  
   4.108  ;; TODO: opacity and blur.
   4.109 +(defrecord Shadow [^double x ^double y content]
   4.110 +  View
   4.111 +  (render! [_]
   4.112 +    (let [w (- *width* (Math/abs x))
   4.113 +          h (- *height* (Math/abs y))]
   4.114 +      (with-color :shadow-color
   4.115 +        (.fillRect *graphics* (max x 0.0) (max y 0.0) w h))
   4.116 +      (draw! content (max (- x) 0.0) (max (- y) 0.0) w h)))
   4.117 +  (geometry [_]
   4.118 +    (->NestedGeometry (geometry content)
   4.119 +                      (max (- y) 0.0) ;; top
   4.120 +                      (max (- x) 0.0) ;; left
   4.121 +                      (max y 0.0)     ;; bottom
   4.122 +                      (max x 0.0))))  ;; right
   4.123  (defn shadow
   4.124    "Adds a shadow to the content view."
   4.125    ([content]
   4.126 -     (shadow 1 1 content))
   4.127 +   (shadow 1 1 content))
   4.128    ([x-offset y-offset content]
   4.129 -     (let [x (if (neg? x-offset) (- x-offset) 0)
   4.130 -           y (if (neg? y-offset) (- y-offset) 0)
   4.131 -           abs-x (if (neg? x-offset) (- x-offset) x-offset)
   4.132 -           abs-y (if (neg? y-offset) (- y-offset) y-offset)
   4.133 -           shadow-x (+ x-offset x)
   4.134 -           shadow-y (+ y-offset y)]
   4.135 -       (reify
   4.136 -        View
   4.137 -        (render! [_]
   4.138 -          (let [w (- *width* abs-x)
   4.139 -                h (- *height* abs-y)]
   4.140 -            (with-color :shadow-color
   4.141 -              (.fillRect *graphics* shadow-x shadow-y w h))
   4.142 -            (draw! content x y w h)))
   4.143 -        (geometry [_]
   4.144 -          (->NestedGeometry (geometry content)
   4.145 -                            y x shadow-y shadow-x))))))
   4.146 +   (->Shadow x-offset y-offset content)))
   4.147 +
   4.148 +(defrecord Panel [back-color content]
   4.149 +  View
   4.150 +  (render! [_]
   4.151 +    (with-color back-color
   4.152 +      (.fill *graphics* (Rectangle2D$Double. 0.0 0.0 *width* *height*)))
   4.153 +    (render! content))
   4.154 +  (geometry [_] (geometry content)))
   4.155  
   4.156  (defn panel
   4.157    "An opaque view using theme's alt-back-color or a custom background
   4.158    color."
   4.159    ([content]
   4.160 -     (panel :alt-back-color content))
   4.161 +   (panel :alt-back-color content))
   4.162    ([back-color content]
   4.163 -     (decorate-view content [_]
   4.164 -       (with-color back-color
   4.165 -         (.fill *graphics* (Rectangle2D$Double. 0.0 0.0 *width* *height*)))
   4.166 -       (render! content))))
   4.167 +   (->Panel back-color content)))
   4.168  
   4.169  (defrecord Box [proportional? translate-geometry contents]
   4.170    View
   4.171 @@ -242,40 +263,53 @@
   4.172                 (.getLeading tl)))
   4.173            0 layouts))
   4.174  
   4.175 +(defrecord Label [h-align v-align lines]
   4.176 +  View
   4.177 +  (render! [view]
   4.178 +    (let [w *width*
   4.179 +          h *height*
   4.180 +          font (.getFont *graphics*)
   4.181 +          layouts (layout-text lines font (font-context))
   4.182 +          y (align-y v-align (text-height layouts) h)]
   4.183 +      (loop [layouts layouts, y y]
   4.184 +        (when-first [^TextLayout layout layouts]
   4.185 +          (let [ascent (.getAscent layout)
   4.186 +                lh (+ ascent (.getDescent layout) (.getLeading layout))
   4.187 +                x (align-x h-align (.getAdvance layout) w)]
   4.188 +            (.draw layout *graphics* x (+ y ascent))
   4.189 +            (recur (next layouts) (+ y lh)))))))
   4.190 +  (geometry [view]
   4.191 +    (let [layouts (layout-text lines (theme-get :font) (font-context))
   4.192 +          w (text-width layouts)
   4.193 +          h (text-height layouts)]
   4.194 +      (->Size w h))))
   4.195 +
   4.196  (defn label
   4.197    "Creates a view to display multiline text."
   4.198    ([text]
   4.199 -     (label :left :top text))
   4.200 +   (label :left :top text))
   4.201    ([h-align v-align text]
   4.202 -     (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" (str text))]
   4.203 -       (reify View
   4.204 -        (render! [view]
   4.205 -          (let [w *width*
   4.206 -                h *height*
   4.207 -                font (.getFont *graphics*)
   4.208 -                layouts (layout-text lines font (font-context))
   4.209 -                y (align-y v-align (text-height layouts) h)]
   4.210 -            (loop [layouts layouts, y y]
   4.211 -              (when-first [^TextLayout layout layouts]
   4.212 -                (let [ascent (.getAscent layout)
   4.213 -                      lh (+ ascent (.getDescent layout) (.getLeading layout))
   4.214 -                      x (align-x h-align (.getAdvance layout) w)]
   4.215 -                  (.draw layout *graphics* x (+ y ascent))
   4.216 -                  (recur (next layouts) (+ y lh)))))))
   4.217 -        (geometry [view]
   4.218 -          (let [layouts (layout-text lines (theme-get :font) (font-context))
   4.219 -                w (text-width layouts)
   4.220 -                h (text-height layouts)]
   4.221 -            (->Size w h)))))))
   4.222 +   (->Label h-align
   4.223 +            v-align
   4.224 +            (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" (str text)))))
   4.225  
   4.226 -(defn- ^ImageObserver image-observer [view]
   4.227 -  (reify
   4.228 -   ImageObserver
   4.229 -   (imageUpdate [this img infoflags x y width height]
   4.230 -     (notify! view)
   4.231 -     (zero? (bit-and infoflags
   4.232 -                     (bit-or ImageObserver/ALLBITS
   4.233 -                             ImageObserver/ABORT))))))
   4.234 +(defrecord ImageView [^Image image]
   4.235 +  View
   4.236 +  (render! [view]
   4.237 +    (repaint-on-update! view)
   4.238 +    (.drawImage *graphics* image 0 0 view))
   4.239 +  (geometry [view]
   4.240 +    (let [width (.getWidth image view)
   4.241 +          height (.getHeight image view)
   4.242 +          width (if (pos? width) width 1)
   4.243 +          height (if (pos? height) height 1)]
   4.244 +      (->Size width height)))
   4.245 +  ImageObserver
   4.246 +  (imageUpdate [view img infoflags x y width height]
   4.247 +    (notify! view)
   4.248 +    (zero? (bit-and infoflags
   4.249 +                    (bit-or ImageObserver/ALLBITS
   4.250 +                            ImageObserver/ABORT)))))
   4.251  
   4.252  (defn image-view
   4.253    [image-or-uri]
   4.254 @@ -284,93 +318,97 @@
   4.255                         (.getImage (Toolkit/getDefaultToolkit)
   4.256                                    ^java.net.URL image-or-uri))]
   4.257      (.prepareImage (Toolkit/getDefaultToolkit) image -1 -1 nil)
   4.258 -    (reify
   4.259 -     View
   4.260 -     (render! [view]
   4.261 -       (repaint-on-update! view)
   4.262 -       (.drawImage *graphics* image 0 0 (image-observer view)))
   4.263 -     (geometry [view]
   4.264 -       (let [observer (image-observer view)
   4.265 -             width (.getWidth image observer)
   4.266 -             height (.getHeight image observer)
   4.267 -             width (if (pos? width) width 1)
   4.268 -             height (if (pos? height) height 1)]
   4.269 -         (->Size width height))))))
   4.270 +    (->ImageView image)))
   4.271  
   4.272 -(def ^:dynamic *miniature-thread-priority* 2)
   4.273 +(defrecord RefView [reference view-delay-atom]
   4.274 +  View
   4.275 +  (render! [v]
   4.276 +    (repaint-on-update! v)
   4.277 +    (when-let [view @@view-delay-atom]
   4.278 +      (render! view)))
   4.279 +  (geometry [v]
   4.280 +    (if-let [view @@view-delay-atom]
   4.281 +      (geometry view)
   4.282 +      (->Size 1 1))))
   4.283  
   4.284  (defn ref-view
   4.285 -  ([view-ref]
   4.286 -   (ref-view view-ref identity))
   4.287 -  ([view-ref view-fn]
   4.288 -   (let [v (reify
   4.289 -             View
   4.290 -             (render! [v]
   4.291 -               (repaint-on-update! v)
   4.292 -               (when-let [view (view-fn @view-ref)]
   4.293 -                 (render! view)))
   4.294 -             (geometry [_]
   4.295 -               (if-let [view (view-fn @view-ref)]
   4.296 -                 (geometry view)
   4.297 -                 (->Size 1 1))))]
   4.298 -     (add-watch view-ref v (fn [_ _ _ _] (notify! v)))
   4.299 +  ([reference]
   4.300 +   (ref-view reference #'identity))
   4.301 +  ([reference view-fn]
   4.302 +   ;; The reference may update multiple times before repaint happens,
   4.303 +   ;; delay is used to avoid unnecessary invocations of view-fn.
   4.304 +   (let [view-delay-atom (atom (delay (view-fn @reference)))
   4.305 +         v (->RefView reference view-delay-atom)]
   4.306 +     (add-watch reference v (fn [_ _ _ n]
   4.307 +                              (reset! view-delay-atom (delay (view-fn n)))
   4.308 +                              (notify! v)))
   4.309       v)))
   4.310  
   4.311  ;;
   4.312  ;; View context decorators
   4.313  ;;
   4.314  
   4.315 -(defmacro add-handlers [view & handlers]
   4.316 +(defrecord Active [handlers content]
   4.317 +  View
   4.318 +  (render! [v]
   4.319 +    (with-handlers* v handlers render! content))
   4.320 +  (geometry [_]
   4.321 +    (geometry content)))
   4.322 +
   4.323 +(defmacro add-handlers [view & event-handlers]
   4.324    "Adds event handling to the view."
   4.325 -  `(let [view# ~view]
   4.326 -     (decorate-view view# [t#]
   4.327 -       (with-handlers t#
   4.328 -         (render! view#)
   4.329 -         ~@handlers))))
   4.330 +  `(->Active (handlers ~@event-handlers) ~view))
   4.331 +
   4.332 +(defrecord Themed [theme view]
   4.333 +  View
   4.334 +  (render! [_]
   4.335 +    (with-theme theme
   4.336 +      (apply-theme)
   4.337 +      (render! view)))
   4.338 +  (geometry [_]
   4.339 +    (with-theme* theme geometry view)))
   4.340  
   4.341  (defn themed [theme view]
   4.342 -  (reify
   4.343 -    View
   4.344 -    (render! [_]
   4.345 -      (with-theme theme
   4.346 -        (apply-theme)
   4.347 -        (render! view)))
   4.348 -    (geometry [_]
   4.349 -      (with-theme* theme geometry view))))
   4.350 +  (->Themed theme view))
   4.351 +
   4.352 +(defrecord Hinted [hints view]
   4.353 +  View
   4.354 +  (render! [_]
   4.355 +    (with-hints* hints render! view))
   4.356 +  (geometry [_]
   4.357 +    (if (bound? #'*graphics*)
   4.358 +      (with-hints* hints geometry view)
   4.359 +      (geometry view))))
   4.360  
   4.361  (defn hinted [hints view]
   4.362 -  (reify
   4.363 -    View
   4.364 -    (render! [_]
   4.365 -      (with-hints* hints render! view))
   4.366 -    (geometry [_]
   4.367 -      (if (bound? #'*graphics*)
   4.368 -        (with-hints* hints geometry view)
   4.369 -        (geometry view)))))
   4.370 +  (->Hinted hints view))
   4.371  
   4.372  (defn- transform-bounds
   4.373    ^Rectangle2D [^AffineTransform tr ^double w ^double h]
   4.374    (.getBounds2D
   4.375     (.createTransformedShape tr (Rectangle2D$Double. 0 0 w h))))
   4.376  
   4.377 +(defrecord Transform [transform view]
   4.378 +  View
   4.379 +  (render! [v]
   4.380 +    (let [g (geometry view)
   4.381 +          w (double (width g))
   4.382 +          h (double (height g))
   4.383 +          ^Rectangle2D bounds (transform-bounds transform w h)
   4.384 +          g *graphics*]
   4.385 +      (.translate g (- (.getX bounds)) (- (.getY bounds)))
   4.386 +      (.transform g transform)
   4.387 +      ;; TODO: scale w and h to fit *width* and *height*.
   4.388 +      (draw! view 0 0 w h)))
   4.389 +  (geometry [_]
   4.390 +    (let [g (geometry view)
   4.391 +          w (double (width g))
   4.392 +          h (double (height g))
   4.393 +          ^Rectangle2D bounds (transform-bounds transform w h)]
   4.394 +      (->Size (.getWidth bounds) (.getHeight bounds)))))
   4.395 +
   4.396  (defn transform [tr view]
   4.397 -  (reify View
   4.398 -    (render! [v]
   4.399 -      (let [g (geometry view)
   4.400 -            w (double (width g))
   4.401 -            h (double (height g))
   4.402 -            ^Rectangle2D bounds (transform-bounds tr w h)
   4.403 -            g *graphics*]
   4.404 -        (.translate g (- (.getX bounds)) (- (.getY bounds)))
   4.405 -        (.transform g tr)
   4.406 -        ;; TODO: scale w and h to fit *width* and *height*.
   4.407 -        (draw! view 0 0 w h)))
   4.408 -    (geometry [_]
   4.409 -      (let [g (geometry view)
   4.410 -            w (double (width g))
   4.411 -            h (double (height g))
   4.412 -            ^Rectangle2D bounds (transform-bounds tr w h)]
   4.413 -        (->Size (.getWidth bounds) (.getHeight bounds))))))
   4.414 +  (->Transform tr view))
   4.415  
   4.416  (defn rotate [^double degrees view]
   4.417    (transform
   4.418 @@ -383,44 +421,51 @@
   4.419  
   4.420  (def ^:dynamic *interval*)
   4.421  
   4.422 +(defrecord IntervalView [last-time content]
   4.423 +  View
   4.424 +  (render! [_]
   4.425 +    (compare-and-set! last-time nil *time*)
   4.426 +    (let [lt @last-time]
   4.427 +      (binding [*interval* (if (compare-and-set! last-time lt *time*)
   4.428 +                             (- *time* lt)
   4.429 +                             0)] ; already measured on parallel thread
   4.430 +        (render! content))))
   4.431 +  (geometry [_]
   4.432 +    (geometry content)))
   4.433 +
   4.434  (defn interval-view
   4.435 -  "Creates a view that measures time between repaints ant draws it's
   4.436 +  "Creates a view that measures time between repaints and draws it's
   4.437    content with the *interval* var bound to the measured time."
   4.438    [content]
   4.439 -  (let [last-time (atom nil)]
   4.440 -    (decorate-view content [_]
   4.441 -      (compare-and-set! last-time nil *time*)
   4.442 -      (let [lt @last-time]
   4.443 -        (binding [*interval* (if (compare-and-set! last-time lt *time*)
   4.444 -                               (- *time* lt)
   4.445 -                               0)] ; already measured on parallel thread
   4.446 -          (render! content))))))
   4.447 +  (->IntervalView (atom nil) content))
   4.448  
   4.449  (defn- fps-label [text]
   4.450    (padding 5 (label :right :bottom text)))
   4.451  
   4.452 +(defrecord FPSView [update-interval frames prev-time display content]
   4.453 +  View
   4.454 +  (render! [_]
   4.455 +    (draw! content)
   4.456 +    (draw!
   4.457 +     (dosync
   4.458 +      (alter frames inc)
   4.459 +      (if @prev-time
   4.460 +        (let [elapsed (- *time* @prev-time)]
   4.461 +          (when (> elapsed update-interval)
   4.462 +            (let [fps (/ @frames (/ elapsed 1E9))]
   4.463 +              (ref-set display (fps-label (format "%.1f" fps)))
   4.464 +              (ref-set frames 0)
   4.465 +              (ref-set prev-time *time*))))
   4.466 +        (ref-set prev-time *time*))
   4.467 +      @display)))
   4.468 +  (geometry [_] (geometry content)))
   4.469 +
   4.470  (defn fps-view
   4.471    "Creates a view that draws content and displays the
   4.472    frames per second rate."
   4.473    [content]
   4.474 -  (let [update-interval 2E8 ; 0.2 s in nanoseconds
   4.475 -        frames (ref 0)
   4.476 -        prev-time (ref nil)
   4.477 -        display (ref (fps-label "fps n/a"))]
   4.478 -    (decorate-view content [_]
   4.479 -      (draw! content)
   4.480 -      (draw!
   4.481 -       (dosync
   4.482 -        (alter frames inc)
   4.483 -        (if @prev-time
   4.484 -          (let [elapsed (- *time* @prev-time)]
   4.485 -            (when (> elapsed update-interval)
   4.486 -              (let [fps (/ @frames (/ elapsed 1E9))]
   4.487 -                (ref-set display (fps-label (format "%.1f" fps)))
   4.488 -                (ref-set frames 0)
   4.489 -                (ref-set prev-time *time*))))
   4.490 -          (ref-set prev-time *time*))
   4.491 -        @display)))))
   4.492 +  ;; 2E8 ns = 0.2 s.
   4.493 +  (->FPSView 2E8 (ref 0) (ref nil) (ref (fps-label "fps n/a")) content))
   4.494  
   4.495  ;;
   4.496  ;; Overlays
   4.497 @@ -457,7 +502,7 @@
   4.498      (draw! view x y w h)))
   4.499  
   4.500  (defn overlay!
   4.501 -  "Draws view in the overlay context above the other views."
   4.502 +  "Draws view in the overlay context above the current layer."
   4.503    ([view]
   4.504       (overlay* draw-relative! view (.getTransform *graphics*) 0 0))
   4.505    ([view x y]
   4.506 @@ -488,9 +533,16 @@
   4.507  (defmacro with-overlays [recursive? & body]
   4.508    `(with-overlays* ~recursive? (fn [] ~@body)))
   4.509  
   4.510 +(defrecord Layered [recursive? content]
   4.511 +  View
   4.512 +  (render! [_]
   4.513 +    (with-overlays* recursive? render! content))
   4.514 +  (geometry [_ ]
   4.515 +    (geometry content)))
   4.516 +
   4.517  (defn layered
   4.518 +  "Allows content view to display pop-ups on top of itself."
   4.519    ([content]
   4.520 -     (layered true content))
   4.521 +   (layered true content))
   4.522    ([recursive? content]
   4.523 -     (decorate-view content [_]
   4.524 -       (with-overlays* recursive? render! content))))
   4.525 +   (->Layered recursive? content)))