changeset 27:61bc04f94d61

Yet another approach at event dispatching (unfinished).
author Mikhail Kryshen <mikhail@kryshen.net>
date Sun, 04 Jul 2010 06:03:48 +0400
parents 1237f7555029
children 828795987d4c
files src/indyvon/core.clj src/indyvon/core_new.clj
diffstat 2 files changed, 284 insertions(+), 22 deletions(-) [+]
line diff
     1.1 --- a/src/indyvon/core.clj	Mon Jun 21 04:00:45 2010 +0400
     1.2 +++ b/src/indyvon/core.clj	Sun Jul 04 06:03:48 2010 +0400
     1.3 @@ -8,6 +8,19 @@
     1.4    (:import (java.awt Color Font)
     1.5             (java.awt.event MouseListener MouseMotionListener)))
     1.6  
     1.7 +(def *context*)
     1.8 +(def *graphics*)
     1.9 +
    1.10 +(defrecord Size [width height])
    1.11 +(defrecord Bounds [x y width height])
    1.12 +
    1.13 +(def *font-context*)
    1.14 +(def *bounds*)
    1.15 +(def *theme*)
    1.16 +(def *target*)
    1.17 +(def *update*)
    1.18 +(def *event-dispatcher*)
    1.19 +
    1.20  (defprotocol Layer
    1.21    "Basic UI element."
    1.22    (render! [this context graphics])
    1.23 @@ -55,11 +68,20 @@
    1.24  (defn default-theme []
    1.25    (Theme. Color/BLACK Color/WHITE Color/BLUE (Font. "Sans" Font/PLAIN 12)))
    1.26    
    1.27 -(defrecord LayerContext [layer parent x y width height update-fn
    1.28 -  dispatcher font-context theme target])
    1.29 +(defrecord LayerContext
    1.30 +  [handle           ; Identifies context for dispatching mouse
    1.31 +                    ; entered/exited and mainaining keyboard focus.
    1.32 +   parent           ; Parent context.
    1.33 +   x y width height ; Geometry.
    1.34 +   update-fn        ; Call to request repaint.
    1.35 +   dispatcher       ; Event dispatcher.
    1.36 +   font-context     ; An instance of java.awt.font.FontRenderContext.
    1.37 +   theme            ; An instance of Theme.
    1.38 +   target           ; Component.
    1.39 +   handlers])       ; Map: event-id -> handler fn.
    1.40  
    1.41  (defn default-context []
    1.42 -  (LayerContext. nil nil 0 0 0 0 nil nil nil (default-theme) nil))
    1.43 +  (LayerContext. nil nil 0 0 0 0 nil nil nil (default-theme) nil nil))
    1.44  
    1.45  (defn update [context]
    1.46    ((:update-fn context)))
    1.47 @@ -75,6 +97,24 @@
    1.48      (.setColor (:fore-color theme))
    1.49      (.setFont (:font theme))))
    1.50  
    1.51 +;; (defn with-context* [opts fn & args]
    1.52 +;;   (let [context (apply assoc *context*
    1.53 +;;                        :parent *context*
    1.54 +;;                        :handlers nil
    1.55 +;;                        opts)
    1.56 +;;         graphics (make-graphics *graphics* x y w h false)
    1.57 +;;         graphics (apply-theme graphics (:theme context))]
    1.58 +;;     (try
    1.59 +;;       (register (:dispatcher context) context)
    1.60 +;;       (with-bindings* {#'*context* context
    1.61 +;;                        #'*graphics* graphics}
    1.62 +;;         fn args)
    1.63 +;;       (finally
    1.64 +;;        (.dispose graphics)))))
    1.65 +
    1.66 +;; (defmacro with-context [opts & body]
    1.67 +;;   `(with-context* ~opts #(~@body)))
    1.68 +
    1.69  (defn draw!
    1.70    "Render layer in a new graphics context."
    1.71    ([layer context graphics]
    1.72 @@ -91,7 +131,7 @@
    1.73              x y w h true))
    1.74    ([layer context graphics x y w h clip]
    1.75       (let [context (assoc context
    1.76 -                     :layer layer
    1.77 +                     :handle layer
    1.78                       :parent context
    1.79                       :x (+ (:x context) x)
    1.80                       :y (+ (:y context) y)
    1.81 @@ -155,19 +195,6 @@
    1.82  (defn- remove-all [coll1 coll2 pred]
    1.83    (filter #(not (some (partial pred %) coll2)) coll1))
    1.84  
    1.85 -;; (defn- translate-mouse-event
    1.86 -;;   [event x y id]
    1.87 -;;   (proxy [MouseEvent] [(.getComponent event)
    1.88 -;;                        id
    1.89 -;;                        (.getWhen event)
    1.90 -;;                        (.getModifiers event)
    1.91 -;;                        (- (.getX event) x)
    1.92 -;;                        (- (.getY event) y)
    1.93 -;;                        (.getClickCount event)
    1.94 -;;                        (.isPopupTrigger event)]
    1.95 -;;     (getXOnScreen [] (.getXOnScreen event))
    1.96 -;;     (getYOnScreen [] (.getYOnScreen event))))
    1.97 -
    1.98  (defn- translate-mouse-event
    1.99    [event x y id]
   1.100    (MouseEvent. id (.getWhen event)
   1.101 @@ -180,11 +207,18 @@
   1.102       (translate-and-dispatch contexts event (awt-events (.getID event))))
   1.103    ([contexts event id]
   1.104       (doseq [context contexts]
   1.105 -       (handle-mouse
   1.106 -        (:layer context) context 
   1.107 -        (translate-mouse-event event (:x context) (:y context) id)))
   1.108 +       (if-let [handler (get (:handlers context) id)]
   1.109 +         (handler context (translate-mouse-event
   1.110 +                           event (:x context) (:y context) id))))
   1.111       id))
   1.112  
   1.113 +(defn- context-id [context]
   1.114 +  (loop [context context
   1.115 +         id nil]
   1.116 +    (if context
   1.117 +      (recur (:parent context) (cons (:handle context) id))
   1.118 +      id)))
   1.119 +
   1.120  (defn- dispatch-mouse-motion*
   1.121    "Dispatches mouse motion events. Returns a new set of contexts which
   1.122    currently are under cursor."
   1.123 @@ -192,7 +226,7 @@
   1.124    (let [x (.getX event)
   1.125          y (.getY event)
   1.126          hovered2 (under-cursor context-tree x y)
   1.127 -        pred #(= (:layer %1) (:layer %2))
   1.128 +        pred #(= (context-id %1) (context-id %2))
   1.129          exited (remove-all hovered hovered2 pred)
   1.130          entered (remove-all hovered2 hovered pred)
   1.131          moved (remove-all hovered2 entered pred)]
   1.132 @@ -231,7 +265,7 @@
   1.133            (.addMouseListener this)
   1.134            (.addMouseMotionListener this)))
   1.135       (register [this context]
   1.136 -        (when (satisfies? MouseHandler (:layer context))
   1.137 +        (if (:handlers context)
   1.138            (dosync (alter context-tree-r add-context context))))
   1.139       (commit [this]
   1.140          (dosync (ref-set context-tree @context-tree-r)
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/src/indyvon/core_new.clj	Sun Jul 04 06:03:48 2010 +0400
     2.3 @@ -0,0 +1,228 @@
     2.4 +;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
     2.5 +;;
     2.6 +;; This file is part of Indyvon.
     2.7 +;;
     2.8 +
     2.9 +(ns indyvon.core_new
    2.10 +  (:import (java.awt Color Font)
    2.11 +           (java.awt.event MouseListener MouseMotionListener)))
    2.12 +
    2.13 +(defrecord Location [x y])
    2.14 +(defrecord Size [width height])
    2.15 +(defrecord Bounds [x y width height])
    2.16 +
    2.17 +(def *graphics*)
    2.18 +(def *font-context*)
    2.19 +(def *bounds*)
    2.20 +(def *theme*)
    2.21 +(def *target*)
    2.22 +(def *update*)
    2.23 +(def *event-dispatcher*)
    2.24 +(def *path*)
    2.25 +
    2.26 +(defprotocol Layer
    2.27 +  "Basic UI element."
    2.28 +  (render! [this opts])
    2.29 +  (size [this opts]))
    2.30 +
    2.31 +(defn layer? [x]
    2.32 +  (satisfies? Layer x)) 
    2.33 +
    2.34 +(defprotocol EventDispatcher
    2.35 +  (listen! [this component])
    2.36 +  (register [this handle-path])
    2.37 +  (handler [this handle-path event-id f])
    2.38 +  (commit [this]))
    2.39 +
    2.40 +;; TODO: modifiers
    2.41 +(defrecord MouseEvent [id when x y x-on-screen y-on-screen button])
    2.42 +
    2.43 +(defn with-translate* [x y w h f & args]
    2.44 +  (let [graphics (.create *graphics* x y w h)]  
    2.45 +    (try
    2.46 +      (apply with-bindings* {#'*bounds* (Bounds. (+ x (:x *bounds*))
    2.47 +                                                 (+ y (:y *bounds*))
    2.48 +                                                 w h)
    2.49 +                             #'*graphics* graphics}
    2.50 +             f args)
    2.51 +      (finally
    2.52 +       (.dispose graphics)))))
    2.53 +
    2.54 +(defn with-handle* [handle f & args]
    2.55 +  (let [path (cons handle *path*)]
    2.56 +    (register *event-dispatcher* path)
    2.57 +    (apply with-bindings* {#'*path* path} f args)))
    2.58 +
    2.59 +(defn- geometry-vec [geometry]
    2.60 +  (if (vector? geometry)
    2.61 +    geometry
    2.62 +    [(:x geometry) (:y geometry) (:width geometry) (:height geometry)]))
    2.63 +
    2.64 +(defn draw! [layer geometry & args]
    2.65 +  "Draw a layer. Geometry is either a map or vector [x y] or
    2.66 +   [x y width height]."
    2.67 +  (let [[x y w h] (geometry-vec geometry)
    2.68 +        size (if-not (and w h) (size layer args))
    2.69 +        w (or w (:width size))
    2.70 +        h (or h (:height size))]
    2.71 +    (with-translate* x y w h render! layer args)))
    2.72 +
    2.73 +(defn draw-root! [layer width height graphics event-dispatcher]
    2.74 +  (with-bindings* {#'*path* nil
    2.75 +                   #'*graphics* graphics
    2.76 +                   #'*event-dispatcher* event-dispatcher
    2.77 +                   #'*bounds* (Bounds. 0 0 width height)}
    2.78 +    render! layer))
    2.79 +
    2.80 +;;
    2.81 +;; EventDispatcher
    2.82 +;;
    2.83 +
    2.84 +(def awt-events
    2.85 +     {java.awt.event.MouseEvent/MOUSE_CLICKED  :mouse-clicked
    2.86 +      java.awt.event.MouseEvent/MOUSE_DRAGGED  :mouse-dragged
    2.87 +      java.awt.event.MouseEvent/MOUSE_ENTERED  :mouse-entered
    2.88 +      java.awt.event.MouseEvent/MOUSE_EXITED   :mouse-exited
    2.89 +      java.awt.event.MouseEvent/MOUSE_MOVED    :mouse-moved
    2.90 +      java.awt.event.MouseEvent/MOUSE_PRESSED  :mouse-pressed
    2.91 +      java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released})
    2.92 +
    2.93 +(defrecord DispatcherNode [id bounds children handlers])
    2.94 +
    2.95 +(defn- add-child [node child]
    2.96 +  (assoc node :children (cons child (:children node))))
    2.97 +
    2.98 +(defn- add-path [tree path]
    2.99 +  (let [parent-path (next path)
   2.100 +        parent-node (get tree parent-path)
   2.101 +        node (DispatcherNode. path *bounds* nil
   2.102 +                              (get-in tree [path :handlers]))]
   2.103 +    (assoc tree
   2.104 +      parent-path (add-child parent-node node)
   2.105 +      path node)))
   2.106 +
   2.107 +(defn add-handler [tree path event-id f]
   2.108 +  (let [keys [path :handlers event-id]]
   2.109 +  (assoc-in tree keys (cons f (get-in tree keys)))))
   2.110 +
   2.111 +(defn- inside?
   2.112 +  ([x y bounds]
   2.113 +     (inside? x y (:x bounds) (:y bounds)
   2.114 +              (:width bounds) (:height bounds)))
   2.115 +  ([px py x y w h]
   2.116 +     (and (>= px x)
   2.117 +          (>= py y)
   2.118 +          (< px (+ x w))
   2.119 +          (< py (+ y h)))))
   2.120 +
   2.121 +(defn- under-cursor
   2.122 +  "Returns a sequence of nodes under cursor."
   2.123 +  ([tree x y]
   2.124 +     (under-cursor tree x y nil))
   2.125 +  ([tree x y node]
   2.126 +     (some #(if (inside? x y (:bounds %))
   2.127 +              (conj (under-cursor tree x y %) %))
   2.128 +           (get tree (:children node)))))
   2.129 +
   2.130 +(defn- remove-all [coll1 coll2 pred]
   2.131 +  (filter #(not (some (partial pred %) coll2)) coll1))
   2.132 +
   2.133 +(defn- translate-mouse-event
   2.134 +  [event x y id]
   2.135 +  (MouseEvent. id (.getWhen event)
   2.136 +               (- (.getX event) x) (- (.getY event) y)
   2.137 +               (.getXOnScreen event) (.getYOnScreen event)
   2.138 +               (.getButton event)))
   2.139 +
   2.140 +(defn- translate-and-dispatch
   2.141 +  ([nodes event]
   2.142 +     (translate-and-dispatch nodes event (awt-events (.getID event))))
   2.143 +  ([nodes event id]
   2.144 +     (doseq [node nodes
   2.145 +             :let [bounds (:bounds node)
   2.146 +                   event (translate-mouse-event event
   2.147 +                           (:x bounds) (:y bounds) id)]
   2.148 +             handler (get (:handlers node) id)]
   2.149 +       ;; TODO restore more of the original context.
   2.150 +       (with-bindings* {#'*bounds* bounds} handler event))
   2.151 +     id))
   2.152 +
   2.153 +(defn- dispatch-mouse-motion*
   2.154 +  "Dispatches mouse motion events. Returns a new set of nodes which
   2.155 +  currently are under cursor."
   2.156 +  [hovered tree event]
   2.157 +  (let [x (.getX event)
   2.158 +        y (.getY event)
   2.159 +        hovered2 (under-cursor tree x y)
   2.160 +        pred #(= (:id %1) (:id %2))
   2.161 +        exited (remove-all hovered hovered2 pred)
   2.162 +        entered (remove-all hovered2 hovered pred)
   2.163 +        moved (remove-all hovered2 entered pred)]
   2.164 +    (translate-and-dispatch exited event :mouse-exited)
   2.165 +    (translate-and-dispatch entered event :mouse-entered)
   2.166 +    (translate-and-dispatch moved event :mouse-moved)
   2.167 +    hovered2))
   2.168 +
   2.169 +(defn- dispatch-mouse-motion
   2.170 +  [hovered-ref tree event]
   2.171 +  (dosync
   2.172 +   (alter hovered-ref dispatch-mouse-motion* tree event)))
   2.173 +
   2.174 +(defn- dispatch-mouse-button*
   2.175 +  "Dispatches mouse button events. Returns a new set of nodes which
   2.176 +  currently are picked with a pressed button."
   2.177 +  [picked hovered event]
   2.178 +  (if (= (translate-and-dispatch hovered event) :mouse-pressed)
   2.179 +    hovered
   2.180 +    nil))
   2.181 +
   2.182 +(defn- dispatch-mouse-button
   2.183 +  [picked-ref hovered-ref event]
   2.184 +  (dosync
   2.185 +   (alter picked-ref dispatch-mouse-button* @hovered-ref event)))
   2.186 +
   2.187 +(defn make-event-dispatcher []
   2.188 +  (let [root-node (DispatcherNode. nil nil nil nil)
   2.189 +        tree-i {nil root-node} ; initial
   2.190 +        tree-r (ref tree-i)    ; register
   2.191 +        tree (ref tree-i)      ; dispatch
   2.192 +        hovered (ref '())
   2.193 +        picked (ref '())]
   2.194 +    (reify
   2.195 +     EventDispatcher
   2.196 +     (listen! [this component]
   2.197 +        (doto component
   2.198 +          (.addMouseListener this)
   2.199 +          (.addMouseMotionListener this)))
   2.200 +     (register [this path]
   2.201 +        (dosync (alter tree-r add-path path)))
   2.202 +     (handler [this path event-id f]
   2.203 +        (dosync (alter tree-r add-handler path event-id f)))
   2.204 +     (commit [this]
   2.205 +        (dosync (ref-set tree @tree-r)
   2.206 +                (ref-set tree-r tree-i)))
   2.207 +     MouseListener
   2.208 +     (mouseEntered [this event]
   2.209 +        (dispatch-mouse-motion hovered @tree event))
   2.210 +     (mouseExited [this event]
   2.211 +        (dispatch-mouse-motion hovered @tree event))
   2.212 +     (mouseClicked [this event]
   2.213 +        (dispatch-mouse-button picked hovered event))
   2.214 +     (mousePressed [this event]
   2.215 +        (dispatch-mouse-button picked hovered event))
   2.216 +     (mouseReleased [this event]
   2.217 +        (dispatch-mouse-button picked hovered event))
   2.218 +     MouseMotionListener
   2.219 +     (mouseDragged [this event]
   2.220 +        (translate-and-dispatch @picked event))
   2.221 +     (mouseMoved [this event]
   2.222 +        (dispatch-mouse-motion hovered @tree event)))))
   2.223 +
   2.224 +;; (with-handle :button1
   2.225 +;;   (draw! button [5 5 100 200] "Cick Me!"))
   2.226 +
   2.227 +;; (when-event :action :button1
   2.228 +;;     ...)
   2.229 +
   2.230 +;; (handle-event :mouse-entered :button1
   2.231 +;;     ...)