Mercurial > hg > indyvon
changeset 34:6975b9a71eec
Finally use var bindings instead of a context record.
author | Mikhail Kryshen <mikhail@kryshen.net> |
---|---|
date | Thu, 08 Jul 2010 05:27:54 +0400 |
parents | 439f6ecee119 (diff) 828795987d4c (current diff) |
children | 0d593970cb76 |
files | src/indyvon/component.clj src/indyvon/core.clj src/indyvon/layers.clj |
diffstat | 3 files changed, 249 insertions(+), 200 deletions(-) [+] |
line wrap: on
line diff
--- a/src/indyvon/component.clj Mon Jul 05 06:11:42 2010 +0400 +++ b/src/indyvon/component.clj Thu Jul 08 05:27:54 2010 +0400 @@ -5,36 +5,41 @@ ;; (ns indyvon.component - (:use indyvon.core) - (:import (java.awt Component Dimension Color) + (:use indyvon.core + indyvon.layers) + (:import (indyvon.core Size Bounds) + (java.awt Graphics2D Component Dimension Color) (javax.swing JFrame JPanel))) -(defn- font-context [component] +(defn- font-context [^Component component] (.getFontRenderContext (.getFontMetrics component (.getFont component)))) -(defn paint-component [component layer graphics event-dispatcher] +(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) - (binding [*path* nil - *graphics* graphics - *font-context*' (.getFontRenderContext graphics) - *event-dispatcher* event-dispatcher - *update* #(.repaint component) - *bounds* (indyvon.core.Bounds. 0 0 width height)] - (render! layer nil) - (commit event-dispatcher)))) + (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 [*path* nil + (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 (make-event-dispatcher))) + (make-jpanel layer (root-event-dispatcher))) ([layer event-dispatcher] (let [panel (proxy [JPanel] [] @@ -53,28 +58,28 @@ (def layer1 (reify Layer - (render! [this context g] - (.setColor g Color/RED) - (.fillRect g 0 0 (:width context) (:height context))) - (size [this context] [30 20]) - MouseHandler - (handle-mouse [this context event] - (println "layer1" event)))) + (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! [this context g] - (.setColor g Color/YELLOW) - (.fillRect g 0 0 (:width context) (:height context)) - (draw! layer1b context g 10 5) - (draw! layer1 context g 55 5)) - (size [this context] [70 65]) - MouseHandler - (handle-mouse [this context event] - (println "layer2" event)))) + (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))) @@ -89,8 +94,8 @@ fl (ref (fps-layer 0.0))] (reify Layer - (render! [this c g] - (draw! @fl c g) + (render! [layer opts] + (render! @fl nil) (dosync (alter frames + 1) (let [time (System/currentTimeMillis) @@ -99,18 +104,19 @@ (ref-set fl (fps-layer (/ @frames elapsed))) (ref-set frames 0) (ref-set last time))))) - (size [this c] (size @fl c))))) + (size [layer opts] (size @fl nil))))) (def layer (reify Layer - (render! [this context g] - ;;(update context) - (.setColor g (rand-nth [Color/BLACK Color/BLUE Color/RED])) - (.drawLine g 0 0 (:width context) (:height context)) - (draw! layer2 context g 15 20) - (draw! layer3 context g 100 100 80 50) - (draw! fps context g)) - (size [this context] [400 300]))) + (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)))) (doto frame (.addWindowListener
--- a/src/indyvon/core.clj Mon Jul 05 06:11:42 2010 +0400 +++ b/src/indyvon/core.clj Thu Jul 08 05:27:54 2010 +0400 @@ -5,56 +5,102 @@ ;; (ns indyvon.core - (:import (java.awt Color Font) - (java.awt.event MouseListener MouseMotionListener))) + (: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]) -(def *graphics*) -(def *font-context*) -(def *bounds*) -(def *target*) -(def *update*) -(def *event-dispatcher*) -(def *path*) - -(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)) - (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- apply-theme [graphics] +(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*)))) + (.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))] + (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. (+ x (:x *bounds*)) - (+ y (:y *bounds*)) - w h) + (apply with-bindings* {#'*bounds* bounds + #'*clip* (intersect bounds *clip*) #'*graphics* graphics} f args) (finally @@ -63,29 +109,36 @@ (defmacro with-translate [x y w h & body] `(with-translate* ~x ~y ~w ~h (fn [] ~@body))) -(defn with-handle* [handle f & args] - (let [path (cons handle *path*)] - (register *event-dispatcher* path) - (apply with-bindings* {#'*path* path} f args))) + + +(defn with-handlers* [handle handlers f & args] + (apply with-bindings* + {#'*event-dispatcher* + (create-dispatcher *event-dispatcher* handle handlers)} + f args)) -(defmacro with-handle [handle & body] - `(with-handle* ~handle (fn [] ~@body))) +(defmacro with-handlers + "specs => (:event-id name & handler-body)* -(defn handle-event* [event-id f & args] - (let [f (if args #(f % args) f)] - (handler *event-dispatcher* *path* event-id f))) - -(defmacro handle-event [event-id name & body] - `(handle-event* ~event-id (fn [~name] ~@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! [layer geometry & args] +(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)) @@ -93,7 +146,7 @@ (with-translate* x y w h render! layer args))) ;; -;; EventDispatcher +;; EventDispatcher implementation ;; (def awt-events @@ -105,23 +158,24 @@ 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)))) +(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- 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- make-node [handle handlers] + (DispatcherNode. handle handlers *event-dispatcher* *clip* + (get-thread-bindings))) -(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- 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] @@ -134,45 +188,41 @@ (< 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))))) + "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 - [event x y id] +(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 event] + ([nodes ^java.awt.event.MouseEvent 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)) + (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 event] + [hovered tree root ^java.awt.event.MouseEvent event] (let [x (.getX event) y (.getY event) - hovered2 (under-cursor tree x y) - pred #(= (:id %1) (:id %2)) + 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)] @@ -182,9 +232,9 @@ hovered2)) (defn- dispatch-mouse-motion - [hovered-ref tree event] + [hovered-ref tree root event] (dosync - (alter hovered-ref dispatch-mouse-motion* tree event))) + (alter hovered-ref dispatch-mouse-motion* tree root event))) (defn- dispatch-mouse-button* "Dispatches mouse button events. Returns a new set of nodes which @@ -199,11 +249,9 @@ (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 +(defn root-event-dispatcher [] + (let [tree-r (ref {}) ; register + tree (ref {}) ; dispatch hovered (ref '()) picked (ref '())] (reify @@ -212,18 +260,18 @@ (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))) + (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 tree-i))) + (ref-set tree-r {}))) MouseListener (mouseEntered [this event] - (dispatch-mouse-motion hovered @tree event)) + (dispatch-mouse-motion hovered @tree this event)) (mouseExited [this event] - (dispatch-mouse-motion hovered @tree event)) + (dispatch-mouse-motion hovered @tree this event)) (mouseClicked [this event] (dispatch-mouse-button picked hovered event)) (mousePressed [this event] @@ -234,7 +282,7 @@ (mouseDragged [this event] (translate-and-dispatch @picked event)) (mouseMoved [this event] - (dispatch-mouse-motion hovered @tree event))))) + (dispatch-mouse-motion hovered @tree this event))))) ;; ;; ИДЕИ:
--- a/src/indyvon/layers.clj Mon Jul 05 06:11:42 2010 +0400 +++ b/src/indyvon/layers.clj Thu Jul 08 05:27:54 2010 +0400 @@ -6,7 +6,8 @@ (ns indyvon.layers (:use indyvon.core) - (:import (java.awt Cursor) + (: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 @@ -32,30 +33,33 @@ ([content width gap] (let [offset (+ width gap)] (reify Layer - (render! [l c g] - (let [w (:width c) - h (:height c)] - (.setColor g (-> c :theme :border-color)) + (render! [l opts] + (let [w (:width *bounds*) + h (:height *bounds*)] + (.setColor *graphics* (:border-color *theme*)) (doseq [i (range 0 width)] - (.drawRect g i i (- w 1 i i) (- h 1 i i))) - (draw! content c g offset offset (- w offset offset) - (- h offset offset)))) - (size [l c] - (let [s (size content c)] - [(+ (s 0) offset offset) - (+ (s 1) offset offset)])))))) + (.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 [re s] +(defn- re-split [^java.util.regex.Pattern re s] (seq (.split re s))) -(defn- layout-text [lines font font-context] - (map #(TextLayout. % font font-context) lines)) +(defn- layout-text [lines ^Font font ^FontRenderContext font-context] + (map #(TextLayout. ^String % font font-context) lines)) (defn- text-width [layouts] - (reduce #(max %1 (.getAdvance %2)) 0 layouts)) + (reduce (fn [w ^TextLayout tl] (max w (.getAdvance tl))) 0 layouts)) (defn- text-height [layouts] - (reduce #(+ %1 (.getAscent %2) (.getDescent %2) (.getLeading %2)) + (reduce (fn [w ^TextLayout tl] (+ w (.getAscent tl) + (.getDescent tl) + (.getLeading tl))) 0 layouts)) (defn text-layer @@ -65,27 +69,24 @@ ([text h-align v-align] (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" text)] (reify Layer - (render! [l c g] - (let [w (:width c) - h (:height c) - font (.getFont g) - font-context (:font-context c) - layouts (layout-text lines font font-context) + (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 [layout layouts] + (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 g x (+ y ascent)) + (.draw layout *graphics* x (+ y ascent)) (recur (next layouts) (+ y lh))))))) - (size [l c] - (let [layouts (layout-text lines - (-> c :theme :font) - (:font-context c)) + (size [layer opts] + (let [layouts (layout-text lines (:font *theme*) *font-context*) width (text-width layouts) height (text-height layouts)] - [width height])))))) + (Size. width height))))))) (defn viewport "Creates scrollable viewport layer." @@ -99,36 +100,30 @@ last-height (ref 0)] (reify Layer - (render! [layer c g] - (let [anchor (anchor content c h-align v-align) - width (:width c) - height (:height c)] + (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 - (alter x + (align-x width @last-width h-align)) - (alter y + (align-y height @last-height v-align)) - (ref-set last-width width) - (ref-set last-height height)) - (draw! content c g - (- 0 @x (anchor 0)) - (- 0 @y (anchor 1))))) - (size [layer c] (size content c)) - MouseHandler - (handle-mouse [layer c e] - (case (:id e) - :mouse-pressed - (do - (dosync - (ref-set fix-x (:x-on-screen e)) - (ref-set fix-y (:y-on-screen e))) - (->> Cursor/MOVE_CURSOR Cursor. (.setCursor (:target c)))) - :mouse-released - (->> Cursor/DEFAULT_CURSOR Cursor. (.setCursor (:target c))) - :mouse-dragged - (do - (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 c)) - nil)))))) + (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))))))