changeset 154:ed36fcf061de

Removed the domain part from namespace names.
author Mikhail Kryshen <mikhail@kryshen.net>
date Mon, 14 Apr 2014 20:01:00 +0400
parents 291afc2a8ca2
children c3782e84486f
files project.clj src/indyvon/async.clj src/indyvon/component.clj src/indyvon/core.clj src/indyvon/demo.clj src/indyvon/viewport.clj src/indyvon/views.clj src/net/kryshen/indyvon/async.clj src/net/kryshen/indyvon/component.clj src/net/kryshen/indyvon/core.clj src/net/kryshen/indyvon/demo.clj src/net/kryshen/indyvon/viewport.clj src/net/kryshen/indyvon/views.clj
diffstat 13 files changed, 2000 insertions(+), 2000 deletions(-) [+]
line wrap: on
line diff
--- a/project.clj	Mon Apr 14 15:37:28 2014 +0400
+++ b/project.clj	Mon Apr 14 20:01:00 2014 +0400
@@ -4,6 +4,6 @@
   :license {:name "GNU LGPL version 3"
             :url "http://www.gnu.org/licenses/lgpl-3.0.html"}
   :warn-on-reflection true
-;  :main net.kryshen.indyvon.demo
+;  :main indyvon.demo
   :dependencies [[org.clojure/clojure "1.6.0"]
                  [com.google.guava/guava "16.0.1"]])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/indyvon/async.clj	Mon Apr 14 20:01:00 2014 +0400
