view src/net/kryshen/indyvon/core.clj @ 60:7e456697924d

Asynchronous drawing (continue).
author Mikhail Kryshen <mikhail@kryshen.net>
date Mon, 23 Aug 2010 21:24:33 +0400
parents 64b67aa224f4
children 88bb47e3a401
line source
1 ;;
2 ;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
3 ;;
4 ;; This file is part of Indyvon.
5 ;;
7 (ns net.kryshen.indyvon.core
8 (:import
9 (java.awt Graphics2D Component Color Font AWTEvent Shape)
10 (java.awt.geom AffineTransform Point2D$Double Rectangle2D$Double Area)
11 (java.awt.event MouseListener MouseMotionListener)
12 (java.awt.font FontRenderContext)))
14 ;;
15 ;; Layer context
16 ;;
18 (def ^Graphics2D *graphics*)
20 (def ^FontRenderContext *font-context*)
22 (def ^{:tag Component
23 :doc "Target AWT component, may be nil if drawing off-screen."}
24 *target*)
26 (def *width*)
28 (def *height*)
30 (def ^Shape *clip*)
32 (def *event-dispatcher*)
34 (def ^{:doc "Fn to be called in a layer context to request redraw."}
35 *update*)
37 (def ^{:tag AffineTransform
38 :doc "Initial transform associated with the graphics context"}
39 *initial-transform*)
41 (def ^{:tag AffineTransform
42 :doc "Inversion of the initial transform associated with
43 the graphics context"}
44 *inverse-initial-transform*)
46 (defrecord Theme [fore-color back-color alt-back-color border-color font])
48 ;; REMIND: use system colors, see java.awt.SystemColor.
49 (defn default-theme []
50 (Theme. Color/BLACK Color/LIGHT_GRAY Color/WHITE
51 Color/BLUE (Font. "Sans" Font/PLAIN 12)))
53 (def *theme* (default-theme))
55 (defrecord Location [x y])
56 (defrecord Size [width height])
57 (defrecord Bounds [x y width height])
59 ;;
60 ;; Core protocols and types
61 ;;
63 (defprotocol Layer
64 "Basic UI element."
65 (render! [this])
66 (layer-size [this]))
68 ;; TODO: modifiers
69 (defrecord MouseEvent [id when x y x-on-screen y-on-screen button])
71 (defprotocol EventDispatcher
72 (listen! [this ^Component component]
73 "Listen for events on the specified AWT Component.")
74 (create-dispatcher [this handle handlers]
75 "Returns new event dispatcher associated with the specified event
76 handlers (an event-id -> handler-fn map). Handle is used to
77 match the contexts between commits.")
78 (commit [this]
79 "Apply the registered handlers for event processing."))
81 (defprotocol Anchored
82 "Provide anchor point for Layers. Used by viewport."
83 (anchor [this h-align v-align]
84 "Anchor point: [x y], h-align could be :left, :center or :right,
85 v-align is :top, :center or :bottom"))
87 ;; Default implementation of Anchored for any Layer.
88 (extend-protocol Anchored
89 net.kryshen.indyvon.core.Layer
90 (anchor [this h-align v-align]
91 (if (and (= h-align :left)
92 (= v-align :top))
93 (Location. 0 0)
94 (let [size (layer-size this)]
95 (Location.
96 (case h-align
97 :top 0
98 :center (/ (:width size) 2)
99 :right (:width size))
100 (case v-align
101 :left 0
102 :center (/ (:height size) 2)
103 :bottom (:height size)))))))
105 ;;
106 ;; Rendering
107 ;;
109 (defn- relative-transform
110 "AffineTransform: layer context -> awt component."
111 []
112 (let [tr (.getTransform *graphics*)]
113 (.preConcatenate tr *inverse-initial-transform*)
114 tr))
116 (defn- inverse-relative-transform
117 "AffineTransform: awt component -> layer context."
118 []
119 (let [tr (.getTransform *graphics*)]
120 (.invert tr) ; absolute -> layer
121 (.concatenate tr *initial-transform*) ; component -> absolute
122 tr))
124 (defn- clip
125 "Intersect clipping area with the specified shape or bounds.
126 Returns new clip (Shape or nil if empty)."
127 ([x y w h]
128 (clip (Rectangle2D$Double. x y w h)))
129 ([shape]
130 (let [a1 (Area. shape)
131 a2 (if (instance? Area *clip*) *clip* (Area. *clip*))]
132 (.transform a1 (relative-transform))
133 (.intersect a1 a2)
134 (if (.isEmpty a1)
135 nil
136 a1))))
138 (defn- ^Graphics2D apply-theme
139 "Set graphics' color and font to match theme.
140 Modifies and returns the first argument."
141 [^Graphics2D graphics theme]
142 (doto graphics
143 (.setColor (:fore-color theme))
144 (.setFont (:font theme))))
146 (defn- ^Graphics2D create-graphics
147 ([]
148 (create-graphics 0 0 *width* *height*))
149 ([x y w h]
150 (apply-theme (.create *graphics* x y w h) *theme*)))
152 (defn with-bounds*
153 [x y w h f & args]
154 (when-let [clip (clip x y w h)]
155 (let [graphics (create-graphics x y w h)]
156 (try
157 (binding [*width* w
158 *height* h
159 *clip* clip
160 *graphics* graphics]
161 (apply f args))
162 (finally
163 (.dispose graphics))))))
165 (defmacro with-bounds
166 [x y w h & body]
167 `(with-bounds* ~x ~y ~w ~h (fn [] ~@body)))
169 (defn with-handlers*
170 [handle handlers f & args]
171 (binding
172 [*event-dispatcher* (create-dispatcher
173 *event-dispatcher* handle handlers)]
174 (apply f args)))
176 (defmacro with-handlers
177 "specs => (:event-id name & handler-body)*
179 Execute form with the specified event handlers."
180 [handle form & specs]
181 `(with-handlers* ~handle
182 ~(reduce (fn [m spec]
183 (assoc m (first spec)
184 `(fn [~(second spec)]
185 ~@(nnext spec)))) {}
186 specs)
187 (fn [] ~form)))
189 (defmacro with-theme
190 [theme & body]
191 `(binding [*theme* (merge *theme* ~theme)]
192 ~@body))
194 (defmacro with-color
195 [color & body]
196 `(let [color# (.getColor *graphics*)]
197 (try
198 (.setColor *graphics* ~color)
199 ~@body
200 (finally
201 (.setColor *graphics* color#)))))
203 ;; TODO:
204 ;;
205 ;; (with-transform
206 ;; (rotate ...)
207 ;; (draw ...)
208 ;; (scale ...)
209 ;; (draw ...))
211 (defmacro with-transform [transform & body]
212 `(let [old-t# (.getTransform *graphics*)]
213 (try
214 (.transform *graphics* ~transform)
215 ~@body
216 (finally
217 (.setTransform *graphics* old-t#)))))
219 (defmacro with-rotate [theta ax ay & body]
220 `(let [transform# (AffineTransform/getRotateInstance ~theta ~ax ~ay)]
221 (with-transform transform# ~@body)))
223 (defn- geometry-vec [geometry]
224 (if (vector? geometry)
225 geometry
226 [(:x geometry) (:y geometry) (:width geometry) (:height geometry)]))
228 (defn draw!
229 "Draws layer."
230 ([layer]
231 (let [graphics (create-graphics)]
232 (try
233 (binding [*graphics* graphics]
234 (render! layer))
235 (finally
236 (.dispose graphics)))))
237 ([layer x y]
238 (let [size (layer-size layer)]
239 (draw! layer x y (:width size) (:height size))))
240 ([layer x y width height]
241 (with-bounds* x y width height render! layer)))
243 (defn context-draw!
244 "Sets up layer context, draws layer and commits event dispatcher."
245 ([layer graphics event-dispatcher update-fn width height]
246 (draw! layer nil graphics event-dispatcher update-fn width height))
247 ([layer component ^Graphics2D graphics event-dispatcher update-fn
248 width height]
249 (binding [*graphics* graphics
250 *font-context* (.getFontRenderContext graphics)
251 *initial-transform* (.getTransform graphics)
252 *inverse-initial-transform*
253 (-> graphics .getTransform .createInverse)
254 *target* component
255 *update* update-fn
256 *event-dispatcher* event-dispatcher
257 *width* width
258 *height* height
259 *clip* (Rectangle2D$Double. 0 0 width height)]
260 (render! layer)
261 (commit event-dispatcher))))
263 (defn draw-anchored!
264 "Draws layer. Location is relative to the layer's anchor point for
265 the specified alignment."
266 ([layer h-align v-align x y]
267 (let [anchor (anchor layer h-align v-align)]
268 (draw! layer (- x (:x anchor)) (- y (:y anchor)))))
269 ([layer h-align v-align x y w h]
270 (let [anchor (anchor layer h-align v-align)]
271 (draw! layer (- x (:x anchor)) (- y (:y anchor)) w h))))
273 ;;
274 ;; EventDispatcher implementation
275 ;;
277 (def awt-events
278 {java.awt.event.MouseEvent/MOUSE_CLICKED :mouse-clicked
279 java.awt.event.MouseEvent/MOUSE_DRAGGED :mouse-dragged
280 java.awt.event.MouseEvent/MOUSE_ENTERED :mouse-entered
281 java.awt.event.MouseEvent/MOUSE_EXITED :mouse-exited
282 java.awt.event.MouseEvent/MOUSE_MOVED :mouse-moved
283 java.awt.event.MouseEvent/MOUSE_PRESSED :mouse-pressed
284 java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released})
286 (def dummy-event-dispatcher
287 (reify
288 EventDispatcher
289 (listen! [this component])
290 (create-dispatcher [this handle handlers] this)
291 (commit [this])))
293 (defrecord DispatcherNode [handle handlers parent
294 ^Shape clip ^AffineTransform transform
295 bindings]
296 EventDispatcher
297 (listen! [this component]
298 (listen! parent component))
299 (create-dispatcher [this handle handlers]
300 (create-dispatcher parent handle handlers))
301 (commit [this]
302 (commit parent)))
304 (defn- make-node [handle handlers]
305 (DispatcherNode. handle handlers *event-dispatcher* *clip*
306 (inverse-relative-transform)
307 (get-thread-bindings)))
309 (defn- assoc-cons [m key val]
310 (assoc m key (cons val (get m key))))
312 (defn- add-node [tree node]
313 (assoc-cons tree (:parent node) node))
315 (defn- under-cursor
316 "Returns a vector of child nodes under cursor."
317 [x y tree node]
318 (some #(if (.contains ^Shape (:clip %) x y)
319 (conj (vec (under-cursor x y tree %)) %))
320 (get tree node)))
322 (defn- remove-all [coll1 coll2 pred]
323 (filter #(not (some (partial pred %) coll2)) coll1))
325 (defn- transform [^AffineTransform tr x y]
326 (let [p (Point2D$Double. x y)]
327 (.transform tr p p)
328 [(.x p) (.y p)]))
330 (defn- translate-mouse-event [^java.awt.event.MouseEvent event
331 ^AffineTransform tr id]
332 (let [[x y] (transform tr (.getX event) (.getY event))]
333 (MouseEvent. id (.getWhen event) x y
334 (.getXOnScreen event) (.getYOnScreen event)
335 (.getButton event))))
337 (defn- translate-and-dispatch
338 ([nodes first-only ^java.awt.event.MouseEvent event]
339 (translate-and-dispatch nodes first-only
340 event (awt-events (.getID event))))
341 ([nodes first-only event id]
342 (if-let [node (first nodes)]
343 (if-let [handler (get (:handlers node) id)]
344 (do
345 (with-bindings* (:bindings node)
346 handler
347 (translate-mouse-event event (:transform node) id))
348 (if-not first-only
349 (recur (rest nodes) false event id)))
350 (recur (rest nodes) first-only event id)))))
352 (defn- dispatch-mouse-motion
353 "Dispatches mouse motion events."
354 [hovered-ref tree root ^java.awt.event.MouseEvent event]
355 (let [x (.getX event)
356 y (.getY event)
357 [hovered hovered2] (dosync
358 [@hovered-ref
359 (ref-set hovered-ref
360 (under-cursor x y tree root))])
361 pred #(= (:handle %1) (:handle %2))
362 exited (remove-all hovered hovered2 pred)
363 entered (remove-all hovered2 hovered pred)
364 moved (remove-all hovered2 entered pred)]
365 (translate-and-dispatch exited false event :mouse-exited)
366 (translate-and-dispatch entered false event :mouse-entered)
367 (translate-and-dispatch moved true event :mouse-moved)))
369 (defn- dispatch-mouse-button
370 [picked-ref hovered-ref ^java.awt.event.MouseEvent event]
371 (let [id (awt-events (.getID event))
372 hovered (if (= id :mouse-pressed)
373 (dosync (ref-set picked-ref @hovered-ref))
374 @hovered-ref)]
375 (translate-and-dispatch hovered true event id)))
377 (defn root-event-dispatcher []
378 (let [tree-r (ref {}) ; register
379 tree (ref {}) ; dispatch
380 hovered (ref '())
381 picked (ref '())]
382 (reify
383 EventDispatcher
384 (listen! [this component]
385 (doto component
386 (.addMouseListener this)
387 (.addMouseMotionListener this)))
388 (create-dispatcher [this handle handlers]
389 (let [node (make-node handle handlers)]
390 (dosync (alter tree-r add-node node))
391 node))
392 (commit [this]
393 (dosync (ref-set tree @tree-r)
394 (ref-set tree-r {})))
395 MouseListener
396 (mouseEntered [this event]
397 (dispatch-mouse-motion hovered @tree this event))
398 (mouseExited [this event]
399 (dispatch-mouse-motion hovered @tree this event))
400 (mouseClicked [this event]
401 (dispatch-mouse-button picked hovered event))
402 (mousePressed [this event]
403 (dispatch-mouse-button picked hovered event))
404 (mouseReleased [this event]
405 (translate-and-dispatch @picked true event))
406 ;;(dispatch-mouse-button picked hovered event))
407 MouseMotionListener
408 (mouseDragged [this event]
409 (translate-and-dispatch @picked true event))
410 (mouseMoved [this event]
411 (dispatch-mouse-motion hovered @tree this event)))))
413 ;;
414 ;; ИДЕИ:
415 ;;
416 ;; Контекст: биндинги или запись?
417 ;;
418 ;; Установка обработчиков (в контексте слоя):
419 ;;
420 ;; (listen
421 ;; (:mouse-entered e
422 ;; ...)
423 ;; (:mouse-exited e
424 ;; ...))
425 ;;
426 ;; Не надо IMGUI.
427 ;; Построение сцены путем декорирования слоев:
428 ;;
429 ;; (listener
430 ;; (:action e (println e))
431 ;; (:mouse-dragged e (println e))
432 ;; (theme :font "Helvetica-14"
433 ;; (vbox
434 ;; (button (text-layer "Button 1"))
435 ;; (button (text-layer "Button 2")))))
436 ;;