view src/indyvon/core.clj @ 160:d149f03d1feb

Reworked implementation of hbox and vbox (DRY).
author Mikhail Kryshen <mikhail@kryshen.net>
date Tue, 18 Nov 2014 17:00:35 +0300
parents e0063c1d0f7f
children 4aa8979938ee
line source
1 ;;
2 ;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net>
3 ;;
4 ;; This file is part of Indyvon.
5 ;;
6 ;; Indyvon is free software: you can redistribute it and/or modify it
7 ;; under the terms of the GNU Lesser General Public License version 3
8 ;; only, as published by the Free Software Foundation.
9 ;;
10 ;; Indyvon is distributed in the hope that it will be useful, but
11 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ;; Lesser General Public License for more details.
14 ;;
15 ;; You should have received a copy of the GNU Lesser General Public
16 ;; License along with Indyvon. If not, see
17 ;; <http://www.gnu.org/licenses/>.
18 ;;
20 (ns indyvon.core
21 (:import
22 (java.awt Graphics2D RenderingHints Component Color Font Shape
23 Rectangle Cursor EventQueue)
24 (java.awt.geom AffineTransform Point2D$Double Rectangle2D$Double Area)
25 (java.awt.event MouseListener MouseMotionListener
26 MouseWheelListener MouseWheelEvent)
27 (java.awt.font FontRenderContext)
28 java.util.concurrent.ConcurrentMap
29 com.google.common.collect.MapMaker))
31 ;;
32 ;; View context
33 ;;
35 (def ^:dynamic ^Graphics2D *graphics*)
37 (def ^:dynamic ^FontRenderContext *font-context*
38 "FontRenderContext to use when Graphics2D is not available."
39 (FontRenderContext.
40 nil
41 RenderingHints/VALUE_TEXT_ANTIALIAS_DEFAULT
42 RenderingHints/VALUE_FRACTIONALMETRICS_DEFAULT))
44 (def ^:dynamic *width*
45 "Width of the rendering area.")
47 (def ^:dynamic *height*
48 "Height of the rendering area.")
50 (def ^:dynamic ^Shape *clip*)
52 (def ^:dynamic ^Shape *input-clip*
53 "Clipping area used for dispatching pointer events (intersected with
54 *clip*). If nil, *clip* will be used.")
56 (def ^:dynamic *time*
57 "Timestamp of the current frame (in nanoseconds).")
59 (def ^:dynamic *scene*
60 "Encloses state that should be retained between repaints.")
62 (def ^:dynamic *states*
63 "Transient scene states, a map.")
65 (def ^:dynamic *event-dispatcher*)
67 (def ^:dynamic ^AffineTransform *initial-transform*
68 "Initial transform associated with the graphics context.")
70 (def ^:dynamic ^AffineTransform *inverse-initial-transform*
71 "Inversion of the initial transform associated with the graphics
72 context.")
74 (defrecord Theme [fore-color back-color alt-back-color border-color
75 shadow-color font])
77 ;; REMIND: use system colors, see java.awt.SystemColor.
78 (defn default-theme []
79 (Theme. Color/BLACK
80 Color/WHITE
81 (Color. 0xDD 0xDD 0xDD)
82 (Color. 0 0 0xCC)
83 (Color. 0x44 0x44 0x44)
84 (Font. "Sans" Font/PLAIN 12)))
86 (def ^:dynamic *theme* (default-theme))
88 ;;
89 ;; Core protocols and types
90 ;;
92 (defprotocol View
93 "Basic UI element."
94 (render! [view]
95 "Draws the view in the current *graphics* context.")
96 (geometry [view]
97 "Returns the preferred Geometry for the view."))
99 (defprotocol Geometry
100 "Describes geometry of a View. Prefer using the available
101 implementations (Size, FixedGeometry and NestedGeometry) over
102 extending this protocol directly as it is likely to be changed in
103 the future versions."
104 (width [geom] [geom height])
105 (height [geom] [geom width])
106 (anchor-x [geom h-align width]
107 "Returns the x coordinate of the anchor point for the specified
108 horizontal alignment and width, h-align could be :left, :center
109 or :right.")
110 (anchor-y [geom v-align height]
111 "Returns the y coordinate of the anchor point for the specified
112 vertical alignment and height, v-align could be :top, :center
113 or :bottom."))
115 (defn- emit-align-xy [align size first center last]
116 `(case ~align
117 ~first 0
118 ~center (/ ~size 2)
119 ~last ~size))
121 ;; Define as macro to avoid unnecessary calculation of width or height.
122 (defmacro align-x
123 ([align inner outer]
124 `(align-x ~align (- ~outer ~inner)))
125 ([align width]
126 (emit-align-xy align width :left :center :right)))
128 (defmacro align-y
129 ([align inner outer]
130 `(align-y ~align (- ~outer ~inner)))
131 ([align height]
132 (emit-align-xy align height :top :center :bottom)))
134 (defrecord Size [width height]
135 Geometry
136 (width [_] width)
137 (width [_ _] width)
138 (height [_] height)
139 (height [_ _] height)
140 (anchor-x [_ h-align width]
141 (align-x h-align width))
142 (anchor-y [_ v-align height]
143 (align-y v-align height)))
145 (defrecord FixedGeometry [ax ay width height]
146 Geometry
147 (width [_] width)
148 (width [_ _] width)
149 (height [_] height)
150 (height [_ _] height)
151 (anchor-x [_ _ _] ax)
152 (anchor-y [_ _ _] ay))
154 (defrecord NestedGeometry [geometry top left bottom right]
155 Geometry
156 (width [_]
157 (+ left right (width geometry)))
158 (width [_ h]
159 (+ left right (width geometry (- h top bottom))))
160 (height [_]
161 (+ top bottom (height geometry)))
162 (height [_ w]
163 (+ top bottom (height geometry (- w left right))))
164 (anchor-x [_ h-align w]
165 (+ left (anchor-x geometry h-align (- w left right))))
166 (anchor-y [_ v-align h]
167 (+ top (anchor-y geometry v-align (- h top bottom)))))
169 (defrecord ScaledGeometry [geometry sx sy]
170 Geometry
171 (width [_]
172 (* sx (width geometry)))
173 (width [_ h]
174 (* sx (width geometry (/ h sy))))
175 (height [_]
176 (* sy (height geometry)))
177 (height [_ w]
178 (* sy (height geometry (/ w sx))))
179 (anchor-x [_ h-align w]
180 (* sx (anchor-x geometry h-align (/ w sx))))
181 (anchor-y [_ v-align h]
182 (* sy (anchor-y geometry v-align (/ h sy)))))
184 (defrecord TransposedGeometry [geometry]
185 Geometry
186 (width [_]
187 (height geometry))
188 (width [_ h]
189 (height geometry h))
190 (height [_]
191 (width geometry))
192 (height [_ w]
193 (width geometry w))
194 (anchor-x [_ h-align w]
195 (anchor-y geometry
196 (get {:left :top
197 :center :center
198 :right :bottom}
199 h-align)
200 w))
201 (anchor-y [_ v-align h]
202 (anchor-x geometry
203 (get {:top :left
204 :center :center
205 :bottom :right}
206 v-align)
207 h)))
209 ;; (defn ^:private to-integer
210 ;; ^long [align x]
211 ;; (if (integer? x)
212 ;; x
213 ;; (let [x (double x)]
214 ;; (Math/round
215 ;; (case align
216 ;; (:top :left) (Math/floor x)
217 ;; :center x
218 ;; (:bottom :right) (Math/ceil x))))))
220 ;; (defrecord IntegerGeometry [geometry]
221 ;; Geometry
222 ;; (width [_]
223 ;; (to-integer :right (width geometry)))
224 ;; (width [_ h]
225 ;; (to-integer :right (width geometry h)))
226 ;; (height [_]
227 ;; (to-integer :bottom (height geometry)))
228 ;; (height [_ w]
229 ;; (to-integer :bottom (height geometry w)))
230 ;; (anchor-x [_ h-align w]
231 ;; (to-integer h-align (anchor-x geometry h-align w)))
232 ;; (anchor-y [_ v-align h]
233 ;; (to-integer v-align (anchor-y geometry v-align h))))
235 ;; TODO: modifiers
236 (defrecord MouseEvent [id when x y x-on-screen y-on-screen button
237 wheel-rotation transform component])
239 ;; TODO: KeyEvent
241 (defprotocol EventDispatcher
242 (listen! [this component]
243 "Listen for events on the specified AWT Component.")
244 (create-dispatcher [this handle handlers]
245 "Returns new event dispatcher associated with the specified event
246 handlers (an event-id -> handler-fn map). Handle is used to match
247 the contexts between commits.")
248 (commit [this]
249 "Apply the registered handlers for event processing.")
250 (handle-picked? [this handle]
251 "Returns true if the specified handle received the :mouse-pressed
252 event and have not yet received :moused-released.")
253 (handle-hovered? [this handle]
254 "Returns true if the specified handle received the :mouse-entered
255 event and have not yet received :mouse-exited."))
257 (defn- assoc-cons [m key val]
258 (->> (get m key) (cons val) (assoc m key)))
260 ;;
261 ;; Observers
262 ;; The mechanism used by views to request repaints
263 ;;
265 (def ^ConcurrentMap observers
266 (-> (MapMaker.) (.weakKeys) (.makeMap)))
268 (defn- cm-replace!
269 "Wrap ConcurrentMap replace method to treat nil value as absent
270 mapping. Use with maps that does not support nil values."
271 [^ConcurrentMap cmap key old new]
272 (if (nil? old)
273 (nil? (.putIfAbsent cmap key new))
274 (.replace cmap key old new)))
276 (defn- cm-swap!
277 "Atomically swaps the value associated with key in ConcurrentMap
278 to be (apply f current-value args). Returns the new value."
279 [^ConcurrentMap cmap key f & args]
280 (loop []
281 (let [old (.get cmap key)
282 new (apply f old args)]
283 (if (cm-replace! cmap key old new)
284 new
285 (recur)))))
287 (defn add-observer
288 "Add observer fn for the target. Watcher identifies the group of
289 observers and could be used to remove the group. Watcher is weakly
290 referenced, all associated observers will be removed when the
291 wathcer is removed by gc. The observer fn will be called with
292 watcher and target arguments and any additional arguments specified
293 in update call."
294 [watcher target f]
295 (cm-swap! observers watcher assoc-cons target f)
296 nil)
298 (defn remove-observers
299 "Remove group of observers associated with the specified watcher."
300 [watcher]
301 (.remove observers watcher)
302 nil)
304 (defn- replace-observers-watcher
305 [old-watcher new-watcher]
306 (if-let [old (.remove observers old-watcher)]
307 (.put observers new-watcher old))
308 nil)
310 (defn update
311 "Notify observers."
312 [target & args]
313 (doseq [entry observers
314 f (get (val entry) target)]
315 (apply f (key entry) target args)))
317 (defn add-context-observer
318 "Observer registered with this function will be automatically
319 removed after the next repaint is complete."
320 [target f]
321 (add-observer *scene* target f))
323 (defn repaint-on-update
324 "Trigger repaint of the current scene when the target updates."
325 [target]
326 (let [scene *scene*]
327 (if-not (identical? scene target)
328 (add-observer scene target (fn [w _] (update w))))))
330 (defn repaint
331 "Requests repaint of the current scene. If handle and state are
332 specified, the handle will be associated with the state in the
333 *states* map for the next paint iteration."
334 ([]
335 (update *scene*))
336 ([handle state]
337 (let [scene *scene*]
338 (swap! (:next-state scene) assoc handle state)
339 (update scene))))
341 ;;
342 ;; Rendering
343 ;;
345 (defn ^FontRenderContext font-context
346 "Returns FontRenderContext for the current view context."
347 []
348 (if (bound? (var *graphics*))
349 (.getFontRenderContext *graphics*)
350 *font-context*))
352 (defn ^AffineTransform relative-transform
353 "Returns AffineTransform: view context -> AWT component."
354 []
355 (let [tr (.getTransform *graphics*)]
356 (.preConcatenate tr *inverse-initial-transform*)
357 tr))
359 (defn ^AffineTransform inverse-relative-transform
360 "Returns AffineTransform: AWT component -> view context."
361 []
362 (let [tr (.getTransform *graphics*)]
363 (.invert tr) ; absolute -> view
364 (.concatenate tr *initial-transform*) ; component -> absolute
365 tr))
367 (defn transform-point [^AffineTransform tr ^double x ^double y]
368 (let [p (Point2D$Double. x y)]
369 (.transform tr p p)
370 [(.x p) (.y p)]))
372 (defn inverse-transform-point [^AffineTransform tr ^double x ^double y]
373 (let [p (Point2D$Double. x y)]
374 (.inverseTransform tr p p)
375 [(.x p) (.y p)]))
377 ;; (defn- clip
378 ;; "Intersect clipping area with the specified shape or bounds.
379 ;; Returns new clip (Shape or nil if empty)."
380 ;; ([x y w h]
381 ;; (clip (Rectangle2D$Double. x y w h)))
382 ;; ([shape]
383 ;; (let [a1 (Area. shape)
384 ;; a2 (if (instance? Area *clip*) *clip* (Area. *clip*))]
385 ;; (.transform a1 (relative-transform))
386 ;; (.intersect a1 a2)
387 ;; (if (.isEmpty a1)
388 ;; nil
389 ;; a1))))
391 ;; Use faster clipping calculation provided by Graphics2D.
392 (defn- clip
393 "Intersect clipping area with the specified Shape in current
394 transform coordinates. Returns new clip in the AWT component
395 coordinates (Shape or nil if empty)."
396 [^Shape shape]
397 (let [^Graphics2D clip-g (.create *graphics*)]
398 (try
399 (doto clip-g
400 (.setClip shape)
401 (.setTransform *initial-transform*)
402 (.clip *clip*))
403 (if (.isEmpty (.getClipBounds clip-g))
404 nil
405 (.getClip clip-g))
406 (finally
407 (.dispose clip-g)))))
409 (defn- theme-get*
410 ([theme key]
411 (theme-get* theme key nil))
412 ([theme key not-found]
413 (if-let [e (find theme key)]
414 (loop [k (val e)]
415 (if-let [e1 (and (keyword? k)
416 (find theme k))]
417 (recur (val e1))
418 k))
419 not-found)))
421 (defn theme-get
422 ([key]
423 (theme-get* *theme* key))
424 ([key not-found]
425 (theme-get* *theme* key not-found)))
427 (defn ^Graphics2D apply-theme
428 "Set graphics' color and font to match theme.
429 Modifies and returns the first argument."
430 ([]
431 (apply-theme *graphics* *theme*))
432 ([^Graphics2D graphics theme]
433 (doto graphics
434 (.setColor (theme-get :fore-color))
435 (.setBackground (theme-get :back-color))
436 (.setFont (theme-get :font)))))
438 (defn- ^Graphics2D create-graphics
439 ([]
440 (apply-theme (.create *graphics*) *theme*))
441 ([^long x ^long y ^long w ^long h]
442 (apply-theme (.create *graphics* x y w h) *theme*)))
444 (defn- with-bounds-noclip*
445 [x y w h f & args]
446 (let [graphics (create-graphics)]
447 (try
448 (.translate graphics (double x) (double y))
449 (binding [*width* w
450 *height* h
451 *input-clip* (Rectangle2D$Double. 0.0 0.0 w h)
452 *graphics* graphics]
453 (apply f args))
454 (finally
455 (.dispose graphics)))))
457 (defn with-bounds*
458 [x y w h f & args]
459 (let [x (double x)
460 y (double y)
461 bounds (Rectangle2D$Double. x y w h)]
462 (when-let [clip (clip bounds)]
463 (let [^Graphics2D graphics (create-graphics)]
464 (try
465 (.clip graphics bounds)
466 (.translate graphics x y)
467 (binding [*width* w
468 *height* h
469 *clip* clip
470 *input-clip* nil
471 *graphics* graphics]
472 (apply f args))
473 (finally
474 (.dispose graphics)))))))
476 (defmacro with-bounds
477 [x y w h & body]
478 `(with-bounds* ~x ~y ~w ~h (fn [] ~@body)))
480 (defmacro with-theme
481 [theme & body]
482 `(binding [*theme* (merge *theme* ~theme)]
483 ~@body))
485 (defn with-theme* [theme f & args]
486 (with-theme theme
487 (apply f args)))
489 (defmacro with-color [color-or-key & body]
490 `(let [color# ~color-or-key
491 color# (theme-get color# color#)
492 g# *graphics*
493 old-color# (.getColor g#)]
494 (try
495 (.setColor g# color#)
496 ~@body
497 (finally
498 (.setColor g# old-color#)))))
500 (defmacro with-font [font-or-key & body]
501 `(let [font# ~font-or-key
502 font# (theme-get font# font#)
503 g# *graphics*
504 old-font# (.getFont g#)]
505 (try
506 (.setFont g# font#)
507 ~@body
508 (finally
509 (.setColor g# old-font#)))))
511 (defmacro with-stroke [stroke & body]
512 `(let [g# *graphics*
513 old-stroke# (.getStroke g#)]
514 (try
515 (.setStroke g# ~stroke)
516 ~@body
517 (finally
518 (.setStroke g# old-stroke#)))))
520 (defmacro with-hints
521 [hints & body]
522 `(let [h# ~hints
523 g# *graphics*
524 old# (.getRenderingHints g#)]
525 (try
526 (.addRenderingHints g# h#)
527 ~@body
528 (finally
529 (.setRenderingHints g# old#)))))
531 (defn with-hints* [hints f & args]
532 (with-hints hints
533 (apply f args)))
535 ;; TODO: constructor for AffineTransform.
536 ;; (transform :scale 0.3 0.5
537 ;; :translate 5 10
538 ;; :rotate (/ Math/PI 2))
540 (defmacro with-transform [transform & body]
541 `(let [g# *graphics*
542 old-t# (.getTransform g#)]
543 (try
544 (.transform g# ~transform)
545 ~@body
546 (finally
547 (.setTransform g# old-t#)))))
549 (defmacro with-rotate [theta ax ay & body]
550 `(let [transform# (AffineTransform/getRotateInstance ~theta ~ax ~ay)]
551 (with-transform transform# ~@body)))
553 (defmacro with-translate [x y & body]
554 `(let [x# ~x
555 y# ~y
556 g# *graphics*]
557 (try
558 (.translate g# x# y#)
559 ~@body
560 (finally
561 (.translate g# (- x#) (- y#))))))
563 (defn draw!
564 "Draws the View."
565 ([view]
566 (let [graphics (create-graphics)]
567 (try
568 (binding [*graphics* graphics]
569 (render! view))
570 (finally
571 (.dispose graphics)))))
572 ([view x y]
573 (draw! view x y true))
574 ([view x y clip?]
575 (let [geom (geometry view)]
576 (draw! view x y (width geom) (height geom) clip?)))
577 ([view x y width height]
578 (draw! view x y width height true))
579 ([view x y width height clip?]
580 (if clip?
581 (with-bounds* x y width height render! view)
582 (with-bounds-noclip* x y width height render! view))))
584 (defn draw-aligned!
585 "Draws the View. Location is relative to the view's anchor point
586 for the specified alignment."
587 ([view h-align v-align x y]
588 (draw-aligned! view (geometry view) h-align v-align x y))
589 ([view geom h-align v-align x y]
590 (draw-aligned! view geom h-align v-align x y (width geom) (height geom)))
591 ([view h-align v-align x y w h]
592 (draw-aligned! view (geometry view) h-align v-align x y w h))
593 ([view geom h-align v-align x y w h]
594 (draw! view
595 (- x (anchor-x geom h-align w))
596 (- y (anchor-y geom v-align h))
597 w h)))
599 ;;
600 ;; Event handling.
601 ;;
603 (defn with-handlers*
604 [handle handlers f & args]
605 (binding [*event-dispatcher* (create-dispatcher
606 *event-dispatcher* handle handlers)]
607 (apply f args)))
609 (defmacro with-handlers
610 "specs => (:event-id name & handler-body)*
612 Execute form with the specified event handlers."
613 [handle form & specs]
614 `(with-handlers* ~handle
615 ~(reduce (fn [m spec]
616 (assoc m (first spec)
617 `(fn [~(second spec)]
618 ~@(nnext spec)))) {}
619 specs)
620 (fn [] ~form)))
622 (defn picked? [handle]
623 (handle-picked? *event-dispatcher* handle))
625 (defn hovered? [handle]
626 (handle-hovered? *event-dispatcher* handle))
628 ;;
629 ;; EventDispatcher implementation
630 ;;
632 (def awt-events
633 {java.awt.event.MouseEvent/MOUSE_CLICKED :mouse-clicked
634 java.awt.event.MouseEvent/MOUSE_DRAGGED :mouse-dragged
635 java.awt.event.MouseEvent/MOUSE_ENTERED :mouse-entered
636 java.awt.event.MouseEvent/MOUSE_EXITED :mouse-exited
637 java.awt.event.MouseEvent/MOUSE_MOVED :mouse-moved
638 java.awt.event.MouseEvent/MOUSE_PRESSED :mouse-pressed
639 java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released
640 java.awt.event.MouseEvent/MOUSE_WHEEL :mouse-wheel})
642 (def dummy-event-dispatcher
643 (reify EventDispatcher
644 (listen! [_ _])
645 (create-dispatcher [this _ _] this)
646 (commit [_])
647 (handle-picked? [_ _])
648 (handle-hovered? [_ _])))
650 ;; Not using defrecord to avoid unacceptable overhead of recursive
651 ;; hash code calculation.
652 (deftype DispatcherNode [handle handlers parent
653 ^Shape clip ^AffineTransform transform
654 bindings]
655 EventDispatcher
656 (listen! [this component]
657 (listen! parent component))
658 (create-dispatcher [this handle handlers]
659 (create-dispatcher parent handle handlers))
660 (commit [this]
661 (commit parent))
662 (handle-picked? [this handle]
663 (handle-picked? parent handle))
664 (handle-hovered? [this handle]
665 (handle-hovered? parent handle)))
667 (defn- make-node [handle handlers]
668 (let [clip (if *input-clip*
669 (clip *input-clip*)
670 *clip*)
671 bindings (-> (get-thread-bindings)
672 (dissoc (var *graphics*))
673 (assoc (var *font-context*) (font-context)))]
674 (DispatcherNode. handle handlers *event-dispatcher* clip
675 (relative-transform)
676 bindings)))
678 (defn- add-node [tree ^DispatcherNode node]
679 (assoc-cons tree (.parent node) node))
681 (defn- nodes [tree]
682 (apply concat (vals tree)))
684 (defn- under-cursor
685 "Returns a vector of child nodes under cursor."
686 [node tree ^long x ^long y]
687 (some (fn [^DispatcherNode n]
688 (if (and (.clip n) (.contains ^Shape (.clip n) x y))
689 (conj (vec (under-cursor n tree x y)) n)))
690 (get tree node)))
692 (defn- translate-mouse-event [^java.awt.event.MouseEvent event
693 ^AffineTransform tr id]
694 (let [[x y] (inverse-transform-point tr (.getX event) (.getY event))
695 rotation (if (instance? MouseWheelEvent event)
696 (.getWheelRotation ^MouseWheelEvent event)
697 nil)]
698 (->MouseEvent id (.getWhen event) x y
699 (.getXOnScreen event) (.getYOnScreen event)
700 (.getButton event)
701 rotation
702 tr
703 (.getComponent event))))
705 (defn- translate-and-dispatch
706 ([nodes first-only ^java.awt.event.MouseEvent event]
707 (translate-and-dispatch nodes first-only
708 event (awt-events (.getID event))))
709 ([nodes first-only event id]
710 (if-let [^DispatcherNode node (first nodes)]
711 (let [handlers (.handlers node)
712 handler (get handlers id)]
713 (if handler
714 (do
715 (with-bindings* (.bindings node)
716 handler
717 (translate-mouse-event event (.transform node) id))
718 (when-not first-only
719 (recur (rest nodes) false event id)))
720 (when-not (and (= id :mouse-dragged)
721 (or (contains? handlers :mouse-pressed)
722 (contains? handlers :mouse-released)))
723 (recur (rest nodes) first-only event id)))))))
725 (defn- process-mouse-event
726 [dispatcher ^java.awt.event.MouseEvent source-event]
727 (let [{active-ref :active
728 hovered-ref :hovered
729 picked-ref :picked
730 last-ref :last-motion
731 tree-ref :tree} dispatcher
732 pressed (and source-event
733 (== (.getID source-event)
734 java.awt.event.MouseEvent/MOUSE_PRESSED))
735 released (and source-event
736 (== (.getID source-event)
737 java.awt.event.MouseEvent/MOUSE_RELEASED))
738 ^java.awt.event.MouseEvent last-event @last-ref
739 ^java.awt.event.MouseEvent event (or source-event last-event)]
740 (when event
741 (let [x (.getX event)
742 y (.getY event)
743 active @active-ref
744 active (if (and active
745 source-event
746 (== (.getX last-event) x)
747 (== (.getY last-event) y))
748 active
749 (ref-set active-ref
750 (under-cursor dispatcher @tree-ref x y)))
751 acted (cond
752 pressed (ref-set picked-ref active)
753 released (let [picked @picked-ref]
754 (ref-set picked-ref nil)
755 picked)
756 :else active)
757 picked (seq @picked-ref)
758 pred #(= (.handle ^DispatcherNode %1) (.handle ^DispatcherNode %2))
759 hovered (if picked
760 (filter #(some (partial pred %) picked) active)
761 active)
762 remove-all (fn [c1 c2]
763 (filter #(not (some (partial pred %) c2)) c1))
764 old-hovered @hovered-ref
765 exited (remove-all old-hovered hovered)
766 entered (remove-all hovered old-hovered)
767 moved (or picked (remove-all hovered entered))]
768 (ref-set hovered-ref hovered)
769 (ref-set last-ref event)
770 [exited entered moved acted event]))))
772 (defn- dispatch-mouse-event
773 [dispatcher source-event button?]
774 (when-let [[exited
775 entered
776 moved
777 acted
778 event] (dosync (process-mouse-event dispatcher source-event))]
779 (when button?
780 (translate-and-dispatch acted true event))
781 (translate-and-dispatch exited false event :mouse-exited)
782 (translate-and-dispatch entered false event :mouse-entered)
783 (when-not button?
784 (translate-and-dispatch moved true source-event))))
786 (defrecord RootEventDispatcher [tree-r ;; register
787 tree ;; dispatch
788 active ;; nodes under cursor
789 hovered ;; mouse entered
790 picked ;; mouse pressed
791 last-motion]
792 EventDispatcher
793 (listen! [dispatcher component]
794 (doto ^Component component
795 (.addMouseListener dispatcher)
796 (.addMouseWheelListener dispatcher)
797 (.addMouseMotionListener dispatcher)))
798 (create-dispatcher [dispatcher handle handlers]
799 (let [node (make-node handle handlers)]
800 (dosync (alter tree-r add-node node))
801 node))
802 (commit [dispatcher]
803 (let [[exited
804 entered
805 _ _
806 event] (dosync
807 ;; TODO: retain contexts that do
808 ;; not intersect graphics
809 ;; clipping area in tree.
810 (ref-set tree @tree-r)
811 (ref-set tree-r {})
812 (process-mouse-event dispatcher nil))]
813 ;; Send mouse entered and exited events if necessary due to
814 ;; updated layout.
815 (translate-and-dispatch exited false event :mouse-exited)
816 (translate-and-dispatch entered false event :mouse-entered)))
817 (handle-picked? [dispatcher handle]
818 (some #(= handle (.handle ^DispatcherNode %)) @picked))
819 (handle-hovered? [dispatcher handle]
820 (some #(= handle (.handle ^DispatcherNode %)) @hovered))
821 MouseListener
822 (mouseEntered [dispatcher event]
823 (dispatch-mouse-event dispatcher event false))
824 (mouseExited [dispatcher event]
825 (dispatch-mouse-event dispatcher event false))
826 (mouseClicked [dispatcher event]
827 (dispatch-mouse-event dispatcher event true))
828 (mousePressed [dispatcher event]
829 (dispatch-mouse-event dispatcher event true))
830 (mouseReleased [dispatcher event]
831 (dispatch-mouse-event dispatcher event true))
832 MouseWheelListener
833 (mouseWheelMoved [dispatcher event]
834 (dispatch-mouse-event dispatcher event true))
835 MouseMotionListener
836 (mouseDragged [dispatcher event]
837 (dispatch-mouse-event dispatcher event false))
838 (mouseMoved [dispatcher event]
839 (dispatch-mouse-event dispatcher event false)))
841 (defn root-event-dispatcher []
842 (->RootEventDispatcher
843 (ref {}) (ref {}) ;; trees
844 (ref nil) (ref nil) (ref nil) ;; node states
845 (ref nil))) ;; last event
847 ;;
848 ;; Scene
849 ;;
851 (defrecord Scene [view
852 event-dispatcher
853 component
854 rendering-hints
855 next-state])
857 ;; Define rendering hints that affect font metrics to make sure that
858 ;; Graphics and Scene FontRenderContexts are consistent.
859 (def ^:private default-rendering-hints
860 {RenderingHints/KEY_TEXT_ANTIALIASING
861 RenderingHints/VALUE_TEXT_ANTIALIAS_DEFAULT,
862 RenderingHints/KEY_FRACTIONALMETRICS
863 RenderingHints/VALUE_FRACTIONALMETRICS_DEFAULT})
865 (defn make-scene
866 ([view]
867 (make-scene view dummy-event-dispatcher nil))
868 ([view event-dispatcher]
869 (make-scene view event-dispatcher nil))
870 ([view event-dispatcher ^Component component]
871 (make-scene view event-dispatcher component nil))
872 ([view event-dispatcher ^Component component hints]
873 (let [hints (merge default-rendering-hints hints)]
874 (->Scene view
875 event-dispatcher
876 component
877 hints
878 (atom nil)))))
880 (defn- get-and-set!
881 "Atomically sets the value of atom to newval and returns the old
882 value."
883 [atom newval]
884 (loop [v @atom]
885 (if (compare-and-set! atom v newval)
886 v
887 (recur @atom))))
889 (defn draw-scene!
890 [scene ^Graphics2D graphics width height]
891 (.addRenderingHints graphics (:rendering-hints scene))
892 (binding [*states* (get-and-set! (:next-state scene) nil)
893 *scene* scene
894 *graphics* graphics
895 *initial-transform* (.getTransform graphics)
896 *inverse-initial-transform* (-> graphics
897 .getTransform
898 .createInverse)
899 *event-dispatcher* (:event-dispatcher scene)
900 *width* width
901 *height* height
902 *clip* (Rectangle2D$Double. 0.0 0.0 width height)
903 *input-clip* nil
904 *time* (System/nanoTime)]
905 (apply-theme)
906 (let [tmp-watcher (Object.)]
907 ;; Keep current context observers until the rendering is
908 ;; complete. Some observers may be invoked twice if they
909 ;; appear in both groups until tmp-watcher is removed.
910 (replace-observers-watcher scene tmp-watcher)
911 (try
912 (render! (:view scene))
913 (finally
914 (remove-observers tmp-watcher)
915 (commit (:event-dispatcher scene)))))))
917 (defn- scene-font-context [scene]
918 (let [hints (:rendering-hints scene)
919 ^Component c (:component scene)
920 t (if c (->> c
921 .getFont
922 (.getFontMetrics c)
923 .getFontRenderContext
924 .getTransform))]
925 (FontRenderContext.
926 t
927 (get hints RenderingHints/KEY_TEXT_ANTIALIASING)
928 (get hints RenderingHints/KEY_FRACTIONALMETRICS))))
930 (defn scene-geometry [scene]
931 (binding [*scene* scene
932 *font-context* (scene-font-context scene)]
933 (geometry (:view scene))))
935 (defn set-cursor! [^Cursor cursor]
936 (when-let [^Component component (:component *scene*)]
937 (EventQueue/invokeLater #(.setCursor component cursor))))