changeset 28:828795987d4c

Some ideas.
author Mikhail Kryshen <mikhail@kryshen.net>
date Mon, 05 Jul 2010 06:11:42 +0400
parents 61bc04f94d61
children 6975b9a71eec
files src/indyvon/component.clj src/indyvon/core.clj src/indyvon/core_new.clj
diffstat 3 files changed, 162 insertions(+), 419 deletions(-) [+]
line diff
     1.1 --- a/src/indyvon/component.clj	Sun Jul 04 06:03:48 2010 +0400
     1.2 +++ b/src/indyvon/component.clj	Mon Jul 05 06:11:42 2010 +0400
     1.3 @@ -5,46 +5,45 @@
     1.4  ;;
     1.5  
     1.6  (ns indyvon.component
     1.7 -  (:use indyvon.core indyvon.layers)
     1.8 +  (:use indyvon.core)
     1.9    (:import (java.awt Component Dimension Color)
    1.10             (javax.swing JFrame JPanel)))
    1.11  
    1.12  (defn- font-context [component]
    1.13    (.getFontRenderContext (.getFontMetrics component (.getFont component))))
    1.14  
    1.15 -(defn paint-component [component layer context graphics]
    1.16 +(defn paint-component [component layer graphics event-dispatcher]
    1.17    (let [size (.getSize component)
    1.18          width (.width size)
    1.19 -        height (.height size)
    1.20 -        context (assoc context
    1.21 -                  :target component
    1.22 -                  :font-context (.getFontRenderContext graphics)
    1.23 -                  :update-fn #(.repaint component))]
    1.24 +        height (.height size)]
    1.25      (.clearRect graphics 0 0 width height)
    1.26 -    (draw! layer context graphics 0 0 width height false))
    1.27 -  (commit (:dispatcher context)))
    1.28 +    (binding [*path* nil
    1.29 +              *graphics* graphics
    1.30 +              *font-context*' (.getFontRenderContext graphics)
    1.31 +              *event-dispatcher* event-dispatcher
    1.32 +              *update* #(.repaint component)
    1.33 +              *bounds* (indyvon.core.Bounds. 0 0 width height)]
    1.34 +      (render! layer nil)
    1.35 +      (commit event-dispatcher))))
    1.36  
    1.37 -(defn preferred-size [component layer context]
    1.38 -  (let [context (assoc context
    1.39 -                  :target component
    1.40 -                  :font-context (font-context component))
    1.41 -        s (size layer context)]
    1.42 -    (Dimension. (s 0) (s 1))))
    1.43 +(defn preferred-size [component layer]
    1.44 +  (binding [*path* nil
    1.45 +            *font-context*' (font-context component)]
    1.46 +    (let [s (size layer nil)]
    1.47 +      (Dimension. (:width s) (:height s)))))
    1.48  
    1.49  (defn make-jpanel
    1.50    ([layer]
    1.51       (make-jpanel layer (make-event-dispatcher)))
    1.52    ([layer event-dispatcher]
    1.53 -     (let [context (default-context)
    1.54 -           context (assoc context :dispatcher event-dispatcher)
    1.55 -           panel
    1.56 +     (let [panel
    1.57             (proxy [JPanel] []
    1.58               (paintComponent [g]
    1.59 -                (paint-component this layer context g))
    1.60 +                (paint-component this layer g event-dispatcher))
    1.61               (getPreferredSize []
    1.62 -                (preferred-size this layer context)))]
    1.63 +                (preferred-size this layer)))]
    1.64 +       (.setBackground panel (:back-color *theme*))
    1.65         (listen! event-dispatcher panel)
    1.66 -       (.setBackground panel (-> context :theme :back-color))
    1.67         panel)))
    1.68  
    1.69  (comment
     2.1 --- a/src/indyvon/core.clj	Sun Jul 04 06:03:48 2010 +0400
     2.2 +++ b/src/indyvon/core.clj	Mon Jul 05 06:11:42 2010 +0400
     2.3 @@ -8,145 +8,92 @@
     2.4    (:import (java.awt Color Font)
     2.5             (java.awt.event MouseListener MouseMotionListener)))
     2.6  
     2.7 -(def *context*)
     2.8 -(def *graphics*)
     2.9 -
    2.10 +(defrecord Location [x y])
    2.11  (defrecord Size [width height])
    2.12  (defrecord Bounds [x y width height])
    2.13  
    2.14 +(def *graphics*)
    2.15  (def *font-context*)
    2.16  (def *bounds*)
    2.17 -(def *theme*)
    2.18  (def *target*)
    2.19  (def *update*)
    2.20  (def *event-dispatcher*)
    2.21 +(def *path*)
    2.22 +
    2.23 +(defrecord Theme [fore-color back-color border-color font])
    2.24 +     
    2.25 +(defn- default-theme []
    2.26 +  (Theme. Color/BLACK Color/WHITE Color/BLUE (Font. "Sans" Font/PLAIN 12)))
    2.27 +
    2.28 +(def *theme* (default-theme))
    2.29  
    2.30  (defprotocol Layer
    2.31    "Basic UI element."
    2.32 -  (render! [this context graphics])
    2.33 -  (size [this context]))
    2.34 +  (render! [this opts])
    2.35 +  (size [this opts]))
    2.36 +
    2.37 +(defn layer? [x]
    2.38 +  (satisfies? Layer x)) 
    2.39 +
    2.40 +(defprotocol EventDispatcher
    2.41 +  (listen! [this component])
    2.42 +  (register [this handle-path])
    2.43 +  (handler [this handle-path event-id f])
    2.44 +  (commit [this]))
    2.45  
    2.46  ;; TODO: modifiers
    2.47  (defrecord MouseEvent [id when x y x-on-screen y-on-screen button])
    2.48  
    2.49 -(defprotocol MouseHandler
    2.50 -  "Layers that also satisfy this protocol will recieve mouse events."
    2.51 -  (handle-mouse [this context event]))
    2.52 +(defn- apply-theme [graphics]
    2.53 +  (doto graphics
    2.54 +    (.setColor (:fore-color *theme*))
    2.55 +    (.setFont (:font *theme*))))
    2.56  
    2.57 -(defprotocol EventDispatcher
    2.58 -  (listen! [this component])
    2.59 -  (register [this context])
    2.60 -  (commit [this])
    2.61 -  (hovered? [this layer])
    2.62 -  (picked? [this layer]))
    2.63 +(defn with-translate* [x y w h f & args]
    2.64 +  (let [graphics (apply-theme (.create *graphics* x y w h))]
    2.65 +    (try
    2.66 +      (apply with-bindings* {#'*bounds* (Bounds. (+ x (:x *bounds*))
    2.67 +                                                 (+ y (:y *bounds*))
    2.68 +                                                 w h)
    2.69 +                             #'*graphics* graphics}
    2.70 +             f args)
    2.71 +      (finally
    2.72 +       (.dispose graphics)))))
    2.73  
    2.74 -(defprotocol Anchored
    2.75 -  "Provide anchor point for Layers. Used by viewport."
    2.76 -  (anchor [this context h-align v-align]
    2.77 -          "Anchor point: [x y], h-align could be :left, :center
    2.78 -           or :right, v-align is :top, :center or :bottom"))
    2.79 +(defmacro with-translate [x y w h & body]
    2.80 +  `(with-translate* ~x ~y ~w ~h (fn [] ~@body)))
    2.81  
    2.82 -;; Default implementation of Anchored for any Layer.
    2.83 -(extend-protocol Anchored
    2.84 -  indyvon.core.Layer
    2.85 -  (anchor [this context h-align v-align]
    2.86 -          (if (and (= h-align :left)
    2.87 -                   (= v-align :top))
    2.88 -            [0 0]
    2.89 -            (let [size (size this context)]
    2.90 -              [(case h-align
    2.91 -                 :top 0
    2.92 -                 :center (/ (size 0) 2)
    2.93 -                 :right (size 0))
    2.94 -               (case v-align
    2.95 -                 :left 0
    2.96 -                 :center (/ (size 1) 2)
    2.97 -                 :bottom (size 1))]))))
    2.98 +(defn with-handle* [handle f & args]
    2.99 +  (let [path (cons handle *path*)]
   2.100 +    (register *event-dispatcher* path)
   2.101 +    (apply with-bindings* {#'*path* path} f args)))
   2.102  
   2.103 -(defrecord Theme [fore-color back-color border-color font])
   2.104 +(defmacro with-handle [handle & body]
   2.105 +  `(with-handle* ~handle (fn [] ~@body)))
   2.106  
   2.107 -(defn default-theme []
   2.108 -  (Theme. Color/BLACK Color/WHITE Color/BLUE (Font. "Sans" Font/PLAIN 12)))
   2.109 -  
   2.110 -(defrecord LayerContext
   2.111 -  [handle           ; Identifies context for dispatching mouse
   2.112 -                    ; entered/exited and mainaining keyboard focus.
   2.113 -   parent           ; Parent context.
   2.114 -   x y width height ; Geometry.
   2.115 -   update-fn        ; Call to request repaint.
   2.116 -   dispatcher       ; Event dispatcher.
   2.117 -   font-context     ; An instance of java.awt.font.FontRenderContext.
   2.118 -   theme            ; An instance of Theme.
   2.119 -   target           ; Component.
   2.120 -   handlers])       ; Map: event-id -> handler fn.
   2.121 +(defn handle-event* [event-id f & args]
   2.122 +  (let [f (if args #(f % args) f)]
   2.123 +    (handler *event-dispatcher* *path* event-id f)))
   2.124  
   2.125 -(defn default-context []
   2.126 -  (LayerContext. nil nil 0 0 0 0 nil nil nil (default-theme) nil nil))
   2.127 +(defmacro handle-event [event-id name & body]
   2.128 +  `(handle-event* ~event-id (fn [~name] ~@body)))
   2.129  
   2.130 -(defn update [context]
   2.131 -  ((:update-fn context)))
   2.132 +(defn- geometry-vec [geometry]
   2.133 +  (if (vector? geometry)
   2.134 +    geometry
   2.135 +    [(:x geometry) (:y geometry) (:width geometry) (:height geometry)]))
   2.136  
   2.137 -(defn- make-graphics [graphics x y w h clip]
   2.138 -  (if clip
   2.139 -    (.create graphics x y w h)
   2.140 -    (doto (.create graphics)
   2.141 -      (.translate x y))))
   2.142 -
   2.143 -(defn- apply-theme [graphics theme]
   2.144 -  (doto graphics
   2.145 -    (.setColor (:fore-color theme))
   2.146 -    (.setFont (:font theme))))
   2.147 -
   2.148 -;; (defn with-context* [opts fn & args]
   2.149 -;;   (let [context (apply assoc *context*
   2.150 -;;                        :parent *context*
   2.151 -;;                        :handlers nil
   2.152 -;;                        opts)
   2.153 -;;         graphics (make-graphics *graphics* x y w h false)
   2.154 -;;         graphics (apply-theme graphics (:theme context))]
   2.155 -;;     (try
   2.156 -;;       (register (:dispatcher context) context)
   2.157 -;;       (with-bindings* {#'*context* context
   2.158 -;;                        #'*graphics* graphics}
   2.159 -;;         fn args)
   2.160 -;;       (finally
   2.161 -;;        (.dispose graphics)))))
   2.162 -
   2.163 -;; (defmacro with-context [opts & body]
   2.164 -;;   `(with-context* ~opts #(~@body)))
   2.165 -
   2.166 -(defn draw!
   2.167 -  "Render layer in a new graphics context."
   2.168 -  ([layer context graphics]
   2.169 -     (draw! layer context graphics
   2.170 -                    0 0 (:width context) (:height context)))
   2.171 -  ([layer context graphics x y]
   2.172 -     (draw! layer context graphics x y true))
   2.173 -  ([layer context graphics x y clip]
   2.174 -     (let [s (size layer context)]
   2.175 -       (draw! layer context graphics
   2.176 -              x y (s 0) (s 1) clip)))
   2.177 -  ([layer context graphics x y w h]
   2.178 -     (draw! layer context graphics
   2.179 -            x y w h true))
   2.180 -  ([layer context graphics x y w h clip]
   2.181 -     (let [context (assoc context
   2.182 -                     :handle layer
   2.183 -                     :parent context
   2.184 -                     :x (+ (:x context) x)
   2.185 -                     :y (+ (:y context) y)
   2.186 -                     :width w
   2.187 -                     :height h)
   2.188 -           graphics (make-graphics graphics x y w h clip)
   2.189 -           graphics (apply-theme graphics (:theme context))]
   2.190 -       (try
   2.191 -         (register (:dispatcher context) context)
   2.192 -         (render! layer context graphics)
   2.193 -         (finally
   2.194 -          (.dispose graphics))))))
   2.195 +(defn draw! [layer geometry & args]
   2.196 +  "Draw a layer. Geometry is either a map or vector [x y] or
   2.197 +   [x y width height]."
   2.198 +  (let [[x y w h] (geometry-vec geometry)
   2.199 +        size (if-not (and w h) (size layer args))
   2.200 +        w (or w (:width size))
   2.201 +        h (or h (:height size))]
   2.202 +    (with-translate* x y w h render! layer args)))
   2.203  
   2.204  ;;
   2.205 -;; EventDispatcher implementation
   2.206 +;; EventDispatcher
   2.207  ;;
   2.208  
   2.209  (def awt-events
   2.210 @@ -158,25 +105,28 @@
   2.211        java.awt.event.MouseEvent/MOUSE_PRESSED  :mouse-pressed
   2.212        java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released})
   2.213  
   2.214 -(defn- registered-parent
   2.215 -  "Returns first context parent registered for event processing."
   2.216 -  [context-tree context]
   2.217 -  (let [parent (:parent context)]
   2.218 -    (cond
   2.219 -     (nil? parent) nil
   2.220 -     (contains? context-tree parent) parent
   2.221 -     :default (recur context-tree parent))))
   2.222 +(defrecord DispatcherNode [id bounds children handlers])
   2.223  
   2.224 -(defn- add-context
   2.225 -  [context-tree context]
   2.226 -  (let [parent (registered-parent context-tree context)]
   2.227 -    (assoc context-tree parent (cons context (context-tree parent))
   2.228 -           context nil)))
   2.229 +(defn- add-child [node child]
   2.230 +  (assoc node :children (cons child (:children node))))
   2.231 +
   2.232 +(defn- add-path [tree path]
   2.233 +  (let [parent-path (next path)
   2.234 +        parent-node (get tree parent-path)
   2.235 +        node (DispatcherNode. path *bounds* nil
   2.236 +                              (get-in tree [path :handlers]))]
   2.237 +    (assoc tree
   2.238 +      parent-path (add-child parent-node node)
   2.239 +      path node)))
   2.240 +
   2.241 +(defn add-handler [tree path event-id f]
   2.242 +  (let [keys [path :handlers event-id]]
   2.243 +  (assoc-in tree keys (cons f (get-in tree keys)))))
   2.244  
   2.245  (defn- inside?
   2.246 -  ([x y context]
   2.247 -     (inside? x y (:x context) (:y context)
   2.248 -              (:width context) (:height context)))
   2.249 +  ([x y bounds]
   2.250 +     (inside? x y (:x bounds) (:y bounds)
   2.251 +              (:width bounds) (:height bounds)))
   2.252    ([px py x y w h]
   2.253       (and (>= px x)
   2.254            (>= py y)
   2.255 @@ -184,13 +134,13 @@
   2.256            (< py (+ y h)))))
   2.257  
   2.258  (defn- under-cursor
   2.259 -  "Returns a sequence of contexts under cursor."
   2.260 -  ([context-tree x y]
   2.261 -     (under-cursor context-tree x y nil))
   2.262 -  ([context-tree x y context]
   2.263 -     (some #(if (inside? x y %)
   2.264 -              (conj (under-cursor context-tree x y %) %))
   2.265 -           (context-tree context))))
   2.266 +  "Returns a sequence of nodes under cursor."
   2.267 +  ([tree x y]
   2.268 +     (under-cursor tree x y nil))
   2.269 +  ([tree x y node]
   2.270 +     (some #(if (inside? x y (:bounds %))
   2.271 +              (conj (under-cursor tree x y %) %))
   2.272 +           (get tree (:children node)))))
   2.273  
   2.274  (defn- remove-all [coll1 coll2 pred]
   2.275    (filter #(not (some (partial pred %) coll2)) coll1))
   2.276 @@ -203,30 +153,26 @@
   2.277                 (.getButton event)))
   2.278  
   2.279  (defn- translate-and-dispatch
   2.280 -  ([contexts event]
   2.281 -     (translate-and-dispatch contexts event (awt-events (.getID event))))
   2.282 -  ([contexts event id]
   2.283 -     (doseq [context contexts]
   2.284 -       (if-let [handler (get (:handlers context) id)]
   2.285 -         (handler context (translate-mouse-event
   2.286 -                           event (:x context) (:y context) id))))
   2.287 +  ([nodes event]
   2.288 +     (translate-and-dispatch nodes event (awt-events (.getID event))))
   2.289 +  ([nodes event id]
   2.290 +     (doseq [node nodes
   2.291 +             :let [bounds (:bounds node)
   2.292 +                   event (translate-mouse-event event
   2.293 +                           (:x bounds) (:y bounds) id)]
   2.294 +             handler (get (:handlers node) id)]
   2.295 +       ;; TODO restore more of the original context.
   2.296 +       (with-bindings* {#'*bounds* bounds} handler event))
   2.297       id))
   2.298  
   2.299 -(defn- context-id [context]
   2.300 -  (loop [context context
   2.301 -         id nil]
   2.302 -    (if context
   2.303 -      (recur (:parent context) (cons (:handle context) id))
   2.304 -      id)))
   2.305 -
   2.306  (defn- dispatch-mouse-motion*
   2.307 -  "Dispatches mouse motion events. Returns a new set of contexts which
   2.308 +  "Dispatches mouse motion events. Returns a new set of nodes which
   2.309    currently are under cursor."
   2.310 -  [hovered context-tree event]
   2.311 +  [hovered tree event]
   2.312    (let [x (.getX event)
   2.313          y (.getY event)
   2.314 -        hovered2 (under-cursor context-tree x y)
   2.315 -        pred #(= (context-id %1) (context-id %2))
   2.316 +        hovered2 (under-cursor tree x y)
   2.317 +        pred #(= (:id %1) (:id %2))
   2.318          exited (remove-all hovered hovered2 pred)
   2.319          entered (remove-all hovered2 hovered pred)
   2.320          moved (remove-all hovered2 entered pred)]
   2.321 @@ -236,12 +182,12 @@
   2.322      hovered2))
   2.323  
   2.324  (defn- dispatch-mouse-motion
   2.325 -  [hovered-ref context-tree event]
   2.326 +  [hovered-ref tree event]
   2.327    (dosync
   2.328 -   (alter hovered-ref dispatch-mouse-motion* context-tree event)))
   2.329 +   (alter hovered-ref dispatch-mouse-motion* tree event)))
   2.330  
   2.331  (defn- dispatch-mouse-button*
   2.332 -  "Dispatches mouse button events. Returns a new set of contexts which
   2.333 +  "Dispatches mouse button events. Returns a new set of nodes which
   2.334    currently are picked with a pressed button."
   2.335    [picked hovered event]
   2.336    (if (= (translate-and-dispatch hovered event) :mouse-pressed)
   2.337 @@ -254,8 +200,10 @@
   2.338     (alter picked-ref dispatch-mouse-button* @hovered-ref event)))
   2.339  
   2.340  (defn make-event-dispatcher []
   2.341 -  (let [context-tree-r (ref {}) ; register
   2.342 -        context-tree (ref {})   ; dispatch
   2.343 +  (let [root-node (DispatcherNode. nil nil nil nil)
   2.344 +        tree-i {nil root-node} ; initial
   2.345 +        tree-r (ref tree-i)    ; register
   2.346 +        tree (ref tree-i)      ; dispatch
   2.347          hovered (ref '())
   2.348          picked (ref '())]
   2.349      (reify
   2.350 @@ -264,19 +212,18 @@
   2.351          (doto component
   2.352            (.addMouseListener this)
   2.353            (.addMouseMotionListener this)))
   2.354 -     (register [this context]
   2.355 -        (if (:handlers context)
   2.356 -          (dosync (alter context-tree-r add-context context))))
   2.357 +     (register [this path]
   2.358 +        (dosync (alter tree-r add-path path)))
   2.359 +     (handler [this path event-id f]
   2.360 +        (dosync (alter tree-r add-handler path event-id f)))
   2.361       (commit [this]
   2.362 -        (dosync (ref-set context-tree @context-tree-r)
   2.363 -                (ref-set context-tree-r {})))
   2.364 -     (picked? [this layer] false)
   2.365 -     (hovered? [this layer] false)
   2.366 +        (dosync (ref-set tree @tree-r)
   2.367 +                (ref-set tree-r tree-i)))
   2.368       MouseListener
   2.369       (mouseEntered [this event]
   2.370 -        (dispatch-mouse-motion hovered @context-tree event))
   2.371 +        (dispatch-mouse-motion hovered @tree event))
   2.372       (mouseExited [this event]
   2.373 -        (dispatch-mouse-motion hovered @context-tree event))
   2.374 +        (dispatch-mouse-motion hovered @tree event))
   2.375       (mouseClicked [this event]
   2.376          (dispatch-mouse-button picked hovered event))
   2.377       (mousePressed [this event]
   2.378 @@ -287,4 +234,29 @@
   2.379       (mouseDragged [this event]
   2.380          (translate-and-dispatch @picked event))
   2.381       (mouseMoved [this event]
   2.382 -        (dispatch-mouse-motion hovered @context-tree event)))))
   2.383 +        (dispatch-mouse-motion hovered @tree event)))))
   2.384 +
   2.385 +;;
   2.386 +;; ИДЕИ:
   2.387 +;;
   2.388 +;; Контекст: биндинги или запись?
   2.389 +;;
   2.390 +;; Установка обработчиков (в контексте слоя):
   2.391 +;;
   2.392 +;; (listen
   2.393 +;;   (:mouse-entered e
   2.394 +;;     ...)
   2.395 +;;   (:mouse-exited e
   2.396 +;;     ...))
   2.397 +;;
   2.398 +;; Не надо IMGUI.
   2.399 +;; Построение сцены путем декорирования слоев:
   2.400 +;;
   2.401 +;; (listener
   2.402 +;;  (:action e (println e))
   2.403 +;;  (:mouse-dragged e (println e))
   2.404 +;;  (theme :font "Helvetica-14"
   2.405 +;;    (vbox
   2.406 +;;      (button (text-layer "Button 1"))
   2.407 +;;      (button (text-layer "Button 2")))))
   2.408 +;;
     3.1 --- a/src/indyvon/core_new.clj	Sun Jul 04 06:03:48 2010 +0400
     3.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3 @@ -1,228 +0,0 @@
     3.4 -;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
     3.5 -;;
     3.6 -;; This file is part of Indyvon.
     3.7 -;;
     3.8 -
     3.9 -(ns indyvon.core_new
    3.10 -  (:import (java.awt Color Font)
    3.11 -           (java.awt.event MouseListener MouseMotionListener)))
    3.12 -
    3.13 -(defrecord Location [x y])
    3.14 -(defrecord Size [width height])
    3.15 -(defrecord Bounds [x y width height])
    3.16 -
    3.17 -(def *graphics*)
    3.18 -(def *font-context*)
    3.19 -(def *bounds*)
    3.20 -(def *theme*)
    3.21 -(def *target*)
    3.22 -(def *update*)
    3.23 -(def *event-dispatcher*)
    3.24 -(def *path*)
    3.25 -
    3.26 -(defprotocol Layer
    3.27 -  "Basic UI element."
    3.28 -  (render! [this opts])
    3.29 -  (size [this opts]))
    3.30 -
    3.31 -(defn layer? [x]
    3.32 -  (satisfies? Layer x)) 
    3.33 -
    3.34 -(defprotocol EventDispatcher
    3.35 -  (listen! [this component])
    3.36 -  (register [this handle-path])
    3.37 -  (handler [this handle-path event-id f])
    3.38 -  (commit [this]))
    3.39 -
    3.40 -;; TODO: modifiers
    3.41 -(defrecord MouseEvent [id when x y x-on-screen y-on-screen button])
    3.42 -
    3.43 -(defn with-translate* [x y w h f & args]
    3.44 -  (let [graphics (.create *graphics* x y w h)]  
    3.45 -    (try
    3.46 -      (apply with-bindings* {#'*bounds* (Bounds. (+ x (:x *bounds*))
    3.47 -                                                 (+ y (:y *bounds*))
    3.48 -                                                 w h)
    3.49 -                             #'*graphics* graphics}
    3.50 -             f args)
    3.51 -      (finally
    3.52 -       (.dispose graphics)))))
    3.53 -
    3.54 -(defn with-handle* [handle f & args]
    3.55 -  (let [path (cons handle *path*)]
    3.56 -    (register *event-dispatcher* path)
    3.57 -    (apply with-bindings* {#'*path* path} f args)))
    3.58 -
    3.59 -(defn- geometry-vec [geometry]
    3.60 -  (if (vector? geometry)
    3.61 -    geometry
    3.62 -    [(:x geometry) (:y geometry) (:width geometry) (:height geometry)]))
    3.63 -
    3.64 -(defn draw! [layer geometry & args]
    3.65 -  "Draw a layer. Geometry is either a map or vector [x y] or
    3.66 -   [x y width height]."
    3.67 -  (let [[x y w h] (geometry-vec geometry)
    3.68 -        size (if-not (and w h) (size layer args))
    3.69 -        w (or w (:width size))
    3.70 -        h (or h (:height size))]
    3.71 -    (with-translate* x y w h render! layer args)))
    3.72 -
    3.73 -(defn draw-root! [layer width height graphics event-dispatcher]
    3.74 -  (with-bindings* {#'*path* nil
    3.75 -                   #'*graphics* graphics
    3.76 -                   #'*event-dispatcher* event-dispatcher
    3.77 -                   #'*bounds* (Bounds. 0 0 width height)}
    3.78 -    render! layer))
    3.79 -
    3.80 -;;
    3.81 -;; EventDispatcher
    3.82 -;;
    3.83 -
    3.84 -(def awt-events
    3.85 -     {java.awt.event.MouseEvent/MOUSE_CLICKED  :mouse-clicked
    3.86 -      java.awt.event.MouseEvent/MOUSE_DRAGGED  :mouse-dragged
    3.87 -      java.awt.event.MouseEvent/MOUSE_ENTERED  :mouse-entered
    3.88 -      java.awt.event.MouseEvent/MOUSE_EXITED   :mouse-exited
    3.89 -      java.awt.event.MouseEvent/MOUSE_MOVED    :mouse-moved
    3.90 -      java.awt.event.MouseEvent/MOUSE_PRESSED  :mouse-pressed
    3.91 -      java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released})
    3.92 -
    3.93 -(defrecord DispatcherNode [id bounds children handlers])
    3.94 -
    3.95 -(defn- add-child [node child]
    3.96 -  (assoc node :children (cons child (:children node))))
    3.97 -
    3.98 -(defn- add-path [tree path]
    3.99 -  (let [parent-path (next path)
   3.100 -        parent-node (get tree parent-path)
   3.101 -        node (DispatcherNode. path *bounds* nil
   3.102 -                              (get-in tree [path :handlers]))]
   3.103 -    (assoc tree
   3.104 -      parent-path (add-child parent-node node)
   3.105 -      path node)))
   3.106 -
   3.107 -(defn add-handler [tree path event-id f]
   3.108 -  (let [keys [path :handlers event-id]]
   3.109 -  (assoc-in tree keys (cons f (get-in tree keys)))))
   3.110 -
   3.111 -(defn- inside?
   3.112 -  ([x y bounds]
   3.113 -     (inside? x y (:x bounds) (:y bounds)
   3.114 -              (:width bounds) (:height bounds)))
   3.115 -  ([px py x y w h]
   3.116 -     (and (>= px x)
   3.117 -          (>= py y)
   3.118 -          (< px (+ x w))
   3.119 -          (< py (+ y h)))))
   3.120 -
   3.121 -(defn- under-cursor
   3.122 -  "Returns a sequence of nodes under cursor."
   3.123 -  ([tree x y]
   3.124 -     (under-cursor tree x y nil))
   3.125 -  ([tree x y node]
   3.126 -     (some #(if (inside? x y (:bounds %))
   3.127 -              (conj (under-cursor tree x y %) %))
   3.128 -           (get tree (:children node)))))
   3.129 -
   3.130 -(defn- remove-all [coll1 coll2 pred]
   3.131 -  (filter #(not (some (partial pred %) coll2)) coll1))
   3.132 -
   3.133 -(defn- translate-mouse-event
   3.134 -  [event x y id]
   3.135 -  (MouseEvent. id (.getWhen event)
   3.136 -               (- (.getX event) x) (- (.getY event) y)
   3.137 -               (.getXOnScreen event) (.getYOnScreen event)
   3.138 -               (.getButton event)))
   3.139 -
   3.140 -(defn- translate-and-dispatch
   3.141 -  ([nodes event]
   3.142 -     (translate-and-dispatch nodes event (awt-events (.getID event))))
   3.143 -  ([nodes event id]
   3.144 -     (doseq [node nodes
   3.145 -             :let [bounds (:bounds node)
   3.146 -                   event (translate-mouse-event event
   3.147 -                           (:x bounds) (:y bounds) id)]
   3.148 -             handler (get (:handlers node) id)]
   3.149 -       ;; TODO restore more of the original context.
   3.150 -       (with-bindings* {#'*bounds* bounds} handler event))
   3.151 -     id))
   3.152 -
   3.153 -(defn- dispatch-mouse-motion*
   3.154 -  "Dispatches mouse motion events. Returns a new set of nodes which
   3.155 -  currently are under cursor."
   3.156 -  [hovered tree event]
   3.157 -  (let [x (.getX event)
   3.158 -        y (.getY event)
   3.159 -        hovered2 (under-cursor tree x y)
   3.160 -        pred #(= (:id %1) (:id %2))
   3.161 -        exited (remove-all hovered hovered2 pred)
   3.162 -        entered (remove-all hovered2 hovered pred)
   3.163 -        moved (remove-all hovered2 entered pred)]
   3.164 -    (translate-and-dispatch exited event :mouse-exited)
   3.165 -    (translate-and-dispatch entered event :mouse-entered)
   3.166 -    (translate-and-dispatch moved event :mouse-moved)
   3.167 -    hovered2))
   3.168 -
   3.169 -(defn- dispatch-mouse-motion
   3.170 -  [hovered-ref tree event]
   3.171 -  (dosync
   3.172 -   (alter hovered-ref dispatch-mouse-motion* tree event)))
   3.173 -
   3.174 -(defn- dispatch-mouse-button*
   3.175 -  "Dispatches mouse button events. Returns a new set of nodes which
   3.176 -  currently are picked with a pressed button."
   3.177 -  [picked hovered event]
   3.178 -  (if (= (translate-and-dispatch hovered event) :mouse-pressed)
   3.179 -    hovered
   3.180 -    nil))
   3.181 -
   3.182 -(defn- dispatch-mouse-button
   3.183 -  [picked-ref hovered-ref event]
   3.184 -  (dosync
   3.185 -   (alter picked-ref dispatch-mouse-button* @hovered-ref event)))
   3.186 -
   3.187 -(defn make-event-dispatcher []
   3.188 -  (let [root-node (DispatcherNode. nil nil nil nil)
   3.189 -        tree-i {nil root-node} ; initial
   3.190 -        tree-r (ref tree-i)    ; register
   3.191 -        tree (ref tree-i)      ; dispatch
   3.192 -        hovered (ref '())
   3.193 -        picked (ref '())]
   3.194 -    (reify
   3.195 -     EventDispatcher
   3.196 -     (listen! [this component]
   3.197 -        (doto component
   3.198 -          (.addMouseListener this)
   3.199 -          (.addMouseMotionListener this)))
   3.200 -     (register [this path]
   3.201 -        (dosync (alter tree-r add-path path)))
   3.202 -     (handler [this path event-id f]
   3.203 -        (dosync (alter tree-r add-handler path event-id f)))
   3.204 -     (commit [this]
   3.205 -        (dosync (ref-set tree @tree-r)
   3.206 -                (ref-set tree-r tree-i)))
   3.207 -     MouseListener
   3.208 -     (mouseEntered [this event]
   3.209 -        (dispatch-mouse-motion hovered @tree event))
   3.210 -     (mouseExited [this event]
   3.211 -        (dispatch-mouse-motion hovered @tree event))
   3.212 -     (mouseClicked [this event]
   3.213 -        (dispatch-mouse-button picked hovered event))
   3.214 -     (mousePressed [this event]
   3.215 -        (dispatch-mouse-button picked hovered event))
   3.216 -     (mouseReleased [this event]
   3.217 -        (dispatch-mouse-button picked hovered event))
   3.218 -     MouseMotionListener
   3.219 -     (mouseDragged [this event]
   3.220 -        (translate-and-dispatch @picked event))
   3.221 -     (mouseMoved [this event]
   3.222 -        (dispatch-mouse-motion hovered @tree event)))))
   3.223 -
   3.224 -;; (with-handle :button1
   3.225 -;;   (draw! button [5 5 100 200] "Cick Me!"))
   3.226 -
   3.227 -;; (when-event :action :button1
   3.228 -;;     ...)
   3.229 -
   3.230 -;; (handle-event :mouse-entered :button1
   3.231 -;;     ...)