changeset 36:5413b188d112

Rename namespaces: indyvon to kryshen.indyvon.
author Mikhail Kryshen <mikhail@kryshen.net>
date Thu, 08 Jul 2010 07:03:24 +0400
parents 0d593970cb76
children d2fb660ca49f
files project.clj src/indyvon/component.clj src/indyvon/core.clj src/indyvon/demo.clj src/indyvon/layers.clj src/kryshen/indyvon/component.clj src/kryshen/indyvon/core.clj src/kryshen/indyvon/demo.clj src/kryshen/indyvon/layers.clj
diffstat 9 files changed, 584 insertions(+), 584 deletions(-) [+]
line wrap: on
line diff
--- a/project.clj	Thu Jul 08 06:02:12 2010 +0400
+++ b/project.clj	Thu Jul 08 07:03:24 2010 +0400
@@ -3,7 +3,7 @@
   :dependencies [[org.clojure/clojure "1.2.0-master-SNAPSHOT"]
                  [org.clojure/clojure-contrib "1.2.0-SNAPSHOT"]]
   :dev-dependencies [[leiningen/lein-swank "1.2.0-SNAPSHOT"]]
-  :namespaces [indyvon.core
-               indyvon.layers
-               indyvon.component
-               indyvon.demo])
+  :namespaces [kryshen.indyvon.core
+               kryshen.indyvon.layers
+               kryshen.indyvon.component
+               kryshen.indyvon.demo])
--- a/src/indyvon/component.clj	Thu Jul 08 06:02:12 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-;;
-;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-
-(ns indyvon.component
-  (:use indyvon.core
-        indyvon.layers)
-  (:import (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 nil)
-        (commit event-dispatcher)))))
-
-(defn preferred-size [component layer]
-  (binding [*target* component
-            *font-context*' (font-context component)]
-    (let [s (size layer nil)]
-      (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/indyvon/core.clj	Thu Jul 08 06:02:12 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,310 +0,0 @@
-;;
-;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-
-(ns indyvon.core
-  (:import (java.awt Graphics2D Component Color Font)
-           (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 border-color font])
-
-(defn default-theme []
-  (Theme. Color/BLACK 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 opts])
-  (size [this opts]))
-
-;; 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 opts]
-     "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
-  indyvon.core.Layer
-  (anchor [this h-align v-align opts]
-          (if (and (= h-align :left)
-                   (= v-align :top))
-            (Location. 0 0)
-            (let [size (size this opts)]
-              (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 [x11 (:x b1)
-        y11 (:y b1)
-        x12 (+ x11 (:width b1))
-        y12 (+ y11 (:height b1))
-        x21 (:x b2)
-        y21 (:y b2)
-        x22 (+ x21 (:width b2))
-        y22 (+ y21 (:height b2))
-        x1 (max x11 x21)
-        y1 (max y11 y21)
-        x2 (min x12 x22)
-        y2 (min y12 y22)]
-    (Bounds. x1 y1 (- x2 x1) (- y2 y1))))
-
-(defn with-translate* [x y w h f & args]
-  (let [graphics (apply-theme (.create *graphics* x y w h) *theme*)
-        bounds (Bounds. (+ x (:x *bounds*))
-                        (+ y (:y *bounds*))
-                        w h)]
-    (try
-      (apply with-bindings* {#'*bounds* bounds
-                             #'*clip* (intersect bounds *clip*)
-                             #'*graphics* graphics}
-             f args)
-      (finally
-       (.dispose graphics)))))
-
-(defmacro with-translate [x y w h & body]
-  `(with-translate* ~x ~y ~w ~h (fn [] ~@body)))
-
-
-
-(defn with-handlers* [handle handlers f & args]
-  (apply with-bindings*
-         {#'*event-dispatcher*
-          (create-dispatcher *event-dispatcher* handle handlers)}
-         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- geometry-vec [geometry]
-  (if (vector? geometry)
-    geometry
-    [(:x geometry) (:y geometry) (:width geometry) (:height geometry)]))
-
-(defn draw!
-  "Draw a layer. Geometry is either a map or vector [x y] or
-   [x y width height]."
-  [layer geometry & args]
-  (let [[x y w h] (geometry-vec geometry)
-        size (if-not (and w h) (size layer args))
-        w (or w (:width size))
-        h (or h (:height size))]
-    (with-translate* x y w h render! layer args)))
-
-;;
-;; 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 sequence of child nodes under cursor."
-  [x y tree node]
-  (some #(if (inside? x y (:bounds %))
-           (conj (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 ^java.awt.event.MouseEvent event]
-     (translate-and-dispatch nodes event (awt-events (.getID event))))
-  ([nodes event id]
-     (doseq [node nodes]
-       (when-let [handler (get (:handlers node) id)]
-         (with-bindings* (:bindings node)
-           handler
-           (translate-mouse-event event
-             (-> node :bounds :x) (-> node :bounds :y) id))))
-     id))
-
-(defn- dispatch-mouse-motion*
-  "Dispatches mouse motion events. Returns a new set of nodes which
-  currently are under cursor."
-  [hovered tree root ^java.awt.event.MouseEvent event]
-  (let [x (.getX event)
-        y (.getY event)
-        hovered2 (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 event :mouse-exited)
-    (translate-and-dispatch entered event :mouse-entered)
-    (translate-and-dispatch moved event :mouse-moved)
-    hovered2))
-
-(defn- dispatch-mouse-motion
-  [hovered-ref tree root event]
-  (dosync
-   (alter hovered-ref dispatch-mouse-motion* tree root event)))
-
-(defn- dispatch-mouse-button*
-  "Dispatches mouse button events. Returns a new set of nodes which
-  currently are picked with a pressed button."
-  [picked hovered event]
-  (if (= (translate-and-dispatch hovered event) :mouse-pressed)
-    hovered
-    nil))
-
-(defn- dispatch-mouse-button
-  [picked-ref hovered-ref event]
-  (dosync
-   (alter picked-ref dispatch-mouse-button* @hovered-ref event)))
-
-(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]
-        (dispatch-mouse-button picked hovered event))
-     MouseMotionListener
-     (mouseDragged [this event]
-        (translate-and-dispatch @picked 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/indyvon/demo.clj	Thu Jul 08 06:02:12 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-;;
-;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-
-(ns indyvon.demo
-  (:gen-class)
-  (:use indyvon.core
-        indyvon.layers
-        indyvon.component)
-  (:import (indyvon.core Size Bounds)
-           (java.awt Graphics2D Component Dimension Color)
-           (javax.swing JFrame JPanel)))
-
-(def frame (JFrame. "Test"))
-
-(def layer1
-     (reify
-      Layer
-      (render! [layer opts]
-         (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))))
-      (size [layer opts] (Size. 30 20))))
-
-(def layer1b (border-layer layer1 2 3))
-
-(def layer2
-     (reify
-      Layer
-      (render! [layer opts]
-         (doto *graphics*
-           (.setColor Color/YELLOW)
-           (.fillRect 0 0 (:width *bounds*) (:height *bounds*)))
-         (draw! layer1b [10 5])
-         (draw! layer1 [55 5]))
-      (size [layer opts] (Size. 70 65))))
-
-(def layer3
-     (border-layer (text-layer "Sample\ntext" :right :center)))
-
-(defn fps-layer [fps]
-  (border-layer (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 opts]
-           (render! @fl nil)
-           (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)))))
-        (size [layer opts] (size @fl nil)))))
-
-(def layer
-     (reify
-      Layer
-      (render! [layer opts]
-         (*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])
-         (render! fps nil))
-      (size [layer opts] (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/indyvon/layers.clj	Thu Jul 08 06:02:12 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +0,0 @@
-;;
-;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
-;;
-;; This file is part of Indyvon.
-;;
-
-(ns indyvon.layers
-  (:use indyvon.core)
-  (:import (indyvon.core Size Location)
-           (java.awt Font Cursor)
-           (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))
-
-(defn border-layer
-  "Decorate layer with a border."
-  ([content]
-     (border-layer content 1))
-  ([content width]
-     (border-layer content width 0))
-  ([content width gap]
-     (let [offset (+ width gap)]
-       (reify Layer
-        (render! [l opts]
-           (let [w (:width *bounds*)
-                 h (:height *bounds*)]
-             (.setColor *graphics* (:border-color *theme*))
-             (doseq [i (range 0 width)]
-               (.drawRect *graphics* i i (- w 1 i i) (- h 1 i i)))
-             (apply draw! content
-                    [offset offset (- w offset offset) (- h offset offset)]
-                    opts)))
-        (size [l opts]
-           (let [s (size content opts)]
-             (Size. (+ (:width s) offset offset)
-                    (+ (:height s) offset offset))))))))
-
-(defn- re-split [^java.util.regex.Pattern re s]
-  (seq (.split re s)))
-
-(defn- layout-text [lines ^Font font ^FontRenderContext font-context]
-  (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 opts]
-           (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)))))))
-        (size [layer opts]
-           (let [layouts (layout-text lines (:font *theme*) *font-context*)
-                 width (text-width layouts)
-                 height (text-height layouts)]
-             (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 opts]
-        (with-handlers layer
-         (let [anchor (anchor content h-align v-align opts)
-               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))
-           (apply draw! content
-                  [(- 0 @x (:x anchor)) (- 0 @y (:y anchor))] opts))
-         (: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*))))
-     (size [layer opts] (size content opts))))))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/kryshen/indyvon/component.clj	Thu Jul 08 07:03:24 2010 +0400
@@ -0,0 +1,52 @@
+;;
+;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+
+(ns kryshen.indyvon.component
+  (:use kryshen.indyvon.core
+        kryshen.indyvon.layers)
+  (: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 nil)
+        (commit event-dispatcher)))))
+
+(defn preferred-size [component layer]
+  (binding [*target* component
+            *font-context*' (font-context component)]
+    (let [s (size layer nil)]
+      (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/kryshen/indyvon/core.clj	Thu Jul 08 07:03:24 2010 +0400
@@ -0,0 +1,310 @@
+;;
+;; 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)
+           (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 border-color font])
+
+(defn default-theme []
+  (Theme. Color/BLACK 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 opts])
+  (size [this opts]))
+
+;; 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 opts]
+     "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 opts]
+          (if (and (= h-align :left)
+                   (= v-align :top))
+            (Location. 0 0)
+            (let [size (size this opts)]
+              (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 [x11 (:x b1)
+        y11 (:y b1)
+        x12 (+ x11 (:width b1))
+        y12 (+ y11 (:height b1))
+        x21 (:x b2)
+        y21 (:y b2)
+        x22 (+ x21 (:width b2))
+        y22 (+ y21 (:height b2))
+        x1 (max x11 x21)
+        y1 (max y11 y21)
+        x2 (min x12 x22)
+        y2 (min y12 y22)]
+    (Bounds. x1 y1 (- x2 x1) (- y2 y1))))
+
+(defn with-translate* [x y w h f & args]
+  (let [graphics (apply-theme (.create *graphics* x y w h) *theme*)
+        bounds (Bounds. (+ x (:x *bounds*))
+                        (+ y (:y *bounds*))
+                        w h)]
+    (try
+      (apply with-bindings* {#'*bounds* bounds
+                             #'*clip* (intersect bounds *clip*)
+                             #'*graphics* graphics}
+             f args)
+      (finally
+       (.dispose graphics)))))
+
+(defmacro with-translate [x y w h & body]
+  `(with-translate* ~x ~y ~w ~h (fn [] ~@body)))
+
+
+
+(defn with-handlers* [handle handlers f & args]
+  (apply with-bindings*
+         {#'*event-dispatcher*
+          (create-dispatcher *event-dispatcher* handle handlers)}
+         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- geometry-vec [geometry]
+  (if (vector? geometry)
+    geometry
+    [(:x geometry) (:y geometry) (:width geometry) (:height geometry)]))
+
+(defn draw!
+  "Draw a layer. Geometry is either a map or vector [x y] or
+   [x y width height]."
+  [layer geometry & args]
+  (let [[x y w h] (geometry-vec geometry)
+        size (if-not (and w h) (size layer args))
+        w (or w (:width size))
+        h (or h (:height size))]
+    (with-translate* x y w h render! layer args)))
+
+;;
+;; 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 sequence of child nodes under cursor."
+  [x y tree node]
+  (some #(if (inside? x y (:bounds %))
+           (conj (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 ^java.awt.event.MouseEvent event]
+     (translate-and-dispatch nodes event (awt-events (.getID event))))
+  ([nodes event id]
+     (doseq [node nodes]
+       (when-let [handler (get (:handlers node) id)]
+         (with-bindings* (:bindings node)
+           handler
+           (translate-mouse-event event
+             (-> node :bounds :x) (-> node :bounds :y) id))))
+     id))
+
+(defn- dispatch-mouse-motion*
+  "Dispatches mouse motion events. Returns a new set of nodes which
+  currently are under cursor."
+  [hovered tree root ^java.awt.event.MouseEvent event]
+  (let [x (.getX event)
+        y (.getY event)
+        hovered2 (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 event :mouse-exited)
+    (translate-and-dispatch entered event :mouse-entered)
+    (translate-and-dispatch moved event :mouse-moved)
+    hovered2))
+
+(defn- dispatch-mouse-motion
+  [hovered-ref tree root event]
+  (dosync
+   (alter hovered-ref dispatch-mouse-motion* tree root event)))
+
+(defn- dispatch-mouse-button*
+  "Dispatches mouse button events. Returns a new set of nodes which
+  currently are picked with a pressed button."
+  [picked hovered event]
+  (if (= (translate-and-dispatch hovered event) :mouse-pressed)
+    hovered
+    nil))
+
+(defn- dispatch-mouse-button
+  [picked-ref hovered-ref event]
+  (dosync
+   (alter picked-ref dispatch-mouse-button* @hovered-ref event)))
+
+(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]
+        (dispatch-mouse-button picked hovered event))
+     MouseMotionListener
+     (mouseDragged [this event]
+        (translate-and-dispatch @picked 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/kryshen/indyvon/demo.clj	Thu Jul 08 07:03:24 2010 +0400
@@ -0,0 +1,89 @@
+;;
+;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+
+(ns kryshen.indyvon.demo
+  (:gen-class)
+  (:use kryshen.indyvon.core
+        kryshen.indyvon.layers
+        kryshen.indyvon.component)
+  (:import (kryshen.indyvon.core Size Bounds)
+           (java.awt Color)
+           (javax.swing JFrame)))
+
+(def frame (JFrame. "Test"))
+
+(def layer1
+     (reify
+      Layer
+      (render! [layer opts]
+         (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))))
+      (size [layer opts] (Size. 30 20))))
+
+(def layer1b (border-layer layer1 2 3))
+
+(def layer2
+     (reify
+      Layer
+      (render! [layer opts]
+         (doto *graphics*
+           (.setColor Color/YELLOW)
+           (.fillRect 0 0 (:width *bounds*) (:height *bounds*)))
+         (draw! layer1b [10 5])
+         (draw! layer1 [55 5]))
+      (size [layer opts] (Size. 70 65))))
+
+(def layer3
+     (border-layer (text-layer "Sample\ntext" :right :center)))
+
+(defn fps-layer [fps]
+  (border-layer (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 opts]
+           (render! @fl nil)
+           (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)))))
+        (size [layer opts] (size @fl nil)))))
+
+(def layer
+     (reify
+      Layer
+      (render! [layer opts]
+         (*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])
+         (render! fps nil))
+      (size [layer opts] (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/kryshen/indyvon/layers.clj	Thu Jul 08 07:03:24 2010 +0400
@@ -0,0 +1,129 @@
+;;
+;; 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.awt Font Cursor)
+           (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))
+
+(defn border-layer
+  "Decorate layer with a border."
+  ([content]
+     (border-layer content 1))
+  ([content width]
+     (border-layer content width 0))
+  ([content width gap]
+     (let [offset (+ width gap)]
+       (reify Layer
+        (render! [l opts]
+           (let [w (:width *bounds*)
+                 h (:height *bounds*)]
+             (.setColor *graphics* (:border-color *theme*))
+             (doseq [i (range 0 width)]
+               (.drawRect *graphics* i i (- w 1 i i) (- h 1 i i)))
+             (apply draw! content
+                    [offset offset (- w offset offset) (- h offset offset)]
+                    opts)))
+        (size [l opts]
+           (let [s (size content opts)]
+             (Size. (+ (:width s) offset offset)
+                    (+ (:height s) offset offset))))))))
+
+(defn- re-split [^java.util.regex.Pattern re s]
+  (seq (.split re s)))
+
+(defn- layout-text [lines ^Font font ^FontRenderContext font-context]
+  (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 opts]
+           (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)))))))
+        (size [layer opts]
+           (let [layouts (layout-text lines (:font *theme*) *font-context*)
+                 width (text-width layouts)
+                 height (text-height layouts)]
+             (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 opts]
+        (with-handlers layer
+         (let [anchor (anchor content h-align v-align opts)
+               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))
+           (apply draw! content
+                  [(- 0 @x (:x anchor)) (- 0 @y (:y anchor))] opts))
+         (: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*))))
+     (size [layer opts] (size content opts))))))