@@ -0,0 +1,178 @@
+;;
+;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+;; Indyvon is free software: you can redistribute it and/or modify it
+;; under the terms of the GNU Lesser General Public License version 3
+;; only, as published by the Free Software Foundation.
+;;
+;; Indyvon is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; Lesser General Public License for more details.
+;;
+;; You should have received a copy of the GNU Lesser General Public
+;; License along with Indyvon.  If not, see
+;; <http://www.gnu.org/licenses/>.
+;;
+
+(ns indyvon.async
+  "Asynchronous drawing."
+  (:use
+   indyvon.core)
+  (:import
+   java.awt.GraphicsConfiguration
+   (java.awt Image AlphaComposite Transparency)
+   (java.awt.image BufferedImage)
+   (java.util.concurrent ThreadFactory ThreadPoolExecutor
+                         ThreadPoolExecutor$DiscardOldestPolicy
+                         ArrayBlockingQueue TimeUnit)))
+
+(defrecord Buffer [id image readers state])
+;; Buffer states:
+;;   :front, readers > 0
+;;      being copied on screen
+;;   :back
+;;      being rendered to (offscreen)
+;;   :fresh
+;;      most recently updated
+;;   :free
+;;      not in use
+
+(defn- create-image [async-view ^GraphicsConfiguration device-conf]
+  ;; TODO: support different image types.
+  (.createCompatibleImage device-conf
+                          (:width async-view)
+                          (:height async-view)
+                          Transparency/TRANSLUCENT))
+
+(defn- create-buffer [async-view device-conf]
+  (Buffer. (Object.) (create-image async-view device-conf) 0 :free))
+
+(defn- find-buffer
+  "Find a buffer with the one of the specified states given
+   in the order of preference."
+  [buffers & states]
+  (some identity
+    (for [state states]
+      (some #(if (= (:state %) state) % nil) buffers))))
+
+(defn- replace-buffer [buffers buffer]
+  (conj (remove #(= (:id %) (:id buffer)) buffers)
+        buffer))
+
+(defn- take-buffer [al type]
+  (dosync
+   (let [buffers @(:buffers al)
+         b (case type
+             :front (find-buffer buffers :front :fresh :free)
+             :back (find-buffer buffers :free :fresh)
+             (throw (IllegalArgumentException.)))
+         readers (if (= type :front)
+                   (inc (:readers b))
+                   (:readers b))
+         b (assoc b
+             :state type
+             :readers readers)]
+     (alter (:buffers al) replace-buffer b)
+     b)))
+
+(defn- release-buffer [al buffer]
+  (dosync
+   (let [state (:state buffer)
+         readers (if (= state :front)
+                   (dec (:readers buffer))
+                   (:readers buffer))
+         fresh (delay (find-buffer @(:buffers al) :fresh))
+         state (cond
+                (pos? readers) :front
+                (= :back state) :fresh
+                @fresh :free
+                :default :fresh)]
+     (if (and (= state :fresh) @fresh)
+       ;; Change state of the prefiously fresh buffer to :free.
+       (alter (:buffers al)
+              replace-buffer (assoc @fresh
+                               :state :free)))
+     (alter (:buffers al)
+            replace-buffer (assoc buffer
+                             :state state
+                             :readers readers)))))
+
+(defmacro with-buffer
+  {:private true}
+  [al type [name] & body]
+  `(let [al# ~al
+         ~name (take-buffer al# ~type)]
+     (try
+       ~@body
+       (finally
+        (release-buffer al# ~name)))))
+
+(defn- draw-offscreen [async-view]
+  ;;(Thread/sleep 1000)
+  (with-buffer async-view :back [b]
+    (let [g (.createGraphics ^BufferedImage (:image b))]
+      ;; Clear the buffer.
+      (.setComposite g AlphaComposite/Clear)
+      (.fillRect g 0 0 (:width async-view) (:height async-view))
+      (.setComposite g AlphaComposite/Src)
+      (draw-scene! (:scene async-view)
+                   g
+                   (:width async-view)
+                   (:height async-view)))
+    (update async-view)))
+
+(defn- draw-offscreen-async [async-view]
+  (.execute ^ThreadPoolExecutor (:executor async-view)
+            #(draw-offscreen async-view)))
+
+(defrecord AsyncView [scene width height executor buffers]
+  View
+  (render! [view]
+    (repaint-on-update view)
+    (add-context-observer scene (fn [_ _] (draw-offscreen-async view)))
+    (when-not @buffers
+      ;; TODO: dynamic size, recreate buffers when size increases.
+      (let [device-conf (.getDeviceConfiguration *graphics*)
+            new-buffers (repeatedly 2
+                          (partial create-buffer view device-conf))]
+        (dosync
+         (ref-set buffers new-buffers)))
+      (draw-offscreen-async view))
+    (with-buffer view :front [b]
+      (.drawImage *graphics* ^Image (:image b) 0 0 nil)))
+  (geometry [view]
+    (->Size width height)))
+
+(defn- create-thread-factory [priority]
+  (reify
+   ThreadFactory
+   (newThread [_ runnable]
+     (let [thread (Thread. runnable)]
+       (when priority
+         (.setPriority thread priority))
+       (.setDaemon thread true)
+       thread))))
+
+(defn- create-executor [priority]
+  (doto (ThreadPoolExecutor.
+         (int 1) (int 1)
+         (long 0) TimeUnit/SECONDS
+         (ArrayBlockingQueue. 1)
+         (ThreadPoolExecutor$DiscardOldestPolicy.))
+    (.setThreadFactory (create-thread-factory priority))))
+
+(defn async-view 
+  "Creates a View that draws the content asynchronously using an
+   offscreen buffer."
+  ([width height content]
+     (async-view width height nil content))
+  ([width height priority content]
+     ;; TODO: use operational event dispatcher.
+     (->AsyncView (make-scene content)
+                  width
+                  height
+                  (create-executor priority)
+                  (ref nil))))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/indyvon/component.clj	Mon Apr 14 20:01:00 2014 +0400
@@ -0,0 +1,69 @@
+;;
+;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+;; Indyvon is free software: you can redistribute it and/or modify it
+;; under the terms of the GNU Lesser General Public License version 3
+;; only, as published by the Free Software Foundation.
+;;
+;; Indyvon is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; Lesser General Public License for more details.
+;;
+;; You should have received a copy of the GNU Lesser General Public
+;; License along with Indyvon.  If not, see
+;; <http://www.gnu.org/licenses/>.
+;;
+
+(ns indyvon.component
+  "Integrating Indyvon into AWT and Swing components."
+  (:use
+   indyvon.core)
+  (:import
+   (java.awt Graphics Component Dimension Color)
+   (java.awt.geom Rectangle2D$Double)
+   (javax.swing JFrame JPanel JOptionPane)))
+
+(defn- paint-component [^Component c ^Graphics g scene]
+  (let [w (.getWidth c)
+        h (.getHeight c)]
+    (.setColor g (:back-color *theme*))
+    (.fillRect g 0 0 w h)
+    (draw-scene! scene g w h)))
+
+(defn- preferred-size [^Component c scene]
+  (let [geom (scene-geometry scene)]
+    (Dimension. (width geom) (height geom))))
+
+(defn ^JPanel make-jpanel
+  ([view]
+     (make-jpanel view (root-event-dispatcher)))
+  ([view event-dispatcher]
+     (let [panel (proxy [JPanel] [])
+           scene (make-scene
+                  view event-dispatcher panel
+                  (.getDesktopProperty (java.awt.Toolkit/getDefaultToolkit)
+                                       "awt.font.desktophints"))]
+       (update-proxy
+        panel
+        {"paintComponent" #(paint-component %1 %2 scene)
+         "getPreferredSize" #(preferred-size % scene)})
+       (.setBackground panel (:back-color *theme*))
+       (add-observer panel scene (fn [w _]
+                                   ;; Use the first observer argument
+                                   ;; instead of closing over panel to
+                                   ;; allow the panel and associated
+                                   ;; observer to be gc'd.
+                                   (.repaint ^Component w)))
+       (listen! event-dispatcher panel)
+       panel)))
+
+(defn ^JFrame make-jframe [^String title view]
+  (doto (JFrame. title)
+    (.. (getContentPane) (add (make-jpanel view)))
+    (.pack)))
+
+(defn message [m]
+  (JOptionPane/showMessageDialog (:component *scene*) m))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/indyvon/core.clj	Mon Apr 14 20:01:00 2014 +0400
@@ -0,0 +1,881 @@
+;;
+;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+;; Indyvon is free software: you can redistribute it and/or modify it
+;; under the terms of the GNU Lesser General Public License version 3
+;; only, as published by the Free Software Foundation.
+;;
+;; Indyvon is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; Lesser General Public License for more details.
+;;
+;; You should have received a copy of the GNU Lesser General Public
+;; License along with Indyvon.  If not, see
+;; <http://www.gnu.org/licenses/>.
+;;
+
+(ns indyvon.core
+  (:import
+   (java.awt Graphics2D RenderingHints Component Color Font Shape
+             Rectangle Cursor EventQueue)
+   (java.awt.geom AffineTransform Point2D$Double Rectangle2D$Double Area)
+   (java.awt.event MouseListener MouseMotionListener
+                   MouseWheelListener MouseWheelEvent)
+   (java.awt.font FontRenderContext)
+   java.util.concurrent.ConcurrentMap
+   com.google.common.collect.MapMaker))
+
+;;
+;; View context
+;;
+
+(def ^:dynamic ^Graphics2D *graphics*)
+
+(def ^:dynamic ^FontRenderContext *font-context*
+  "FontRenderContext to use when Graphics2D is not available."
+  (FontRenderContext.
+   nil
+   RenderingHints/VALUE_TEXT_ANTIALIAS_DEFAULT
+   RenderingHints/VALUE_FRACTIONALMETRICS_DEFAULT))
+
+(def ^:dynamic *width*
+  "Width of the rendering area.")
+
+(def ^:dynamic *height*
+  "Height of the rendering area.")
+
+(def ^:dynamic ^Shape *clip*)
+
+(def ^:dynamic ^Shape *input-clip*
+  "Clipping area used for dispatching pointer events (intersected with
+  *clip*). If nil, *clip* will be used.")
+
+(def ^:dynamic *time*
+  "Timestamp of the current frame (in nanoseconds).")
+
+(def ^:dynamic *scene*
+  "Encloses state that should be retained between repaints.")
+
+(def ^:dynamic *states*
+  "Transient scene states, a map.")
+
+(def ^:dynamic *event-dispatcher*)
+
+(def ^:dynamic ^AffineTransform *initial-transform*
+  "Initial transform associated with the graphics context.")
+
+(def ^:dynamic ^AffineTransform *inverse-initial-transform*
+  "Inversion of the initial transform associated with the graphics
+  context.")
+
+(defrecord Theme [fore-color back-color alt-back-color border-color
+                  shadow-color font])
+
+;; REMIND: use system colors, see java.awt.SystemColor.
+(defn default-theme []
+  (Theme. Color/BLACK
+          Color/WHITE
+          (Color. 0xDD 0xDD 0xDD)
+          (Color. 0 0 0xCC)
+          (Color. 0x44 0x44 0x44)
+          (Font. "Sans" Font/PLAIN 12)))
+
+(def ^:dynamic *theme* (default-theme))
+
+;;
+;; Core protocols and types
+;;
+
+(defprotocol View
+  "Basic UI element."
+  (render! [view]
+    "Draws the view in the current *graphics* context.")
+  (geometry [view]
+    "Returns the preferred Geometry for the view."))
+
+(defprotocol Geometry
+  "Describes geometry of a View.  Prefer using the available
+  implementations (Size, FixedGeometry and NestedGeometry) over
+  extending this protocol directly as it is likely to be changed in
+  the future versions."
+  (width [geom] [geom height])
+  (height [geom] [geom width])
+  (anchor-x [geom h-align width]
+    "Returns the x coordinate of the anchor point for the specified
+     horizontal alignment and width, h-align could be :left, :center
+     or :right.")
+  (anchor-y [geom v-align height]
+    "Returns the y coordinate of the anchor point for the specified
+    vertical alignment and height, v-align could be :top, :center
+    or :bottom."))
+
+(defn- emit-align-xy [align size first center last]
+  `(case ~align
+         ~first 0
+         ~center (/ ~size 2)
+         ~last ~size))
+
+;; Define as macro to avoid unnecessary calculation of width or height.
+(defmacro align-x
+  ([align inner outer]
+     `(align-x ~align (- ~outer ~inner)))
+  ([align width]
+     (emit-align-xy align width :left :center :right)))
+
+(defmacro align-y
+  ([align inner outer]
+     `(align-y ~align (- ~outer ~inner)))
+  ([align height]
+     (emit-align-xy align height :top :center :bottom)))
+
+(defrecord Size [width height]
+  Geometry
+  (width  [_] width)
+  (width [_ _] width)
+  (height [_] height)
+  (height [_ _] height)
+  (anchor-x [_ h-align width]
+    (align-x h-align width))
+  (anchor-y [_ v-align height]
+    (align-y v-align height)))
+
+(defrecord FixedGeometry [ax ay width height]
+  Geometry
+  (width  [_] width)
+  (width [_ _] width)
+  (height [_] height)
+  (height [_ _] height)
+  (anchor-x [_ _ _] ax)
+  (anchor-y [_ _ _] ay))
+
+(defrecord NestedGeometry [geometry top left bottom right]
+  Geometry
+  (width  [_]
+    (+ left right (width geometry)))
+  (width [_ h]
+    (+ left right (width geometry (- h top bottom))))
+  (height [_]
+    (+ top bottom (height geometry)))
+  (height [_ w]
+    (+ top bottom (height geometry (- w left right))))
+  (anchor-x [_ h-align w]
+    (+ left (anchor-x geometry h-align (- w left right))))
+  (anchor-y [_ v-align h]
+    (+ top (anchor-y geometry v-align (- h top bottom)))))
+
+(defrecord ScaledGeometry [geometry sx sy]
+  Geometry
+  (width  [_]
+    (* sx (width geometry)))
+  (width [_ h]
+    (* sx (width geometry (/ h sy))))
+  (height [_]
+    (* sy (height geometry)))
+  (height [_ w]
+    (* sy (height geometry (/ w sx))))
+  (anchor-x [_ h-align w]
+    (* sx (anchor-x geometry h-align (/ w sx))))
+  (anchor-y [_ v-align h]
+    (* sy (anchor-y geometry v-align (/ h sy)))))
+
+;; (defn ^:private to-integer
+;;   ^long [align x]
+;;   (if (integer? x)
+;;     x
+;;     (let [x (double x)]
+;;       (Math/round
+;;        (case align
+;;          (:top :left) (Math/floor x)
+;;          :center x
+;;          (:bottom :right) (Math/ceil x))))))
+
+;; (defrecord IntegerGeometry [geometry]
+;;   Geometry
+;;   (width [_]
+;;     (to-integer :right (width geometry)))
+;;   (width [_ h]
+;;     (to-integer :right (width geometry h)))
+;;   (height [_]
+;;     (to-integer :bottom (height geometry)))
+;;   (height [_ w]
+;;     (to-integer :bottom (height geometry w)))
+;;   (anchor-x [_ h-align w]
+;;     (to-integer h-align (anchor-x geometry h-align w)))
+;;   (anchor-y [_ v-align h]
+;;     (to-integer v-align (anchor-y geometry v-align h))))
+
+;; TODO: modifiers
+(defrecord MouseEvent [id when x y x-on-screen y-on-screen button
+                       wheel-rotation transform component])
+
+;; TODO: KeyEvent
+
+(defprotocol EventDispatcher
+  (listen! [this component]
+     "Listen for events on the specified AWT Component.")
+  (create-dispatcher [this handle handlers]
+     "Returns new event dispatcher associated with the specified event
+      handlers (an event-id -> handler-fn map). Handle is used to
+      match the contexts between commits.")
+  (commit [this]
+     "Apply the registered handlers for event processing.")
+  (handle-picked? [this handle]
+     "Returns true if the specified handle received the :mouse-pressed
+      event and have not yet received :moused-released.")
+  (handle-hovered? [this handle]
+     "Returns true if the specified handle received the :mouse-entered
+      event and have not yet received :mouse-exited."))
+
+(defn- assoc-cons [m key val]
+  (->> (get m key) (cons val) (assoc m key)))
+
+;;
+;; Observers
+;; The mechanism used by views to request repaints
+;;
+
+(def ^ConcurrentMap observers
+     (-> (MapMaker.) (.weakKeys) (.makeMap)))
+
+(defn- cm-replace!
+  "Wrap ConcurrentMap replace method to treat nil value as absent
+   mapping. Use with maps that does not support nil values."
+  [^ConcurrentMap cmap key old new]
+  (if (nil? old)
+    (nil? (.putIfAbsent cmap key new))
+    (.replace cmap key old new)))
+
+(defn- cm-swap!
+  "Atomically swaps the value associated with key in ConcurrentMap
+   to be (apply f current-value args). Returns the new value."
+  [^ConcurrentMap cmap key f & args]
+  (loop []
+    (let [old (.get cmap key)
+          new (apply f old args)]
+      (if (cm-replace! cmap key old new)
+        new
+        (recur)))))
+
+(defn add-observer
+  "Add observer fn for the target. Watcher identifies the group of
+  observers and could be used to remove the group. Watcher is weakly
+  referenced, all associated observers will be removed when the
+  wathcer is removed by gc. The observer fn will be called with
+  watcher and target arguments and any additional arguments specified
+  in update call."
+  [watcher target f]
+  (cm-swap! observers watcher assoc-cons target f)
+  nil)
+
+(defn remove-observers
+  "Remove group of observers associated with the specified watcher."
+  [watcher]
+  (.remove observers watcher)
+  nil)
+
+(defn- replace-observers-watcher
+  [old-watcher new-watcher]
+  (if-let [old (.remove observers old-watcher)]
+    (.put observers new-watcher old))
+  nil)
+
+(defn update
+  "Notify observers."
+  [target & args]
+  (doseq [entry observers
+          f (get (val entry) target)]
+    (apply f (key entry) target args)))
+
+(defn add-context-observer
+  "Observer registered with this function will be automatically
+  removed after the next repaint is complete."
+  [target f]
+  (add-observer *scene* target f))
+
+(defn repaint-on-update
+  "Trigger repaint of the current scene when the target updates."
+  [target]
+  (let [scene *scene*]
+    (if-not (identical? scene target)
+      (add-observer scene target (fn [w _] (update w))))))
+
+(defn repaint
+  "Requests repaint of the current scene. If handle and state are
+  specified, the handle will be associated with the state in the
+  *states* map for the next paint iteration."
+  ([]
+     (update *scene*))
+  ([handle state]
+     (let [scene *scene*]
+       (swap! (:next-state scene) assoc handle state)
+       (update scene))))
+
+;;
+;; Rendering
+;;
+
+(defn ^FontRenderContext font-context
+  "Returns FontRenderContext for the current view context."
+  []
+  (if (bound? (var *graphics*))
+    (.getFontRenderContext *graphics*)
+    *font-context*))
+
+(defn ^AffineTransform relative-transform
+  "Returns AffineTransform: view context -> AWT component."
+  []
+  (let [tr (.getTransform *graphics*)]
+    (.preConcatenate tr *inverse-initial-transform*)
+    tr))
+
+(defn ^AffineTransform inverse-relative-transform
+  "Returns AffineTransform: AWT component -> view context."
+  []
+  (let [tr (.getTransform *graphics*)]
+    (.invert tr)                          ; absolute -> view
+    (.concatenate tr *initial-transform*) ; component -> absolute
+    tr))
+
+(defn transform-point [^AffineTransform tr ^double x ^double y]
+  (let [p (Point2D$Double. x y)]
+    (.transform tr p p)
+    [(.x p) (.y p)]))
+
+(defn inverse-transform-point [^AffineTransform tr ^double x ^double y]
+  (let [p (Point2D$Double. x y)]
+    (.inverseTransform tr p p)
+    [(.x p) (.y p)]))
+
+;; (defn- clip
+;;   "Intersect clipping area with the specified shape or bounds.
+;;    Returns new clip (Shape or nil if empty)."
+;;   ([x y w h]
+;;      (clip (Rectangle2D$Double. x y w h)))
+;;   ([shape]
+;;      (let [a1 (Area. shape)
+;;            a2 (if (instance? Area *clip*) *clip* (Area. *clip*))]
+;;        (.transform a1 (relative-transform))
+;;        (.intersect a1 a2)
+;;        (if (.isEmpty a1)
+;;          nil
+;;          a1))))
+
+;; Use faster clipping calculation provided by Graphics2D.
+(defn- clip
+  "Intersect clipping area with the specified Shape in current
+   transform coordinates. Returns new clip in the AWT component
+   coordinates (Shape or nil if empty)."
+  [^Shape shape]
+  (let [^Graphics2D clip-g (.create *graphics*)]
+    (try
+      (doto clip-g
+        (.setClip shape)
+        (.setTransform *initial-transform*)
+        (.clip *clip*))
+      (if (.isEmpty (.getClipBounds clip-g))
+        nil
+        (.getClip clip-g))
+      (finally
+       (.dispose clip-g)))))
+
+(defn- ^Graphics2D apply-theme
+  "Set graphics' color and font to match theme.
+   Modifies and returns the first argument."
+  ([]
+     (apply-theme *graphics* *theme*))
+  ([^Graphics2D graphics theme]
+     (doto graphics
+       (.setColor (:fore-color theme))
+       (.setFont (:font theme)))))
+
+(defn- ^Graphics2D create-graphics
+  ([]
+     (apply-theme (.create *graphics*) *theme*))
+  ([^long x ^long y ^long w ^long h]
+     (apply-theme (.create *graphics* x y w h) *theme*)))
+
+(defn- with-bounds-noclip*
+  [x y w h f & args]
+  (let [graphics (create-graphics)]
+    (try
+      (.translate graphics (double x) (double y))
+      (binding [*width* w
+                *height* h
+                *input-clip* (Rectangle2D$Double. 0.0 0.0 w h)
+                *graphics* graphics]
+        (apply f args))
+      (finally
+       (.dispose graphics)))))
+
+(defn with-bounds*
+  [x y w h f & args]
+  (let [x (double x)
+        y (double y)
+        bounds (Rectangle2D$Double. x y w h)]
+    (when-let [clip (clip bounds)]
+      (let [^Graphics2D graphics (create-graphics)]
+        (try
+          (.clip graphics bounds)
+          (.translate graphics x y)
+          (binding [*width* w
+                    *height* h
+                    *clip* clip
+                    *input-clip* nil
+                    *graphics* graphics]
+            (apply f args))
+          (finally
+           (.dispose graphics)))))))
+
+(defmacro with-bounds
+  [x y w h & body]
+  `(with-bounds* ~x ~y ~w ~h (fn [] ~@body)))
+
+(defmacro with-theme
+  [theme & body]
+  `(binding [*theme* (merge *theme* ~theme)]
+     ~@body))
+
+(defmacro with-color [color-or-key & body]
+  `(let [color# ~color-or-key
+         color# (get *theme* color# color#)
+         g# *graphics*
+         old-color# (.getColor g#)]
+     (try
+       (.setColor g# color#)
+       ~@body
+       (finally
+         (.setColor g# old-color#)))))
+
+(defmacro with-stroke [stroke & body]
+  `(let [g# *graphics*
+         old-stroke# (.getStroke g#)]
+     (try
+       (.setStroke g# ~stroke)
+       ~@body
+       (finally
+        (.setStroke g# old-stroke#)))))
+
+(defmacro with-hints
+  [hints & body]
+  `(let [h# ~hints
+         g# *graphics*
+         old# (.getRenderingHints g#)]
+     (try
+       (.addRenderingHints g# h#)
+       ~@body
+       (finally
+        (.setRenderingHints g# old#)))))
+
+(defn with-hints* [hints f & args]
+  (with-hints hints
+    (apply f args)))
+
+;; TODO: constructor for AffineTransform.
+;; (transform :scale 0.3 0.5
+;;            :translate 5 10
+;;            :rotate (/ Math/PI 2))
+
+(defmacro with-transform [transform & body]
+  `(let [g# *graphics*
+         old-t# (.getTransform g#)]
+     (try
+       (.transform g# ~transform)
+       ~@body
+       (finally
+        (.setTransform g# old-t#)))))
+
+(defmacro with-rotate [theta ax ay & body]
+  `(let [transform# (AffineTransform/getRotateInstance ~theta ~ax ~ay)]
+     (with-transform transform# ~@body)))
+
+(defmacro with-translate [x y & body]
+  `(let [x# ~x
+         y# ~y
+         g# *graphics*]
+     (try
+       (.translate g# x# y#)
+       ~@body
+       (finally
+        (.translate g# (- x#) (- y#))))))
+
+(defn draw!
+  "Draws the View."
+  ([view]
+     (let [graphics (create-graphics)]
+       (try
+         (binding [*graphics* graphics]
+           (render! view))
+         (finally
+          (.dispose graphics)))))
+  ([x y view]
+     (draw! x y true view))
+  ([x y clip? view]
+     (let [geom (geometry view)]
+       (draw! x y (width geom) (height geom) clip? view)))
+  ([x y width height view]
+     (draw! x y width height true view))
+  ([x y width height clip? view]
+     (if clip?
+       (with-bounds* x y width height render! view)
+       (with-bounds-noclip* x y width height render! view))))
+
+(defn draw-aligned!
+  "Draws the View.  Location is relative to the view's anchor point
+   for the specified alignment."
+  ([h-align v-align x y view]
+     (let [geom (geometry view)
+           w (width geom)
+           h (height geom)]
+       (draw! (- x (anchor-x geom h-align w))
+              (- y (anchor-y geom v-align h))
+              w h
+              view)))
+  ([h-align v-align x y w h view]
+     (let [geom (geometry view)]
+       (draw! (- x (anchor-x geom h-align w))
+              (- y (anchor-y geom v-align h))
+              w h
+              view))))
+
+;;
+;; Event handling.
+;;
+
+(defn with-handlers*
+  [handle handlers f & args]
+  (binding [*event-dispatcher* (create-dispatcher
+                                *event-dispatcher* handle handlers)]
+    (apply f args)))
+
+(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)
+     (fn [] ~form)))
+
+(defn picked? [handle]
+  (handle-picked? *event-dispatcher* handle))
+
+(defn hovered? [handle]
+  (handle-hovered? *event-dispatcher* handle))
+
+;;
+;; EventDispatcher implementation
+;;
+
+(def awt-events
+     {java.awt.event.MouseEvent/MOUSE_CLICKED  :mouse-clicked
+      java.awt.event.MouseEvent/MOUSE_DRAGGED  :mouse-dragged
+      java.awt.event.MouseEvent/MOUSE_ENTERED  :mouse-entered
+      java.awt.event.MouseEvent/MOUSE_EXITED   :mouse-exited
+      java.awt.event.MouseEvent/MOUSE_MOVED    :mouse-moved
+      java.awt.event.MouseEvent/MOUSE_PRESSED  :mouse-pressed
+      java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released
+      java.awt.event.MouseEvent/MOUSE_WHEEL    :mouse-wheel})
+
+(def dummy-event-dispatcher
+  (reify EventDispatcher
+    (listen! [_ _])
+    (create-dispatcher [this _ _] this)
+    (commit [_])
+    (handle-picked? [_ _])
+    (handle-hovered? [_ _])))
+
+;; Not using defrecord to avoid unacceptable overhead of recursive
+;; hash code calculation.
+(deftype DispatcherNode [handle handlers parent
+                         ^Shape clip ^AffineTransform transform
+                         bindings]
+  EventDispatcher
+  (listen! [this component]
+    (listen! parent component))
+  (create-dispatcher [this handle handlers]
+    (create-dispatcher parent handle handlers))
+  (commit [this]
+    (commit parent))
+  (handle-picked? [this handle]
+    (handle-picked? parent handle))
+  (handle-hovered? [this handle]
+    (handle-hovered? parent handle)))
+
+(defn- make-node [handle handlers]
+  (let [clip (if *input-clip*
+               (clip *input-clip*)
+               *clip*)
+        bindings (-> (get-thread-bindings)
+                     (dissoc (var *graphics*))
+                     (assoc (var *font-context*) (font-context)))]
+    (DispatcherNode. handle handlers *event-dispatcher* clip
+                     (relative-transform)
+                     bindings)))
+
+(defn- add-node [tree ^DispatcherNode node]
+  (assoc-cons tree (.parent node) node))
+
+(defn- nodes [tree]
+  (apply concat (vals tree)))
+
+(defn- under-cursor
+  "Returns a vector of child nodes under cursor."
+  [node tree ^long x ^long y]
+  (some (fn [^DispatcherNode n]
+          (if (and (.clip n) (.contains ^Shape (.clip n) x y))
+            (conj (vec (under-cursor n tree x y)) n)))
+        (get tree node)))
+
+(defn- translate-mouse-event [^java.awt.event.MouseEvent event
+                              ^AffineTransform tr id]
+  (let [[x y] (inverse-transform-point tr (.getX event) (.getY event))
+        rotation (if (instance? MouseWheelEvent event)
+                   (.getWheelRotation ^MouseWheelEvent event)
+                   nil)]
+    (->MouseEvent id (.getWhen event) x y
+                  (.getXOnScreen event) (.getYOnScreen event)
+                  (.getButton event)
+                  rotation
+                  tr
+                  (.getComponent event))))
+
+(defn- translate-and-dispatch
+  ([nodes first-only ^java.awt.event.MouseEvent event]
+     (translate-and-dispatch nodes first-only
+                             event (awt-events (.getID event))))
+  ([nodes first-only event id]
+     (if-let [^DispatcherNode node (first nodes)]
+       (let [handlers (.handlers node)
+             handler (get handlers id)]
+         (if handler
+           (do
+             (with-bindings* (.bindings node)
+               handler
+               (translate-mouse-event event (.transform node) id))
+             (when-not first-only
+               (recur (rest nodes) false event id)))
+           (when-not (and (= id :mouse-dragged)
+                          (or (contains? handlers :mouse-pressed)
+                              (contains? handlers :mouse-released)))
+             (recur (rest nodes) first-only event id)))))))
+
+(defn- process-mouse-event
+  [dispatcher ^java.awt.event.MouseEvent source-event]
+  (let [{active-ref :active
+         hovered-ref :hovered
+         picked-ref :picked
+         last-ref :last-motion
+         tree-ref :tree} dispatcher
+         pressed (and source-event
+                      (== (.getID source-event)
+                          java.awt.event.MouseEvent/MOUSE_PRESSED))
+         released (and source-event
+                       (== (.getID source-event)
+                           java.awt.event.MouseEvent/MOUSE_RELEASED))
+         ^java.awt.event.MouseEvent last-event @last-ref
+         ^java.awt.event.MouseEvent event (or source-event last-event)]
+    (when event
+      (let [x (.getX event)
+            y (.getY event)
+            active @active-ref
+            active (if (and active
+                            source-event
+                            (== (.getX last-event) x)
+                            (== (.getY last-event) y))
+                     active
+                     (ref-set active-ref
+                              (under-cursor dispatcher @tree-ref x y)))
+            acted (cond
+                   pressed (ref-set picked-ref active)
+                   released (let [picked @picked-ref]
+                              (ref-set picked-ref nil)
+                              picked)
+                   :else active)
+            picked (seq @picked-ref)
+            pred #(= (.handle ^DispatcherNode %1) (.handle ^DispatcherNode %2))
+            hovered (if picked
+                      (filter #(some (partial pred %) picked) active)
+                      active)
+            remove-all (fn [c1 c2]
+                         (filter #(not (some (partial pred %) c2)) c1))
+            old-hovered @hovered-ref
+            exited (remove-all old-hovered hovered)
+            entered (remove-all hovered old-hovered)
+            moved (or picked (remove-all hovered entered))]
+        (ref-set hovered-ref hovered)
+        (ref-set last-ref event)
+        [exited entered moved acted event]))))
+
+(defn- dispatch-mouse-event
+  [dispatcher source-event button?]
+  (when-let [[exited
+              entered
+              moved
+              acted
+              event] (dosync (process-mouse-event dispatcher source-event))]
+    (when button?
+      (translate-and-dispatch acted true event))
+    (translate-and-dispatch exited false event :mouse-exited)
+    (translate-and-dispatch entered false event :mouse-entered)
+    (when-not button?
+      (translate-and-dispatch moved true source-event))))
+
+(defrecord RootEventDispatcher [tree-r  ;; register
+                                tree    ;; dispatch
+                                active  ;; nodes under cursor
+                                hovered ;; mouse entered
+                                picked  ;; mouse pressed
+                                last-motion]
+  EventDispatcher
+  (listen! [dispatcher component]
+    (doto ^Component component
+          (.addMouseListener dispatcher)
+          (.addMouseWheelListener dispatcher)
+          (.addMouseMotionListener dispatcher)))
+  (create-dispatcher [dispatcher handle handlers]
+    (let [node (make-node handle handlers)]
+      (dosync (alter tree-r add-node node))
+      node))
+  (commit [dispatcher]
+    (let [[exited
+           entered
+           _ _
+           event] (dosync
+                   ;; TODO: retain contexts that do
+                   ;; not intersect graphics
+                   ;; clipping area in tree.
+                   (ref-set tree @tree-r)
+                   (ref-set tree-r {})
+                   (process-mouse-event dispatcher nil))]
+      ;; Send mouse entered and exited events if necessary due to
+      ;; updated layout.
+      (translate-and-dispatch exited false event :mouse-exited)
+      (translate-and-dispatch entered false event :mouse-entered)))
+  (handle-picked? [dispatcher handle]
+    (some #(= handle (.handle ^DispatcherNode %)) @picked))
+  (handle-hovered? [dispatcher handle]
+    (some #(= handle (.handle ^DispatcherNode %)) @hovered))
+  MouseListener
+  (mouseEntered [dispatcher event]
+    (dispatch-mouse-event dispatcher event false))
+  (mouseExited [dispatcher event]
+    (dispatch-mouse-event dispatcher event false))
+  (mouseClicked [dispatcher event]
+    (dispatch-mouse-event dispatcher event true))
+  (mousePressed [dispatcher event]
+    (dispatch-mouse-event dispatcher event true))
+  (mouseReleased [dispatcher event]
+    (dispatch-mouse-event dispatcher event true))
+  MouseWheelListener
+  (mouseWheelMoved [dispatcher event]
+    (dispatch-mouse-event dispatcher event true))
+  MouseMotionListener
+  (mouseDragged [dispatcher event]
+    (dispatch-mouse-event dispatcher event false))
+  (mouseMoved [dispatcher event]
+    (dispatch-mouse-event dispatcher event false)))
+
+(defn root-event-dispatcher []
+  (->RootEventDispatcher
+   (ref {}) (ref {})             ;; trees
+   (ref nil) (ref nil) (ref nil) ;; node states
+   (ref nil)))                   ;; last event
+
+;;
+;; Scene
+;;
+
+(defrecord Scene [view
+                  event-dispatcher
+                  component
+                  rendering-hints
+                  next-state])
+
+;; Define rendering hints that affect font metrics to make sure that
+;; Graphics and Scene FontRenderContexts are consistent.
+(def ^:private default-rendering-hints
+  {RenderingHints/KEY_TEXT_ANTIALIASING
+   RenderingHints/VALUE_TEXT_ANTIALIAS_DEFAULT,
+   RenderingHints/KEY_FRACTIONALMETRICS
+   RenderingHints/VALUE_FRACTIONALMETRICS_DEFAULT})
+
+(defn make-scene
+  ([view]
+     (make-scene view dummy-event-dispatcher nil))
+  ([view event-dispatcher]
+     (make-scene view event-dispatcher nil))
+  ([view event-dispatcher ^Component component]
+     (make-scene view event-dispatcher component nil))
+  ([view event-dispatcher ^Component component hints]
+     (let [hints (merge default-rendering-hints hints)]
+       (->Scene view
+                event-dispatcher
+                component
+                hints
+                (atom nil)))))
+
+(defn- get-and-set!
+  "Atomically sets the value of atom to newval and returns the old
+  value."
+  [atom newval]
+  (loop [v @atom]
+    (if (compare-and-set! atom v newval)
+      v
+      (recur @atom))))
+
+(defn draw-scene!
+  [scene ^Graphics2D graphics width height]
+  (.addRenderingHints graphics (:rendering-hints scene))
+  (binding [*states* (get-and-set! (:next-state scene) nil)
+            *scene* scene
+            *graphics* graphics
+            *initial-transform* (.getTransform graphics)
+            *inverse-initial-transform* (-> graphics
+                                            .getTransform
+                                            .createInverse)
+            *event-dispatcher* (:event-dispatcher scene)
+            *width* width
+            *height* height
+            *clip* (Rectangle2D$Double. 0.0 0.0 width height)
+            *input-clip* nil
+            *time* (System/nanoTime)]
+    (apply-theme)
+    (let [tmp-watcher (Object.)]
+      ;; Keep current context observers until the rendering is
+      ;; complete. Some observers may be invoked twice if they
+      ;; appear in both groups until tmp-watcher is removed.
+      (replace-observers-watcher scene tmp-watcher)
+      (try
+        (render! (:view scene))
+        (finally
+         (remove-observers tmp-watcher)
+         (commit (:event-dispatcher scene)))))))
+
+(defn- scene-font-context [scene]
+  (let [hints (:rendering-hints scene)
+        ^Component c (:component scene)
+        t (if c (->> c
+                     .getFont
+                     (.getFontMetrics c)
+                     .getFontRenderContext
+                     .getTransform))]
+    (FontRenderContext.
+     t
+     (get hints RenderingHints/KEY_TEXT_ANTIALIASING)
+     (get hints RenderingHints/KEY_FRACTIONALMETRICS))))      
+
+(defn scene-geometry [scene]
+  (binding [*scene* scene
+            *font-context* (scene-font-context scene)]
+    (geometry (:view scene))))
+
+(defn set-cursor! [^Cursor cursor]
+  (when-let [^Component component (:component *scene*)]
+    (EventQueue/invokeLater #(.setCursor component cursor))))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/indyvon/demo.clj	Mon Apr 14 20:01:00 2014 +0400
@@ -0,0 +1,223 @@
+;;
+;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+;; Indyvon is free software: you can redistribute it and/or modify it
+;; under the terms of the GNU Lesser General Public License version 3
+;; only, as published by the Free Software Foundation.
+;;
+;; Indyvon is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; Lesser General Public License for more details.
+;;
+;; You should have received a copy of the GNU Lesser General Public
+;; License along with Indyvon.  If not, see
+;; <http://www.gnu.org/licenses/>.
+;;
+
+(ns indyvon.demo
+  "Indyvon demo and experiments."
+  (:gen-class)
+  (:use
+   (indyvon core views viewport component))
+  (:import
+   (java.awt Color)
+   (javax.swing JFrame)))
+
+(defn draw-button!
+  "Draws a button immediately (but uses callback for the action unlike
+   IMGUI)."
+  [id content callback & args]
+  (with-handlers id
+    (let [shadow-offset 2
+          padding 4
+          border-width 1
+          offset (if (picked? id) (/ shadow-offset 2) 0)
+          ^Color color (:alt-back-color *theme*)
+          color (if (hovered? id) (.brighter color) color)
+          width (- *width* shadow-offset)
+          height (- *height* shadow-offset)]
+      (with-color (:shadow-color *theme*)
+        (.fillRect *graphics* shadow-offset shadow-offset width height))
+      (with-color color
+        (.fillRect *graphics* offset offset width height))
+      (draw! offset offset width height
+             (border border-width padding content)))
+    ;; Event handlers
+    (:mouse-entered _ (repaint))
+    (:mouse-exited _ (repaint))
+    (:mouse-pressed _ (repaint))
+    (:mouse-released _ (repaint))
+    (:mouse-clicked _ (apply callback args))))
+
+(defn combine-colors
+  "Returns color between color1 and color2. When c (0 <= c <= 1.0) is
+   closer to 0 the returned сolor is closer to color1."
+  [^Color color1 ^Color color2 c]
+  (case c
+    0.0 color1
+    1.0 color2
+    (let [rgb1 (.getRGBComponents color1 nil)
+          rgb2 (.getRGBComponents color2 nil)
+          rgb (float-array (map #(+ (* (- 1 c) %1) (* c %2)) rgb1 rgb2))]
+      (Color. (aget rgb 0) (aget rgb 1) (aget rgb 2) (aget rgb 3)))))
+
+(defn animate
+  "Changes the value of atom according to the specified range, speed,
+   and current frame interval.  Invokes repaint if change happens."
+  [atom from to speed]
+  (let [prev @atom
+        state (cond
+               (zero? speed) :stop
+               (= prev from) (if (pos? speed) :start :stop)               
+               (= prev to) (if (neg? speed) :start :stop)
+               :default :continue)]
+    (if (= state :stop)
+       prev
+       (let [interval (if (= state :start) 1 *interval*)
+             step (* speed interval 1E-9)
+             val (swap! atom #(-> % (+ step) (max from) (min to)))]
+         (repaint)
+         val))))
+
+(defn animated-button
+  "Creates an animated button."
+  [content callback & args]
+  (let [padding 4
+        border-width 1
+        shadow-offset 2
+        face (border padding border-width content)
+        highlight (atom 0)
+        animation-speed (atom 0)]
+    (interval-view
+     (reify
+      View
+      (render! [button]
+        (with-handlers button
+          (let [hovered (hovered? button)
+                offset (if (picked? button) (/ shadow-offset 2) 0)
+                color (combine-colors
+                       (:alt-back-color *theme*) Color/WHITE
+                       (animate highlight 0.0 1.0 @animation-speed))
+                width (- *width* shadow-offset)
+                height (- *height* shadow-offset)]
+            (with-color (:shadow-color *theme*)
+              (.fillRect *graphics*
+                         shadow-offset shadow-offset
+                         width height))
+            (with-color color
+              (.fillRect *graphics* offset offset width height))
+            (draw! offset offset width height
+                   (border border-width padding content)))
+          ;; Event handlers
+          (:mouse-entered _
+            (reset! animation-speed 4)
+            (repaint))
+          (:mouse-exited _
+            (reset! animation-speed -2)
+            (repaint))
+          (:mouse-pressed _ (repaint))
+          (:mouse-released _ (repaint))
+          (:mouse-clicked _ (apply callback args))))
+     (geometry [button]
+       (let [face-geom (geometry face)]
+         (->Size (+ (width face-geom) shadow-offset)
+                 (+ (height face-geom) shadow-offset))))))))
+
+(def button1 (animated-button (label "Animated button 1")
+                              println "Animated button 1 clicked"))
+
+(def button2 (animated-button (label "Animated button 2")
+                              println "Animated button 2 clicked"))
+
+(def test-view1
+  (reify
+   View
+   (render! [view]
+     (with-handlers view
+       (with-color (if (hovered? view) Color/ORANGE Color/RED)
+         (.fillRect *graphics* 0 0 *width* *height*))
+       (:mouse-entered e
+        (repaint)
+        (println e))
+       (:mouse-exited e
+        (repaint)
+        (println e))
+       (:mouse-moved e
+        (println e))))
+   (geometry [view]
+     (->Size 30 20))))
+
+(def test-view1b (border 2 3 test-view1))
+
+(def test-view2
+  (reify
+   View
+   (render! [view]
+     (doto *graphics*
+       (.setColor Color/YELLOW)
+       (.fillRect 0 0 *width* *height*))
+     (with-rotate 0.5 0 0
+       (draw! 30 25 test-view1b))
+     (draw! 55 5 test-view1))
+   (geometry [view]
+     (->Size 70 65))))
+
+(def test-view2m (miniature 30 30 test-view2))
+
+(def test-view3 (border (label :right :bottom "Sample\ntext")))
+
+(def root
+  (reify
+   View
+   (render! [view]
+     ;;(repaint)
+     (doto *graphics*
+       (.drawLine 0 0 *width* *height*)
+       (.drawLine *width* 0 0 *height*)
+       ;; Random color to see when repaint happens.
+       (.setColor (rand-nth [Color/BLACK Color/BLUE Color/RED]))
+       (.fillOval 5 5 20 20))
+     (draw! 30 20 test-view2)
+     (draw! 120 50 test-view2m)
+     (draw! 100 100 80 50 test-view3)
+     (draw! 50 160 button1)
+     (with-rotate (/ Math/PI 6) 250 200
+       (draw! 210 140 button1))
+     (draw! 100 200 button2)
+     (with-bounds 180 240 140 30
+       (draw-button! :button
+        (label :center :center "Immediate button")
+        #(println "Button clicked!"))))
+   (geometry [view]
+     (->Size 400 300))))
+
+;; Main viewport
+(def vp (viewport root))
+
+;; Miniature (rendered asynchronously)
+(def vp-miniature (->> vp (viewport-miniature 100 75) border shadow))
+
+;; Main scene
+(def scene
+  (fps-view
+   (decorate-view vp [_]
+     (draw! vp)
+     (draw-aligned!
+      :left :bottom 5 (- *height* 5)
+      (label (str "Drag mouse to pan," \newline
+                  "use mouse wheel to zoom.")))
+     (draw! (- *width* 105) 5 vp-miniature))))
+
+(defn show-frame [view]
+  (doto (make-jframe "Test" view)
+    (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE)
+    (.setVisible true)))
+
+(defn -main []
+  (show-frame scene))
+
+(comment
+  (show-frame (viewport-miniature 200 150 vp)))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/indyvon/viewport.clj	Mon Apr 14 20:01:00 2014 +0400
@@ -0,0 +1,238 @@
+;;
+;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+;; Indyvon is free software: you can redistribute it and/or modify it
+;; under the terms of the GNU Lesser General Public License version 3
+;; only, as published by the Free Software Foundation.
+;;
+;; Indyvon is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; Lesser General Public License for more details.
+;;
+;; You should have received a copy of the GNU Lesser General Public
+;; License along with Indyvon.  If not, see
+;; <http://www.gnu.org/licenses/>.
+;;
+
+(ns indyvon.viewport
+  "Scrollable viewport and miniature."
+  (:use
+   (indyvon core async views))
+  (:import
+   java.awt.Cursor
+   java.awt.geom.AffineTransform))
+  
+;;(defn- translate [^AffineTransform transform ^double x ^double y]
+;;  (doto ^AffineTransform (.clone transform)
+;;        (.translate x y)))
+
+(defn- scale [^AffineTransform transform ^double sx ^double sy]
+    (doto ^AffineTransform (.clone transform)
+        (.scale sx sy)))
+
+(defn- pre-translate [^AffineTransform transform ^double x ^double y]
+  (if (== 0.0 x y)
+    transform
+    (doto (AffineTransform/getTranslateInstance x y)
+      (.concatenate transform))))
+
+(def ^:dynamic *viewport-scaling-step* (double 3/4))
+(def ^:dynamic *viewport-min-scale* 1E-6)
+(def ^:dynamic *viewport-max-scale* 1E6)
+
+(def ^:dynamic *viewport* nil)
+(def ^:dynamic ^AffineTransform *viewport-transform*)
+
+(declare scale-viewport!)
+
+(defrecord ViewportState [transform
+                          fix-x fix-y
+                          last-width last-height
+                          last-anchor-x last-anchor-y])
+
+(defn- update-viewport [state content-geom h-align v-align]
+  (let [w *width*
+        h *height*
+        cw (width content-geom)
+        ch (height content-geom)
+        ax (anchor-x content-geom h-align cw)
+        ay (anchor-y content-geom v-align ch)
+        ax1 (align-x h-align (:last-width state) w)
+        ay1 (align-y v-align (:last-height state) h)
+        ax2 (- (:last-anchor-x state) ax)
+        ay2 (- (:last-anchor-y state) ay)
+        transform (:transform state)
+        transform (if (and (zero? ax1) (zero? ay1)
+                           (zero? ax2) (zero? ay2))
+                    transform
+                    (doto
+                        (AffineTransform/getTranslateInstance ax1 ay1)
+                      (.concatenate transform)
+                      (.translate ax2 ay2)))]
+    (assoc state
+      :last-width w
+      :last-height h
+      :last-anchor-x ax
+      :last-anchor-y ay
+      :transform transform)))
+
+(defrecord Viewport [content h-align v-align state]
+  View
+  (render! [view]
+    (repaint-on-update view)
+    (with-handlers view
+      (let [geom (geometry content)
+            new-state (swap! state update-viewport geom h-align v-align)
+            transform (:transform new-state)]
+        ;; TODO: notify observers when size changes.
+        (binding [*viewport* view
+                  *viewport-transform* transform]
+          (with-transform transform
+            (draw! 0 0 (width geom) (height geom) false content))))
+      (:mouse-pressed e
+       (swap! state assoc
+              :fix-x (:x-on-screen e)
+              :fix-y (:y-on-screen e))
+       (set-cursor! (Cursor. Cursor/MOVE_CURSOR)))
+      (:mouse-released e
+       (set-cursor! (Cursor. Cursor/DEFAULT_CURSOR)))
+      (:mouse-dragged e
+       (swap! state
+              (fn [s]
+                (assoc s
+                  :transform (pre-translate
+                              (:transform s)
+                              (- (:x-on-screen e) (:fix-x s))
+                              (- (:y-on-screen e) (:fix-y s)))
+                  :fix-x (:x-on-screen e)
+                  :fix-y (:y-on-screen e))))
+       (update view))
+      (:mouse-wheel e
+       (scale-viewport!
+        view
+        (Math/pow *viewport-scaling-step* (:wheel-rotation e))
+        true (:x e) (:y e)))))
+  (geometry [_]
+    (geometry content)))
+
+(def ^:private viewport-initial-state
+  (->ViewportState
+   (AffineTransform.) ; transform
+   0 0                ; fix-x fix-y
+   0 0                ; last-width last-height
+   0 0))
+
+(defn viewport
+  "Creates scrollable viewport view."
+  ([content]
+     (viewport :left :top content))
+  ([h-align v-align content]
+     (->Viewport content h-align v-align (atom viewport-initial-state))))
+
+(defn- scale-viewport [state vp s relative? x y]
+  (let [^AffineTransform tr (:transform state)
+        sx (if relative? s (/ s (.getScaleX tr)))
+        sy (if relative? s (/ s (.getScaleY tr)))
+        x (or x (align-x (:h-align vp) (:last-width state)))
+        y (or y (align-y (:v-align vp) (:last-height state)))
+        x (- x (* x sx))
+        y (- y (* y sy))
+        scaled (doto (AffineTransform/getTranslateInstance x y)
+                 (.scale sx sy)
+                 (.concatenate tr))
+        sx (.getScaleX scaled)
+        sy (.getScaleY scaled)]
+    (if (<= *viewport-min-scale*
+            (min sx sy)
+            (max sx sy)
+            *viewport-max-scale*)
+      (assoc state
+        :transform scaled)
+      state)))
+
+(defn scale-viewport!
+  ([viewport s]
+     (scale-viewport! viewport s true))
+  ([viewport s relative?]
+     (scale-viewport! viewport s relative? nil nil))
+  ([viewport s relative? x y]
+     (swap! (:state viewport) scale-viewport viewport s relative? x y)
+     (update viewport)))
+
+(defn reset-viewport! [viewport]
+  (reset! (:state viewport) viewport-initial-state)
+  (update viewport))
+
+(defn ^AffineTransform viewport-transform [viewport]
+  (:transform @(:state viewport)))
+
+(defn- scaling
+  [width height max-width max-height]
+  (min (/ max-width width)
+       (/ max-height height)))
+
+(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! (align-x :center cw (/ mw s))
+               (align-y :center ch (/ mh s))
+               cw ch
+               content)))
+    (geometry [_]
+      (->Size mw mh)))))
+
+(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)
+                       (update 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)))))))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/indyvon/views.clj	Mon Apr 14 20:01:00 2014 +0400
@@ -0,0 +1,410 @@
+;;
+;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+;; Indyvon is free software: you can redistribute it and/or modify it
+;; under the terms of the GNU Lesser General Public License version 3
+;; only, as published by the Free Software Foundation.
+;;
+;; Indyvon is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; Lesser General Public License for more details.
+;;
+;; You should have received a copy of the GNU Lesser General Public
+;; License along with Indyvon.  If not, see
+;; <http://www.gnu.org/licenses/>.
+;;
+
+(ns indyvon.views
+  "Implementations of the View protocol."
+  (:use
+   (indyvon core async))
+  (:import
+   (java.awt Font Image Toolkit)
+   java.awt.image.ImageObserver
+   (java.awt.geom Area AffineTransform Rectangle2D$Double Point2D
+                  Point2D$Double)
+   (java.awt.font FontRenderContext TextLayout)
+   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 Empty []
+  View
+  (render! [_])
+  (geometry [_]
+    (->Size 0 0)))
+
+(def empty-view (->Empty))
+
+;; TODO: change argument order for decorators, content should be the
+;; last.
+
+(defn padding
+  "Adds padding to the content view."
+  ([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! left top
+                  (- *width* left right)
+                  (- *height* top bottom)
+                  false
+                  content))
+        (geometry [l]
+          (->NestedGeometry (geometry content) top left bottom right))))))
+
+(defn border
+  "Adds a border to the content view."
+  ([content]
+     (border 1 content))
+  ([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)))))))
+
+;; TODO: opacity and blur.
+(defn shadow
+  "Adds a shadow to the content view."
+  ([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! x y w h content)))
+        (geometry [_]
+          (->NestedGeometry (geometry content)
+                            y x shadow-y shadow-x))))))
+
+(defn panel
+  "An opaque view using theme's alt-back-color or a custom background
+  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))))
+
+(defn hbox
+  "Creates a view that draws the specified content views placing them
+   horizontally."
+  [& contents]
+  (reify
+   View
+   (render! [_]
+     (let [widths (map #(width (geometry %)) contents)
+           xs (cons 0 (reductions + widths))
+           widths-sum (last xs)
+           scale (/ *width* widths-sum)]
+       (doseq [[c w x] (map vector contents widths xs)]
+         (draw! x 0 w *height* c))))
+   (geometry [_]
+     (reduce #(->Size (+ (width %1) (width %2))
+                      (max (height %1) (height %2)))
+             (->Size 0 0)
+             (map geometry contents)))))
+
+(defn vbox
+  "Creates a view that draws the specified content views placing them
+   vertically."
+  [& contents]
+  (reify
+   View
+   (render! [_]
+     (let [heights (map #(height (geometry %)) contents)
+           ys (cons 0 (reductions + heights))
+           heights-sum (last ys)
+           scale (/ *height* heights-sum)]
+       (doseq [[c h y] (map vector contents heights ys)]
+         (draw! 0 y *width* h c))))
+   (geometry [_]
+     (reduce #(->Size (max (width %1) (width %2))
+                      (+ (height %1) (height %2)))
+             (->Size 0 0)
+             (map geometry contents)))))
+
+(defn- re-split [^java.util.regex.Pattern re s]
+  (seq (.split re s)))
+
+(def ^:private ^Cache text-layout-cache
+  (-> (CacheBuilder/newBuilder)
+      (.softValues)
+      (.expireAfterAccess (long 1) TimeUnit/SECONDS)
+      (.build)))
+
+(defn- get-text-layout
+  [^String line ^Font font ^FontRenderContext font-context]
+  (.get text-layout-cache [line font font-context]
+        #(TextLayout. line font font-context)))
+
+(defn- layout-text
+  [lines font font-context]
+  (map #(get-text-layout % font font-context) lines))
+
+(defn- text-width [layouts]
+  (reduce (fn [w ^TextLayout tl] (max w (.getAdvance tl))) 0 layouts))
+
+(defn- text-height [layouts]
+  (reduce (fn [w ^TextLayout tl]
+            (+ w (.getAscent tl)
+               (.getDescent tl)
+               (.getLeading tl)))
+          0 layouts))
+
+(defn label
+  "Creates a view to display multiline text."
+  ([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 (:font *theme*) (font-context))
+                w (text-width layouts)
+                h (text-height layouts)]
+            (->Size w h)))))))
+
+(defn- ^ImageObserver image-observer [view]
+  (reify
+   ImageObserver
+   (imageUpdate [this img infoflags x y width height]
+     (update view)
+     (zero? (bit-and infoflags
+                     (bit-or ImageObserver/ALLBITS
+                             ImageObserver/ABORT))))))
+
+(defn image-view
+  [image-or-uri]
+  (let [^Image image (if (instance? Image image-or-uri)
+                       image-or-uri
+                       (.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))))))
+
+(def ^:dynamic *miniature-thread-priority* 2)
+
+(defn ref-view
+  [view-ref]
+  (let [l (reify
+           View
+           (render! [l]
+             (repaint-on-update l)
+             (if-let [view @view-ref]
+               (render! view)))
+           (geometry [_]
+             (if-let [view @view-ref]
+               (geometry view)
+               (->Size 1 1))))]
+    (add-watch view-ref l (fn [_ _ _ _] (update l)))
+    l))
+
+;;
+;; View context decorators
+;;
+
+(defmacro handler [view & handlers]
+  "Adds event handling to the view."
+  `(let [view# ~view]
+     (decorate-view view# [t#]
+       (with-handlers t#
+         (render! view#)
+         ~@handlers))))
+
+(defn themed [view & map-or-keyvals]
+  (let [theme (if (== (count map-or-keyvals) 1)
+                (first map-or-keyvals)
+                (apply array-map map-or-keyvals))]
+    (reify
+     View
+     (render! [_]
+       (with-theme theme
+         (render! view)))
+     (geometry [_]
+       (with-theme theme
+         (geometry view))))))
+
+(defn hinted [view & map-or-keyvals]
+  (let [hints (if (== (count map-or-keyvals) 1)
+                (first map-or-keyvals)
+                (apply array-map map-or-keyvals))]
+    (decorate-view view [_]
+      (with-hints* hints render! view))))
+
+;;
+;; Measuring time
+;;
+
+(def ^:dynamic *interval*)
+
+(defn interval-view
+  "Creates a view that measures time between repaints ant 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))))))
+
+(defn- fps-label [text]
+  (padding 5 (label :right :bottom text)))
+
+(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)))))
+
+;;
+;; Overlays
+;;
+
+(def ^:private ^:dynamic *above*)
+
+(defn- overlay* [f & args]
+  (var-set #'*above* (conj *above* (apply partial f args))))
+
+(defn- ^Point2D to-graphics-coords
+  [^AffineTransform transform x y]
+  (let [p (Point2D$Double. x y)]
+    (.transform transform p p)
+    (.transform (.createInverse (.getTransform *graphics*)) p p)
+    p))
+
+(defn- draw-relative!
+  ([transform x y view]
+     (let [p (to-graphics-coords transform x y)]
+       (draw! (.getX p) (.getY p) view)))
+  ([transform x y w h view]
+     (let [p (to-graphics-coords transform x y)]
+       (draw! (.getX p) (.getY p) w h view))))
+
+(defn- draw-relative-aligned!
+  [transform h-align v-align x y view]
+  (let [geom (geometry view)
+        w (width geom)
+        h (height geom)
+        p (to-graphics-coords transform x y)
+        x (- (.getX p) (anchor-x geom h-align w))
+        y (- (.getY p) (anchor-y geom v-align h))]
+    (draw! x y w h view)))
+
+(defn overlay!
+  "Draws view in the overlay context above the other views."
+  ([view]
+     (overlay* draw-relative! (.getTransform *graphics*) 0 0 view))
+  ([x y view]
+     (overlay* draw-relative! (.getTransform *graphics*) x y view))
+  ([x y w h view]
+     (overlay* draw-relative! (.getTransform *graphics*) x y w h view)))
+
+(defn overlay-aligned! [h-align v-align x y view]
+  (overlay* draw-relative-aligned!
+            (.getTransform *graphics*)
+            h-align v-align x y
+            view))
+
+(defn with-overlays* [rec? f & args]
+  (binding [*above* []]
+    (apply f args)
+    (if rec?
+      (loop [above *above*]
+        (when (seq above)
+          (var-set #'*above* [])
+          (doseq [f above]
+            (f))
+          (recur *above*)))
+      (doseq [of *above*]
+        (of)))))
+
+(defmacro with-overlays [rec? & body]
+  `(with-overlays* ~rec? (fn [] ~@body)))
+
+(defn layered
+  ([content]
+     (layered true content))
+  ([rec? content]
+     (decorate-view content [_]
+       (with-overlays* rec? render! content))))
--- a/src/net/kryshen/indyvon/async.clj	Mon Apr 14 15:37:28 2014 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,178 +0,0 @@
-;;
-;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-;; Indyvon is free software: you can redistribute it and/or modify it
-;; under the terms of the GNU Lesser General Public License version 3
-;; only, as published by the Free Software Foundation.
-;;
-;; Indyvon is distributed in the hope that it will be useful, but
-;; WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-;; Lesser General Public License for more details.
-;;
-;; You should have received a copy of the GNU Lesser General Public
-;; License along with Indyvon.  If not, see
-;; <http://www.gnu.org/licenses/>.
-;;
-
-(ns net.kryshen.indyvon.async
-  "Asynchronous drawing."
-  (:use
-   net.kryshen.indyvon.core)
-  (:import
-   java.awt.GraphicsConfiguration
-   (java.awt Image AlphaComposite Transparency)
-   (java.awt.image BufferedImage)
-   (java.util.concurrent ThreadFactory ThreadPoolExecutor
-                         ThreadPoolExecutor$DiscardOldestPolicy
-                         ArrayBlockingQueue TimeUnit)))
-
-(defrecord Buffer [id image readers state])
-;; Buffer states:
-;;   :front, readers > 0
-;;      being copied on screen
-;;   :back
-;;      being rendered to (offscreen)
-;;   :fresh
-;;      most recently updated
-;;   :free
-;;      not in use
-
-(defn- create-image [async-view ^GraphicsConfiguration device-conf]
-  ;; TODO: support different image types.
-  (.createCompatibleImage device-conf
-                          (:width async-view)
-                          (:height async-view)
-                          Transparency/TRANSLUCENT))
-
-(defn- create-buffer [async-view device-conf]
-  (Buffer. (Object.) (create-image async-view device-conf) 0 :free))
-
-(defn- find-buffer
-  "Find a buffer with the one of the specified states given
-   in the order of preference."
-  [buffers & states]
-  (some identity
-    (for [state states]
-      (some #(if (= (:state %) state) % nil) buffers))))
-
-(defn- replace-buffer [buffers buffer]
-  (conj (remove #(= (:id %) (:id buffer)) buffers)
-        buffer))
-
-(defn- take-buffer [al type]
-  (dosync
-   (let [buffers @(:buffers al)
-         b (case type
-             :front (find-buffer buffers :front :fresh :free)
-             :back (find-buffer buffers :free :fresh)
-             (throw (IllegalArgumentException.)))
-         readers (if (= type :front)
-                   (inc (:readers b))
-                   (:readers b))
-         b (assoc b
-             :state type
-             :readers readers)]
-     (alter (:buffers al) replace-buffer b)
-     b)))
-
-(defn- release-buffer [al buffer]
-  (dosync
-   (let [state (:state buffer)
-         readers (if (= state :front)
-                   (dec (:readers buffer))
-                   (:readers buffer))
-         fresh (delay (find-buffer @(:buffers al) :fresh))
-         state (cond
-                (pos? readers) :front
-                (= :back state) :fresh
-                @fresh :free
-                :default :fresh)]
-     (if (and (= state :fresh) @fresh)
-       ;; Change state of the prefiously fresh buffer to :free.
-       (alter (:buffers al)
-              replace-buffer (assoc @fresh
-                               :state :free)))
-     (alter (:buffers al)
-            replace-buffer (assoc buffer
-                             :state state
-                             :readers readers)))))
-
-(defmacro with-buffer
-  {:private true}
-  [al type [name] & body]
-  `(let [al# ~al
-         ~name (take-buffer al# ~type)]
-     (try
-       ~@body
-       (finally
-        (release-buffer al# ~name)))))
-
-(defn- draw-offscreen [async-view]
-  ;;(Thread/sleep 1000)
-  (with-buffer async-view :back [b]
-    (let [g (.createGraphics ^BufferedImage (:image b))]
-      ;; Clear the buffer.
-      (.setComposite g AlphaComposite/Clear)
-      (.fillRect g 0 0 (:width async-view) (:height async-view))
-      (.setComposite g AlphaComposite/Src)
-      (draw-scene! (:scene async-view)
-                   g
-                   (:width async-view)
-                   (:height async-view)))
-    (update async-view)))
-
-(defn- draw-offscreen-async [async-view]
-  (.execute ^ThreadPoolExecutor (:executor async-view)
-            #(draw-offscreen async-view)))
-
-(defrecord AsyncView [scene width height executor buffers]
-  View
-  (render! [view]
-    (repaint-on-update view)
-    (add-context-observer scene (fn [_ _] (draw-offscreen-async view)))
-    (when-not @buffers
-      ;; TODO: dynamic size, recreate buffers when size increases.
-      (let [device-conf (.getDeviceConfiguration *graphics*)
-            new-buffers (repeatedly 2
-                          (partial create-buffer view device-conf))]
-        (dosync
-         (ref-set buffers new-buffers)))
-      (draw-offscreen-async view))
-    (with-buffer view :front [b]
-      (.drawImage *graphics* ^Image (:image b) 0 0 nil)))
-  (geometry [view]
-    (->Size width height)))
-
-(defn- create-thread-factory [priority]
-  (reify
-   ThreadFactory
-   (newThread [_ runnable]
-     (let [thread (Thread. runnable)]
-       (when priority
-         (.setPriority thread priority))
-       (.setDaemon thread true)
-       thread))))
-
-(defn- create-executor [priority]
-  (doto (ThreadPoolExecutor.
-         (int 1) (int 1)
-         (long 0) TimeUnit/SECONDS
-         (ArrayBlockingQueue. 1)
-         (ThreadPoolExecutor$DiscardOldestPolicy.))
-    (.setThreadFactory (create-thread-factory priority))))
-
-(defn async-view 
-  "Creates a View that draws the content asynchronously using an
-   offscreen buffer."
-  ([width height content]
-     (async-view width height nil content))
-  ([width height priority content]
-     ;; TODO: use operational event dispatcher.
-     (->AsyncView (make-scene content)
-                  width
-                  height
-                  (create-executor priority)
-                  (ref nil))))
--- a/src/net/kryshen/indyvon/component.clj	Mon Apr 14 15:37:28 2014 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-;;
-;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-;; Indyvon is free software: you can redistribute it and/or modify it
-;; under the terms of the GNU Lesser General Public License version 3
-;; only, as published by the Free Software Foundation.
-;;
-;; Indyvon is distributed in the hope that it will be useful, but
-;; WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-;; Lesser General Public License for more details.
-;;
-;; You should have received a copy of the GNU Lesser General Public
-;; License along with Indyvon.  If not, see
-;; <http://www.gnu.org/licenses/>.
-;;
-
-(ns net.kryshen.indyvon.component
-  "Integrating Indyvon into AWT and Swing components."
-  (:use
-   net.kryshen.indyvon.core)
-  (:import
-   (java.awt Graphics Component Dimension Color)
-   (java.awt.geom Rectangle2D$Double)
-   (javax.swing JFrame JPanel JOptionPane)))
-
-(defn- paint-component [^Component c ^Graphics g scene]
-  (let [w (.getWidth c)
-        h (.getHeight c)]
-    (.setColor g (:back-color *theme*))
-    (.fillRect g 0 0 w h)
-    (draw-scene! scene g w h)))
-
-(defn- preferred-size [^Component c scene]
-  (let [geom (scene-geometry scene)]
-    (Dimension. (width geom) (height geom))))
-
-(defn ^JPanel make-jpanel
-  ([view]
-     (make-jpanel view (root-event-dispatcher)))
-  ([view event-dispatcher]
-     (let [panel (proxy [JPanel] [])
-           scene (make-scene
-                  view event-dispatcher panel
-                  (.getDesktopProperty (java.awt.Toolkit/getDefaultToolkit)
-                                       "awt.font.desktophints"))]
-       (update-proxy
-        panel
-        {"paintComponent" #(paint-component %1 %2 scene)
-         "getPreferredSize" #(preferred-size % scene)})
-       (.setBackground panel (:back-color *theme*))
-       (add-observer panel scene (fn [w _]
-                                   ;; Use the first observer argument
-                                   ;; instead of closing over panel to
-                                   ;; allow the panel and associated
-                                   ;; observer to be gc'd.
-                                   (.repaint ^Component w)))
-       (listen! event-dispatcher panel)
-       panel)))
-
-(defn ^JFrame make-jframe [^String title view]
-  (doto (JFrame. title)
-    (.. (getContentPane) (add (make-jpanel view)))
-    (.pack)))
-
-(defn message [m]
-  (JOptionPane/showMessageDialog (:component *scene*) m))
--- a/src/net/kryshen/indyvon/core.clj	Mon Apr 14 15:37:28 2014 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,881 +0,0 @@
-;;
-;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-;; Indyvon is free software: you can redistribute it and/or modify it
-;; under the terms of the GNU Lesser General Public License version 3
-;; only, as published by the Free Software Foundation.
-;;
-;; Indyvon is distributed in the hope that it will be useful, but
-;; WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-;; Lesser General Public License for more details.
-;;
-;; You should have received a copy of the GNU Lesser General Public
-;; License along with Indyvon.  If not, see
-;; <http://www.gnu.org/licenses/>.
-;;
-
-(ns net.kryshen.indyvon.core
-  (:import
-   (java.awt Graphics2D RenderingHints Component Color Font Shape
-             Rectangle Cursor EventQueue)
-   (java.awt.geom AffineTransform Point2D$Double Rectangle2D$Double Area)
-   (java.awt.event MouseListener MouseMotionListener
-                   MouseWheelListener MouseWheelEvent)
-   (java.awt.font FontRenderContext)
-   java.util.concurrent.ConcurrentMap
-   com.google.common.collect.MapMaker))
-
-;;
-;; View context
-;;
-
-(def ^:dynamic ^Graphics2D *graphics*)
-
-(def ^:dynamic ^FontRenderContext *font-context*
-  "FontRenderContext to use when Graphics2D is not available."
-  (FontRenderContext.
-   nil
-   RenderingHints/VALUE_TEXT_ANTIALIAS_DEFAULT
-   RenderingHints/VALUE_FRACTIONALMETRICS_DEFAULT))
-
-(def ^:dynamic *width*
-  "Width of the rendering area.")
-
-(def ^:dynamic *height*
-  "Height of the rendering area.")
-
-(def ^:dynamic ^Shape *clip*)
-
-(def ^:dynamic ^Shape *input-clip*
-  "Clipping area used for dispatching pointer events (intersected with
-  *clip*). If nil, *clip* will be used.")
-
-(def ^:dynamic *time*
-  "Timestamp of the current frame (in nanoseconds).")
-
-(def ^:dynamic *scene*
-  "Encloses state that should be retained between repaints.")
-
-(def ^:dynamic *states*
-  "Transient scene states, a map.")
-
-(def ^:dynamic *event-dispatcher*)
-
-(def ^:dynamic ^AffineTransform *initial-transform*
-  "Initial transform associated with the graphics context.")
-
-(def ^:dynamic ^AffineTransform *inverse-initial-transform*
-  "Inversion of the initial transform associated with the graphics
-  context.")
-
-(defrecord Theme [fore-color back-color alt-back-color border-color
-                  shadow-color font])
-
-;; REMIND: use system colors, see java.awt.SystemColor.
-(defn default-theme []
-  (Theme. Color/BLACK
-          Color/WHITE
-          (Color. 0xDD 0xDD 0xDD)
-          (Color. 0 0 0xCC)
-          (Color. 0x44 0x44 0x44)
-          (Font. "Sans" Font/PLAIN 12)))
-
-(def ^:dynamic *theme* (default-theme))
-
-;;
-;; Core protocols and types
-;;
-
-(defprotocol View
-  "Basic UI element."
-  (render! [view]
-    "Draws the view in the current *graphics* context.")
-  (geometry [view]
-    "Returns the preferred Geometry for the view."))
-
-(defprotocol Geometry
-  "Describes geometry of a View.  Prefer using the available
-  implementations (Size, FixedGeometry and NestedGeometry) over
-  extending this protocol directly as it is likely to be changed in
-  the future versions."
-  (width [geom] [geom height])
-  (height [geom] [geom width])
-  (anchor-x [geom h-align width]
-    "Returns the x coordinate of the anchor point for the specified
-     horizontal alignment and width, h-align could be :left, :center
-     or :right.")
-  (anchor-y [geom v-align height]
-    "Returns the y coordinate of the anchor point for the specified
-    vertical alignment and height, v-align could be :top, :center
-    or :bottom."))
-
-(defn- emit-align-xy [align size first center last]
-  `(case ~align
-         ~first 0
-         ~center (/ ~size 2)
-         ~last ~size))
-
-;; Define as macro to avoid unnecessary calculation of width or height.
-(defmacro align-x
-  ([align inner outer]
-     `(align-x ~align (- ~outer ~inner)))
-  ([align width]
-     (emit-align-xy align width :left :center :right)))
-
-(defmacro align-y
-  ([align inner outer]
-     `(align-y ~align (- ~outer ~inner)))
-  ([align height]
-     (emit-align-xy align height :top :center :bottom)))
-
-(defrecord Size [width height]
-  Geometry
-  (width  [_] width)
-  (width [_ _] width)
-  (height [_] height)
-  (height [_ _] height)
-  (anchor-x [_ h-align width]
-    (align-x h-align width))
-  (anchor-y [_ v-align height]
-    (align-y v-align height)))
-
-(defrecord FixedGeometry [ax ay width height]
-  Geometry
-  (width  [_] width)
-  (width [_ _] width)
-  (height [_] height)
-  (height [_ _] height)
-  (anchor-x [_ _ _] ax)
-  (anchor-y [_ _ _] ay))
-
-(defrecord NestedGeometry [geometry top left bottom right]
-  Geometry
-  (width  [_]
-    (+ left right (width geometry)))
-  (width [_ h]
-    (+ left right (width geometry (- h top bottom))))
-  (height [_]
-    (+ top bottom (height geometry)))
-  (height [_ w]
-    (+ top bottom (height geometry (- w left right))))
-  (anchor-x [_ h-align w]
-    (+ left (anchor-x geometry h-align (- w left right))))
-  (anchor-y [_ v-align h]
-    (+ top (anchor-y geometry v-align (- h top bottom)))))
-
-(defrecord ScaledGeometry [geometry sx sy]
-  Geometry
-  (width  [_]
-    (* sx (width geometry)))
-  (width [_ h]
-    (* sx (width geometry (/ h sy))))
-  (height [_]
-    (* sy (height geometry)))
-  (height [_ w]
-    (* sy (height geometry (/ w sx))))
-  (anchor-x [_ h-align w]
-    (* sx (anchor-x geometry h-align (/ w sx))))
-  (anchor-y [_ v-align h]
-    (* sy (anchor-y geometry v-align (/ h sy)))))
-
-;; (defn ^:private to-integer
-;;   ^long [align x]
-;;   (if (integer? x)
-;;     x
-;;     (let [x (double x)]
-;;       (Math/round
-;;        (case align
-;;          (:top :left) (Math/floor x)
-;;          :center x
-;;          (:bottom :right) (Math/ceil x))))))
-
-;; (defrecord IntegerGeometry [geometry]
-;;   Geometry
-;;   (width [_]
-;;     (to-integer :right (width geometry)))
-;;   (width [_ h]
-;;     (to-integer :right (width geometry h)))
-;;   (height [_]
-;;     (to-integer :bottom (height geometry)))
-;;   (height [_ w]
-;;     (to-integer :bottom (height geometry w)))
-;;   (anchor-x [_ h-align w]
-;;     (to-integer h-align (anchor-x geometry h-align w)))
-;;   (anchor-y [_ v-align h]
-;;     (to-integer v-align (anchor-y geometry v-align h))))
-
-;; TODO: modifiers
-(defrecord MouseEvent [id when x y x-on-screen y-on-screen button
-                       wheel-rotation transform component])
-
-;; TODO: KeyEvent
-
-(defprotocol EventDispatcher
-  (listen! [this component]
-     "Listen for events on the specified AWT Component.")
-  (create-dispatcher [this handle handlers]
-     "Returns new event dispatcher associated with the specified event
-      handlers (an event-id -> handler-fn map). Handle is used to
-      match the contexts between commits.")
-  (commit [this]
-     "Apply the registered handlers for event processing.")
-  (handle-picked? [this handle]
-     "Returns true if the specified handle received the :mouse-pressed
-      event and have not yet received :moused-released.")
-  (handle-hovered? [this handle]
-     "Returns true if the specified handle received the :mouse-entered
-      event and have not yet received :mouse-exited."))
-
-(defn- assoc-cons [m key val]
-  (->> (get m key) (cons val) (assoc m key)))
-
-;;
-;; Observers
-;; The mechanism used by views to request repaints
-;;
-
-(def ^ConcurrentMap observers
-     (-> (MapMaker.) (.weakKeys) (.makeMap)))
-
-(defn- cm-replace!
-  "Wrap ConcurrentMap replace method to treat nil value as absent
-   mapping. Use with maps that does not support nil values."
-  [^ConcurrentMap cmap key old new]
-  (if (nil? old)
-    (nil? (.putIfAbsent cmap key new))
-    (.replace cmap key old new)))
-
-(defn- cm-swap!
-  "Atomically swaps the value associated with key in ConcurrentMap
-   to be (apply f current-value args). Returns the new value."
-  [^ConcurrentMap cmap key f & args]
-  (loop []
-    (let [old (.get cmap key)
-          new (apply f old args)]
-      (if (cm-replace! cmap key old new)
-        new
-        (recur)))))
-
-(defn add-observer
-  "Add observer fn for the target. Watcher identifies the group of
-  observers and could be used to remove the group. Watcher is weakly
-  referenced, all associated observers will be removed when the
-  wathcer is removed by gc. The observer fn will be called with
-  watcher and target arguments and any additional arguments specified
-  in update call."
-  [watcher target f]
-  (cm-swap! observers watcher assoc-cons target f)
-  nil)
-
-(defn remove-observers
-  "Remove group of observers associated with the specified watcher."
-  [watcher]
-  (.remove observers watcher)
-  nil)
-
-(defn- replace-observers-watcher
-  [old-watcher new-watcher]
-  (if-let [old (.remove observers old-watcher)]
-    (.put observers new-watcher old))
-  nil)
-
-(defn update
-  "Notify observers."
-  [target & args]
-  (doseq [entry observers
-          f (get (val entry) target)]
-    (apply f (key entry) target args)))
-
-(defn add-context-observer
-  "Observer registered with this function will be automatically
-  removed after the next repaint is complete."
-  [target f]
-  (add-observer *scene* target f))
-
-(defn repaint-on-update
-  "Trigger repaint of the current scene when the target updates."
-  [target]
-  (let [scene *scene*]
-    (if-not (identical? scene target)
-      (add-observer scene target (fn [w _] (update w))))))
-
-(defn repaint
-  "Requests repaint of the current scene. If handle and state are
-  specified, the handle will be associated with the state in the
-  *states* map for the next paint iteration."
-  ([]
-     (update *scene*))
-  ([handle state]
-     (let [scene *scene*]
-       (swap! (:next-state scene) assoc handle state)
-       (update scene))))
-
-;;
-;; Rendering
-;;
-
-(defn ^FontRenderContext font-context
-  "Returns FontRenderContext for the current view context."
-  []
-  (if (bound? (var *graphics*))
-    (.getFontRenderContext *graphics*)
-    *font-context*))
-
-(defn ^AffineTransform relative-transform
-  "Returns AffineTransform: view context -> AWT component."
-  []
-  (let [tr (.getTransform *graphics*)]
-    (.preConcatenate tr *inverse-initial-transform*)
-    tr))
-
-(defn ^AffineTransform inverse-relative-transform
-  "Returns AffineTransform: AWT component -> view context."
-  []
-  (let [tr (.getTransform *graphics*)]
-    (.invert tr)                          ; absolute -> view
-    (.concatenate tr *initial-transform*) ; component -> absolute
-    tr))
-
-(defn transform-point [^AffineTransform tr ^double x ^double y]
-  (let [p (Point2D$Double. x y)]
-    (.transform tr p p)
-    [(.x p) (.y p)]))
-
-(defn inverse-transform-point [^AffineTransform tr ^double x ^double y]
-  (let [p (Point2D$Double. x y)]
-    (.inverseTransform tr p p)
-    [(.x p) (.y p)]))
-
-;; (defn- clip
-;;   "Intersect clipping area with the specified shape or bounds.
-;;    Returns new clip (Shape or nil if empty)."
-;;   ([x y w h]
-;;      (clip (Rectangle2D$Double. x y w h)))
-;;   ([shape]
-;;      (let [a1 (Area. shape)
-;;            a2 (if (instance? Area *clip*) *clip* (Area. *clip*))]
-;;        (.transform a1 (relative-transform))
-;;        (.intersect a1 a2)
-;;        (if (.isEmpty a1)
-;;          nil
-;;          a1))))
-
-;; Use faster clipping calculation provided by Graphics2D.
-(defn- clip
-  "Intersect clipping area with the specified Shape in current
-   transform coordinates. Returns new clip in the AWT component
-   coordinates (Shape or nil if empty)."
-  [^Shape shape]
-  (let [^Graphics2D clip-g (.create *graphics*)]
-    (try
-      (doto clip-g
-        (.setClip shape)
-        (.setTransform *initial-transform*)
-        (.clip *clip*))
-      (if (.isEmpty (.getClipBounds clip-g))
-        nil
-        (.getClip clip-g))
-      (finally
-       (.dispose clip-g)))))
-
-(defn- ^Graphics2D apply-theme
-  "Set graphics' color and font to match theme.
-   Modifies and returns the first argument."
-  ([]
-     (apply-theme *graphics* *theme*))
-  ([^Graphics2D graphics theme]
-     (doto graphics
-       (.setColor (:fore-color theme))
-       (.setFont (:font theme)))))
-
-(defn- ^Graphics2D create-graphics
-  ([]
-     (apply-theme (.create *graphics*) *theme*))
-  ([^long x ^long y ^long w ^long h]
-     (apply-theme (.create *graphics* x y w h) *theme*)))
-
-(defn- with-bounds-noclip*
-  [x y w h f & args]
-  (let [graphics (create-graphics)]
-    (try
-      (.translate graphics (double x) (double y))
-      (binding [*width* w
-                *height* h
-                *input-clip* (Rectangle2D$Double. 0.0 0.0 w h)
-                *graphics* graphics]
-        (apply f args))
-      (finally
-       (.dispose graphics)))))
-
-(defn with-bounds*
-  [x y w h f & args]
-  (let [x (double x)
-        y (double y)
-        bounds (Rectangle2D$Double. x y w h)]
-    (when-let [clip (clip bounds)]
-      (let [^Graphics2D graphics (create-graphics)]
-        (try
-          (.clip graphics bounds)
-          (.translate graphics x y)
-          (binding [*width* w
-                    *height* h
-                    *clip* clip
-                    *input-clip* nil
-                    *graphics* graphics]
-            (apply f args))
-          (finally
-           (.dispose graphics)))))))
-
-(defmacro with-bounds
-  [x y w h & body]
-  `(with-bounds* ~x ~y ~w ~h (fn [] ~@body)))
-
-(defmacro with-theme
-  [theme & body]
-  `(binding [*theme* (merge *theme* ~theme)]
-     ~@body))
-
-(defmacro with-color [color-or-key & body]
-  `(let [color# ~color-or-key
-         color# (get *theme* color# color#)
-         g# *graphics*
-         old-color# (.getColor g#)]
-     (try
-       (.setColor g# color#)
-       ~@body
-       (finally
-         (.setColor g# old-color#)))))
-
-(defmacro with-stroke [stroke & body]
-  `(let [g# *graphics*
-         old-stroke# (.getStroke g#)]
-     (try
-       (.setStroke g# ~stroke)
-       ~@body
-       (finally
-        (.setStroke g# old-stroke#)))))
-
-(defmacro with-hints
-  [hints & body]
-  `(let [h# ~hints
-         g# *graphics*
-         old# (.getRenderingHints g#)]
-     (try
-       (.addRenderingHints g# h#)
-       ~@body
-       (finally
-        (.setRenderingHints g# old#)))))
-
-(defn with-hints* [hints f & args]
-  (with-hints hints
-    (apply f args)))
-
-;; TODO: constructor for AffineTransform.
-;; (transform :scale 0.3 0.5
-;;            :translate 5 10
-;;            :rotate (/ Math/PI 2))
-
-(defmacro with-transform [transform & body]
-  `(let [g# *graphics*
-         old-t# (.getTransform g#)]
-     (try
-       (.transform g# ~transform)
-       ~@body
-       (finally
-        (.setTransform g# old-t#)))))
-
-(defmacro with-rotate [theta ax ay & body]
-  `(let [transform# (AffineTransform/getRotateInstance ~theta ~ax ~ay)]
-     (with-transform transform# ~@body)))
-
-(defmacro with-translate [x y & body]
-  `(let [x# ~x
-         y# ~y
-         g# *graphics*]
-     (try
-       (.translate g# x# y#)
-       ~@body
-       (finally
-        (.translate g# (- x#) (- y#))))))
-
-(defn draw!
-  "Draws the View."
-  ([view]
-     (let [graphics (create-graphics)]
-       (try
-         (binding [*graphics* graphics]
-           (render! view))
-         (finally
-          (.dispose graphics)))))
-  ([x y view]
-     (draw! x y true view))
-  ([x y clip? view]
-     (let [geom (geometry view)]
-       (draw! x y (width geom) (height geom) clip? view)))
-  ([x y width height view]
-     (draw! x y width height true view))
-  ([x y width height clip? view]
-     (if clip?
-       (with-bounds* x y width height render! view)
-       (with-bounds-noclip* x y width height render! view))))
-
-(defn draw-aligned!
-  "Draws the View.  Location is relative to the view's anchor point
-   for the specified alignment."
-  ([h-align v-align x y view]
-     (let [geom (geometry view)
-           w (width geom)
-           h (height geom)]
-       (draw! (- x (anchor-x geom h-align w))
-              (- y (anchor-y geom v-align h))
-              w h
-              view)))
-  ([h-align v-align x y w h view]
-     (let [geom (geometry view)]
-       (draw! (- x (anchor-x geom h-align w))
-              (- y (anchor-y geom v-align h))
-              w h
-              view))))
-
-;;
-;; Event handling.
-;;
-
-(defn with-handlers*
-  [handle handlers f & args]
-  (binding [*event-dispatcher* (create-dispatcher
-                                *event-dispatcher* handle handlers)]
-    (apply f args)))
-
-(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)
-     (fn [] ~form)))
-
-(defn picked? [handle]
-  (handle-picked? *event-dispatcher* handle))
-
-(defn hovered? [handle]
-  (handle-hovered? *event-dispatcher* handle))
-
-;;
-;; EventDispatcher implementation
-;;
-
-(def awt-events
-     {java.awt.event.MouseEvent/MOUSE_CLICKED  :mouse-clicked
-      java.awt.event.MouseEvent/MOUSE_DRAGGED  :mouse-dragged
-      java.awt.event.MouseEvent/MOUSE_ENTERED  :mouse-entered
-      java.awt.event.MouseEvent/MOUSE_EXITED   :mouse-exited
-      java.awt.event.MouseEvent/MOUSE_MOVED    :mouse-moved
-      java.awt.event.MouseEvent/MOUSE_PRESSED  :mouse-pressed
-      java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released
-      java.awt.event.MouseEvent/MOUSE_WHEEL    :mouse-wheel})
-
-(def dummy-event-dispatcher
-  (reify EventDispatcher
-    (listen! [_ _])
-    (create-dispatcher [this _ _] this)
-    (commit [_])
-    (handle-picked? [_ _])
-    (handle-hovered? [_ _])))
-
-;; Not using defrecord to avoid unacceptable overhead of recursive
-;; hash code calculation.
-(deftype DispatcherNode [handle handlers parent
-                         ^Shape clip ^AffineTransform transform
-                         bindings]
-  EventDispatcher
-  (listen! [this component]
-    (listen! parent component))
-  (create-dispatcher [this handle handlers]
-    (create-dispatcher parent handle handlers))
-  (commit [this]
-    (commit parent))
-  (handle-picked? [this handle]
-    (handle-picked? parent handle))
-  (handle-hovered? [this handle]
-    (handle-hovered? parent handle)))
-
-(defn- make-node [handle handlers]
-  (let [clip (if *input-clip*
-               (clip *input-clip*)
-               *clip*)
-        bindings (-> (get-thread-bindings)
-                     (dissoc (var *graphics*))
-                     (assoc (var *font-context*) (font-context)))]
-    (DispatcherNode. handle handlers *event-dispatcher* clip
-                     (relative-transform)
-                     bindings)))
-
-(defn- add-node [tree ^DispatcherNode node]
-  (assoc-cons tree (.parent node) node))
-
-(defn- nodes [tree]
-  (apply concat (vals tree)))
-
-(defn- under-cursor
-  "Returns a vector of child nodes under cursor."
-  [node tree ^long x ^long y]
-  (some (fn [^DispatcherNode n]
-          (if (and (.clip n) (.contains ^Shape (.clip n) x y))
-            (conj (vec (under-cursor n tree x y)) n)))
-        (get tree node)))
-
-(defn- translate-mouse-event [^java.awt.event.MouseEvent event
-                              ^AffineTransform tr id]
-  (let [[x y] (inverse-transform-point tr (.getX event) (.getY event))
-        rotation (if (instance? MouseWheelEvent event)
-                   (.getWheelRotation ^MouseWheelEvent event)
-                   nil)]
-    (->MouseEvent id (.getWhen event) x y
-                  (.getXOnScreen event) (.getYOnScreen event)
-                  (.getButton event)
-                  rotation
-                  tr
-                  (.getComponent event))))
-
-(defn- translate-and-dispatch
-  ([nodes first-only ^java.awt.event.MouseEvent event]
-     (translate-and-dispatch nodes first-only
-                             event (awt-events (.getID event))))
-  ([nodes first-only event id]
-     (if-let [^DispatcherNode node (first nodes)]
-       (let [handlers (.handlers node)
-             handler (get handlers id)]
-         (if handler
-           (do
-             (with-bindings* (.bindings node)
-               handler
-               (translate-mouse-event event (.transform node) id))
-             (when-not first-only
-               (recur (rest nodes) false event id)))
-           (when-not (and (= id :mouse-dragged)
-                          (or (contains? handlers :mouse-pressed)
-                              (contains? handlers :mouse-released)))
-             (recur (rest nodes) first-only event id)))))))
-
-(defn- process-mouse-event
-  [dispatcher ^java.awt.event.MouseEvent source-event]
-  (let [{active-ref :active
-         hovered-ref :hovered
-         picked-ref :picked
-         last-ref :last-motion
-         tree-ref :tree} dispatcher
-         pressed (and source-event
-                      (== (.getID source-event)
-                          java.awt.event.MouseEvent/MOUSE_PRESSED))
-         released (and source-event
-                       (== (.getID source-event)
-                           java.awt.event.MouseEvent/MOUSE_RELEASED))
-         ^java.awt.event.MouseEvent last-event @last-ref
-         ^java.awt.event.MouseEvent event (or source-event last-event)]
-    (when event
-      (let [x (.getX event)
-            y (.getY event)
-            active @active-ref
-            active (if (and active
-                            source-event
-                            (== (.getX last-event) x)
-                            (== (.getY last-event) y))
-                     active
-                     (ref-set active-ref
-                              (under-cursor dispatcher @tree-ref x y)))
-            acted (cond
-                   pressed (ref-set picked-ref active)
-                   released (let [picked @picked-ref]
-                              (ref-set picked-ref nil)
-                              picked)
-                   :else active)
-            picked (seq @picked-ref)
-            pred #(= (.handle ^DispatcherNode %1) (.handle ^DispatcherNode %2))
-            hovered (if picked
-                      (filter #(some (partial pred %) picked) active)
-                      active)
-            remove-all (fn [c1 c2]
-                         (filter #(not (some (partial pred %) c2)) c1))
-            old-hovered @hovered-ref
-            exited (remove-all old-hovered hovered)
-            entered (remove-all hovered old-hovered)
-            moved (or picked (remove-all hovered entered))]
-        (ref-set hovered-ref hovered)
-        (ref-set last-ref event)
-        [exited entered moved acted event]))))
-
-(defn- dispatch-mouse-event
-  [dispatcher source-event button?]
-  (when-let [[exited
-              entered
-              moved
-              acted
-              event] (dosync (process-mouse-event dispatcher source-event))]
-    (when button?
-      (translate-and-dispatch acted true event))
-    (translate-and-dispatch exited false event :mouse-exited)
-    (translate-and-dispatch entered false event :mouse-entered)
-    (when-not button?
-      (translate-and-dispatch moved true source-event))))
-
-(defrecord RootEventDispatcher [tree-r  ;; register
-                                tree    ;; dispatch
-                                active  ;; nodes under cursor
-                                hovered ;; mouse entered
-                                picked  ;; mouse pressed
-                                last-motion]
-  EventDispatcher
-  (listen! [dispatcher component]
-    (doto ^Component component
-          (.addMouseListener dispatcher)
-          (.addMouseWheelListener dispatcher)
-          (.addMouseMotionListener dispatcher)))
-  (create-dispatcher [dispatcher handle handlers]
-    (let [node (make-node handle handlers)]
-      (dosync (alter tree-r add-node node))
-      node))
-  (commit [dispatcher]
-    (let [[exited
-           entered
-           _ _
-           event] (dosync
-                   ;; TODO: retain contexts that do
-                   ;; not intersect graphics
-                   ;; clipping area in tree.
-                   (ref-set tree @tree-r)
-                   (ref-set tree-r {})
-                   (process-mouse-event dispatcher nil))]
-      ;; Send mouse entered and exited events if necessary due to
-      ;; updated layout.
-      (translate-and-dispatch exited false event :mouse-exited)
-      (translate-and-dispatch entered false event :mouse-entered)))
-  (handle-picked? [dispatcher handle]
-    (some #(= handle (.handle ^DispatcherNode %)) @picked))
-  (handle-hovered? [dispatcher handle]
-    (some #(= handle (.handle ^DispatcherNode %)) @hovered))
-  MouseListener
-  (mouseEntered [dispatcher event]
-    (dispatch-mouse-event dispatcher event false))
-  (mouseExited [dispatcher event]
-    (dispatch-mouse-event dispatcher event false))
-  (mouseClicked [dispatcher event]
-    (dispatch-mouse-event dispatcher event true))
-  (mousePressed [dispatcher event]
-    (dispatch-mouse-event dispatcher event true))
-  (mouseReleased [dispatcher event]
-    (dispatch-mouse-event dispatcher event true))
-  MouseWheelListener
-  (mouseWheelMoved [dispatcher event]
-    (dispatch-mouse-event dispatcher event true))
-  MouseMotionListener
-  (mouseDragged [dispatcher event]
-    (dispatch-mouse-event dispatcher event false))
-  (mouseMoved [dispatcher event]
-    (dispatch-mouse-event dispatcher event false)))
-
-(defn root-event-dispatcher []
-  (->RootEventDispatcher
-   (ref {}) (ref {})             ;; trees
-   (ref nil) (ref nil) (ref nil) ;; node states
-   (ref nil)))                   ;; last event
-
-;;
-;; Scene
-;;
-
-(defrecord Scene [view
-                  event-dispatcher
-                  component
-                  rendering-hints
-                  next-state])
-
-;; Define rendering hints that affect font metrics to make sure that
-;; Graphics and Scene FontRenderContexts are consistent.
-(def ^:private default-rendering-hints
-  {RenderingHints/KEY_TEXT_ANTIALIASING
-   RenderingHints/VALUE_TEXT_ANTIALIAS_DEFAULT,
-   RenderingHints/KEY_FRACTIONALMETRICS
-   RenderingHints/VALUE_FRACTIONALMETRICS_DEFAULT})
-
-(defn make-scene
-  ([view]
-     (make-scene view dummy-event-dispatcher nil))
-  ([view event-dispatcher]
-     (make-scene view event-dispatcher nil))
-  ([view event-dispatcher ^Component component]
-     (make-scene view event-dispatcher component nil))
-  ([view event-dispatcher ^Component component hints]
-     (let [hints (merge default-rendering-hints hints)]
-       (->Scene view
-                event-dispatcher
-                component
-                hints
-                (atom nil)))))
-
-(defn- get-and-set!
-  "Atomically sets the value of atom to newval and returns the old
-  value."
-  [atom newval]
-  (loop [v @atom]
-    (if (compare-and-set! atom v newval)
-      v
-      (recur @atom))))
-
-(defn draw-scene!
-  [scene ^Graphics2D graphics width height]
-  (.addRenderingHints graphics (:rendering-hints scene))
-  (binding [*states* (get-and-set! (:next-state scene) nil)
-            *scene* scene
-            *graphics* graphics
-            *initial-transform* (.getTransform graphics)
-            *inverse-initial-transform* (-> graphics
-                                            .getTransform
-                                            .createInverse)
-            *event-dispatcher* (:event-dispatcher scene)
-            *width* width
-            *height* height
-            *clip* (Rectangle2D$Double. 0.0 0.0 width height)
-            *input-clip* nil
-            *time* (System/nanoTime)]
-    (apply-theme)
-    (let [tmp-watcher (Object.)]
-      ;; Keep current context observers until the rendering is
-      ;; complete. Some observers may be invoked twice if they
-      ;; appear in both groups until tmp-watcher is removed.
-      (replace-observers-watcher scene tmp-watcher)
-      (try
-        (render! (:view scene))
-        (finally
-         (remove-observers tmp-watcher)
-         (commit (:event-dispatcher scene)))))))
-
-(defn- scene-font-context [scene]
-  (let [hints (:rendering-hints scene)
-        ^Component c (:component scene)
-        t (if c (->> c
-                     .getFont
-                     (.getFontMetrics c)
-                     .getFontRenderContext
-                     .getTransform))]
-    (FontRenderContext.
-     t
-     (get hints RenderingHints/KEY_TEXT_ANTIALIASING)
-     (get hints RenderingHints/KEY_FRACTIONALMETRICS))))      
-
-(defn scene-geometry [scene]
-  (binding [*scene* scene
-            *font-context* (scene-font-context scene)]
-    (geometry (:view scene))))
-
-(defn set-cursor! [^Cursor cursor]
-  (when-let [^Component component (:component *scene*)]
-    (EventQueue/invokeLater #(.setCursor component cursor))))
--- a/src/net/kryshen/indyvon/demo.clj	Mon Apr 14 15:37:28 2014 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,223 +0,0 @@
-;;
-;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-;; Indyvon is free software: you can redistribute it and/or modify it
-;; under the terms of the GNU Lesser General Public License version 3
-;; only, as published by the Free Software Foundation.
-;;
-;; Indyvon is distributed in the hope that it will be useful, but
-;; WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-;; Lesser General Public License for more details.
-;;
-;; You should have received a copy of the GNU Lesser General Public
-;; License along with Indyvon.  If not, see
-;; <http://www.gnu.org/licenses/>.
-;;
-
-(ns net.kryshen.indyvon.demo
-  "Indyvon demo and experiments."
-  (:gen-class)
-  (:use
-   (net.kryshen.indyvon core views viewport component))
-  (:import
-   (java.awt Color)
-   (javax.swing JFrame)))
-
-(defn draw-button!
-  "Draws a button immediately (but uses callback for the action unlike
-   IMGUI)."
-  [id content callback & args]
-  (with-handlers id
-    (let [shadow-offset 2
-          padding 4
-          border-width 1
-          offset (if (picked? id) (/ shadow-offset 2) 0)
-          ^Color color (:alt-back-color *theme*)
-          color (if (hovered? id) (.brighter color) color)
-          width (- *width* shadow-offset)
-          height (- *height* shadow-offset)]
-      (with-color (:shadow-color *theme*)
-        (.fillRect *graphics* shadow-offset shadow-offset width height))
-      (with-color color
-        (.fillRect *graphics* offset offset width height))
-      (draw! offset offset width height
-             (border border-width padding content)))
-    ;; Event handlers
-    (:mouse-entered _ (repaint))
-    (:mouse-exited _ (repaint))
-    (:mouse-pressed _ (repaint))
-    (:mouse-released _ (repaint))
-    (:mouse-clicked _ (apply callback args))))
-
-(defn combine-colors
-  "Returns color between color1 and color2. When c (0 <= c <= 1.0) is
-   closer to 0 the returned сolor is closer to color1."
-  [^Color color1 ^Color color2 c]
-  (case c
-    0.0 color1
-    1.0 color2
-    (let [rgb1 (.getRGBComponents color1 nil)
-          rgb2 (.getRGBComponents color2 nil)
-          rgb (float-array (map #(+ (* (- 1 c) %1) (* c %2)) rgb1 rgb2))]
-      (Color. (aget rgb 0) (aget rgb 1) (aget rgb 2) (aget rgb 3)))))
-
-(defn animate
-  "Changes the value of atom according to the specified range, speed,
-   and current frame interval.  Invokes repaint if change happens."
-  [atom from to speed]
-  (let [prev @atom
-        state (cond
-               (zero? speed) :stop
-               (= prev from) (if (pos? speed) :start :stop)               
-               (= prev to) (if (neg? speed) :start :stop)
-               :default :continue)]
-    (if (= state :stop)
-       prev
-       (let [interval (if (= state :start) 1 *interval*)
-             step (* speed interval 1E-9)
-             val (swap! atom #(-> % (+ step) (max from) (min to)))]
-         (repaint)
-         val))))
-
-(defn animated-button
-  "Creates an animated button."
-  [content callback & args]
-  (let [padding 4
-        border-width 1
-        shadow-offset 2
-        face (border padding border-width content)
-        highlight (atom 0)
-        animation-speed (atom 0)]
-    (interval-view
-     (reify
-      View
-      (render! [button]
-        (with-handlers button
-          (let [hovered (hovered? button)
-                offset (if (picked? button) (/ shadow-offset 2) 0)
-                color (combine-colors
-                       (:alt-back-color *theme*) Color/WHITE
-                       (animate highlight 0.0 1.0 @animation-speed))
-                width (- *width* shadow-offset)
-                height (- *height* shadow-offset)]
-            (with-color (:shadow-color *theme*)
-              (.fillRect *graphics*
-                         shadow-offset shadow-offset
-                         width height))
-            (with-color color
-              (.fillRect *graphics* offset offset width height))
-            (draw! offset offset width height
-                   (border border-width padding content)))
-          ;; Event handlers
-          (:mouse-entered _
-            (reset! animation-speed 4)
-            (repaint))
-          (:mouse-exited _
-            (reset! animation-speed -2)
-            (repaint))
-          (:mouse-pressed _ (repaint))
-          (:mouse-released _ (repaint))
-          (:mouse-clicked _ (apply callback args))))
-     (geometry [button]
-       (let [face-geom (geometry face)]
-         (->Size (+ (width face-geom) shadow-offset)
-                 (+ (height face-geom) shadow-offset))))))))
-
-(def button1 (animated-button (label "Animated button 1")
-                              println "Animated button 1 clicked"))
-
-(def button2 (animated-button (label "Animated button 2")
-                              println "Animated button 2 clicked"))
-
-(def test-view1
-  (reify
-   View
-   (render! [view]
-     (with-handlers view
-       (with-color (if (hovered? view) Color/ORANGE Color/RED)
-         (.fillRect *graphics* 0 0 *width* *height*))
-       (:mouse-entered e
-        (repaint)
-        (println e))
-       (:mouse-exited e
-        (repaint)
-        (println e))
-       (:mouse-moved e
-        (println e))))
-   (geometry [view]
-     (->Size 30 20))))
-
-(def test-view1b (border 2 3 test-view1))
-
-(def test-view2
-  (reify
-   View
-   (render! [view]
-     (doto *graphics*
-       (.setColor Color/YELLOW)
-       (.fillRect 0 0 *width* *height*))
-     (with-rotate 0.5 0 0
-       (draw! 30 25 test-view1b))
-     (draw! 55 5 test-view1))
-   (geometry [view]
-     (->Size 70 65))))
-
-(def test-view2m (miniature 30 30 test-view2))
-
-(def test-view3 (border (label :right :bottom "Sample\ntext")))
-
-(def root
-  (reify
-   View
-   (render! [view]
-     ;;(repaint)
-     (doto *graphics*
-       (.drawLine 0 0 *width* *height*)
-       (.drawLine *width* 0 0 *height*)
-       ;; Random color to see when repaint happens.
-       (.setColor (rand-nth [Color/BLACK Color/BLUE Color/RED]))
-       (.fillOval 5 5 20 20))
-     (draw! 30 20 test-view2)
-     (draw! 120 50 test-view2m)
-     (draw! 100 100 80 50 test-view3)
-     (draw! 50 160 button1)
-     (with-rotate (/ Math/PI 6) 250 200
-       (draw! 210 140 button1))
-     (draw! 100 200 button2)
-     (with-bounds 180 240 140 30
-       (draw-button! :button
-        (label :center :center "Immediate button")
-        #(println "Button clicked!"))))
-   (geometry [view]
-     (->Size 400 300))))
-
-;; Main viewport
-(def vp (viewport root))
-
-;; Miniature (rendered asynchronously)
-(def vp-miniature (->> vp (viewport-miniature 100 75) border shadow))
-
-;; Main scene
-(def scene
-  (fps-view
-   (decorate-view vp [_]
-     (draw! vp)
-     (draw-aligned!
-      :left :bottom 5 (- *height* 5)
-      (label (str "Drag mouse to pan," \newline
-                  "use mouse wheel to zoom.")))
-     (draw! (- *width* 105) 5 vp-miniature))))
-
-(defn show-frame [view]
-  (doto (make-jframe "Test" view)
-    (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE)
-    (.setVisible true)))
-
-(defn -main []
-  (show-frame scene))
-
-(comment
-  (show-frame (viewport-miniature 200 150 vp)))
--- a/src/net/kryshen/indyvon/viewport.clj	Mon Apr 14 15:37:28 2014 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,238 +0,0 @@
-;;
-;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-;; Indyvon is free software: you can redistribute it and/or modify it
-;; under the terms of the GNU Lesser General Public License version 3
-;; only, as published by the Free Software Foundation.
-;;
-;; Indyvon is distributed in the hope that it will be useful, but
-;; WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-;; Lesser General Public License for more details.
-;;
-;; You should have received a copy of the GNU Lesser General Public
-;; License along with Indyvon.  If not, see
-;; <http://www.gnu.org/licenses/>.
-;;
-
-(ns net.kryshen.indyvon.viewport
-  "Scrollable viewport and miniature."
-  (:use
-   (net.kryshen.indyvon core async views))
-  (:import
-   java.awt.Cursor
-   java.awt.geom.AffineTransform))
-  
-;;(defn- translate [^AffineTransform transform ^double x ^double y]
-;;  (doto ^AffineTransform (.clone transform)
-;;        (.translate x y)))
-
-(defn- scale [^AffineTransform transform ^double sx ^double sy]
-    (doto ^AffineTransform (.clone transform)
-        (.scale sx sy)))
-
-(defn- pre-translate [^AffineTransform transform ^double x ^double y]
-  (if (== 0.0 x y)
-    transform
-    (doto (AffineTransform/getTranslateInstance x y)
-      (.concatenate transform))))
-
-(def ^:dynamic *viewport-scaling-step* (double 3/4))
-(def ^:dynamic *viewport-min-scale* 1E-6)
-(def ^:dynamic *viewport-max-scale* 1E6)
-
-(def ^:dynamic *viewport* nil)
-(def ^:dynamic ^AffineTransform *viewport-transform*)
-
-(declare scale-viewport!)
-
-(defrecord ViewportState [transform
-                          fix-x fix-y
-                          last-width last-height
-                          last-anchor-x last-anchor-y])
-
-(defn- update-viewport [state content-geom h-align v-align]
-  (let [w *width*
-        h *height*
-        cw (width content-geom)
-        ch (height content-geom)
-        ax (anchor-x content-geom h-align cw)
-        ay (anchor-y content-geom v-align ch)
-        ax1 (align-x h-align (:last-width state) w)
-        ay1 (align-y v-align (:last-height state) h)
-        ax2 (- (:last-anchor-x state) ax)
-        ay2 (- (:last-anchor-y state) ay)
-        transform (:transform state)
-        transform (if (and (zero? ax1) (zero? ay1)
-                           (zero? ax2) (zero? ay2))
-                    transform
-                    (doto
-                        (AffineTransform/getTranslateInstance ax1 ay1)
-                      (.concatenate transform)
-                      (.translate ax2 ay2)))]
-    (assoc state
-      :last-width w
-      :last-height h
-      :last-anchor-x ax
-      :last-anchor-y ay
-      :transform transform)))
-
-(defrecord Viewport [content h-align v-align state]
-  View
-  (render! [view]
-    (repaint-on-update view)
-    (with-handlers view
-      (let [geom (geometry content)
-            new-state (swap! state update-viewport geom h-align v-align)
-            transform (:transform new-state)]
-        ;; TODO: notify observers when size changes.
-        (binding [*viewport* view
-                  *viewport-transform* transform]
-          (with-transform transform
-            (draw! 0 0 (width geom) (height geom) false content))))
-      (:mouse-pressed e
-       (swap! state assoc
-              :fix-x (:x-on-screen e)
-              :fix-y (:y-on-screen e))
-       (set-cursor! (Cursor. Cursor/MOVE_CURSOR)))
-      (:mouse-released e
-       (set-cursor! (Cursor. Cursor/DEFAULT_CURSOR)))
-      (:mouse-dragged e
-       (swap! state
-              (fn [s]
-                (assoc s
-                  :transform (pre-translate
-                              (:transform s)
-                              (- (:x-on-screen e) (:fix-x s))
-                              (- (:y-on-screen e) (:fix-y s)))
-                  :fix-x (:x-on-screen e)
-                  :fix-y (:y-on-screen e))))
-       (update view))
-      (:mouse-wheel e
-       (scale-viewport!
-        view
-        (Math/pow *viewport-scaling-step* (:wheel-rotation e))
-        true (:x e) (:y e)))))
-  (geometry [_]
-    (geometry content)))
-
-(def ^:private viewport-initial-state
-  (->ViewportState
-   (AffineTransform.) ; transform
-   0 0                ; fix-x fix-y
-   0 0                ; last-width last-height
-   0 0))
-
-(defn viewport
-  "Creates scrollable viewport view."
-  ([content]
-     (viewport :left :top content))
-  ([h-align v-align content]
-     (->Viewport content h-align v-align (atom viewport-initial-state))))
-
-(defn- scale-viewport [state vp s relative? x y]
-  (let [^AffineTransform tr (:transform state)
-        sx (if relative? s (/ s (.getScaleX tr)))
-        sy (if relative? s (/ s (.getScaleY tr)))
-        x (or x (align-x (:h-align vp) (:last-width state)))
-        y (or y (align-y (:v-align vp) (:last-height state)))
-        x (- x (* x sx))
-        y (- y (* y sy))
-        scaled (doto (AffineTransform/getTranslateInstance x y)
-                 (.scale sx sy)
-                 (.concatenate tr))
-        sx (.getScaleX scaled)
-        sy (.getScaleY scaled)]
-    (if (<= *viewport-min-scale*
-            (min sx sy)
-            (max sx sy)
-            *viewport-max-scale*)
-      (assoc state
-        :transform scaled)
-      state)))
-
-(defn scale-viewport!
-  ([viewport s]
-     (scale-viewport! viewport s true))
-  ([viewport s relative?]
-     (scale-viewport! viewport s relative? nil nil))
-  ([viewport s relative? x y]
-     (swap! (:state viewport) scale-viewport viewport s relative? x y)
-     (update viewport)))
-
-(defn reset-viewport! [viewport]
-  (reset! (:state viewport) viewport-initial-state)
-  (update viewport))
-
-(defn ^AffineTransform viewport-transform [viewport]
-  (:transform @(:state viewport)))
-
-(defn- scaling
-  [width height max-width max-height]
-  (min (/ max-width width)
-       (/ max-height height)))
-
-(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! (align-x :center cw (/ mw s))
-               (align-y :center ch (/ mh s))
-               cw ch
-               content)))
-    (geometry [_]
-      (->Size mw mh)))))
-
-(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)
-                       (update 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)))))))
--- a/src/net/kryshen/indyvon/views.clj	Mon Apr 14 15:37:28 2014 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,410 +0,0 @@
-;;
-;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-;; Indyvon is free software: you can redistribute it and/or modify it
-;; under the terms of the GNU Lesser General Public License version 3
-;; only, as published by the Free Software Foundation.
-;;
-;; Indyvon is distributed in the hope that it will be useful, but
-;; WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-;; Lesser General Public License for more details.
-;;
-;; You should have received a copy of the GNU Lesser General Public
-;; License along with Indyvon.  If not, see
-;; <http://www.gnu.org/licenses/>.
-;;
-
-(ns net.kryshen.indyvon.views
-  "Implementations of the View protocol."
-  (:use
-   (net.kryshen.indyvon core async))
-  (:import
-   (java.awt Font Image Toolkit)
-   java.awt.image.ImageObserver
-   (java.awt.geom Area AffineTransform Rectangle2D$Double Point2D
-                  Point2D$Double)
-   (java.awt.font FontRenderContext TextLayout)
-   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 Empty []
-  View
-  (render! [_])
-  (geometry [_]
-    (->Size 0 0)))
-
-(def empty-view (->Empty))
-
-;; TODO: change argument order for decorators, content should be the
-;; last.
-
-(defn padding
-  "Adds padding to the content view."
-  ([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! left top
-                  (- *width* left right)
-                  (- *height* top bottom)
-                  false
-                  content))
-        (geometry [l]
-          (->NestedGeometry (geometry content) top left bottom right))))))
-
-(defn border
-  "Adds a border to the content view."
-  ([content]
-     (border 1 content))
-  ([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)))))))
-
-;; TODO: opacity and blur.
-(defn shadow
-  "Adds a shadow to the content view."
-  ([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! x y w h content)))
-        (geometry [_]
-          (->NestedGeometry (geometry content)
-                            y x shadow-y shadow-x))))))
-
-(defn panel
-  "An opaque view using theme's alt-back-color or a custom background
-  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))))
-
-(defn hbox
-  "Creates a view that draws the specified content views placing them
-   horizontally."
-  [& contents]
-  (reify
-   View
-   (render! [_]
-     (let [widths (map #(width (geometry %)) contents)
-           xs (cons 0 (reductions + widths))
-           widths-sum (last xs)
-           scale (/ *width* widths-sum)]
-       (doseq [[c w x] (map vector contents widths xs)]
-         (draw! x 0 w *height* c))))
-   (geometry [_]
-     (reduce #(->Size (+ (width %1) (width %2))
-                      (max (height %1) (height %2)))
-             (->Size 0 0)
-             (map geometry contents)))))
-
-(defn vbox
-  "Creates a view that draws the specified content views placing them
-   vertically."
-  [& contents]
-  (reify
-   View
-   (render! [_]
-     (let [heights (map #(height (geometry %)) contents)
-           ys (cons 0 (reductions + heights))
-           heights-sum (last ys)
-           scale (/ *height* heights-sum)]
-       (doseq [[c h y] (map vector contents heights ys)]
-         (draw! 0 y *width* h c))))
-   (geometry [_]
-     (reduce #(->Size (max (width %1) (width %2))
-                      (+ (height %1) (height %2)))
-             (->Size 0 0)
-             (map geometry contents)))))
-
-(defn- re-split [^java.util.regex.Pattern re s]
-  (seq (.split re s)))
-
-(def ^:private ^Cache text-layout-cache
-  (-> (CacheBuilder/newBuilder)
-      (.softValues)
-      (.expireAfterAccess (long 1) TimeUnit/SECONDS)
-      (.build)))
-
-(defn- get-text-layout
-  [^String line ^Font font ^FontRenderContext font-context]
-  (.get text-layout-cache [line font font-context]
-        #(TextLayout. line font font-context)))
-
-(defn- layout-text
-  [lines font font-context]
-  (map #(get-text-layout % font font-context) lines))
-
-(defn- text-width [layouts]
-  (reduce (fn [w ^TextLayout tl] (max w (.getAdvance tl))) 0 layouts))
-
-(defn- text-height [layouts]
-  (reduce (fn [w ^TextLayout tl]
-            (+ w (.getAscent tl)
-               (.getDescent tl)
-               (.getLeading tl)))
-          0 layouts))
-
-(defn label
-  "Creates a view to display multiline text."
-  ([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 (:font *theme*) (font-context))
-                w (text-width layouts)
-                h (text-height layouts)]
-            (->Size w h)))))))
-
-(defn- ^ImageObserver image-observer [view]
-  (reify
-   ImageObserver
-   (imageUpdate [this img infoflags x y width height]
-     (update view)
-     (zero? (bit-and infoflags
-                     (bit-or ImageObserver/ALLBITS
-                             ImageObserver/ABORT))))))
-
-(defn image-view
-  [image-or-uri]
-  (let [^Image image (if (instance? Image image-or-uri)
-                       image-or-uri
-                       (.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))))))
-
-(def ^:dynamic *miniature-thread-priority* 2)
-
-(defn ref-view
-  [view-ref]
-  (let [l (reify
-           View
-           (render! [l]
-             (repaint-on-update l)
-             (if-let [view @view-ref]
-               (render! view)))
-           (geometry [_]
-             (if-let [view @view-ref]
-               (geometry view)
-               (->Size 1 1))))]
-    (add-watch view-ref l (fn [_ _ _ _] (update l)))
-    l))
-
-;;
-;; View context decorators
-;;
-
-(defmacro handler [view & handlers]
-  "Adds event handling to the view."
-  `(let [view# ~view]
-     (decorate-view view# [t#]
-       (with-handlers t#
-         (render! view#)
-         ~@handlers))))
-
-(defn themed [view & map-or-keyvals]
-  (let [theme (if (== (count map-or-keyvals) 1)
-                (first map-or-keyvals)
-                (apply array-map map-or-keyvals))]
-    (reify
-     View
-     (render! [_]
-       (with-theme theme
-         (render! view)))
-     (geometry [_]
-       (with-theme theme
-         (geometry view))))))
-
-(defn hinted [view & map-or-keyvals]
-  (let [hints (if (== (count map-or-keyvals) 1)
-                (first map-or-keyvals)
-                (apply array-map map-or-keyvals))]
-    (decorate-view view [_]
-      (with-hints* hints render! view))))
-
-;;
-;; Measuring time
-;;
-
-(def ^:dynamic *interval*)
-
-(defn interval-view
-  "Creates a view that measures time between repaints ant 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))))))
-
-(defn- fps-label [text]
-  (padding 5 (label :right :bottom text)))
-
-(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)))))
-
-;;
-;; Overlays
-;;
-
-(def ^:private ^:dynamic *above*)
-
-(defn- overlay* [f & args]
-  (var-set #'*above* (conj *above* (apply partial f args))))
-
-(defn- ^Point2D to-graphics-coords
-  [^AffineTransform transform x y]
-  (let [p (Point2D$Double. x y)]
-    (.transform transform p p)
-    (.transform (.createInverse (.getTransform *graphics*)) p p)
-    p))
-
-(defn- draw-relative!
-  ([transform x y view]
-     (let [p (to-graphics-coords transform x y)]
-       (draw! (.getX p) (.getY p) view)))
-  ([transform x y w h view]
-     (let [p (to-graphics-coords transform x y)]
-       (draw! (.getX p) (.getY p) w h view))))
-
-(defn- draw-relative-aligned!
-  [transform h-align v-align x y view]
-  (let [geom (geometry view)
-        w (width geom)
-        h (height geom)
-        p (to-graphics-coords transform x y)
-        x (- (.getX p) (anchor-x geom h-align w))
-        y (- (.getY p) (anchor-y geom v-align h))]
-    (draw! x y w h view)))
-
-(defn overlay!
-  "Draws view in the overlay context above the other views."
-  ([view]
-     (overlay* draw-relative! (.getTransform *graphics*) 0 0 view))
-  ([x y view]
-     (overlay* draw-relative! (.getTransform *graphics*) x y view))
-  ([x y w h view]
-     (overlay* draw-relative! (.getTransform *graphics*) x y w h view)))
-
-(defn overlay-aligned! [h-align v-align x y view]
-  (overlay* draw-relative-aligned!
-            (.getTransform *graphics*)
-            h-align v-align x y
-            view))
-
-(defn with-overlays* [rec? f & args]
-  (binding [*above* []]
-    (apply f args)
-    (if rec?
-      (loop [above *above*]
-        (when (seq above)
-          (var-set #'*above* [])
-          (doseq [f above]
-            (f))
-          (recur *above*)))
-      (doseq [of *above*]
-        (of)))))
-
-(defmacro with-overlays [rec? & body]
-  `(with-overlays* ~rec? (fn [] ~@body)))
-
-(defn layered
-  ([content]
-     (layered true content))
-  ([rec? content]
-     (decorate-view content [_]
-       (with-overlays* rec? render! content))))