view src/kryshen/indyvon/core.clj @ 43:7d67064f0880

More layers.
author Mikhail Kryshen <mikhail@kryshen.net>
date Mon, 12 Jul 2010 03:52:21 +0400
parents d3e3c43df1cd
children 064b21604f74
line source
1 ;;
2 ;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
3 ;;
4 ;; This file is part of Indyvon.
5 ;;
7 (ns kryshen.indyvon.core
8 (:import
9 (java.awt Graphics2D Component Color Font AWTEvent)
10 (java.awt.event MouseListener MouseMotionListener)
11 (java.awt.font FontRenderContext)))
13 (def ^Graphics2D *graphics*)
14 (def ^FontRenderContext *font-context*)
15 (def ^Component *target*)
16 (def *bounds*)
17 (def *clip*)
18 (def *update*)
19 (def *event-dispatcher*)
21 (defrecord Theme [fore-color back-color alt-back-color border-color font])
23 (defn default-theme []
24 (Theme. Color/BLACK Color/LIGHT_GRAY Color/WHITE
25 Color/BLUE (Font. "Sans" Font/PLAIN 12)))
27 (def *theme* (default-theme))
29 (defrecord Location [x y])
30 (defrecord Size [width height])
31 (defrecord Bounds [x y width height])
33 (defprotocol Layer
34 "Basic UI element."
35 (render! [this])
36 (layer-size [this]))
38 ;; TODO: modifiers
39 (defrecord MouseEvent [id when x y x-on-screen y-on-screen button])
41 (defprotocol EventDispatcher
42 (listen! [this ^Component component]
43 "Listen for events on the specified AWT Component.")
44 (create-dispatcher [this handle handlers]
45 "Returns new event dispatcher associated with the specified event
46 handlers (an event-id -> handler-fn map). Handle is used to
47 match the contexts between commits.")
48 (commit [this]
49 "Apply the registered handlers for event processing."))
51 (defprotocol Anchored
52 "Provide anchor point for Layers. Used by viewport."
53 (anchor [this h-align v-align]
54 "Anchor point: [x y], h-align could be :left, :center or :right,
55 v-align is :top, :center or :bottom"))
57 ;; Default implementation of Anchored for any Layer.
58 (extend-protocol Anchored
59 kryshen.indyvon.core.Layer
60 (anchor [this h-align v-align]
61 (if (and (= h-align :left)
62 (= v-align :top))
63 (Location. 0 0)
64 (let [size (layer-size this)]
65 (Location.
66 (case h-align
67 :top 0
68 :center (/ (:width size) 2)
69 :right (:width size))
70 (case v-align
71 :left 0
72 :center (/ (:height size) 2)
73 :bottom (:height size)))))))
75 (defn- ^Graphics2D make-graphics [^Graphics2D graphics x y w h]
76 (.create graphics x y w h))
78 (defn- ^Graphics2D apply-theme [^Graphics2D graphics theme]
79 (doto graphics
80 (.setColor (:fore-color theme))
81 (.setFont (:font theme))))
83 (defn intersect
84 ([b1 b2]
85 (let [x1 (:x b1)
86 y1 (:y b1)
87 x2 (:x b2)
88 y2 (:y b2)]
89 (intersect x1 y1 (+ x1 (:width b1)) (+ y1 (:height b1))
90 x2 y2 (+ x2 (:width b2)) (+ y2 (:height b2)))))
91 ([x11 y11 x12 y12, x21 y21 x22 y22]
92 (let [x1 (max x11 x21)
93 y1 (max y11 y21)
94 x2 (min x12 x22)
95 y2 (min y12 y22)]
96 (Bounds. x1 y1 (- x2 x1) (- y2 y1)))))
98 (defn- ^Graphics2D create-graphics
99 ([]
100 (create-graphics 0 0 (:width *bounds*) (:height *bounds*)))
101 ([x y w h]
102 (apply-theme (.create *graphics* x y w h) *theme*)))
104 (defn with-bounds* [x y w h f & args]
105 (let [graphics (create-graphics x y w h)
106 bounds (Bounds. (+ x (:x *bounds*))
107 (+ y (:y *bounds*))
108 w h)]
109 (try
110 (apply with-bindings* {#'*bounds* bounds
111 #'*clip* (intersect bounds *clip*)
112 #'*graphics* graphics}
113 f args)
114 (finally
115 (.dispose graphics)))))
117 (defmacro with-bounds [x y w h & body]
118 `(with-bounds* ~x ~y ~w ~h (fn [] ~@body)))
120 (defn with-handlers* [handle handlers f & args]
121 (apply with-bindings*
122 {#'*event-dispatcher*
123 (create-dispatcher *event-dispatcher* handle handlers)}
124 f args))
126 (defmacro with-handlers
127 "specs => (:event-id name & handler-body)*
129 Execute form with the specified event handlers."
130 [handle form & specs]
131 `(with-handlers* ~handle
132 ~(reduce (fn [m spec]
133 (assoc m (first spec)
134 `(fn [~(second spec)]
135 ~@(nnext spec)))) {}
136 specs)
137 (fn [] ~form)))
139 (defn with-theme* [theme f & args]
140 (apply with-bindings* {#'*theme* (merge *theme* theme)}
141 f args))
143 (defmacro with-theme [theme & body]
144 `(with-theme* ~theme (fn [] ~@body)))
146 (defmacro with-color [color & body]
147 `(let [color# (.getColor *graphics*)]
148 (try
149 (.setColor *graphics* ~color)
150 ~@body
151 (finally
152 (.setColor *graphics* color#)))))
154 (defn- geometry-vec [geometry]
155 (if (vector? geometry)
156 geometry
157 [(:x geometry) (:y geometry) (:width geometry) (:height geometry)]))
159 (defn draw!
160 ([layer]
161 (let [graphics (create-graphics)]
162 (try
163 (with-bindings* {#'*graphics* graphics} render! layer)
164 (finally
165 (.dispose graphics)))))
166 ([layer x y]
167 (let [size (layer-size layer)]
168 (draw! layer x y (:width size) (:height size))))
169 ([layer x y width height]
170 (with-bounds* x y width height render! layer)))
172 (defn draw-anchored!
173 "Draw with location relative to the anchor point."
174 ([layer h-align v-align x y]
175 (let [anchor (anchor layer h-align v-align)]
176 (draw! layer (- x (:x anchor)) (- y (:y anchor)))))
177 ([layer h-align v-align x y w h]
178 (let [anchor (anchor layer h-align v-align)]
179 (draw! layer (- x (:x anchor)) (- y (:y anchor)) w h))))
181 ;;
182 ;; EventDispatcher implementation
183 ;;
185 (def awt-events
186 {java.awt.event.MouseEvent/MOUSE_CLICKED :mouse-clicked
187 java.awt.event.MouseEvent/MOUSE_DRAGGED :mouse-dragged
188 java.awt.event.MouseEvent/MOUSE_ENTERED :mouse-entered
189 java.awt.event.MouseEvent/MOUSE_EXITED :mouse-exited
190 java.awt.event.MouseEvent/MOUSE_MOVED :mouse-moved
191 java.awt.event.MouseEvent/MOUSE_PRESSED :mouse-pressed
192 java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released})
194 (defrecord DispatcherNode [handle handlers parent bounds bindings]
195 EventDispatcher
196 (listen! [this component]
197 (listen! parent component))
198 (create-dispatcher [this handle handlers]
199 (create-dispatcher parent handle handlers))
200 (commit [this]
201 (commit parent)))
203 (defn- make-node [handle handlers]
204 (DispatcherNode. handle handlers *event-dispatcher* *clip*
205 (get-thread-bindings)))
207 (defn- assoc-cons [m key val]
208 (assoc m key (cons val (get m key))))
210 (defn- add-node [tree node]
211 (assoc-cons tree (:parent node) node))
213 (defn- inside?
214 ([x y bounds]
215 (inside? x y (:x bounds) (:y bounds)
216 (:width bounds) (:height bounds)))
217 ([px py x y w h]
218 (and (>= px x)
219 (>= py y)
220 (< px (+ x w))
221 (< py (+ y h)))))
223 (defn- under-cursor
224 "Returns a vector of child nodes under cursor."
225 [x y tree node]
226 (some #(if (inside? x y (:bounds %))
227 (conj (vec (under-cursor x y tree %)) %))
228 (get tree node)))
230 (defn- remove-all [coll1 coll2 pred]
231 (filter #(not (some (partial pred %) coll2)) coll1))
233 (defn- translate-mouse-event [^java.awt.event.MouseEvent event x y id]
234 (MouseEvent. id (.getWhen event)
235 (- (.getX event) x) (- (.getY event) y)
236 (.getXOnScreen event) (.getYOnScreen event)
237 (.getButton event)))
239 (defn- translate-and-dispatch
240 ([nodes first-only ^java.awt.event.MouseEvent event]
241 (translate-and-dispatch nodes first-only
242 event (awt-events (.getID event))))
243 ([nodes first-only event id]
244 (if-let [node (first nodes)]
245 (if-let [handler (get (:handlers node) id)]
246 (do
247 (with-bindings* (:bindings node)
248 handler
249 (translate-mouse-event event
250 (-> node :bounds :x) (-> node :bounds :y) id))
251 (if-not first-only
252 (recur (rest nodes) false event id)))
253 (recur (rest nodes) first-only event id)))))
255 (defn- dispatch-mouse-motion
256 "Dispatches mouse motion events."
257 [hovered-ref tree root ^java.awt.event.MouseEvent event]
258 (let [x (.getX event)
259 y (.getY event)
260 [hovered hovered2] (dosync
261 [@hovered-ref
262 (ref-set hovered-ref
263 (under-cursor x y tree root))])
264 pred #(= (:handle %1) (:handle %2))
265 exited (remove-all hovered hovered2 pred)
266 entered (remove-all hovered2 hovered pred)
267 moved (remove-all hovered2 entered pred)]
268 (translate-and-dispatch exited false event :mouse-exited)
269 (translate-and-dispatch entered false event :mouse-entered)
270 (translate-and-dispatch moved true event :mouse-moved)))
272 (defn- dispatch-mouse-button
273 [picked-ref hovered-ref ^java.awt.event.MouseEvent event]
274 (let [id (awt-events (.getID event))
275 hovered (if (= id :mouse-pressed)
276 (dosync (ref-set picked-ref @hovered-ref))
277 @hovered-ref)]
278 (translate-and-dispatch hovered true event id)))
280 (defn root-event-dispatcher []
281 (let [tree-r (ref {}) ; register
282 tree (ref {}) ; dispatch
283 hovered (ref '())
284 picked (ref '())]
285 (reify
286 EventDispatcher
287 (listen! [this component]
288 (doto component
289 (.addMouseListener this)
290 (.addMouseMotionListener this)))
291 (create-dispatcher [this handle handlers]
292 (let [node (make-node handle handlers)]
293 (dosync (alter tree-r add-node node))
294 node))
295 (commit [this]
296 (dosync (ref-set tree @tree-r)
297 (ref-set tree-r {})))
298 MouseListener
299 (mouseEntered [this event]
300 (dispatch-mouse-motion hovered @tree this event))
301 (mouseExited [this event]
302 (dispatch-mouse-motion hovered @tree this event))
303 (mouseClicked [this event]
304 (dispatch-mouse-button picked hovered event))
305 (mousePressed [this event]
306 (dispatch-mouse-button picked hovered event))
307 (mouseReleased [this event]
308 (dispatch-mouse-button picked hovered event))
309 MouseMotionListener
310 (mouseDragged [this event]
311 (translate-and-dispatch @picked true event))
312 (mouseMoved [this event]
313 (dispatch-mouse-motion hovered @tree this event)))))
315 ;;
316 ;; ИДЕИ:
317 ;;
318 ;; Контекст: биндинги или запись?
319 ;;
320 ;; Установка обработчиков (в контексте слоя):
321 ;;
322 ;; (listen
323 ;; (:mouse-entered e
324 ;; ...)
325 ;; (:mouse-exited e
326 ;; ...))
327 ;;
328 ;; Не надо IMGUI.
329 ;; Построение сцены путем декорирования слоев:
330 ;;
331 ;; (listener
332 ;; (:action e (println e))
333 ;; (:mouse-dragged e (println e))
334 ;; (theme :font "Helvetica-14"
335 ;; (vbox
336 ;; (button (text-layer "Button 1"))
337 ;; (button (text-layer "Button 2")))))
338 ;;