changeset 49:ca728127d605

Use conventional namespace/package name.
author Mikhail Kryshen <mikhail@kryshen.net>
date Thu, 29 Jul 2010 01:08:34 +0400
parents a948ac563f6f
children 409b1b16053d
files project.clj src/kryshen/indyvon/component.clj src/kryshen/indyvon/core.clj src/kryshen/indyvon/demo.clj src/kryshen/indyvon/graph.clj src/kryshen/indyvon/layers.clj src/net/kryshen/indyvon/component.clj src/net/kryshen/indyvon/core.clj src/net/kryshen/indyvon/demo.clj src/net/kryshen/indyvon/graph.clj src/net/kryshen/indyvon/layers.clj test/indyvon/core_test.clj
diffstat 12 files changed, 865 insertions(+), 870 deletions(-) [+]
line wrap: on
line diff
--- a/project.clj	Wed Jul 28 04:47:30 2010 +0400
+++ b/project.clj	Thu Jul 29 01:08:34 2010 +0400
@@ -3,7 +3,7 @@
   :dependencies [[org.clojure/clojure "1.2.0-beta1"]
                  [org.clojure/clojure-contrib "1.2.0-beta1"]]
   :dev-dependencies [[swank-clojure/swank-clojure "1.2.1"]]
-  :namespaces [kryshen.indyvon.core
-               kryshen.indyvon.layers
-               kryshen.indyvon.component
-               kryshen.indyvon.demo])
+  :namespaces [net.kryshen.indyvon.core
+               net.kryshen.indyvon.layers
+               net.kryshen.indyvon.component
+               net.kryshen.indyvon.demo])
--- a/src/kryshen/indyvon/component.clj	Wed Jul 28 04:47:30 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-;;
-;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-
-(ns kryshen.indyvon.component
-  (:use
-   kryshen.indyvon.core)
-  (:import
-   (kryshen.indyvon.core Size Bounds)
-   (java.awt Graphics2D Component Dimension Color)
-   (javax.swing JFrame JPanel)))
-
-(defn- font-context [^Component component]
-  (.getFontRenderContext (.getFontMetrics component (.getFont component))))
-
-(defn paint-component
-  [^Component component layer ^Graphics2D graphics event-dispatcher]
-  (let [size (.getSize component)
-        width (.width size)
-        height (.height size)]
-    (.clearRect graphics 0 0 width height)
-    (let [bounds (Bounds. 0 0 width height)]
-      (binding [*graphics* graphics
-                *font-context* (.getFontRenderContext graphics)
-                *target* component
-                *event-dispatcher* event-dispatcher
-                *update* #(.repaint component)
-                *bounds* bounds
-                *clip* bounds]
-        (render! layer)
-        (commit event-dispatcher)))))
-
-(defn preferred-size [component layer]
-  (binding [*target* component
-            *font-context*' (font-context component)]
-    (let [s (layer-size layer)]
-      (Dimension. (:width s) (:height s)))))
-
-(defn make-jpanel
-  ([layer]
-     (make-jpanel layer (root-event-dispatcher)))
-  ([layer event-dispatcher]
-     (let [panel
-           (proxy [JPanel] []
-             (paintComponent [g]
-                (paint-component this layer g event-dispatcher))
-             (getPreferredSize []
-                (preferred-size this layer)))]
-       (.setBackground panel (:back-color *theme*))
-       (listen! event-dispatcher panel)
-       panel)))
--- a/src/kryshen/indyvon/core.clj	Wed Jul 28 04:47:30 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,341 +0,0 @@
-;;
-;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-
-(ns kryshen.indyvon.core
-  (:import
-   (java.awt Graphics2D Component Color Font AWTEvent)
-   (java.awt.event MouseListener MouseMotionListener)
-   (java.awt.font FontRenderContext)))
-
-(def ^Graphics2D *graphics*)
-(def ^FontRenderContext *font-context*)
-(def ^Component *target*)
-(def *bounds*)
-(def *clip*)
-(def *update*)
-(def *event-dispatcher*)
-
-(defrecord Theme [fore-color back-color alt-back-color border-color font])
-
-(defn default-theme []
-  (Theme. Color/BLACK Color/LIGHT_GRAY Color/WHITE
-          Color/BLUE (Font. "Sans" Font/PLAIN 12)))
-
-(def *theme* (default-theme))
-
-(defrecord Location [x y])
-(defrecord Size [width height])
-(defrecord Bounds [x y width height])
-
-(defprotocol Layer
-  "Basic UI element."
-  (render! [this])
-  (layer-size [this]))
-
-;; TODO: modifiers
-(defrecord MouseEvent [id when x y x-on-screen y-on-screen button])
-
-(defprotocol EventDispatcher
-  (listen! [this ^Component 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."))
-
-(defprotocol Anchored
-  "Provide anchor point for Layers. Used by viewport."
-  (anchor [this h-align v-align]
-     "Anchor point: [x y], h-align could be :left, :center or :right,
-      v-align is :top, :center or :bottom"))
-
-;; Default implementation of Anchored for any Layer.
-(extend-protocol Anchored
-  kryshen.indyvon.core.Layer
-  (anchor [this h-align v-align]
-          (if (and (= h-align :left)
-                   (= v-align :top))
-            (Location. 0 0)
-            (let [size (layer-size this)]
-              (Location.
-               (case h-align
-                     :top 0
-                     :center (/ (:width size) 2)
-                     :right (:width size))
-               (case v-align
-                     :left 0
-                     :center (/ (:height size) 2)
-                     :bottom (:height size)))))))
-
-(defn- ^Graphics2D make-graphics [^Graphics2D graphics x y w h]
-  (.create graphics x y w h))
-
-(defn- ^Graphics2D apply-theme [^Graphics2D graphics theme]
-  (doto graphics
-    (.setColor (:fore-color theme))
-    (.setFont (:font theme))))
-
-(defn intersect
-  ([b1 b2]
-     (let [x1 (:x b1)
-           y1 (:y b1)
-           x2 (:x b2)
-           y2 (:y b2)]
-       (intersect x1 y1 (+ x1 (:width b1)) (+ y1 (:height b1))
-                  x2 y2 (+ x2 (:width b2)) (+ y2 (:height b2)))))
-  ([x11 y11 x12 y12, x21 y21 x22 y22]
-     (let [x1 (max x11 x21)
-           y1 (max y11 y21)
-           x2 (min x12 x22)
-           y2 (min y12 y22)]
-       (Bounds. x1 y1 (- x2 x1) (- y2 y1)))))
-
-(defn ^Graphics2D create-graphics
-  ([]
-     (create-graphics 0 0 (:width *bounds*) (:height *bounds*)))
-  ([x y w h]
-     (apply-theme (.create *graphics* x y w h) *theme*)))
-
-(defmacro with-bounds [x y w h & body]
-  `(let [bounds# (Bounds. (+ ~x (:x *bounds*))
-                          (+ ~y (:y *bounds*))
-                          ~w ~h)
-         clip# (intersect bounds# *clip*)]
-     (when (and (pos? (:width clip#)) (pos? (:height clip#)))
-       (let [graphics# (create-graphics ~x ~y ~w ~h)]
-         (try
-           (binding [*bounds* bounds#
-                     *clip* clip#
-                     *graphics* graphics#]
-             ~@body)
-           (finally
-            (.dispose graphics#)))))))
-
-(defmacro with-handlers* [handle handlers & body]
-  `(binding
-       [*event-dispatcher*
-        (create-dispatcher *event-dispatcher* ~handle ~handlers)]
-     ~@body))
-
-(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)
-     ~form))
-
-(defn with-theme* [theme f & args]
-  (apply with-bindings* {#'*theme* (merge *theme* theme)}
-         f args))
-
-(defmacro with-theme [theme & body]
-  `(binding [*theme* (merge *theme* ~theme)]
-     ~@body))
-
-(defmacro with-color [color & body]
-  `(let [color# (.getColor *graphics*)]
-     (try
-       (.setColor *graphics* ~color)
-       ~@body
-       (finally
-        (.setColor *graphics* color#)))))
-
-(defn- geometry-vec [geometry]
-  (if (vector? geometry)
-    geometry
-    [(:x geometry) (:y geometry) (:width geometry) (:height geometry)]))
-
-(defn draw!
-  ([layer]
-     (let [graphics (create-graphics)]
-       (try
-         (binding [*graphics* graphics]
-           (render! layer))
-         (finally
-          (.dispose graphics)))))
-  ([layer x y]
-     (let [size (layer-size layer)]
-       (draw! layer x y (:width size) (:height size))))
-  ([layer x y width height]
-     (with-bounds x y width height
-       (render! layer))))
-
-(defn draw-anchored!
-  "Draw with location relative to the anchor point."
-  ([layer h-align v-align x y]
-     (let [anchor (anchor layer h-align v-align)]
-       (draw! layer (- x (:x anchor)) (- y (:y anchor)))))
-  ([layer h-align v-align x y w h]
-     (let [anchor (anchor layer h-align v-align)]
-       (draw! layer (- x (:x anchor)) (- y (:y anchor)) w h))))
-
-;;
-;; 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})
-
-(defrecord DispatcherNode [handle handlers parent bounds bindings]
-  EventDispatcher
-  (listen! [this component]
-     (listen! parent component))
-  (create-dispatcher [this handle handlers]
-     (create-dispatcher parent handle handlers))
-  (commit [this]
-     (commit parent)))
-
-(defn- make-node [handle handlers]
-  (DispatcherNode. handle handlers *event-dispatcher* *clip*
-                   (get-thread-bindings)))
-
-(defn- assoc-cons [m key val]
-  (assoc m key (cons val (get m key))))
-
-(defn- add-node [tree node]
-  (assoc-cons tree (:parent node) node))
-
-(defn- inside?
-  ([x y bounds]
-     (inside? x y (:x bounds) (:y bounds)
-              (:width bounds) (:height bounds)))
-  ([px py x y w h]
-     (and (>= px x)
-          (>= py y)
-          (< px (+ x w))
-          (< py (+ y h)))))
-
-(defn- under-cursor
-  "Returns a vector of child nodes under cursor."
-  [x y tree node]
-  (some #(if (inside? x y (:bounds %))
-           (conj (vec (under-cursor x y tree %)) %))
-        (get tree node)))
-
-(defn- remove-all [coll1 coll2 pred]
-  (filter #(not (some (partial pred %) coll2)) coll1))
-
-(defn- translate-mouse-event [^java.awt.event.MouseEvent event x y id]
-  (MouseEvent. id (.getWhen event)
-               (- (.getX event) x) (- (.getY event) y)
-               (.getXOnScreen event) (.getYOnScreen event)
-               (.getButton 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 [node (first nodes)]
-       (if-let [handler (get (:handlers node) id)]
-         (do
-           (with-bindings* (:bindings node)
-             handler
-             (translate-mouse-event event
-               (-> node :bounds :x) (-> node :bounds :y) id))
-           (if-not first-only
-             (recur (rest nodes) false event id)))
-         (recur (rest nodes) first-only event id)))))
-
-(defn- dispatch-mouse-motion
-  "Dispatches mouse motion events."
-  [hovered-ref tree root ^java.awt.event.MouseEvent event]
-  (let [x (.getX event)
-        y (.getY event)
-        [hovered hovered2] (dosync
-                            [@hovered-ref
-                             (ref-set hovered-ref
-                                      (under-cursor x y tree root))])
-        pred #(= (:handle %1) (:handle %2))
-        exited (remove-all hovered hovered2 pred)
-        entered (remove-all hovered2 hovered pred)
-        moved (remove-all hovered2 entered pred)]
-    (translate-and-dispatch exited false event :mouse-exited)
-    (translate-and-dispatch entered false event :mouse-entered)
-    (translate-and-dispatch moved true event :mouse-moved)))
-
-(defn- dispatch-mouse-button
-  [picked-ref hovered-ref ^java.awt.event.MouseEvent event]
-  (let [id (awt-events (.getID event))
-        hovered (if (= id :mouse-pressed)
-                  (dosync (ref-set picked-ref @hovered-ref))
-                  @hovered-ref)]
-    (translate-and-dispatch hovered true event id)))
-
-(defn root-event-dispatcher []
-  (let [tree-r (ref {})   ; register
-        tree (ref {})     ; dispatch
-        hovered (ref '())
-        picked (ref '())]
-    (reify
-     EventDispatcher
-     (listen! [this component]
-        (doto component
-          (.addMouseListener this)
-          (.addMouseMotionListener this)))
-     (create-dispatcher [this handle handlers]
-        (let [node (make-node handle handlers)]
-          (dosync (alter tree-r add-node node))
-          node))
-     (commit [this]
-        (dosync (ref-set tree @tree-r)
-                (ref-set tree-r {})))
-     MouseListener
-     (mouseEntered [this event]
-        (dispatch-mouse-motion hovered @tree this event))
-     (mouseExited [this event]
-        (dispatch-mouse-motion hovered @tree this event))
-     (mouseClicked [this event]
-        (dispatch-mouse-button picked hovered event))
-     (mousePressed [this event]
-        (dispatch-mouse-button picked hovered event))
-     (mouseReleased [this event]
-        (translate-and-dispatch @picked true event))
-        ;;(dispatch-mouse-button picked hovered event))
-     MouseMotionListener
-     (mouseDragged [this event]
-        (translate-and-dispatch @picked true event))
-     (mouseMoved [this event]
-        (dispatch-mouse-motion hovered @tree this event)))))
-
-;;
-;; ИДЕИ:
-;;
-;; Контекст: биндинги или запись?
-;;
-;; Установка обработчиков (в контексте слоя):
-;;
-;; (listen
-;;   (:mouse-entered e
-;;     ...)
-;;   (:mouse-exited e
-;;     ...))
-;;
-;; Не надо IMGUI.
-;; Построение сцены путем декорирования слоев:
-;;
-;; (listener
-;;  (:action e (println e))
-;;  (:mouse-dragged e (println e))
-;;  (theme :font "Helvetica-14"
-;;    (vbox
-;;      (button (text-layer "Button 1"))
-;;      (button (text-layer "Button 2")))))
-;;
--- a/src/kryshen/indyvon/demo.clj	Wed Jul 28 04:47:30 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-;;
-;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-
-(ns kryshen.indyvon.demo
-  (:gen-class)
-  (:use
-   (kryshen.indyvon core layers component))
-  (:import
-   (kryshen.indyvon.core Size Bounds)
-   (java.awt Color)
-   (javax.swing JFrame)))
-
-(def frame (JFrame. "Test"))
-
-(def layer1
-     (reify
-      Layer
-      (render! [layer]
-         (with-handlers layer
-           (doto *graphics*
-             (.setColor Color/RED)
-             (.fillRect 0 0 (:width *bounds*) (:height *bounds*)))
-           (:mouse-entered e (println e))
-           (:mouse-exited e (println e))
-           (:mouse-moved e (println e))))
-      (layer-size [layer] (Size. 30 20))))
-
-(def layer1b (border layer1 2 3))
-
-(def layer2
-     (reify
-      Layer
-      (render! [layer]
-         (doto *graphics*
-           (.setColor Color/YELLOW)
-           (.fillRect 0 0 (:width *bounds*) (:height *bounds*)))
-         (draw! layer1b 10 5)
-         (draw! layer1 55 5))
-      (layer-size [layer] (Size. 70 65))))
-
-(def layer3
-     (border (text-layer "Sample\ntext" :right :center)))
-
-(defn fps-layer [fps]
-  (border (text-layer (format "%.1f" fps) :right :bottom) 0 5))
-
-(def fps
-     (let [update-interval 0.1
-           frames (ref 0)
-           last (ref 0)
-           fl (ref (fps-layer 0.0))]
-       (reify
-        Layer
-        (render! [layer]
-           (draw! @fl)
-           (dosync
-            (alter frames + 1)
-            (let [time (System/currentTimeMillis)
-                  elapsed (/ (- time @last) 1000.0)]
-              (when (> elapsed update-interval)
-                (ref-set fl (fps-layer (/ @frames elapsed)))
-                (ref-set frames 0)
-                (ref-set last time)))))
-        (layer-size [layer] (layer-size @fl)))))
-
-(def layer
-     (reify
-      Layer
-      (render! [layer]
-         (*update*)
-         (doto *graphics*
-           (.setColor (rand-nth [Color/BLACK Color/BLUE Color/RED]))
-           (.drawLine 0 0 (:width *bounds*) (:height *bounds*)))
-         (draw! layer2 15 20)
-         (draw! layer3 100 100 80 50)
-         (draw! fps))
-      (layer-size [layer] (Size. 400 300))))
-
-(defn -main []
-  (doto frame
-    (.addWindowListener
-     (proxy [java.awt.event.WindowAdapter] []
-       (windowClosing [event] (.dispose frame))))
-    (.. (getContentPane) (add (make-jpanel (viewport layer))))
-    (.pack)
-    (.setVisible true)))
-
--- a/src/kryshen/indyvon/graph.clj	Wed Jul 28 04:47:30 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-;;
-;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-
-(ns kryshen.indyvon.graph
-  (:use
-   (kryshen.indyvon core component layers))
-  (:import
-   (kryshen.indyvon.core Location Size)
-   (kryshen.indygraph Graph DefaultGraph GraphLayout Vertex2D Edge2D
-                      RectangularVertex2D DefaultEdge2D)
-   (kryshen.indygraph.fdl ForceDirectedLayout)
-   (java.awt.geom Path2D$Double)
-   (javax.swing JFrame)))
-
-(extend-type Vertex2D
-  Layer
-  (render! [v]
-     (.drawOval *graphics* 0 0 (:width *bounds*) (:height *bounds*)))
-  (layer-size [v]
-     (Size.
-      (+ (.getLeftBound v) (.getRightBound v))
-      (+ (.getTopBound v) (.getBottomBound v))))
-  Anchored
-  (anchor [v _ _]
-     (Location.
-      (.getLeftBound v)
-      (.getTopBound v))))
-
-(defn- draw-vertices! [^GraphLayout layout x y]
-  (doseq [v (.vertices (.getGraph layout))]
-    (draw-anchored! v :center :center (+ x (.getX v)) (+ y (.getY v)))))
-
-(defn- draw-movable-vertex!
-  [^Vertex2D v ^GraphLayout layout x y dragged fix-x fix-y]
-  (let [x (+ x (.getX v))
-        y (+ y (.getY v))
-        anchor (anchor v :center :center)
-        size (layer-size v)
-        x (- x (:x anchor))
-        y (- y (:y anchor))]
-    (with-bounds x y (:width size) (:height size)
-      (with-handlers v
-        (draw! v)
-        (:mouse-pressed e
-          (dosync (ref-set fix-x (:x-on-screen e))
-                  (ref-set fix-y (:y-on-screen e))
-                  (ref-set dragged v)))
-        (:mouse-released e
-          (dosync (if (= v @dragged)
-                    (ref-set dragged nil))))
-        (:mouse-dragged e
-          (let [x (:x-on-screen e)
-                y (:y-on-screen e)
-                vx (.getX v)
-                vy (.getY v)]
-            (dosync
-             (when @dragged
-               (let [dx (- x @fix-x)
-                     dy (- y @fix-y)]
-                 (.layoutLocation v (+ vx dx) (+ vy dy))
-                 (.invalidateLayout layout)
-                 (*update*)
-                 (ref-set fix-x x)
-                 (ref-set fix-y y))))))))))
-  
-(defn- draw-movable-vertices!
-  [^GraphLayout layout x y dragged-ref fix-x fix-y]
-  (let [dragged @dragged-ref]
-    (doseq [v (.vertices (.getGraph layout))
-            :when (not= v dragged)]
-      (draw-movable-vertex! v layout x y dragged-ref fix-x fix-y))
-    ;; Draw the vertex being dragged above others.
-    (when dragged
-      (draw-movable-vertex! dragged layout x y dragged-ref fix-x fix-y))))
-    
-(defn- draw-edges! [^GraphLayout layout x y]
-  ;; TODO: extend Layer on Edge2D and draw like vertices.
-  (.translate *graphics* x y)
-  (let [path (Path2D$Double.)]
-  (doseq [^Edge2D e (.edges (.getGraph layout))]
-    (.getPath e path)
-    (.draw *graphics* path)))
-  (.translate *graphics* (- x) (- y)))
-
-(defrecord GraphLayer [layout movable dragged fix-x fix-y]
-  Layer
-  (render! [layer]
-     (let [bounds (.getBounds layout)
-           x (- (.getX bounds))
-           y (- (.getY bounds))]
-       (draw-edges! layout x y)
-       (if movable
-         (draw-movable-vertices! layout x y dragged fix-x fix-y)
-         (draw-vertices! layout x y))))
-  (layer-size [layer]
-     (let [bounds (.getBounds layout)]
-       (Size. (.getWidth bounds) (.getHeight bounds))))
-  Anchored
-  (anchor [layer x-align y-align]
-          (let [bounds (.getBounds layout)]
-            (Location. (- (.getX bounds))
-                       (- (.getY bounds))))))
-
-(defn graph-layer
-  ([graph-layout]
-     (graph-layer graph-layout false))
-  ([^GraphLayout graph-layout movable]
-     (GraphLayer. graph-layout movable (ref nil) (ref 0) (ref 0))))
-
-(defn build-graph
-  "Returns Graph defined by a sequence of pairs of vertex ids,
-   and a function that maps vertex id's to Vertex objects."
-  [relations f]
-  (let [graph (DefaultGraph.)
-        vs (reduce #(conj %1 (first %2) (second %2)) #{} relations)
-        vm (reduce #(assoc %1 %2 (f %2)) {} vs)
-        vs (vals vm)
-        es (map #(DefaultEdge2D. (vm (first %)) (vm (second %))) relations)]
-    (doseq [v vs]
-      (.addVertex graph v))
-    (doseq [e es]
-      (.addEdge graph e))
-    graph))
-
-(comment
-  (let [graph (build-graph
-               [[1 2] [1 3] [1 4] [2 4]]
-               (fn [_] (RectangularVertex2D. 100 30)))
-        layout (ForceDirectedLayout. graph)
-        frame (JFrame. "Graph test")
-        layer (graph-layer layout true)
-        layer (viewport layer :center :center)]
-    (.add (.getContentPane frame) (make-jpanel layer))
-    (while (not (.update layout)))
-    (doto frame
-      (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE)
-      (.pack)
-      (.setVisible true)))
-  )
--- a/src/kryshen/indyvon/layers.clj	Wed Jul 28 04:47:30 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,234 +0,0 @@
-;;
-;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-
-(ns kryshen.indyvon.layers
-  (:use kryshen.indyvon.core)
-  (:import (kryshen.indyvon.core Size Location)
-           (java.lang.ref SoftReference)
-           (java.awt Font Cursor Image Toolkit)
-           (java.awt.image ImageObserver)
-           (java.awt.font FontRenderContext TextLayout)))
-
-;; Define as macro to avoid unnecessary calculation of inner and outer
-;; sizes in the first case.
-(defmacro align-xy [inner outer align first center last]
-  `(case ~align
-         ~first 0
-         ~center (/ (- ~outer ~inner) 2)
-         ~last (- ~outer ~inner)))
-
-(defmacro align-x [inner outer align]
-  `(align-xy ~inner ~outer ~align :left :center :right))
-
-(defmacro align-y [inner outer align]
-  `(align-xy ~inner ~outer ~align :top :center :bottom))
-
-(defmacro decorate-layer [layer & render-tail]
-  `(reify
-    Layer
-    (render! ~@render-tail)
-    (layer-size [t#] (layer-size ~layer))
-    Anchored
-    (anchor [t# xa# ya#] (anchor ~layer xa# ya#))))
-
-(defn padding
-  ([content pad]
-     (padding content pad pad pad pad))
-  ([content top left bottom right]
-     (if (== 0 top left bottom right)
-       content
-       (reify
-        Layer
-        (render! [l]
-           (draw! content
-                  left top
-                  (- (:width *bounds*) left right)
-                  (- (:height *bounds*) top bottom)))
-        (layer-size [l]
-           (let [s (layer-size content)]
-             (Size. (+ (:width s) left right)
-                    (+ (:height s) top bottom))))))))
-
-(defn border
-  "Decorate layer with a border."
-  ([content]
-     (border content 1))
-  ([content width]
-     (border content width 0))
-  ([content width gap]
-     (let [layer (padding content (+ width gap))]
-       (decorate-layer layer [_]
-         (let [w (:width *bounds*)
-               h (:height *bounds*)]
-           (with-color (:border-color *theme*)
-             (doseq [i (range 0 width)]
-               (.drawRect *graphics* i i (- w 1 i i) (- h 1 i i))))
-           (render! layer))))))
-
-(defn panel
-  "Opaque layer using theme's alt-back-color."
-  ([content]
-     (panel content 0))
-  ([content gap]
-     (let [layer (padding content gap)]
-       (decorate-layer layer [_]
-         (with-color (:alt-back-color *theme*)
-           (.fillRect *graphics* 0 0
-                      (:width *bounds*) (:height *bounds*)))
-         (render! layer)))))
-
-(defn- re-split [^java.util.regex.Pattern re s]
-  (seq (.split re s)))
-
-(def text-layout-cache (atom {}))
-
-(defn- get-text-layout
-  [^String line ^Font font ^FontRenderContext font-context]
-  (let [key [line font font-context]]
-    (or (if-let [^SoftReference softref (@text-layout-cache key)]
-          (.get softref)
-          (do (swap! text-layout-cache dissoc key)
-              false))
-        (let [layout (TextLayout. line font font-context)]
-          (println "text-layout-cache miss" line)
-          (swap! text-layout-cache assoc key (SoftReference. layout))
-          layout))))
-
-(defn- layout-text
-  [lines ^Font font ^FontRenderContext font-context]
-  (map #(get-text-layout % font font-context) lines))
-  ;;(map #(TextLayout. ^String % 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 text-layer
-  "Creates a layer to display multiline text."
-  ([text]
-     (text-layer text :left :top))
-  ([text h-align v-align]
-     (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" text)]
-       (reify Layer
-        (render! [layer]
-           (let [w (:width *bounds*)
-                 h (:height *bounds*)
-                 font (.getFont *graphics*)
-                 layouts (layout-text lines font *font-context*)
-                 y (align-y (text-height layouts) h v-align)]
-             (loop [layouts layouts, y y]
-               (when-first [^TextLayout layout layouts]
-                 (let [ascent (.getAscent layout)
-                       lh (+ ascent (.getDescent layout) (.getLeading layout))
-                       x (align-x (.getAdvance layout) w h-align)]
-                   (.draw layout *graphics* x (+ y ascent))
-                   (recur (next layouts) (+ y lh)))))))
-        (layer-size [layer]
-           (let [layouts (layout-text lines (:font *theme*) *font-context*)
-                 width (text-width layouts)
-                 height (text-height layouts)]
-             (Size. width height)))))))
-
-(defn- image-observer [update-fn]
-  (reify
-   ImageObserver
-   (imageUpdate [this img infoflags x y width height]
-      (update-fn)
-      (zero? (bit-and infoflags
-                      (bit-or ImageObserver/ALLBITS
-                              ImageObserver/ABORT))))))
-
-(defn image-layer
-  [image-or-uri]
-  (let [^Image image (if (isa? image-or-uri Image)
-                       image-or-uri
-                       (.getImage (Toolkit/getDefaultToolkit)
-                                  ^java.net.URL image-or-uri))]
-    (.prepareImage (Toolkit/getDefaultToolkit) image -1 -1 nil)
-    (reify
-     Layer
-     (render! [layer]
-        (.drawImage *graphics* image 0 0
-                    ^ImageObserver (image-observer *update*)))
-     (layer-size [layer]
-        (let [observer (image-observer *update*)
-              width (.getWidth image observer)
-              height (.getHeight image observer)
-              width (if (pos? width) width 1)
-              height (if (pos? height) height 1)]
-          (Size. width height))))))
-
-(defn viewport
-  "Creates scrollable viewport layer."
-  ([content] (viewport content :left :top))
-  ([content h-align v-align]
-  (let [x (ref 0)
-        y (ref 0)
-        fix-x (ref 0)
-        fix-y (ref 0)
-        last-width (ref 0)
-        last-height (ref 0)]
-    (reify
-     Layer
-     (render! [layer]
-        (with-handlers layer
-         (let [anchor (anchor content h-align v-align)
-               width (:width *bounds*)
-               height (:height *bounds*)]
-           (dosync
-            (alter x + (align-x width @last-width h-align))
-            (alter y + (align-y height @last-height v-align))
-            (ref-set last-width width)
-            (ref-set last-height height))
-           (draw! content (- 0 @x (:x anchor)) (- 0 @y (:y anchor))))
-         (:mouse-pressed e
-          (dosync
-           (ref-set fix-x (:x-on-screen e))
-           (ref-set fix-y (:y-on-screen e)))
-          (->> Cursor/MOVE_CURSOR Cursor. (.setCursor *target*)))
-         (:mouse-released e
-          (->> Cursor/DEFAULT_CURSOR Cursor. (.setCursor *target*)))
-         (:mouse-dragged e
-          (dosync
-           (alter x + (- @fix-x (:x-on-screen e)))
-           (alter y + (- @fix-y (:y-on-screen e)))
-           (ref-set fix-x (:x-on-screen e))
-           (ref-set fix-y (:y-on-screen e)))
-          (*update*))))
-     (layer-size [layer] (layer-size content))))))
-
-;;
-;; Layer context decorators.
-;;
-
-(defmacro handler [layer & handlers]
-  `(let [layer# ~layer]
-     (decorate-layer layer# [t#]
-        (with-handlers t#
-          (render! layer#)
-          ~@handlers))))
-
-(defn theme [layer & map-or-keyvals]
-  (let [theme (if (== (count map-or-keyvals) 1)
-                map-or-keyvals
-                (apply array-map map-or-keyvals))]
-    (reify
-     Layer
-     (render! [t]
-        (with-theme theme
-          (render! layer)))
-     (layer-size [t]
-        (with-theme theme
-          (layer-size layer)))
-     Anchored
-     (anchor [t xa ya]
-        (with-theme theme
-          (anchor layer xa ya))))))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/kryshen/indyvon/component.clj	Thu Jul 29 01:08:34 2010 +0400
@@ -0,0 +1,53 @@
+;;
+;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+
+(ns net.kryshen.indyvon.component
+  (:use
+   net.kryshen.indyvon.core)
+  (:import
+   (net.kryshen.indyvon.core Size Bounds)
+   (java.awt Graphics2D Component Dimension Color)
+   (javax.swing JFrame JPanel)))
+
+(defn- font-context [^Component component]
+  (.getFontRenderContext (.getFontMetrics component (.getFont component))))
+
+(defn paint-component
+  [^Component component layer ^Graphics2D graphics event-dispatcher]
+  (let [size (.getSize component)
+        width (.width size)
+        height (.height size)]
+    (.clearRect graphics 0 0 width height)
+    (let [bounds (Bounds. 0 0 width height)]
+      (binding [*graphics* graphics
+                *font-context* (.getFontRenderContext graphics)
+                *target* component
+                *event-dispatcher* event-dispatcher
+                *update* #(.repaint component)
+                *bounds* bounds
+                *clip* bounds]
+        (render! layer)
+        (commit event-dispatcher)))))
+
+(defn preferred-size [component layer]
+  (binding [*target* component
+            *font-context*' (font-context component)]
+    (let [s (layer-size layer)]
+      (Dimension. (:width s) (:height s)))))
+
+(defn make-jpanel
+  ([layer]
+     (make-jpanel layer (root-event-dispatcher)))
+  ([layer event-dispatcher]
+     (let [panel
+           (proxy [JPanel] []
+             (paintComponent [g]
+                (paint-component this layer g event-dispatcher))
+             (getPreferredSize []
+                (preferred-size this layer)))]
+       (.setBackground panel (:back-color *theme*))
+       (listen! event-dispatcher panel)
+       panel)))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/kryshen/indyvon/core.clj	Thu Jul 29 01:08:34 2010 +0400
@@ -0,0 +1,341 @@
+;;
+;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+
+(ns net.kryshen.indyvon.core
+  (:import
+   (java.awt Graphics2D Component Color Font AWTEvent)
+   (java.awt.event MouseListener MouseMotionListener)
+   (java.awt.font FontRenderContext)))
+
+(def ^Graphics2D *graphics*)
+(def ^FontRenderContext *font-context*)
+(def ^Component *target*)
+(def *bounds*)
+(def *clip*)
+(def *update*)
+(def *event-dispatcher*)
+
+(defrecord Theme [fore-color back-color alt-back-color border-color font])
+
+(defn default-theme []
+  (Theme. Color/BLACK Color/LIGHT_GRAY Color/WHITE
+          Color/BLUE (Font. "Sans" Font/PLAIN 12)))
+
+(def *theme* (default-theme))
+
+(defrecord Location [x y])
+(defrecord Size [width height])
+(defrecord Bounds [x y width height])
+
+(defprotocol Layer
+  "Basic UI element."
+  (render! [this])
+  (layer-size [this]))
+
+;; TODO: modifiers
+(defrecord MouseEvent [id when x y x-on-screen y-on-screen button])
+
+(defprotocol EventDispatcher
+  (listen! [this ^Component 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."))
+
+(defprotocol Anchored
+  "Provide anchor point for Layers. Used by viewport."
+  (anchor [this h-align v-align]
+     "Anchor point: [x y], h-align could be :left, :center or :right,
+      v-align is :top, :center or :bottom"))
+
+;; Default implementation of Anchored for any Layer.
+(extend-protocol Anchored
+  net.kryshen.indyvon.core.Layer
+  (anchor [this h-align v-align]
+          (if (and (= h-align :left)
+                   (= v-align :top))
+            (Location. 0 0)
+            (let [size (layer-size this)]
+              (Location.
+               (case h-align
+                     :top 0
+                     :center (/ (:width size) 2)
+                     :right (:width size))
+               (case v-align
+                     :left 0
+                     :center (/ (:height size) 2)
+                     :bottom (:height size)))))))
+
+(defn- ^Graphics2D make-graphics [^Graphics2D graphics x y w h]
+  (.create graphics x y w h))
+
+(defn- ^Graphics2D apply-theme [^Graphics2D graphics theme]
+  (doto graphics
+    (.setColor (:fore-color theme))
+    (.setFont (:font theme))))
+
+(defn intersect
+  ([b1 b2]
+     (let [x1 (:x b1)
+           y1 (:y b1)
+           x2 (:x b2)
+           y2 (:y b2)]
+       (intersect x1 y1 (+ x1 (:width b1)) (+ y1 (:height b1))
+                  x2 y2 (+ x2 (:width b2)) (+ y2 (:height b2)))))
+  ([x11 y11 x12 y12, x21 y21 x22 y22]
+     (let [x1 (max x11 x21)
+           y1 (max y11 y21)
+           x2 (min x12 x22)
+           y2 (min y12 y22)]
+       (Bounds. x1 y1 (- x2 x1) (- y2 y1)))))
+
+(defn ^Graphics2D create-graphics
+  ([]
+     (create-graphics 0 0 (:width *bounds*) (:height *bounds*)))
+  ([x y w h]
+     (apply-theme (.create *graphics* x y w h) *theme*)))
+
+(defmacro with-bounds [x y w h & body]
+  `(let [bounds# (Bounds. (+ ~x (:x *bounds*))
+                          (+ ~y (:y *bounds*))
+                          ~w ~h)
+         clip# (intersect bounds# *clip*)]
+     (when (and (pos? (:width clip#)) (pos? (:height clip#)))
+       (let [graphics# (create-graphics ~x ~y ~w ~h)]
+         (try
+           (binding [*bounds* bounds#
+                     *clip* clip#
+                     *graphics* graphics#]
+             ~@body)
+           (finally
+            (.dispose graphics#)))))))
+
+(defmacro with-handlers* [handle handlers & body]
+  `(binding
+       [*event-dispatcher*
+        (create-dispatcher *event-dispatcher* ~handle ~handlers)]
+     ~@body))
+
+(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)
+     ~form))
+
+(defn with-theme* [theme f & args]
+  (apply with-bindings* {#'*theme* (merge *theme* theme)}
+         f args))
+
+(defmacro with-theme [theme & body]
+  `(binding [*theme* (merge *theme* ~theme)]
+     ~@body))
+
+(defmacro with-color [color & body]
+  `(let [color# (.getColor *graphics*)]
+     (try
+       (.setColor *graphics* ~color)
+       ~@body
+       (finally
+        (.setColor *graphics* color#)))))
+
+(defn- geometry-vec [geometry]
+  (if (vector? geometry)
+    geometry
+    [(:x geometry) (:y geometry) (:width geometry) (:height geometry)]))
+
+(defn draw!
+  ([layer]
+     (let [graphics (create-graphics)]
+       (try
+         (binding [*graphics* graphics]
+           (render! layer))
+         (finally
+          (.dispose graphics)))))
+  ([layer x y]
+     (let [size (layer-size layer)]
+       (draw! layer x y (:width size) (:height size))))
+  ([layer x y width height]
+     (with-bounds x y width height
+       (render! layer))))
+
+(defn draw-anchored!
+  "Draw with location relative to the anchor point."
+  ([layer h-align v-align x y]
+     (let [anchor (anchor layer h-align v-align)]
+       (draw! layer (- x (:x anchor)) (- y (:y anchor)))))
+  ([layer h-align v-align x y w h]
+     (let [anchor (anchor layer h-align v-align)]
+       (draw! layer (- x (:x anchor)) (- y (:y anchor)) w h))))
+
+;;
+;; 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})
+
+(defrecord DispatcherNode [handle handlers parent bounds bindings]
+  EventDispatcher
+  (listen! [this component]
+     (listen! parent component))
+  (create-dispatcher [this handle handlers]
+     (create-dispatcher parent handle handlers))
+  (commit [this]
+     (commit parent)))
+
+(defn- make-node [handle handlers]
+  (DispatcherNode. handle handlers *event-dispatcher* *clip*
+                   (get-thread-bindings)))
+
+(defn- assoc-cons [m key val]
+  (assoc m key (cons val (get m key))))
+
+(defn- add-node [tree node]
+  (assoc-cons tree (:parent node) node))
+
+(defn- inside?
+  ([x y bounds]
+     (inside? x y (:x bounds) (:y bounds)
+              (:width bounds) (:height bounds)))
+  ([px py x y w h]
+     (and (>= px x)
+          (>= py y)
+          (< px (+ x w))
+          (< py (+ y h)))))
+
+(defn- under-cursor
+  "Returns a vector of child nodes under cursor."
+  [x y tree node]
+  (some #(if (inside? x y (:bounds %))
+           (conj (vec (under-cursor x y tree %)) %))
+        (get tree node)))
+
+(defn- remove-all [coll1 coll2 pred]
+  (filter #(not (some (partial pred %) coll2)) coll1))
+
+(defn- translate-mouse-event [^java.awt.event.MouseEvent event x y id]
+  (MouseEvent. id (.getWhen event)
+               (- (.getX event) x) (- (.getY event) y)
+               (.getXOnScreen event) (.getYOnScreen event)
+               (.getButton 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 [node (first nodes)]
+       (if-let [handler (get (:handlers node) id)]
+         (do
+           (with-bindings* (:bindings node)
+             handler
+             (translate-mouse-event event
+               (-> node :bounds :x) (-> node :bounds :y) id))
+           (if-not first-only
+             (recur (rest nodes) false event id)))
+         (recur (rest nodes) first-only event id)))))
+
+(defn- dispatch-mouse-motion
+  "Dispatches mouse motion events."
+  [hovered-ref tree root ^java.awt.event.MouseEvent event]
+  (let [x (.getX event)
+        y (.getY event)
+        [hovered hovered2] (dosync
+                            [@hovered-ref
+                             (ref-set hovered-ref
+                                      (under-cursor x y tree root))])
+        pred #(= (:handle %1) (:handle %2))
+        exited (remove-all hovered hovered2 pred)
+        entered (remove-all hovered2 hovered pred)
+        moved (remove-all hovered2 entered pred)]
+    (translate-and-dispatch exited false event :mouse-exited)
+    (translate-and-dispatch entered false event :mouse-entered)
+    (translate-and-dispatch moved true event :mouse-moved)))
+
+(defn- dispatch-mouse-button
+  [picked-ref hovered-ref ^java.awt.event.MouseEvent event]
+  (let [id (awt-events (.getID event))
+        hovered (if (= id :mouse-pressed)
+                  (dosync (ref-set picked-ref @hovered-ref))
+                  @hovered-ref)]
+    (translate-and-dispatch hovered true event id)))
+
+(defn root-event-dispatcher []
+  (let [tree-r (ref {})   ; register
+        tree (ref {})     ; dispatch
+        hovered (ref '())
+        picked (ref '())]
+    (reify
+     EventDispatcher
+     (listen! [this component]
+        (doto component
+          (.addMouseListener this)
+          (.addMouseMotionListener this)))
+     (create-dispatcher [this handle handlers]
+        (let [node (make-node handle handlers)]
+          (dosync (alter tree-r add-node node))
+          node))
+     (commit [this]
+        (dosync (ref-set tree @tree-r)
+                (ref-set tree-r {})))
+     MouseListener
+     (mouseEntered [this event]
+        (dispatch-mouse-motion hovered @tree this event))
+     (mouseExited [this event]
+        (dispatch-mouse-motion hovered @tree this event))
+     (mouseClicked [this event]
+        (dispatch-mouse-button picked hovered event))
+     (mousePressed [this event]
+        (dispatch-mouse-button picked hovered event))
+     (mouseReleased [this event]
+        (translate-and-dispatch @picked true event))
+        ;;(dispatch-mouse-button picked hovered event))
+     MouseMotionListener
+     (mouseDragged [this event]
+        (translate-and-dispatch @picked true event))
+     (mouseMoved [this event]
+        (dispatch-mouse-motion hovered @tree this event)))))
+
+;;
+;; ИДЕИ:
+;;
+;; Контекст: биндинги или запись?
+;;
+;; Установка обработчиков (в контексте слоя):
+;;
+;; (listen
+;;   (:mouse-entered e
+;;     ...)
+;;   (:mouse-exited e
+;;     ...))
+;;
+;; Не надо IMGUI.
+;; Построение сцены путем декорирования слоев:
+;;
+;; (listener
+;;  (:action e (println e))
+;;  (:mouse-dragged e (println e))
+;;  (theme :font "Helvetica-14"
+;;    (vbox
+;;      (button (text-layer "Button 1"))
+;;      (button (text-layer "Button 2")))))
+;;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/kryshen/indyvon/demo.clj	Thu Jul 29 01:08:34 2010 +0400
@@ -0,0 +1,89 @@
+;;
+;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+
+(ns net.kryshen.indyvon.demo
+  (:gen-class)
+  (:use
+   (net.kryshen.indyvon core layers component))
+  (:import
+   (net.kryshen.indyvon.core Size Bounds)
+   (java.awt Color)
+   (javax.swing JFrame)))
+
+(def frame (JFrame. "Test"))
+
+(def layer1
+     (reify
+      Layer
+      (render! [layer]
+         (with-handlers layer
+           (doto *graphics*
+             (.setColor Color/RED)
+             (.fillRect 0 0 (:width *bounds*) (:height *bounds*)))
+           (:mouse-entered e (println e))
+           (:mouse-exited e (println e))
+           (:mouse-moved e (println e))))
+      (layer-size [layer] (Size. 30 20))))
+
+(def layer1b (border layer1 2 3))
+
+(def layer2
+     (reify
+      Layer
+      (render! [layer]
+         (doto *graphics*
+           (.setColor Color/YELLOW)
+           (.fillRect 0 0 (:width *bounds*) (:height *bounds*)))
+         (draw! layer1b 10 5)
+         (draw! layer1 55 5))
+      (layer-size [layer] (Size. 70 65))))
+
+(def layer3
+     (border (text-layer "Sample\ntext" :right :center)))
+
+(defn fps-layer [fps]
+  (border (text-layer (format "%.1f" fps) :right :bottom) 0 5))
+
+(def fps
+     (let [update-interval 0.1
+           frames (ref 0)
+           last (ref 0)
+           fl (ref (fps-layer 0.0))]
+       (reify
+        Layer
+        (render! [layer]
+           (draw! @fl)
+           (dosync
+            (alter frames + 1)
+            (let [time (System/currentTimeMillis)
+                  elapsed (/ (- time @last) 1000.0)]
+              (when (> elapsed update-interval)
+                (ref-set fl (fps-layer (/ @frames elapsed)))
+                (ref-set frames 0)
+                (ref-set last time)))))
+        (layer-size [layer] (layer-size @fl)))))
+
+(def layer
+     (reify
+      Layer
+      (render! [layer]
+         (*update*)
+         (doto *graphics*
+           (.setColor (rand-nth [Color/BLACK Color/BLUE Color/RED]))
+           (.drawLine 0 0 (:width *bounds*) (:height *bounds*)))
+         (draw! layer2 15 20)
+         (draw! layer3 100 100 80 50)
+         (draw! fps))
+      (layer-size [layer] (Size. 400 300))))
+
+(defn -main []
+  (doto frame
+    (.addWindowListener
+     (proxy [java.awt.event.WindowAdapter] []
+       (windowClosing [event] (.dispose frame))))
+    (.. (getContentPane) (add (make-jpanel (viewport layer))))
+    (.pack)
+    (.setVisible true)))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/kryshen/indyvon/graph.clj	Thu Jul 29 01:08:34 2010 +0400
@@ -0,0 +1,142 @@
+;;
+;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+
+(ns net.kryshen.indyvon.graph
+  (:use
+   (net.kryshen.indyvon core component layers))
+  (:import
+   (net.kryshen.indyvon.core Location Size)
+   (net.kryshen.indygraph Graph DefaultGraph GraphLayout Vertex2D
+                          Edge2D RectangularVertex2D DefaultEdge2D)
+   (net.kryshen.indygraph.fdl ForceDirectedLayout)
+   (java.awt.geom Path2D$Double)
+   (javax.swing JFrame)))
+
+(extend-type Vertex2D
+  Layer
+  (render! [v]
+     (.drawOval *graphics* 0 0 (:width *bounds*) (:height *bounds*)))
+  (layer-size [v]
+     (Size.
+      (+ (.getLeftBound v) (.getRightBound v))
+      (+ (.getTopBound v) (.getBottomBound v))))
+  Anchored
+  (anchor [v _ _]
+     (Location.
+      (.getLeftBound v)
+      (.getTopBound v))))
+
+(defn- draw-vertices! [^GraphLayout layout x y]
+  (doseq [v (.vertices (.getGraph layout))]
+    (draw-anchored! v :center :center (+ x (.getX v)) (+ y (.getY v)))))
+
+(defn- draw-movable-vertex!
+  [^Vertex2D v ^GraphLayout layout x y dragged fix-x fix-y]
+  (let [x (+ x (.getX v))
+        y (+ y (.getY v))
+        anchor (anchor v :center :center)
+        size (layer-size v)
+        x (- x (:x anchor))
+        y (- y (:y anchor))]
+    (with-bounds x y (:width size) (:height size)
+      (with-handlers v
+        (draw! v)
+        (:mouse-pressed e
+          (dosync (ref-set fix-x (:x-on-screen e))
+                  (ref-set fix-y (:y-on-screen e))
+                  (ref-set dragged v)))
+        (:mouse-released e
+          (dosync (if (= v @dragged)
+                    (ref-set dragged nil))))
+        (:mouse-dragged e
+          (let [x (:x-on-screen e)
+                y (:y-on-screen e)
+                vx (.getX v)
+                vy (.getY v)]
+            (dosync
+             (when @dragged
+               (let [dx (- x @fix-x)
+                     dy (- y @fix-y)]
+                 (.layoutLocation v (+ vx dx) (+ vy dy))
+                 (.invalidateLayout layout)
+                 (*update*)
+                 (ref-set fix-x x)
+                 (ref-set fix-y y))))))))))
+  
+(defn- draw-movable-vertices!
+  [^GraphLayout layout x y dragged-ref fix-x fix-y]
+  (let [dragged @dragged-ref]
+    (doseq [v (.vertices (.getGraph layout))
+            :when (not= v dragged)]
+      (draw-movable-vertex! v layout x y dragged-ref fix-x fix-y))
+    ;; Draw the vertex being dragged above others.
+    (when dragged
+      (draw-movable-vertex! dragged layout x y dragged-ref fix-x fix-y))))
+    
+(defn- draw-edges! [^GraphLayout layout x y]
+  ;; TODO: extend Layer on Edge2D and draw like vertices.
+  (.translate *graphics* x y)
+  (let [path (Path2D$Double.)]
+  (doseq [^Edge2D e (.edges (.getGraph layout))]
+    (.getPath e path)
+    (.draw *graphics* path)))
+  (.translate *graphics* (- x) (- y)))
+
+(defrecord GraphLayer [layout movable dragged fix-x fix-y]
+  Layer
+  (render! [layer]
+     (let [bounds (.getBounds layout)
+           x (- (.getX bounds))
+           y (- (.getY bounds))]
+       (draw-edges! layout x y)
+       (if movable
+         (draw-movable-vertices! layout x y dragged fix-x fix-y)
+         (draw-vertices! layout x y))))
+  (layer-size [layer]
+     (let [bounds (.getBounds layout)]
+       (Size. (.getWidth bounds) (.getHeight bounds))))
+  Anchored
+  (anchor [layer x-align y-align]
+          (let [bounds (.getBounds layout)]
+            (Location. (- (.getX bounds))
+                       (- (.getY bounds))))))
+
+(defn graph-layer
+  ([graph-layout]
+     (graph-layer graph-layout false))
+  ([^GraphLayout graph-layout movable]
+     (GraphLayer. graph-layout movable (ref nil) (ref 0) (ref 0))))
+
+(defn build-graph
+  "Returns Graph defined by a sequence of pairs of vertex ids,
+   and a function that maps vertex id's to Vertex objects."
+  [relations f]
+  (let [graph (DefaultGraph.)
+        vs (reduce #(conj %1 (first %2) (second %2)) #{} relations)
+        vm (reduce #(assoc %1 %2 (f %2)) {} vs)
+        vs (vals vm)
+        es (map #(DefaultEdge2D. (vm (first %)) (vm (second %))) relations)]
+    (doseq [v vs]
+      (.addVertex graph v))
+    (doseq [e es]
+      (.addEdge graph e))
+    graph))
+
+(comment
+  (let [graph (build-graph
+               [[1 2] [1 3] [1 4] [2 4]]
+               (fn [_] (RectangularVertex2D. 100 30)))
+        layout (ForceDirectedLayout. graph)
+        frame (JFrame. "Graph test")
+        layer (graph-layer layout true)
+        layer (viewport layer :center :center)]
+    (.add (.getContentPane frame) (make-jpanel layer))
+    (while (not (.update layout)))
+    (doto frame
+      (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE)
+      (.pack)
+      (.setVisible true)))
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/kryshen/indyvon/layers.clj	Thu Jul 29 01:08:34 2010 +0400
@@ -0,0 +1,236 @@
+;;
+;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+
+(ns net.kryshen.indyvon.layers
+  (:use
+   net.kryshen.indyvon.core)
+  (:import
+   (net.kryshen.indyvon.core Size Location)
+   (java.lang.ref SoftReference)
+   (java.awt Font Cursor Image Toolkit)
+   (java.awt.image ImageObserver)
+   (java.awt.font FontRenderContext TextLayout)))
+
+;; Define as macro to avoid unnecessary calculation of inner and outer
+;; sizes in the first case.
+(defmacro align-xy [inner outer align first center last]
+  `(case ~align
+         ~first 0
+         ~center (/ (- ~outer ~inner) 2)
+         ~last (- ~outer ~inner)))
+
+(defmacro align-x [inner outer align]
+  `(align-xy ~inner ~outer ~align :left :center :right))
+
+(defmacro align-y [inner outer align]
+  `(align-xy ~inner ~outer ~align :top :center :bottom))
+
+(defmacro decorate-layer [layer & render-tail]
+  `(reify
+    Layer
+    (render! ~@render-tail)
+    (layer-size [t#] (layer-size ~layer))
+    Anchored
+    (anchor [t# xa# ya#] (anchor ~layer xa# ya#))))
+
+(defn padding
+  ([content pad]
+     (padding content pad pad pad pad))
+  ([content top left bottom right]
+     (if (== 0 top left bottom right)
+       content
+       (reify
+        Layer
+        (render! [l]
+           (draw! content
+                  left top
+                  (- (:width *bounds*) left right)
+                  (- (:height *bounds*) top bottom)))
+        (layer-size [l]
+           (let [s (layer-size content)]
+             (Size. (+ (:width s) left right)
+                    (+ (:height s) top bottom))))))))
+
+(defn border
+  "Decorate layer with a border."
+  ([content]
+     (border content 1))
+  ([content width]
+     (border content width 0))
+  ([content width gap]
+     (let [layer (padding content (+ width gap))]
+       (decorate-layer layer [_]
+         (let [w (:width *bounds*)
+               h (:height *bounds*)]
+           (with-color (:border-color *theme*)
+             (doseq [i (range 0 width)]
+               (.drawRect *graphics* i i (- w 1 i i) (- h 1 i i))))
+           (render! layer))))))
+
+(defn panel
+  "Opaque layer using theme's alt-back-color."
+  ([content]
+     (panel content 0))
+  ([content gap]
+     (let [layer (padding content gap)]
+       (decorate-layer layer [_]
+         (with-color (:alt-back-color *theme*)
+           (.fillRect *graphics* 0 0
+                      (:width *bounds*) (:height *bounds*)))
+         (render! layer)))))
+
+(defn- re-split [^java.util.regex.Pattern re s]
+  (seq (.split re s)))
+
+(def text-layout-cache (atom {}))
+
+(defn- get-text-layout
+  [^String line ^Font font ^FontRenderContext font-context]
+  (let [key [line font font-context]]
+    (or (if-let [^SoftReference softref (@text-layout-cache key)]
+          (.get softref)
+          (do (swap! text-layout-cache dissoc key)
+              false))
+        (let [layout (TextLayout. line font font-context)]
+          ;;(println "text-layout-cache miss" line)
+          (swap! text-layout-cache assoc key (SoftReference. layout))
+          layout))))
+
+(defn- layout-text
+  [lines ^Font font ^FontRenderContext font-context]
+  (map #(get-text-layout % font font-context) lines))
+  ;;(map #(TextLayout. ^String % 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 text-layer
+  "Creates a layer to display multiline text."
+  ([text]
+     (text-layer text :left :top))
+  ([text h-align v-align]
+     (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" text)]
+       (reify Layer
+        (render! [layer]
+           (let [w (:width *bounds*)
+                 h (:height *bounds*)
+                 font (.getFont *graphics*)
+                 layouts (layout-text lines font *font-context*)
+                 y (align-y (text-height layouts) h v-align)]
+             (loop [layouts layouts, y y]
+               (when-first [^TextLayout layout layouts]
+                 (let [ascent (.getAscent layout)
+                       lh (+ ascent (.getDescent layout) (.getLeading layout))
+                       x (align-x (.getAdvance layout) w h-align)]
+                   (.draw layout *graphics* x (+ y ascent))
+                   (recur (next layouts) (+ y lh)))))))
+        (layer-size [layer]
+           (let [layouts (layout-text lines (:font *theme*) *font-context*)
+                 width (text-width layouts)
+                 height (text-height layouts)]
+             (Size. width height)))))))
+
+(defn- image-observer [update-fn]
+  (reify
+   ImageObserver
+   (imageUpdate [this img infoflags x y width height]
+      (update-fn)
+      (zero? (bit-and infoflags
+                      (bit-or ImageObserver/ALLBITS
+                              ImageObserver/ABORT))))))
+
+(defn image-layer
+  [image-or-uri]
+  (let [^Image image (if (isa? image-or-uri Image)
+                       image-or-uri
+                       (.getImage (Toolkit/getDefaultToolkit)
+                                  ^java.net.URL image-or-uri))]
+    (.prepareImage (Toolkit/getDefaultToolkit) image -1 -1 nil)
+    (reify
+     Layer
+     (render! [layer]
+        (.drawImage *graphics* image 0 0
+                    ^ImageObserver (image-observer *update*)))
+     (layer-size [layer]
+        (let [observer (image-observer *update*)
+              width (.getWidth image observer)
+              height (.getHeight image observer)
+              width (if (pos? width) width 1)
+              height (if (pos? height) height 1)]
+          (Size. width height))))))
+
+(defn viewport
+  "Creates scrollable viewport layer."
+  ([content] (viewport content :left :top))
+  ([content h-align v-align]
+  (let [x (ref 0)
+        y (ref 0)
+        fix-x (ref 0)
+        fix-y (ref 0)
+        last-width (ref 0)
+        last-height (ref 0)]
+    (reify
+     Layer
+     (render! [layer]
+        (with-handlers layer
+         (let [anchor (anchor content h-align v-align)
+               width (:width *bounds*)
+               height (:height *bounds*)]
+           (dosync
+            (alter x + (align-x width @last-width h-align))
+            (alter y + (align-y height @last-height v-align))
+            (ref-set last-width width)
+            (ref-set last-height height))
+           (draw! content (- 0 @x (:x anchor)) (- 0 @y (:y anchor))))
+         (:mouse-pressed e
+          (dosync
+           (ref-set fix-x (:x-on-screen e))
+           (ref-set fix-y (:y-on-screen e)))
+          (->> Cursor/MOVE_CURSOR Cursor. (.setCursor *target*)))
+         (:mouse-released e
+          (->> Cursor/DEFAULT_CURSOR Cursor. (.setCursor *target*)))
+         (:mouse-dragged e
+          (dosync
+           (alter x + (- @fix-x (:x-on-screen e)))
+           (alter y + (- @fix-y (:y-on-screen e)))
+           (ref-set fix-x (:x-on-screen e))
+           (ref-set fix-y (:y-on-screen e)))
+          (*update*))))
+     (layer-size [layer] (layer-size content))))))
+
+;;
+;; Layer context decorators.
+;;
+
+(defmacro handler [layer & handlers]
+  `(let [layer# ~layer]
+     (decorate-layer layer# [t#]
+        (with-handlers t#
+          (render! layer#)
+          ~@handlers))))
+
+(defn theme [layer & map-or-keyvals]
+  (let [theme (if (== (count map-or-keyvals) 1)
+                map-or-keyvals
+                (apply array-map map-or-keyvals))]
+    (reify
+     Layer
+     (render! [t]
+        (with-theme theme
+          (render! layer)))
+     (layer-size [t]
+        (with-theme theme
+          (layer-size layer)))
+     Anchored
+     (anchor [t xa ya]
+        (with-theme theme
+          (anchor layer xa ya))))))
--- a/test/indyvon/core_test.clj	Wed Jul 28 04:47:30 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-(ns indyvon.core-test
-  (:use [indyvon.core] :reload-all)
-  (:use [clojure.test]))
-
-(deftest replace-me ;; FIXME: write
-  (is false))