Mercurial > hg > indyvon
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 wrap: on
line diff
--- a/src/indyvon/core.clj Mon Jun 21 04:00:45 2010 +0400 +++ b/src/indyvon/core.clj Sun Jul 04 06:03:48 2010 +0400 @@ -8,6 +8,19 @@ (:import (java.awt Color Font) (java.awt.event MouseListener MouseMotionListener))) +(def *context*) +(def *graphics*) + +(defrecord Size [width height]) +(defrecord Bounds [x y width height]) + +(def *font-context*) +(def *bounds*) +(def *theme*) +(def *target*) +(def *update*) +(def *event-dispatcher*) + (defprotocol Layer "Basic UI element." (render! [this context graphics]) @@ -55,11 +68,20 @@ (defn default-theme [] (Theme. Color/BLACK Color/WHITE Color/BLUE (Font. "Sans" Font/PLAIN 12))) -(defrecord LayerContext [layer parent x y width height update-fn - dispatcher font-context theme target]) +(defrecord LayerContext + [handle ; Identifies context for dispatching mouse + ; entered/exited and mainaining keyboard focus. + parent ; Parent context. + x y width height ; Geometry. + update-fn ; Call to request repaint. + dispatcher ; Event dispatcher. + font-context ; An instance of java.awt.font.FontRenderContext. + theme ; An instance of Theme. + target ; Component. + handlers]) ; Map: event-id -> handler fn. (defn default-context [] - (LayerContext. nil nil 0 0 0 0 nil nil nil (default-theme) nil)) + (LayerContext. nil nil 0 0 0 0 nil nil nil (default-theme) nil nil)) (defn update [context] ((:update-fn context))) @@ -75,6 +97,24 @@ (.setColor (:fore-color theme)) (.setFont (:font theme)))) +;; (defn with-context* [opts fn & args] +;; (let [context (apply assoc *context* +;; :parent *context* +;; :handlers nil +;; opts) +;; graphics (make-graphics *graphics* x y w h false) +;; graphics (apply-theme graphics (:theme context))] +;; (try +;; (register (:dispatcher context) context) +;; (with-bindings* {#'*context* context +;; #'*graphics* graphics} +;; fn args) +;; (finally +;; (.dispose graphics))))) + +;; (defmacro with-context [opts & body] +;; `(with-context* ~opts #(~@body))) + (defn draw! "Render layer in a new graphics context." ([layer context graphics] @@ -91,7 +131,7 @@ x y w h true)) ([layer context graphics x y w h clip] (let [context (assoc context - :layer layer + :handle layer :parent context :x (+ (:x context) x) :y (+ (:y context) y) @@ -155,19 +195,6 @@ (defn- remove-all [coll1 coll2 pred] (filter #(not (some (partial pred %) coll2)) coll1)) -;; (defn- translate-mouse-event -;; [event x y id] -;; (proxy [MouseEvent] [(.getComponent event) -;; id -;; (.getWhen event) -;; (.getModifiers event) -;; (- (.getX event) x) -;; (- (.getY event) y) -;; (.getClickCount event) -;; (.isPopupTrigger event)] -;; (getXOnScreen [] (.getXOnScreen event)) -;; (getYOnScreen [] (.getYOnScreen event)))) - (defn- translate-mouse-event [event x y id] (MouseEvent. id (.getWhen event) @@ -180,11 +207,18 @@ (translate-and-dispatch contexts event (awt-events (.getID event)))) ([contexts event id] (doseq [context contexts] - (handle-mouse - (:layer context) context - (translate-mouse-event event (:x context) (:y context) id))) + (if-let [handler (get (:handlers context) id)] + (handler context (translate-mouse-event + event (:x context) (:y context) id)))) id)) +(defn- context-id [context] + (loop [context context + id nil] + (if context + (recur (:parent context) (cons (:handle context) id)) + id))) + (defn- dispatch-mouse-motion* "Dispatches mouse motion events. Returns a new set of contexts which currently are under cursor." @@ -192,7 +226,7 @@ (let [x (.getX event) y (.getY event) hovered2 (under-cursor context-tree x y) - pred #(= (:layer %1) (:layer %2)) + pred #(= (context-id %1) (context-id %2)) exited (remove-all hovered hovered2 pred) entered (remove-all hovered2 hovered pred) moved (remove-all hovered2 entered pred)] @@ -231,7 +265,7 @@ (.addMouseListener this) (.addMouseMotionListener this))) (register [this context] - (when (satisfies? MouseHandler (:layer context)) + (if (:handlers context) (dosync (alter context-tree-r add-context context)))) (commit [this] (dosync (ref-set context-tree @context-tree-r)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/indyvon/core_new.clj Sun Jul 04 06:03:48 2010 +0400 @@ -0,0 +1,228 @@ +;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net> +;; +;; This file is part of Indyvon. +;; + +(ns indyvon.core_new + (:import (java.awt Color Font) + (java.awt.event MouseListener MouseMotionListener))) + +(defrecord Location [x y]) +(defrecord Size [width height]) +(defrecord Bounds [x y width height]) + +(def *graphics*) +(def *font-context*) +(def *bounds*) +(def *theme*) +(def *target*) +(def *update*) +(def *event-dispatcher*) +(def *path*) + +(defprotocol Layer + "Basic UI element." + (render! [this opts]) + (size [this opts])) + +(defn layer? [x] + (satisfies? Layer x)) + +(defprotocol EventDispatcher + (listen! [this component]) + (register [this handle-path]) + (handler [this handle-path event-id f]) + (commit [this])) + +;; TODO: modifiers +(defrecord MouseEvent [id when x y x-on-screen y-on-screen button]) + +(defn with-translate* [x y w h f & args] + (let [graphics (.create *graphics* x y w h)] + (try + (apply with-bindings* {#'*bounds* (Bounds. (+ x (:x *bounds*)) + (+ y (:y *bounds*)) + w h) + #'*graphics* graphics} + f args) + (finally + (.dispose graphics))))) + +(defn with-handle* [handle f & args] + (let [path (cons handle *path*)] + (register *event-dispatcher* path) + (apply with-bindings* {#'*path* path} f args))) + +(defn- geometry-vec [geometry] + (if (vector? geometry) + geometry + [(:x geometry) (:y geometry) (:width geometry) (:height geometry)])) + +(defn draw! [layer geometry & args] + "Draw a layer. Geometry is either a map or vector [x y] or + [x y width height]." + (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))) + +(defn draw-root! [layer width height graphics event-dispatcher] + (with-bindings* {#'*path* nil + #'*graphics* graphics + #'*event-dispatcher* event-dispatcher + #'*bounds* (Bounds. 0 0 width height)} + render! layer)) + +;; +;; EventDispatcher +;; + +(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 [id bounds children handlers]) + +(defn- add-child [node child] + (assoc node :children (cons child (:children node)))) + +(defn- add-path [tree path] + (let [parent-path (next path) + parent-node (get tree parent-path) + node (DispatcherNode. path *bounds* nil + (get-in tree [path :handlers]))] + (assoc tree + parent-path (add-child parent-node node) + path node))) + +(defn add-handler [tree path event-id f] + (let [keys [path :handlers event-id]] + (assoc-in tree keys (cons f (get-in tree keys))))) + +(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 nodes under cursor." + ([tree x y] + (under-cursor tree x y nil)) + ([tree x y node] + (some #(if (inside? x y (:bounds %)) + (conj (under-cursor tree x y %) %)) + (get tree (:children node))))) + +(defn- remove-all [coll1 coll2 pred] + (filter #(not (some (partial pred %) coll2)) coll1)) + +(defn- translate-mouse-event + [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 event] + (translate-and-dispatch nodes event (awt-events (.getID event)))) + ([nodes event id] + (doseq [node nodes + :let [bounds (:bounds node) + event (translate-mouse-event event + (:x bounds) (:y bounds) id)] + handler (get (:handlers node) id)] + ;; TODO restore more of the original context. + (with-bindings* {#'*bounds* bounds} handler event)) + id)) + +(defn- dispatch-mouse-motion* + "Dispatches mouse motion events. Returns a new set of nodes which + currently are under cursor." + [hovered tree event] + (let [x (.getX event) + y (.getY event) + hovered2 (under-cursor tree x y) + pred #(= (:id %1) (:id %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 event] + (dosync + (alter hovered-ref dispatch-mouse-motion* tree 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 make-event-dispatcher [] + (let [root-node (DispatcherNode. nil nil nil nil) + tree-i {nil root-node} ; initial + tree-r (ref tree-i) ; register + tree (ref tree-i) ; dispatch + hovered (ref '()) + picked (ref '())] + (reify + EventDispatcher + (listen! [this component] + (doto component + (.addMouseListener this) + (.addMouseMotionListener this))) + (register [this path] + (dosync (alter tree-r add-path path))) + (handler [this path event-id f] + (dosync (alter tree-r add-handler path event-id f))) + (commit [this] + (dosync (ref-set tree @tree-r) + (ref-set tree-r tree-i))) + MouseListener + (mouseEntered [this event] + (dispatch-mouse-motion hovered @tree event)) + (mouseExited [this event] + (dispatch-mouse-motion hovered @tree 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 event))))) + +;; (with-handle :button1 +;; (draw! button [5 5 100 200] "Cick Me!")) + +;; (when-event :action :button1 +;; ...) + +;; (handle-event :mouse-entered :button1 +;; ...)