Mercurial > hg > indyvon
changeset 154:ed36fcf061de
Removed the domain part from namespace names.
author | Mikhail Kryshen <mikhail@kryshen.net> |
---|---|
date | Mon, 14 Apr 2014 20:01:00 +0400 |
parents | 291afc2a8ca2 |
children | c3782e84486f |
files | project.clj src/indyvon/async.clj src/indyvon/component.clj src/indyvon/core.clj src/indyvon/demo.clj src/indyvon/viewport.clj src/indyvon/views.clj src/net/kryshen/indyvon/async.clj src/net/kryshen/indyvon/component.clj src/net/kryshen/indyvon/core.clj src/net/kryshen/indyvon/demo.clj src/net/kryshen/indyvon/viewport.clj src/net/kryshen/indyvon/views.clj |
diffstat | 13 files changed, 2000 insertions(+), 2000 deletions(-) [+] |
line diff
1.1 --- a/project.clj Mon Apr 14 15:37:28 2014 +0400 1.2 +++ b/project.clj Mon Apr 14 20:01:00 2014 +0400 1.3 @@ -4,6 +4,6 @@ 1.4 :license {:name "GNU LGPL version 3" 1.5 :url "http://www.gnu.org/licenses/lgpl-3.0.html"} 1.6 :warn-on-reflection true 1.7 -; :main net.kryshen.indyvon.demo 1.8 +; :main indyvon.demo 1.9 :dependencies [[org.clojure/clojure "1.6.0"] 1.10 [com.google.guava/guava "16.0.1"]])
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/src/indyvon/async.clj Mon Apr 14 20:01:00 2014 +0400 2.3 @@ -0,0 +1,178 @@ 2.4 +;; 2.5 +;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> 2.6 +;; 2.7 +;; This file is part of Indyvon. 2.8 +;; 2.9 +;; Indyvon is free software: you can redistribute it and/or modify it 2.10 +;; under the terms of the GNU Lesser General Public License version 3 2.11 +;; only, as published by the Free Software Foundation. 2.12 +;; 2.13 +;; Indyvon is distributed in the hope that it will be useful, but 2.14 +;; WITHOUT ANY WARRANTY; without even the implied warranty of 2.15 +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 2.16 +;; Lesser General Public License for more details. 2.17 +;; 2.18 +;; You should have received a copy of the GNU Lesser General Public 2.19 +;; License along with Indyvon. If not, see 2.20 +;; <http://www.gnu.org/licenses/>. 2.21 +;; 2.22 + 2.23 +(ns indyvon.async 2.24 + "Asynchronous drawing." 2.25 + (:use 2.26 + indyvon.core) 2.27 + (:import 2.28 + java.awt.GraphicsConfiguration 2.29 + (java.awt Image AlphaComposite Transparency) 2.30 + (java.awt.image BufferedImage) 2.31 + (java.util.concurrent ThreadFactory ThreadPoolExecutor 2.32 + ThreadPoolExecutor$DiscardOldestPolicy 2.33 + ArrayBlockingQueue TimeUnit))) 2.34 + 2.35 +(defrecord Buffer [id image readers state]) 2.36 +;; Buffer states: 2.37 +;; :front, readers > 0 2.38 +;; being copied on screen 2.39 +;; :back 2.40 +;; being rendered to (offscreen) 2.41 +;; :fresh 2.42 +;; most recently updated 2.43 +;; :free 2.44 +;; not in use 2.45 + 2.46 +(defn- create-image [async-view ^GraphicsConfiguration device-conf] 2.47 + ;; TODO: support different image types. 2.48 + (.createCompatibleImage device-conf 2.49 + (:width async-view) 2.50 + (:height async-view) 2.51 + Transparency/TRANSLUCENT)) 2.52 + 2.53 +(defn- create-buffer [async-view device-conf] 2.54 + (Buffer. (Object.) (create-image async-view device-conf) 0 :free)) 2.55 + 2.56 +(defn- find-buffer 2.57 + "Find a buffer with the one of the specified states given 2.58 + in the order of preference." 2.59 + [buffers & states] 2.60 + (some identity 2.61 + (for [state states] 2.62 + (some #(if (= (:state %) state) % nil) buffers)))) 2.63 + 2.64 +(defn- replace-buffer [buffers buffer] 2.65 + (conj (remove #(= (:id %) (:id buffer)) buffers) 2.66 + buffer)) 2.67 + 2.68 +(defn- take-buffer [al type] 2.69 + (dosync 2.70 + (let [buffers @(:buffers al) 2.71 + b (case type 2.72 + :front (find-buffer buffers :front :fresh :free) 2.73 + :back (find-buffer buffers :free :fresh) 2.74 + (throw (IllegalArgumentException.))) 2.75 + readers (if (= type :front) 2.76 + (inc (:readers b)) 2.77 + (:readers b)) 2.78 + b (assoc b 2.79 + :state type 2.80 + :readers readers)] 2.81 + (alter (:buffers al) replace-buffer b) 2.82 + b))) 2.83 + 2.84 +(defn- release-buffer [al buffer] 2.85 + (dosync 2.86 + (let [state (:state buffer) 2.87 + readers (if (= state :front) 2.88 + (dec (:readers buffer)) 2.89 + (:readers buffer)) 2.90 + fresh (delay (find-buffer @(:buffers al) :fresh)) 2.91 + state (cond 2.92 + (pos? readers) :front 2.93 + (= :back state) :fresh 2.94 + @fresh :free 2.95 + :default :fresh)] 2.96 + (if (and (= state :fresh) @fresh) 2.97 + ;; Change state of the prefiously fresh buffer to :free. 2.98 + (alter (:buffers al) 2.99 + replace-buffer (assoc @fresh 2.100 + :state :free))) 2.101 + (alter (:buffers al) 2.102 + replace-buffer (assoc buffer 2.103 + :state state 2.104 + :readers readers))))) 2.105 + 2.106 +(defmacro with-buffer 2.107 + {:private true} 2.108 + [al type [name] & body] 2.109 + `(let [al# ~al 2.110 + ~name (take-buffer al# ~type)] 2.111 + (try 2.112 + ~@body 2.113 + (finally 2.114 + (release-buffer al# ~name))))) 2.115 + 2.116 +(defn- draw-offscreen [async-view] 2.117 + ;;(Thread/sleep 1000) 2.118 + (with-buffer async-view :back [b] 2.119 + (let [g (.createGraphics ^BufferedImage (:image b))] 2.120 + ;; Clear the buffer. 2.121 + (.setComposite g AlphaComposite/Clear) 2.122 + (.fillRect g 0 0 (:width async-view) (:height async-view)) 2.123 + (.setComposite g AlphaComposite/Src) 2.124 + (draw-scene! (:scene async-view) 2.125 + g 2.126 + (:width async-view) 2.127 + (:height async-view))) 2.128 + (update async-view))) 2.129 + 2.130 +(defn- draw-offscreen-async [async-view] 2.131 + (.execute ^ThreadPoolExecutor (:executor async-view) 2.132 + #(draw-offscreen async-view))) 2.133 + 2.134 +(defrecord AsyncView [scene width height executor buffers] 2.135 + View 2.136 + (render! [view] 2.137 + (repaint-on-update view) 2.138 + (add-context-observer scene (fn [_ _] (draw-offscreen-async view))) 2.139 + (when-not @buffers 2.140 + ;; TODO: dynamic size, recreate buffers when size increases. 2.141 + (let [device-conf (.getDeviceConfiguration *graphics*) 2.142 + new-buffers (repeatedly 2 2.143 + (partial create-buffer view device-conf))] 2.144 + (dosync 2.145 + (ref-set buffers new-buffers))) 2.146 + (draw-offscreen-async view)) 2.147 + (with-buffer view :front [b] 2.148 + (.drawImage *graphics* ^Image (:image b) 0 0 nil))) 2.149 + (geometry [view] 2.150 + (->Size width height))) 2.151 + 2.152 +(defn- create-thread-factory [priority] 2.153 + (reify 2.154 + ThreadFactory 2.155 + (newThread [_ runnable] 2.156 + (let [thread (Thread. runnable)] 2.157 + (when priority 2.158 + (.setPriority thread priority)) 2.159 + (.setDaemon thread true) 2.160 + thread)))) 2.161 + 2.162 +(defn- create-executor [priority] 2.163 + (doto (ThreadPoolExecutor. 2.164 + (int 1) (int 1) 2.165 + (long 0) TimeUnit/SECONDS 2.166 + (ArrayBlockingQueue. 1) 2.167 + (ThreadPoolExecutor$DiscardOldestPolicy.)) 2.168 + (.setThreadFactory (create-thread-factory priority)))) 2.169 + 2.170 +(defn async-view 2.171 + "Creates a View that draws the content asynchronously using an 2.172 + offscreen buffer." 2.173 + ([width height content] 2.174 + (async-view width height nil content)) 2.175 + ([width height priority content] 2.176 + ;; TODO: use operational event dispatcher. 2.177 + (->AsyncView (make-scene content) 2.178 + width 2.179 + height 2.180 + (create-executor priority) 2.181 + (ref nil))))
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/src/indyvon/component.clj Mon Apr 14 20:01:00 2014 +0400 3.3 @@ -0,0 +1,69 @@ 3.4 +;; 3.5 +;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> 3.6 +;; 3.7 +;; This file is part of Indyvon. 3.8 +;; 3.9 +;; Indyvon is free software: you can redistribute it and/or modify it 3.10 +;; under the terms of the GNU Lesser General Public License version 3 3.11 +;; only, as published by the Free Software Foundation. 3.12 +;; 3.13 +;; Indyvon is distributed in the hope that it will be useful, but 3.14 +;; WITHOUT ANY WARRANTY; without even the implied warranty of 3.15 +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 3.16 +;; Lesser General Public License for more details. 3.17 +;; 3.18 +;; You should have received a copy of the GNU Lesser General Public 3.19 +;; License along with Indyvon. If not, see 3.20 +;; <http://www.gnu.org/licenses/>. 3.21 +;; 3.22 + 3.23 +(ns indyvon.component 3.24 + "Integrating Indyvon into AWT and Swing components." 3.25 + (:use 3.26 + indyvon.core) 3.27 + (:import 3.28 + (java.awt Graphics Component Dimension Color) 3.29 + (java.awt.geom Rectangle2D$Double) 3.30 + (javax.swing JFrame JPanel JOptionPane))) 3.31 + 3.32 +(defn- paint-component [^Component c ^Graphics g scene] 3.33 + (let [w (.getWidth c) 3.34 + h (.getHeight c)] 3.35 + (.setColor g (:back-color *theme*)) 3.36 + (.fillRect g 0 0 w h) 3.37 + (draw-scene! scene g w h))) 3.38 + 3.39 +(defn- preferred-size [^Component c scene] 3.40 + (let [geom (scene-geometry scene)] 3.41 + (Dimension. (width geom) (height geom)))) 3.42 + 3.43 +(defn ^JPanel make-jpanel 3.44 + ([view] 3.45 + (make-jpanel view (root-event-dispatcher))) 3.46 + ([view event-dispatcher] 3.47 + (let [panel (proxy [JPanel] []) 3.48 + scene (make-scene 3.49 + view event-dispatcher panel 3.50 + (.getDesktopProperty (java.awt.Toolkit/getDefaultToolkit) 3.51 + "awt.font.desktophints"))] 3.52 + (update-proxy 3.53 + panel 3.54 + {"paintComponent" #(paint-component %1 %2 scene) 3.55 + "getPreferredSize" #(preferred-size % scene)}) 3.56 + (.setBackground panel (:back-color *theme*)) 3.57 + (add-observer panel scene (fn [w _] 3.58 + ;; Use the first observer argument 3.59 + ;; instead of closing over panel to 3.60 + ;; allow the panel and associated 3.61 + ;; observer to be gc'd. 3.62 + (.repaint ^Component w))) 3.63 + (listen! event-dispatcher panel) 3.64 + panel))) 3.65 + 3.66 +(defn ^JFrame make-jframe [^String title view] 3.67 + (doto (JFrame. title) 3.68 + (.. (getContentPane) (add (make-jpanel view))) 3.69 + (.pack))) 3.70 + 3.71 +(defn message [m] 3.72 + (JOptionPane/showMessageDialog (:component *scene*) m))
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 4.2 +++ b/src/indyvon/core.clj Mon Apr 14 20:01:00 2014 +0400 4.3 @@ -0,0 +1,881 @@ 4.4 +;; 4.5 +;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> 4.6 +;; 4.7 +;; This file is part of Indyvon. 4.8 +;; 4.9 +;; Indyvon is free software: you can redistribute it and/or modify it 4.10 +;; under the terms of the GNU Lesser General Public License version 3 4.11 +;; only, as published by the Free Software Foundation. 4.12 +;; 4.13 +;; Indyvon is distributed in the hope that it will be useful, but 4.14 +;; WITHOUT ANY WARRANTY; without even the implied warranty of 4.15 +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 4.16 +;; Lesser General Public License for more details. 4.17 +;; 4.18 +;; You should have received a copy of the GNU Lesser General Public 4.19 +;; License along with Indyvon. If not, see 4.20 +;; <http://www.gnu.org/licenses/>. 4.21 +;; 4.22 + 4.23 +(ns indyvon.core 4.24 + (:import 4.25 + (java.awt Graphics2D RenderingHints Component Color Font Shape 4.26 + Rectangle Cursor EventQueue) 4.27 + (java.awt.geom AffineTransform Point2D$Double Rectangle2D$Double Area) 4.28 + (java.awt.event MouseListener MouseMotionListener 4.29 + MouseWheelListener MouseWheelEvent) 4.30 + (java.awt.font FontRenderContext) 4.31 + java.util.concurrent.ConcurrentMap 4.32 + com.google.common.collect.MapMaker)) 4.33 + 4.34 +;; 4.35 +;; View context 4.36 +;; 4.37 + 4.38 +(def ^:dynamic ^Graphics2D *graphics*) 4.39 + 4.40 +(def ^:dynamic ^FontRenderContext *font-context* 4.41 + "FontRenderContext to use when Graphics2D is not available." 4.42 + (FontRenderContext. 4.43 + nil 4.44 + RenderingHints/VALUE_TEXT_ANTIALIAS_DEFAULT 4.45 + RenderingHints/VALUE_FRACTIONALMETRICS_DEFAULT)) 4.46 + 4.47 +(def ^:dynamic *width* 4.48 + "Width of the rendering area.") 4.49 + 4.50 +(def ^:dynamic *height* 4.51 + "Height of the rendering area.") 4.52 + 4.53 +(def ^:dynamic ^Shape *clip*) 4.54 + 4.55 +(def ^:dynamic ^Shape *input-clip* 4.56 + "Clipping area used for dispatching pointer events (intersected with 4.57 + *clip*). If nil, *clip* will be used.") 4.58 + 4.59 +(def ^:dynamic *time* 4.60 + "Timestamp of the current frame (in nanoseconds).") 4.61 + 4.62 +(def ^:dynamic *scene* 4.63 + "Encloses state that should be retained between repaints.") 4.64 + 4.65 +(def ^:dynamic *states* 4.66 + "Transient scene states, a map.") 4.67 + 4.68 +(def ^:dynamic *event-dispatcher*) 4.69 + 4.70 +(def ^:dynamic ^AffineTransform *initial-transform* 4.71 + "Initial transform associated with the graphics context.") 4.72 + 4.73 +(def ^:dynamic ^AffineTransform *inverse-initial-transform* 4.74 + "Inversion of the initial transform associated with the graphics 4.75 + context.") 4.76 + 4.77 +(defrecord Theme [fore-color back-color alt-back-color border-color 4.78 + shadow-color font]) 4.79 + 4.80 +;; REMIND: use system colors, see java.awt.SystemColor. 4.81 +(defn default-theme [] 4.82 + (Theme. Color/BLACK 4.83 + Color/WHITE 4.84 + (Color. 0xDD 0xDD 0xDD) 4.85 + (Color. 0 0 0xCC) 4.86 + (Color. 0x44 0x44 0x44) 4.87 + (Font. "Sans" Font/PLAIN 12))) 4.88 + 4.89 +(def ^:dynamic *theme* (default-theme)) 4.90 + 4.91 +;; 4.92 +;; Core protocols and types 4.93 +;; 4.94 + 4.95 +(defprotocol View 4.96 + "Basic UI element." 4.97 + (render! [view] 4.98 + "Draws the view in the current *graphics* context.") 4.99 + (geometry [view] 4.100 + "Returns the preferred Geometry for the view.")) 4.101 + 4.102 +(defprotocol Geometry 4.103 + "Describes geometry of a View. Prefer using the available 4.104 + implementations (Size, FixedGeometry and NestedGeometry) over 4.105 + extending this protocol directly as it is likely to be changed in 4.106 + the future versions." 4.107 + (width [geom] [geom height]) 4.108 + (height [geom] [geom width]) 4.109 + (anchor-x [geom h-align width] 4.110 + "Returns the x coordinate of the anchor point for the specified 4.111 + horizontal alignment and width, h-align could be :left, :center 4.112 + or :right.") 4.113 + (anchor-y [geom v-align height] 4.114 + "Returns the y coordinate of the anchor point for the specified 4.115 + vertical alignment and height, v-align could be :top, :center 4.116 + or :bottom.")) 4.117 + 4.118 +(defn- emit-align-xy [align size first center last] 4.119 + `(case ~align 4.120 + ~first 0 4.121 + ~center (/ ~size 2) 4.122 + ~last ~size)) 4.123 + 4.124 +;; Define as macro to avoid unnecessary calculation of width or height. 4.125 +(defmacro align-x 4.126 + ([align inner outer] 4.127 + `(align-x ~align (- ~outer ~inner))) 4.128 + ([align width] 4.129 + (emit-align-xy align width :left :center :right))) 4.130 + 4.131 +(defmacro align-y 4.132 + ([align inner outer] 4.133 + `(align-y ~align (- ~outer ~inner))) 4.134 + ([align height] 4.135 + (emit-align-xy align height :top :center :bottom))) 4.136 + 4.137 +(defrecord Size [width height] 4.138 + Geometry 4.139 + (width [_] width) 4.140 + (width [_ _] width) 4.141 + (height [_] height) 4.142 + (height [_ _] height) 4.143 + (anchor-x [_ h-align width] 4.144 + (align-x h-align width)) 4.145 + (anchor-y [_ v-align height] 4.146 + (align-y v-align height))) 4.147 + 4.148 +(defrecord FixedGeometry [ax ay width height] 4.149 + Geometry 4.150 + (width [_] width) 4.151 + (width [_ _] width) 4.152 + (height [_] height) 4.153 + (height [_ _] height) 4.154 + (anchor-x [_ _ _] ax) 4.155 + (anchor-y [_ _ _] ay)) 4.156 + 4.157 +(defrecord NestedGeometry [geometry top left bottom right] 4.158 + Geometry 4.159 + (width [_] 4.160 + (+ left right (width geometry))) 4.161 + (width [_ h] 4.162 + (+ left right (width geometry (- h top bottom)))) 4.163 + (height [_] 4.164 + (+ top bottom (height geometry))) 4.165 + (height [_ w] 4.166 + (+ top bottom (height geometry (- w left right)))) 4.167 + (anchor-x [_ h-align w] 4.168 + (+ left (anchor-x geometry h-align (- w left right)))) 4.169 + (anchor-y [_ v-align h] 4.170 + (+ top (anchor-y geometry v-align (- h top bottom))))) 4.171 + 4.172 +(defrecord ScaledGeometry [geometry sx sy] 4.173 + Geometry 4.174 + (width [_] 4.175 + (* sx (width geometry))) 4.176 + (width [_ h] 4.177 + (* sx (width geometry (/ h sy)))) 4.178 + (height [_] 4.179 + (* sy (height geometry))) 4.180 + (height [_ w] 4.181 + (* sy (height geometry (/ w sx)))) 4.182 + (anchor-x [_ h-align w] 4.183 + (* sx (anchor-x geometry h-align (/ w sx)))) 4.184 + (anchor-y [_ v-align h] 4.185 + (* sy (anchor-y geometry v-align (/ h sy))))) 4.186 + 4.187 +;; (defn ^:private to-integer 4.188 +;; ^long [align x] 4.189 +;; (if (integer? x) 4.190 +;; x 4.191 +;; (let [x (double x)] 4.192 +;; (Math/round 4.193 +;; (case align 4.194 +;; (:top :left) (Math/floor x) 4.195 +;; :center x 4.196 +;; (:bottom :right) (Math/ceil x)))))) 4.197 + 4.198 +;; (defrecord IntegerGeometry [geometry] 4.199 +;; Geometry 4.200 +;; (width [_] 4.201 +;; (to-integer :right (width geometry))) 4.202 +;; (width [_ h] 4.203 +;; (to-integer :right (width geometry h))) 4.204 +;; (height [_] 4.205 +;; (to-integer :bottom (height geometry))) 4.206 +;; (height [_ w] 4.207 +;; (to-integer :bottom (height geometry w))) 4.208 +;; (anchor-x [_ h-align w] 4.209 +;; (to-integer h-align (anchor-x geometry h-align w))) 4.210 +;; (anchor-y [_ v-align h] 4.211 +;; (to-integer v-align (anchor-y geometry v-align h)))) 4.212 + 4.213 +;; TODO: modifiers 4.214 +(defrecord MouseEvent [id when x y x-on-screen y-on-screen button 4.215 + wheel-rotation transform component]) 4.216 + 4.217 +;; TODO: KeyEvent 4.218 + 4.219 +(defprotocol EventDispatcher 4.220 + (listen! [this component] 4.221 + "Listen for events on the specified AWT Component.") 4.222 + (create-dispatcher [this handle handlers] 4.223 + "Returns new event dispatcher associated with the specified event 4.224 + handlers (an event-id -> handler-fn map). Handle is used to 4.225 + match the contexts between commits.") 4.226 + (commit [this] 4.227 + "Apply the registered handlers for event processing.") 4.228 + (handle-picked? [this handle] 4.229 + "Returns true if the specified handle received the :mouse-pressed 4.230 + event and have not yet received :moused-released.") 4.231 + (handle-hovered? [this handle] 4.232 + "Returns true if the specified handle received the :mouse-entered 4.233 + event and have not yet received :mouse-exited.")) 4.234 + 4.235 +(defn- assoc-cons [m key val] 4.236 + (->> (get m key) (cons val) (assoc m key))) 4.237 + 4.238 +;; 4.239 +;; Observers 4.240 +;; The mechanism used by views to request repaints 4.241 +;; 4.242 + 4.243 +(def ^ConcurrentMap observers 4.244 + (-> (MapMaker.) (.weakKeys) (.makeMap))) 4.245 + 4.246 +(defn- cm-replace! 4.247 + "Wrap ConcurrentMap replace method to treat nil value as absent 4.248 + mapping. Use with maps that does not support nil values." 4.249 + [^ConcurrentMap cmap key old new] 4.250 + (if (nil? old) 4.251 + (nil? (.putIfAbsent cmap key new)) 4.252 + (.replace cmap key old new))) 4.253 + 4.254 +(defn- cm-swap! 4.255 + "Atomically swaps the value associated with key in ConcurrentMap 4.256 + to be (apply f current-value args). Returns the new value." 4.257 + [^ConcurrentMap cmap key f & args] 4.258 + (loop [] 4.259 + (let [old (.get cmap key) 4.260 + new (apply f old args)] 4.261 + (if (cm-replace! cmap key old new) 4.262 + new 4.263 + (recur))))) 4.264 + 4.265 +(defn add-observer 4.266 + "Add observer fn for the target. Watcher identifies the group of 4.267 + observers and could be used to remove the group. Watcher is weakly 4.268 + referenced, all associated observers will be removed when the 4.269 + wathcer is removed by gc. The observer fn will be called with 4.270 + watcher and target arguments and any additional arguments specified 4.271 + in update call." 4.272 + [watcher target f] 4.273 + (cm-swap! observers watcher assoc-cons target f) 4.274 + nil) 4.275 + 4.276 +(defn remove-observers 4.277 + "Remove group of observers associated with the specified watcher." 4.278 + [watcher] 4.279 + (.remove observers watcher) 4.280 + nil) 4.281 + 4.282 +(defn- replace-observers-watcher 4.283 + [old-watcher new-watcher] 4.284 + (if-let [old (.remove observers old-watcher)] 4.285 + (.put observers new-watcher old)) 4.286 + nil) 4.287 + 4.288 +(defn update 4.289 + "Notify observers." 4.290 + [target & args] 4.291 + (doseq [entry observers 4.292 + f (get (val entry) target)] 4.293 + (apply f (key entry) target args))) 4.294 + 4.295 +(defn add-context-observer 4.296 + "Observer registered with this function will be automatically 4.297 + removed after the next repaint is complete." 4.298 + [target f] 4.299 + (add-observer *scene* target f)) 4.300 + 4.301 +(defn repaint-on-update 4.302 + "Trigger repaint of the current scene when the target updates." 4.303 + [target] 4.304 + (let [scene *scene*] 4.305 + (if-not (identical? scene target) 4.306 + (add-observer scene target (fn [w _] (update w)))))) 4.307 + 4.308 +(defn repaint 4.309 + "Requests repaint of the current scene. If handle and state are 4.310 + specified, the handle will be associated with the state in the 4.311 + *states* map for the next paint iteration." 4.312 + ([] 4.313 + (update *scene*)) 4.314 + ([handle state] 4.315 + (let [scene *scene*] 4.316 + (swap! (:next-state scene) assoc handle state) 4.317 + (update scene)))) 4.318 + 4.319 +;; 4.320 +;; Rendering 4.321 +;; 4.322 + 4.323 +(defn ^FontRenderContext font-context 4.324 + "Returns FontRenderContext for the current view context." 4.325 + [] 4.326 + (if (bound? (var *graphics*)) 4.327 + (.getFontRenderContext *graphics*) 4.328 + *font-context*)) 4.329 + 4.330 +(defn ^AffineTransform relative-transform 4.331 + "Returns AffineTransform: view context -> AWT component." 4.332 + [] 4.333 + (let [tr (.getTransform *graphics*)] 4.334 + (.preConcatenate tr *inverse-initial-transform*) 4.335 + tr)) 4.336 + 4.337 +(defn ^AffineTransform inverse-relative-transform 4.338 + "Returns AffineTransform: AWT component -> view context." 4.339 + [] 4.340 + (let [tr (.getTransform *graphics*)] 4.341 + (.invert tr) ; absolute -> view 4.342 + (.concatenate tr *initial-transform*) ; component -> absolute 4.343 + tr)) 4.344 + 4.345 +(defn transform-point [^AffineTransform tr ^double x ^double y] 4.346 + (let [p (Point2D$Double. x y)] 4.347 + (.transform tr p p) 4.348 + [(.x p) (.y p)])) 4.349 + 4.350 +(defn inverse-transform-point [^AffineTransform tr ^double x ^double y] 4.351 + (let [p (Point2D$Double. x y)] 4.352 + (.inverseTransform tr p p) 4.353 + [(.x p) (.y p)])) 4.354 + 4.355 +;; (defn- clip 4.356 +;; "Intersect clipping area with the specified shape or bounds. 4.357 +;; Returns new clip (Shape or nil if empty)." 4.358 +;; ([x y w h] 4.359 +;; (clip (Rectangle2D$Double. x y w h))) 4.360 +;; ([shape] 4.361 +;; (let [a1 (Area. shape) 4.362 +;; a2 (if (instance? Area *clip*) *clip* (Area. *clip*))] 4.363 +;; (.transform a1 (relative-transform)) 4.364 +;; (.intersect a1 a2) 4.365 +;; (if (.isEmpty a1) 4.366 +;; nil 4.367 +;; a1)))) 4.368 + 4.369 +;; Use faster clipping calculation provided by Graphics2D. 4.370 +(defn- clip 4.371 + "Intersect clipping area with the specified Shape in current 4.372 + transform coordinates. Returns new clip in the AWT component 4.373 + coordinates (Shape or nil if empty)." 4.374 + [^Shape shape] 4.375 + (let [^Graphics2D clip-g (.create *graphics*)] 4.376 + (try 4.377 + (doto clip-g 4.378 + (.setClip shape) 4.379 + (.setTransform *initial-transform*) 4.380 + (.clip *clip*)) 4.381 + (if (.isEmpty (.getClipBounds clip-g)) 4.382 + nil 4.383 + (.getClip clip-g)) 4.384 + (finally 4.385 + (.dispose clip-g))))) 4.386 + 4.387 +(defn- ^Graphics2D apply-theme 4.388 + "Set graphics' color and font to match theme. 4.389 + Modifies and returns the first argument." 4.390 + ([] 4.391 + (apply-theme *graphics* *theme*)) 4.392 + ([^Graphics2D graphics theme] 4.393 + (doto graphics 4.394 + (.setColor (:fore-color theme)) 4.395 + (.setFont (:font theme))))) 4.396 + 4.397 +(defn- ^Graphics2D create-graphics 4.398 + ([] 4.399 + (apply-theme (.create *graphics*) *theme*)) 4.400 + ([^long x ^long y ^long w ^long h] 4.401 + (apply-theme (.create *graphics* x y w h) *theme*))) 4.402 + 4.403 +(defn- with-bounds-noclip* 4.404 + [x y w h f & args] 4.405 + (let [graphics (create-graphics)] 4.406 + (try 4.407 + (.translate graphics (double x) (double y)) 4.408 + (binding [*width* w 4.409 + *height* h 4.410 + *input-clip* (Rectangle2D$Double. 0.0 0.0 w h) 4.411 + *graphics* graphics] 4.412 + (apply f args)) 4.413 + (finally 4.414 + (.dispose graphics))))) 4.415 + 4.416 +(defn with-bounds* 4.417 + [x y w h f & args] 4.418 + (let [x (double x) 4.419 + y (double y) 4.420 + bounds (Rectangle2D$Double. x y w h)] 4.421 + (when-let [clip (clip bounds)] 4.422 + (let [^Graphics2D graphics (create-graphics)] 4.423 + (try 4.424 + (.clip graphics bounds) 4.425 + (.translate graphics x y) 4.426 + (binding [*width* w 4.427 + *height* h 4.428 + *clip* clip 4.429 + *input-clip* nil 4.430 + *graphics* graphics] 4.431 + (apply f args)) 4.432 + (finally 4.433 + (.dispose graphics))))))) 4.434 + 4.435 +(defmacro with-bounds 4.436 + [x y w h & body] 4.437 + `(with-bounds* ~x ~y ~w ~h (fn [] ~@body))) 4.438 + 4.439 +(defmacro with-theme 4.440 + [theme & body] 4.441 + `(binding [*theme* (merge *theme* ~theme)] 4.442 + ~@body)) 4.443 + 4.444 +(defmacro with-color [color-or-key & body] 4.445 + `(let [color# ~color-or-key 4.446 + color# (get *theme* color# color#) 4.447 + g# *graphics* 4.448 + old-color# (.getColor g#)] 4.449 + (try 4.450 + (.setColor g# color#) 4.451 + ~@body 4.452 + (finally 4.453 + (.setColor g# old-color#))))) 4.454 + 4.455 +(defmacro with-stroke [stroke & body] 4.456 + `(let [g# *graphics* 4.457 + old-stroke# (.getStroke g#)] 4.458 + (try 4.459 + (.setStroke g# ~stroke) 4.460 + ~@body 4.461 + (finally 4.462 + (.setStroke g# old-stroke#))))) 4.463 + 4.464 +(defmacro with-hints 4.465 + [hints & body] 4.466 + `(let [h# ~hints 4.467 + g# *graphics* 4.468 + old# (.getRenderingHints g#)] 4.469 + (try 4.470 + (.addRenderingHints g# h#) 4.471 + ~@body 4.472 + (finally 4.473 + (.setRenderingHints g# old#))))) 4.474 + 4.475 +(defn with-hints* [hints f & args] 4.476 + (with-hints hints 4.477 + (apply f args))) 4.478 + 4.479 +;; TODO: constructor for AffineTransform. 4.480 +;; (transform :scale 0.3 0.5 4.481 +;; :translate 5 10 4.482 +;; :rotate (/ Math/PI 2)) 4.483 + 4.484 +(defmacro with-transform [transform & body] 4.485 + `(let [g# *graphics* 4.486 + old-t# (.getTransform g#)] 4.487 + (try 4.488 + (.transform g# ~transform) 4.489 + ~@body 4.490 + (finally 4.491 + (.setTransform g# old-t#))))) 4.492 + 4.493 +(defmacro with-rotate [theta ax ay & body] 4.494 + `(let [transform# (AffineTransform/getRotateInstance ~theta ~ax ~ay)] 4.495 + (with-transform transform# ~@body))) 4.496 + 4.497 +(defmacro with-translate [x y & body] 4.498 + `(let [x# ~x 4.499 + y# ~y 4.500 + g# *graphics*] 4.501 + (try 4.502 + (.translate g# x# y#) 4.503 + ~@body 4.504 + (finally 4.505 + (.translate g# (- x#) (- y#)))))) 4.506 + 4.507 +(defn draw! 4.508 + "Draws the View." 4.509 + ([view] 4.510 + (let [graphics (create-graphics)] 4.511 + (try 4.512 + (binding [*graphics* graphics] 4.513 + (render! view)) 4.514 + (finally 4.515 + (.dispose graphics))))) 4.516 + ([x y view] 4.517 + (draw! x y true view)) 4.518 + ([x y clip? view] 4.519 + (let [geom (geometry view)] 4.520 + (draw! x y (width geom) (height geom) clip? view))) 4.521 + ([x y width height view] 4.522 + (draw! x y width height true view)) 4.523 + ([x y width height clip? view] 4.524 + (if clip? 4.525 + (with-bounds* x y width height render! view) 4.526 + (with-bounds-noclip* x y width height render! view)))) 4.527 + 4.528 +(defn draw-aligned! 4.529 + "Draws the View. Location is relative to the view's anchor point 4.530 + for the specified alignment." 4.531 + ([h-align v-align x y view] 4.532 + (let [geom (geometry view) 4.533 + w (width geom) 4.534 + h (height geom)] 4.535 + (draw! (- x (anchor-x geom h-align w)) 4.536 + (- y (anchor-y geom v-align h)) 4.537 + w h 4.538 + view))) 4.539 + ([h-align v-align x y w h view] 4.540 + (let [geom (geometry view)] 4.541 + (draw! (- x (anchor-x geom h-align w)) 4.542 + (- y (anchor-y geom v-align h)) 4.543 + w h 4.544 + view)))) 4.545 + 4.546 +;; 4.547 +;; Event handling. 4.548 +;; 4.549 + 4.550 +(defn with-handlers* 4.551 + [handle handlers f & args] 4.552 + (binding [*event-dispatcher* (create-dispatcher 4.553 + *event-dispatcher* handle handlers)] 4.554 + (apply f args))) 4.555 + 4.556 +(defmacro with-handlers 4.557 + "specs => (:event-id name & handler-body)* 4.558 + 4.559 + Execute form with the specified event handlers." 4.560 + [handle form & specs] 4.561 + `(with-handlers* ~handle 4.562 + ~(reduce (fn [m spec] 4.563 + (assoc m (first spec) 4.564 + `(fn [~(second spec)] 4.565 + ~@(nnext spec)))) {} 4.566 + specs) 4.567 + (fn [] ~form))) 4.568 + 4.569 +(defn picked? [handle] 4.570 + (handle-picked? *event-dispatcher* handle)) 4.571 + 4.572 +(defn hovered? [handle] 4.573 + (handle-hovered? *event-dispatcher* handle)) 4.574 + 4.575 +;; 4.576 +;; EventDispatcher implementation 4.577 +;; 4.578 + 4.579 +(def awt-events 4.580 + {java.awt.event.MouseEvent/MOUSE_CLICKED :mouse-clicked 4.581 + java.awt.event.MouseEvent/MOUSE_DRAGGED :mouse-dragged 4.582 + java.awt.event.MouseEvent/MOUSE_ENTERED :mouse-entered 4.583 + java.awt.event.MouseEvent/MOUSE_EXITED :mouse-exited 4.584 + java.awt.event.MouseEvent/MOUSE_MOVED :mouse-moved 4.585 + java.awt.event.MouseEvent/MOUSE_PRESSED :mouse-pressed 4.586 + java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released 4.587 + java.awt.event.MouseEvent/MOUSE_WHEEL :mouse-wheel}) 4.588 + 4.589 +(def dummy-event-dispatcher 4.590 + (reify EventDispatcher 4.591 + (listen! [_ _]) 4.592 + (create-dispatcher [this _ _] this) 4.593 + (commit [_]) 4.594 + (handle-picked? [_ _]) 4.595 + (handle-hovered? [_ _]))) 4.596 + 4.597 +;; Not using defrecord to avoid unacceptable overhead of recursive 4.598 +;; hash code calculation. 4.599 +(deftype DispatcherNode [handle handlers parent 4.600 + ^Shape clip ^AffineTransform transform 4.601 + bindings] 4.602 + EventDispatcher 4.603 + (listen! [this component] 4.604 + (listen! parent component)) 4.605 + (create-dispatcher [this handle handlers] 4.606 + (create-dispatcher parent handle handlers)) 4.607 + (commit [this] 4.608 + (commit parent)) 4.609 + (handle-picked? [this handle] 4.610 + (handle-picked? parent handle)) 4.611 + (handle-hovered? [this handle] 4.612 + (handle-hovered? parent handle))) 4.613 + 4.614 +(defn- make-node [handle handlers] 4.615 + (let [clip (if *input-clip* 4.616 + (clip *input-clip*) 4.617 + *clip*) 4.618 + bindings (-> (get-thread-bindings) 4.619 + (dissoc (var *graphics*)) 4.620 + (assoc (var *font-context*) (font-context)))] 4.621 + (DispatcherNode. handle handlers *event-dispatcher* clip 4.622 + (relative-transform) 4.623 + bindings))) 4.624 + 4.625 +(defn- add-node [tree ^DispatcherNode node] 4.626 + (assoc-cons tree (.parent node) node)) 4.627 + 4.628 +(defn- nodes [tree] 4.629 + (apply concat (vals tree))) 4.630 + 4.631 +(defn- under-cursor 4.632 + "Returns a vector of child nodes under cursor." 4.633 + [node tree ^long x ^long y] 4.634 + (some (fn [^DispatcherNode n] 4.635 + (if (and (.clip n) (.contains ^Shape (.clip n) x y)) 4.636 + (conj (vec (under-cursor n tree x y)) n))) 4.637 + (get tree node))) 4.638 + 4.639 +(defn- translate-mouse-event [^java.awt.event.MouseEvent event 4.640 + ^AffineTransform tr id] 4.641 + (let [[x y] (inverse-transform-point tr (.getX event) (.getY event)) 4.642 + rotation (if (instance? MouseWheelEvent event) 4.643 + (.getWheelRotation ^MouseWheelEvent event) 4.644 + nil)] 4.645 + (->MouseEvent id (.getWhen event) x y 4.646 + (.getXOnScreen event) (.getYOnScreen event) 4.647 + (.getButton event) 4.648 + rotation 4.649 + tr 4.650 + (.getComponent event)))) 4.651 + 4.652 +(defn- translate-and-dispatch 4.653 + ([nodes first-only ^java.awt.event.MouseEvent event] 4.654 + (translate-and-dispatch nodes first-only 4.655 + event (awt-events (.getID event)))) 4.656 + ([nodes first-only event id] 4.657 + (if-let [^DispatcherNode node (first nodes)] 4.658 + (let [handlers (.handlers node) 4.659 + handler (get handlers id)] 4.660 + (if handler 4.661 + (do 4.662 + (with-bindings* (.bindings node) 4.663 + handler 4.664 + (translate-mouse-event event (.transform node) id)) 4.665 + (when-not first-only 4.666 + (recur (rest nodes) false event id))) 4.667 + (when-not (and (= id :mouse-dragged) 4.668 + (or (contains? handlers :mouse-pressed) 4.669 + (contains? handlers :mouse-released))) 4.670 + (recur (rest nodes) first-only event id))))))) 4.671 + 4.672 +(defn- process-mouse-event 4.673 + [dispatcher ^java.awt.event.MouseEvent source-event] 4.674 + (let [{active-ref :active 4.675 + hovered-ref :hovered 4.676 + picked-ref :picked 4.677 + last-ref :last-motion 4.678 + tree-ref :tree} dispatcher 4.679 + pressed (and source-event 4.680 + (== (.getID source-event) 4.681 + java.awt.event.MouseEvent/MOUSE_PRESSED)) 4.682 + released (and source-event 4.683 + (== (.getID source-event) 4.684 + java.awt.event.MouseEvent/MOUSE_RELEASED)) 4.685 + ^java.awt.event.MouseEvent last-event @last-ref 4.686 + ^java.awt.event.MouseEvent event (or source-event last-event)] 4.687 + (when event 4.688 + (let [x (.getX event) 4.689 + y (.getY event) 4.690 + active @active-ref 4.691 + active (if (and active 4.692 + source-event 4.693 + (== (.getX last-event) x) 4.694 + (== (.getY last-event) y)) 4.695 + active 4.696 + (ref-set active-ref 4.697 + (under-cursor dispatcher @tree-ref x y))) 4.698 + acted (cond 4.699 + pressed (ref-set picked-ref active) 4.700 + released (let [picked @picked-ref] 4.701 + (ref-set picked-ref nil) 4.702 + picked) 4.703 + :else active) 4.704 + picked (seq @picked-ref) 4.705 + pred #(= (.handle ^DispatcherNode %1) (.handle ^DispatcherNode %2)) 4.706 + hovered (if picked 4.707 + (filter #(some (partial pred %) picked) active) 4.708 + active) 4.709 + remove-all (fn [c1 c2] 4.710 + (filter #(not (some (partial pred %) c2)) c1)) 4.711 + old-hovered @hovered-ref 4.712 + exited (remove-all old-hovered hovered) 4.713 + entered (remove-all hovered old-hovered) 4.714 + moved (or picked (remove-all hovered entered))] 4.715 + (ref-set hovered-ref hovered) 4.716 + (ref-set last-ref event) 4.717 + [exited entered moved acted event])))) 4.718 + 4.719 +(defn- dispatch-mouse-event 4.720 + [dispatcher source-event button?] 4.721 + (when-let [[exited 4.722 + entered 4.723 + moved 4.724 + acted 4.725 + event] (dosync (process-mouse-event dispatcher source-event))] 4.726 + (when button? 4.727 + (translate-and-dispatch acted true event)) 4.728 + (translate-and-dispatch exited false event :mouse-exited) 4.729 + (translate-and-dispatch entered false event :mouse-entered) 4.730 + (when-not button? 4.731 + (translate-and-dispatch moved true source-event)))) 4.732 + 4.733 +(defrecord RootEventDispatcher [tree-r ;; register 4.734 + tree ;; dispatch 4.735 + active ;; nodes under cursor 4.736 + hovered ;; mouse entered 4.737 + picked ;; mouse pressed 4.738 + last-motion] 4.739 + EventDispatcher 4.740 + (listen! [dispatcher component] 4.741 + (doto ^Component component 4.742 + (.addMouseListener dispatcher) 4.743 + (.addMouseWheelListener dispatcher) 4.744 + (.addMouseMotionListener dispatcher))) 4.745 + (create-dispatcher [dispatcher handle handlers] 4.746 + (let [node (make-node handle handlers)] 4.747 + (dosync (alter tree-r add-node node)) 4.748 + node)) 4.749 + (commit [dispatcher] 4.750 + (let [[exited 4.751 + entered 4.752 + _ _ 4.753 + event] (dosync 4.754 + ;; TODO: retain contexts that do 4.755 + ;; not intersect graphics 4.756 + ;; clipping area in tree. 4.757 + (ref-set tree @tree-r) 4.758 + (ref-set tree-r {}) 4.759 + (process-mouse-event dispatcher nil))] 4.760 + ;; Send mouse entered and exited events if necessary due to 4.761 + ;; updated layout. 4.762 + (translate-and-dispatch exited false event :mouse-exited) 4.763 + (translate-and-dispatch entered false event :mouse-entered))) 4.764 + (handle-picked? [dispatcher handle] 4.765 + (some #(= handle (.handle ^DispatcherNode %)) @picked)) 4.766 + (handle-hovered? [dispatcher handle] 4.767 + (some #(= handle (.handle ^DispatcherNode %)) @hovered)) 4.768 + MouseListener 4.769 + (mouseEntered [dispatcher event] 4.770 + (dispatch-mouse-event dispatcher event false)) 4.771 + (mouseExited [dispatcher event] 4.772 + (dispatch-mouse-event dispatcher event false)) 4.773 + (mouseClicked [dispatcher event] 4.774 + (dispatch-mouse-event dispatcher event true)) 4.775 + (mousePressed [dispatcher event] 4.776 + (dispatch-mouse-event dispatcher event true)) 4.777 + (mouseReleased [dispatcher event] 4.778 + (dispatch-mouse-event dispatcher event true)) 4.779 + MouseWheelListener 4.780 + (mouseWheelMoved [dispatcher event] 4.781 + (dispatch-mouse-event dispatcher event true)) 4.782 + MouseMotionListener 4.783 + (mouseDragged [dispatcher event] 4.784 + (dispatch-mouse-event dispatcher event false)) 4.785 + (mouseMoved [dispatcher event] 4.786 + (dispatch-mouse-event dispatcher event false))) 4.787 + 4.788 +(defn root-event-dispatcher [] 4.789 + (->RootEventDispatcher 4.790 + (ref {}) (ref {}) ;; trees 4.791 + (ref nil) (ref nil) (ref nil) ;; node states 4.792 + (ref nil))) ;; last event 4.793 + 4.794 +;; 4.795 +;; Scene 4.796 +;; 4.797 + 4.798 +(defrecord Scene [view 4.799 + event-dispatcher 4.800 + component 4.801 + rendering-hints 4.802 + next-state]) 4.803 + 4.804 +;; Define rendering hints that affect font metrics to make sure that 4.805 +;; Graphics and Scene FontRenderContexts are consistent. 4.806 +(def ^:private default-rendering-hints 4.807 + {RenderingHints/KEY_TEXT_ANTIALIASING 4.808 + RenderingHints/VALUE_TEXT_ANTIALIAS_DEFAULT, 4.809 + RenderingHints/KEY_FRACTIONALMETRICS 4.810 + RenderingHints/VALUE_FRACTIONALMETRICS_DEFAULT}) 4.811 + 4.812 +(defn make-scene 4.813 + ([view] 4.814 + (make-scene view dummy-event-dispatcher nil)) 4.815 + ([view event-dispatcher] 4.816 + (make-scene view event-dispatcher nil)) 4.817 + ([view event-dispatcher ^Component component] 4.818 + (make-scene view event-dispatcher component nil)) 4.819 + ([view event-dispatcher ^Component component hints] 4.820 + (let [hints (merge default-rendering-hints hints)] 4.821 + (->Scene view 4.822 + event-dispatcher 4.823 + component 4.824 + hints 4.825 + (atom nil))))) 4.826 + 4.827 +(defn- get-and-set! 4.828 + "Atomically sets the value of atom to newval and returns the old 4.829 + value." 4.830 + [atom newval] 4.831 + (loop [v @atom] 4.832 + (if (compare-and-set! atom v newval) 4.833 + v 4.834 + (recur @atom)))) 4.835 + 4.836 +(defn draw-scene! 4.837 + [scene ^Graphics2D graphics width height] 4.838 + (.addRenderingHints graphics (:rendering-hints scene)) 4.839 + (binding [*states* (get-and-set! (:next-state scene) nil) 4.840 + *scene* scene 4.841 + *graphics* graphics 4.842 + *initial-transform* (.getTransform graphics) 4.843 + *inverse-initial-transform* (-> graphics 4.844 + .getTransform 4.845 + .createInverse) 4.846 + *event-dispatcher* (:event-dispatcher scene) 4.847 + *width* width 4.848 + *height* height 4.849 + *clip* (Rectangle2D$Double. 0.0 0.0 width height) 4.850 + *input-clip* nil 4.851 + *time* (System/nanoTime)] 4.852 + (apply-theme) 4.853 + (let [tmp-watcher (Object.)] 4.854 + ;; Keep current context observers until the rendering is 4.855 + ;; complete. Some observers may be invoked twice if they 4.856 + ;; appear in both groups until tmp-watcher is removed. 4.857 + (replace-observers-watcher scene tmp-watcher) 4.858 + (try 4.859 + (render! (:view scene)) 4.860 + (finally 4.861 + (remove-observers tmp-watcher) 4.862 + (commit (:event-dispatcher scene))))))) 4.863 + 4.864 +(defn- scene-font-context [scene] 4.865 + (let [hints (:rendering-hints scene) 4.866 + ^Component c (:component scene) 4.867 + t (if c (->> c 4.868 + .getFont 4.869 + (.getFontMetrics c) 4.870 + .getFontRenderContext 4.871 + .getTransform))] 4.872 + (FontRenderContext. 4.873 + t 4.874 + (get hints RenderingHints/KEY_TEXT_ANTIALIASING) 4.875 + (get hints RenderingHints/KEY_FRACTIONALMETRICS)))) 4.876 + 4.877 +(defn scene-geometry [scene] 4.878 + (binding [*scene* scene 4.879 + *font-context* (scene-font-context scene)] 4.880 + (geometry (:view scene)))) 4.881 + 4.882 +(defn set-cursor! [^Cursor cursor] 4.883 + (when-let [^Component component (:component *scene*)] 4.884 + (EventQueue/invokeLater #(.setCursor component cursor))))
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/src/indyvon/demo.clj Mon Apr 14 20:01:00 2014 +0400 5.3 @@ -0,0 +1,223 @@ 5.4 +;; 5.5 +;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> 5.6 +;; 5.7 +;; This file is part of Indyvon. 5.8 +;; 5.9 +;; Indyvon is free software: you can redistribute it and/or modify it 5.10 +;; under the terms of the GNU Lesser General Public License version 3 5.11 +;; only, as published by the Free Software Foundation. 5.12 +;; 5.13 +;; Indyvon is distributed in the hope that it will be useful, but 5.14 +;; WITHOUT ANY WARRANTY; without even the implied warranty of 5.15 +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 5.16 +;; Lesser General Public License for more details. 5.17 +;; 5.18 +;; You should have received a copy of the GNU Lesser General Public 5.19 +;; License along with Indyvon. If not, see 5.20 +;; <http://www.gnu.org/licenses/>. 5.21 +;; 5.22 + 5.23 +(ns indyvon.demo 5.24 + "Indyvon demo and experiments." 5.25 + (:gen-class) 5.26 + (:use 5.27 + (indyvon core views viewport component)) 5.28 + (:import 5.29 + (java.awt Color) 5.30 + (javax.swing JFrame))) 5.31 + 5.32 +(defn draw-button! 5.33 + "Draws a button immediately (but uses callback for the action unlike 5.34 + IMGUI)." 5.35 + [id content callback & args] 5.36 + (with-handlers id 5.37 + (let [shadow-offset 2 5.38 + padding 4 5.39 + border-width 1 5.40 + offset (if (picked? id) (/ shadow-offset 2) 0) 5.41 + ^Color color (:alt-back-color *theme*) 5.42 + color (if (hovered? id) (.brighter color) color) 5.43 + width (- *width* shadow-offset) 5.44 + height (- *height* shadow-offset)] 5.45 + (with-color (:shadow-color *theme*) 5.46 + (.fillRect *graphics* shadow-offset shadow-offset width height)) 5.47 + (with-color color 5.48 + (.fillRect *graphics* offset offset width height)) 5.49 + (draw! offset offset width height 5.50 + (border border-width padding content))) 5.51 + ;; Event handlers 5.52 + (:mouse-entered _ (repaint)) 5.53 + (:mouse-exited _ (repaint)) 5.54 + (:mouse-pressed _ (repaint)) 5.55 + (:mouse-released _ (repaint)) 5.56 + (:mouse-clicked _ (apply callback args)))) 5.57 + 5.58 +(defn combine-colors 5.59 + "Returns color between color1 and color2. When c (0 <= c <= 1.0) is 5.60 + closer to 0 the returned сolor is closer to color1." 5.61 + [^Color color1 ^Color color2 c] 5.62 + (case c 5.63 + 0.0 color1 5.64 + 1.0 color2 5.65 + (let [rgb1 (.getRGBComponents color1 nil) 5.66 + rgb2 (.getRGBComponents color2 nil) 5.67 + rgb (float-array (map #(+ (* (- 1 c) %1) (* c %2)) rgb1 rgb2))] 5.68 + (Color. (aget rgb 0) (aget rgb 1) (aget rgb 2) (aget rgb 3))))) 5.69 + 5.70 +(defn animate 5.71 + "Changes the value of atom according to the specified range, speed, 5.72 + and current frame interval. Invokes repaint if change happens." 5.73 + [atom from to speed] 5.74 + (let [prev @atom 5.75 + state (cond 5.76 + (zero? speed) :stop 5.77 + (= prev from) (if (pos? speed) :start :stop) 5.78 + (= prev to) (if (neg? speed) :start :stop) 5.79 + :default :continue)] 5.80 + (if (= state :stop) 5.81 + prev 5.82 + (let [interval (if (= state :start) 1 *interval*) 5.83 + step (* speed interval 1E-9) 5.84 + val (swap! atom #(-> % (+ step) (max from) (min to)))] 5.85 + (repaint) 5.86 + val)))) 5.87 + 5.88 +(defn animated-button 5.89 + "Creates an animated button." 5.90 + [content callback & args] 5.91 + (let [padding 4 5.92 + border-width 1 5.93 + shadow-offset 2 5.94 + face (border padding border-width content) 5.95 + highlight (atom 0) 5.96 + animation-speed (atom 0)] 5.97 + (interval-view 5.98 + (reify 5.99 + View 5.100 + (render! [button] 5.101 + (with-handlers button 5.102 + (let [hovered (hovered? button) 5.103 + offset (if (picked? button) (/ shadow-offset 2) 0) 5.104 + color (combine-colors 5.105 + (:alt-back-color *theme*) Color/WHITE 5.106 + (animate highlight 0.0 1.0 @animation-speed)) 5.107 + width (- *width* shadow-offset) 5.108 + height (- *height* shadow-offset)] 5.109 + (with-color (:shadow-color *theme*) 5.110 + (.fillRect *graphics* 5.111 + shadow-offset shadow-offset 5.112 + width height)) 5.113 + (with-color color 5.114 + (.fillRect *graphics* offset offset width height)) 5.115 + (draw! offset offset width height 5.116 + (border border-width padding content))) 5.117 + ;; Event handlers 5.118 + (:mouse-entered _ 5.119 + (reset! animation-speed 4) 5.120 + (repaint)) 5.121 + (:mouse-exited _ 5.122 + (reset! animation-speed -2) 5.123 + (repaint)) 5.124 + (:mouse-pressed _ (repaint)) 5.125 + (:mouse-released _ (repaint)) 5.126 + (:mouse-clicked _ (apply callback args)))) 5.127 + (geometry [button] 5.128 + (let [face-geom (geometry face)] 5.129 + (->Size (+ (width face-geom) shadow-offset) 5.130 + (+ (height face-geom) shadow-offset)))))))) 5.131 + 5.132 +(def button1 (animated-button (label "Animated button 1") 5.133 + println "Animated button 1 clicked")) 5.134 + 5.135 +(def button2 (animated-button (label "Animated button 2") 5.136 + println "Animated button 2 clicked")) 5.137 + 5.138 +(def test-view1 5.139 + (reify 5.140 + View 5.141 + (render! [view] 5.142 + (with-handlers view 5.143 + (with-color (if (hovered? view) Color/ORANGE Color/RED) 5.144 + (.fillRect *graphics* 0 0 *width* *height*)) 5.145 + (:mouse-entered e 5.146 + (repaint) 5.147 + (println e)) 5.148 + (:mouse-exited e 5.149 + (repaint) 5.150 + (println e)) 5.151 + (:mouse-moved e 5.152 + (println e)))) 5.153 + (geometry [view] 5.154 + (->Size 30 20)))) 5.155 + 5.156 +(def test-view1b (border 2 3 test-view1)) 5.157 + 5.158 +(def test-view2 5.159 + (reify 5.160 + View 5.161 + (render! [view] 5.162 + (doto *graphics* 5.163 + (.setColor Color/YELLOW) 5.164 + (.fillRect 0 0 *width* *height*)) 5.165 + (with-rotate 0.5 0 0 5.166 + (draw! 30 25 test-view1b)) 5.167 + (draw! 55 5 test-view1)) 5.168 + (geometry [view] 5.169 + (->Size 70 65)))) 5.170 + 5.171 +(def test-view2m (miniature 30 30 test-view2)) 5.172 + 5.173 +(def test-view3 (border (label :right :bottom "Sample\ntext"))) 5.174 + 5.175 +(def root 5.176 + (reify 5.177 + View 5.178 + (render! [view] 5.179 + ;;(repaint) 5.180 + (doto *graphics* 5.181 + (.drawLine 0 0 *width* *height*) 5.182 + (.drawLine *width* 0 0 *height*) 5.183 + ;; Random color to see when repaint happens. 5.184 + (.setColor (rand-nth [Color/BLACK Color/BLUE Color/RED])) 5.185 + (.fillOval 5 5 20 20)) 5.186 + (draw! 30 20 test-view2) 5.187 + (draw! 120 50 test-view2m) 5.188 + (draw! 100 100 80 50 test-view3) 5.189 + (draw! 50 160 button1) 5.190 + (with-rotate (/ Math/PI 6) 250 200 5.191 + (draw! 210 140 button1)) 5.192 + (draw! 100 200 button2) 5.193 + (with-bounds 180 240 140 30 5.194 + (draw-button! :button 5.195 + (label :center :center "Immediate button") 5.196 + #(println "Button clicked!")))) 5.197 + (geometry [view] 5.198 + (->Size 400 300)))) 5.199 + 5.200 +;; Main viewport 5.201 +(def vp (viewport root)) 5.202 + 5.203 +;; Miniature (rendered asynchronously) 5.204 +(def vp-miniature (->> vp (viewport-miniature 100 75) border shadow)) 5.205 + 5.206 +;; Main scene 5.207 +(def scene 5.208 + (fps-view 5.209 + (decorate-view vp [_] 5.210 + (draw! vp) 5.211 + (draw-aligned! 5.212 + :left :bottom 5 (- *height* 5) 5.213 + (label (str "Drag mouse to pan," \newline 5.214 + "use mouse wheel to zoom."))) 5.215 + (draw! (- *width* 105) 5 vp-miniature)))) 5.216 + 5.217 +(defn show-frame [view] 5.218 + (doto (make-jframe "Test" view) 5.219 + (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE) 5.220 + (.setVisible true))) 5.221 + 5.222 +(defn -main [] 5.223 + (show-frame scene)) 5.224 + 5.225 +(comment 5.226 + (show-frame (viewport-miniature 200 150 vp)))
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 6.2 +++ b/src/indyvon/viewport.clj Mon Apr 14 20:01:00 2014 +0400 6.3 @@ -0,0 +1,238 @@ 6.4 +;; 6.5 +;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> 6.6 +;; 6.7 +;; This file is part of Indyvon. 6.8 +;; 6.9 +;; Indyvon is free software: you can redistribute it and/or modify it 6.10 +;; under the terms of the GNU Lesser General Public License version 3 6.11 +;; only, as published by the Free Software Foundation. 6.12 +;; 6.13 +;; Indyvon is distributed in the hope that it will be useful, but 6.14 +;; WITHOUT ANY WARRANTY; without even the implied warranty of 6.15 +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 6.16 +;; Lesser General Public License for more details. 6.17 +;; 6.18 +;; You should have received a copy of the GNU Lesser General Public 6.19 +;; License along with Indyvon. If not, see 6.20 +;; <http://www.gnu.org/licenses/>. 6.21 +;; 6.22 + 6.23 +(ns indyvon.viewport 6.24 + "Scrollable viewport and miniature." 6.25 + (:use 6.26 + (indyvon core async views)) 6.27 + (:import 6.28 + java.awt.Cursor 6.29 + java.awt.geom.AffineTransform)) 6.30 + 6.31 +;;(defn- translate [^AffineTransform transform ^double x ^double y] 6.32 +;; (doto ^AffineTransform (.clone transform) 6.33 +;; (.translate x y))) 6.34 + 6.35 +(defn- scale [^AffineTransform transform ^double sx ^double sy] 6.36 + (doto ^AffineTransform (.clone transform) 6.37 + (.scale sx sy))) 6.38 + 6.39 +(defn- pre-translate [^AffineTransform transform ^double x ^double y] 6.40 + (if (== 0.0 x y) 6.41 + transform 6.42 + (doto (AffineTransform/getTranslateInstance x y) 6.43 + (.concatenate transform)))) 6.44 + 6.45 +(def ^:dynamic *viewport-scaling-step* (double 3/4)) 6.46 +(def ^:dynamic *viewport-min-scale* 1E-6) 6.47 +(def ^:dynamic *viewport-max-scale* 1E6) 6.48 + 6.49 +(def ^:dynamic *viewport* nil) 6.50 +(def ^:dynamic ^AffineTransform *viewport-transform*) 6.51 + 6.52 +(declare scale-viewport!) 6.53 + 6.54 +(defrecord ViewportState [transform 6.55 + fix-x fix-y 6.56 + last-width last-height 6.57 + last-anchor-x last-anchor-y]) 6.58 + 6.59 +(defn- update-viewport [state content-geom h-align v-align] 6.60 + (let [w *width* 6.61 + h *height* 6.62 + cw (width content-geom) 6.63 + ch (height content-geom) 6.64 + ax (anchor-x content-geom h-align cw) 6.65 + ay (anchor-y content-geom v-align ch) 6.66 + ax1 (align-x h-align (:last-width state) w) 6.67 + ay1 (align-y v-align (:last-height state) h) 6.68 + ax2 (- (:last-anchor-x state) ax) 6.69 + ay2 (- (:last-anchor-y state) ay) 6.70 + transform (:transform state) 6.71 + transform (if (and (zero? ax1) (zero? ay1) 6.72 + (zero? ax2) (zero? ay2)) 6.73 + transform 6.74 + (doto 6.75 + (AffineTransform/getTranslateInstance ax1 ay1) 6.76 + (.concatenate transform) 6.77 + (.translate ax2 ay2)))] 6.78 + (assoc state 6.79 + :last-width w 6.80 + :last-height h 6.81 + :last-anchor-x ax 6.82 + :last-anchor-y ay 6.83 + :transform transform))) 6.84 + 6.85 +(defrecord Viewport [content h-align v-align state] 6.86 + View 6.87 + (render! [view] 6.88 + (repaint-on-update view) 6.89 + (with-handlers view 6.90 + (let [geom (geometry content) 6.91 + new-state (swap! state update-viewport geom h-align v-align) 6.92 + transform (:transform new-state)] 6.93 + ;; TODO: notify observers when size changes. 6.94 + (binding [*viewport* view 6.95 + *viewport-transform* transform] 6.96 + (with-transform transform 6.97 + (draw! 0 0 (width geom) (height geom) false content)))) 6.98 + (:mouse-pressed e 6.99 + (swap! state assoc 6.100 + :fix-x (:x-on-screen e) 6.101 + :fix-y (:y-on-screen e)) 6.102 + (set-cursor! (Cursor. Cursor/MOVE_CURSOR))) 6.103 + (:mouse-released e 6.104 + (set-cursor! (Cursor. Cursor/DEFAULT_CURSOR))) 6.105 + (:mouse-dragged e 6.106 + (swap! state 6.107 + (fn [s] 6.108 + (assoc s 6.109 + :transform (pre-translate 6.110 + (:transform s) 6.111 + (- (:x-on-screen e) (:fix-x s)) 6.112 + (- (:y-on-screen e) (:fix-y s))) 6.113 + :fix-x (:x-on-screen e) 6.114 + :fix-y (:y-on-screen e)))) 6.115 + (update view)) 6.116 + (:mouse-wheel e 6.117 + (scale-viewport! 6.118 + view 6.119 + (Math/pow *viewport-scaling-step* (:wheel-rotation e)) 6.120 + true (:x e) (:y e))))) 6.121 + (geometry [_] 6.122 + (geometry content))) 6.123 + 6.124 +(def ^:private viewport-initial-state 6.125 + (->ViewportState 6.126 + (AffineTransform.) ; transform 6.127 + 0 0 ; fix-x fix-y 6.128 + 0 0 ; last-width last-height 6.129 + 0 0)) 6.130 + 6.131 +(defn viewport 6.132 + "Creates scrollable viewport view." 6.133 + ([content] 6.134 + (viewport :left :top content)) 6.135 + ([h-align v-align content] 6.136 + (->Viewport content h-align v-align (atom viewport-initial-state)))) 6.137 + 6.138 +(defn- scale-viewport [state vp s relative? x y] 6.139 + (let [^AffineTransform tr (:transform state) 6.140 + sx (if relative? s (/ s (.getScaleX tr))) 6.141 + sy (if relative? s (/ s (.getScaleY tr))) 6.142 + x (or x (align-x (:h-align vp) (:last-width state))) 6.143 + y (or y (align-y (:v-align vp) (:last-height state))) 6.144 + x (- x (* x sx)) 6.145 + y (- y (* y sy)) 6.146 + scaled (doto (AffineTransform/getTranslateInstance x y) 6.147 + (.scale sx sy) 6.148 + (.concatenate tr)) 6.149 + sx (.getScaleX scaled) 6.150 + sy (.getScaleY scaled)] 6.151 + (if (<= *viewport-min-scale* 6.152 + (min sx sy) 6.153 + (max sx sy) 6.154 + *viewport-max-scale*) 6.155 + (assoc state 6.156 + :transform scaled) 6.157 + state))) 6.158 + 6.159 +(defn scale-viewport! 6.160 + ([viewport s] 6.161 + (scale-viewport! viewport s true)) 6.162 + ([viewport s relative?] 6.163 + (scale-viewport! viewport s relative? nil nil)) 6.164 + ([viewport s relative? x y] 6.165 + (swap! (:state viewport) scale-viewport viewport s relative? x y) 6.166 + (update viewport))) 6.167 + 6.168 +(defn reset-viewport! [viewport] 6.169 + (reset! (:state viewport) viewport-initial-state) 6.170 + (update viewport)) 6.171 + 6.172 +(defn ^AffineTransform viewport-transform [viewport] 6.173 + (:transform @(:state viewport))) 6.174 + 6.175 +(defn- scaling 6.176 + [width height max-width max-height] 6.177 + (min (/ max-width width) 6.178 + (/ max-height height))) 6.179 + 6.180 +(defn miniature 6.181 + "Creates a view that asynchronously renders the content view scaled to 6.182 + the specified size." 6.183 + [mw mh content] 6.184 + (async-view 6.185 + mw mh *miniature-thread-priority* 6.186 + (reify 6.187 + View 6.188 + (render! [this] 6.189 + (let [geom (geometry content) 6.190 + cw (width geom) 6.191 + ch (height geom) 6.192 + s (scaling cw ch mw mh)] 6.193 + (.scale *graphics* s s) 6.194 + (draw! (align-x :center cw (/ mw s)) 6.195 + (align-y :center ch (/ mh s)) 6.196 + cw ch 6.197 + content))) 6.198 + (geometry [_] 6.199 + (->Size mw mh))))) 6.200 + 6.201 +(defn viewport-miniature 6.202 + "Creates miniature view of the viewport's contents." 6.203 + [m-width m-height viewport] 6.204 + (let [miniature (miniature m-width m-height (:content viewport))] 6.205 + (decorate-view miniature [l] 6.206 + (repaint-on-update viewport) 6.207 + (let [geom (geometry (:content viewport)) 6.208 + s (scaling (width geom) (height geom) m-width m-height) 6.209 + vp-state @(:state viewport) 6.210 + {:keys [transform last-width last-height]} @(:state viewport) 6.211 + ox (align-x :center (width geom) (/ m-width s)) 6.212 + oy (align-y :center (height geom) (/ m-height s)) 6.213 + inverse (.createInverse ^AffineTransform transform) 6.214 + transform (doto (AffineTransform.) 6.215 + (.scale s s) 6.216 + (.translate ox oy) 6.217 + (.concatenate inverse)) 6.218 + move-vp (fn [state x y] 6.219 + (let [x (- (/ x s) ox) 6.220 + y (- (/ y s) oy) 6.221 + tr (:transform state) 6.222 + [x y] (transform-point tr x y) 6.223 + x (- x (/ (:last-width state) 2)) 6.224 + y (- y (/ (:last-height state) 2))] 6.225 + (assoc state 6.226 + :transform (pre-translate tr (- x) (- y))))) 6.227 + move-vp! (fn [x y] 6.228 + (swap! (:state viewport) move-vp x y) 6.229 + (update viewport))] 6.230 + (with-color :alt-back-color 6.231 + (.fillRect *graphics* 0 0 *width* *height*)) 6.232 + (with-transform transform 6.233 + (with-color :back-color 6.234 + (.fillRect *graphics* 0 0 last-width last-height))) 6.235 + (with-handlers l 6.236 + (draw! miniature) 6.237 + (:mouse-pressed e (move-vp! (:x e) (:y e))) 6.238 + (:mouse-dragged e (move-vp! (:x e) (:y e)))) 6.239 + (with-transform transform 6.240 + (with-color :border-color 6.241 + (.drawRect *graphics* 0 0 last-width last-height)))))))
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 7.2 +++ b/src/indyvon/views.clj Mon Apr 14 20:01:00 2014 +0400 7.3 @@ -0,0 +1,410 @@ 7.4 +;; 7.5 +;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> 7.6 +;; 7.7 +;; This file is part of Indyvon. 7.8 +;; 7.9 +;; Indyvon is free software: you can redistribute it and/or modify it 7.10 +;; under the terms of the GNU Lesser General Public License version 3 7.11 +;; only, as published by the Free Software Foundation. 7.12 +;; 7.13 +;; Indyvon is distributed in the hope that it will be useful, but 7.14 +;; WITHOUT ANY WARRANTY; without even the implied warranty of 7.15 +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 7.16 +;; Lesser General Public License for more details. 7.17 +;; 7.18 +;; You should have received a copy of the GNU Lesser General Public 7.19 +;; License along with Indyvon. If not, see 7.20 +;; <http://www.gnu.org/licenses/>. 7.21 +;; 7.22 + 7.23 +(ns indyvon.views 7.24 + "Implementations of the View protocol." 7.25 + (:use 7.26 + (indyvon core async)) 7.27 + (:import 7.28 + (java.awt Font Image Toolkit) 7.29 + java.awt.image.ImageObserver 7.30 + (java.awt.geom Area AffineTransform Rectangle2D$Double Point2D 7.31 + Point2D$Double) 7.32 + (java.awt.font FontRenderContext TextLayout) 7.33 + java.util.concurrent.TimeUnit 7.34 + (com.google.common.cache Cache CacheBuilder CacheLoader))) 7.35 + 7.36 +(defmacro decorate-view 7.37 + "Decorate the view replacing render! implementation." 7.38 + [view & render-tail] 7.39 + `(let [view# ~view] 7.40 + (reify 7.41 + View 7.42 + (render! ~@render-tail) 7.43 + (geometry [t#] (geometry view#))))) 7.44 + 7.45 +(defrecord Empty [] 7.46 + View 7.47 + (render! [_]) 7.48 + (geometry [_] 7.49 + (->Size 0 0))) 7.50 + 7.51 +(def empty-view (->Empty)) 7.52 + 7.53 +;; TODO: change argument order for decorators, content should be the 7.54 +;; last. 7.55 + 7.56 +(defn padding 7.57 + "Adds padding to the content view." 7.58 + ([distance content] 7.59 + (padding distance distance distance distance content)) 7.60 + ([top left bottom right content] 7.61 + (if (== 0 top left bottom right) 7.62 + content 7.63 + (reify 7.64 + View 7.65 + (render! [l] 7.66 + (draw! left top 7.67 + (- *width* left right) 7.68 + (- *height* top bottom) 7.69 + false 7.70 + content)) 7.71 + (geometry [l] 7.72 + (->NestedGeometry (geometry content) top left bottom right)))))) 7.73 + 7.74 +(defn border 7.75 + "Adds a border to the content view." 7.76 + ([content] 7.77 + (border 1 content)) 7.78 + ([thickness content] 7.79 + (border thickness 0 content)) 7.80 + ([thickness gap content] 7.81 + (let [view (padding (+ thickness gap) content) 7.82 + t (double thickness)] 7.83 + (decorate-view view [_] 7.84 + (render! view) 7.85 + (with-color :border-color 7.86 + (let [w (double *width*) 7.87 + h (double *height*) 7.88 + outer (Area. (Rectangle2D$Double. 0.0 0.0 w h)) 7.89 + inner (Area. (Rectangle2D$Double. t t (- w t t) (- h t t)))] 7.90 + (.subtract outer inner) 7.91 + (.fill *graphics* outer))))))) 7.92 + 7.93 +;; TODO: opacity and blur. 7.94 +(defn shadow 7.95 + "Adds a shadow to the content view." 7.96 + ([content] 7.97 + (shadow 1 1 content)) 7.98 + ([x-offset y-offset content] 7.99 + (let [x (if (neg? x-offset) (- x-offset) 0) 7.100 + y (if (neg? y-offset) (- y-offset) 0) 7.101 + abs-x (if (neg? x-offset) (- x-offset) x-offset) 7.102 + abs-y (if (neg? y-offset) (- y-offset) y-offset) 7.103 + shadow-x (+ x-offset x) 7.104 + shadow-y (+ y-offset y)] 7.105 + (reify 7.106 + View 7.107 + (render! [_] 7.108 + (let [w (- *width* abs-x) 7.109 + h (- *height* abs-y)] 7.110 + (with-color :shadow-color 7.111 + (.fillRect *graphics* shadow-x shadow-y w h)) 7.112 + (draw! x y w h content))) 7.113 + (geometry [_] 7.114 + (->NestedGeometry (geometry content) 7.115 + y x shadow-y shadow-x)))))) 7.116 + 7.117 +(defn panel 7.118 + "An opaque view using theme's alt-back-color or a custom background 7.119 + color." 7.120 + ([content] 7.121 + (panel :alt-back-color content)) 7.122 + ([back-color content] 7.123 + (decorate-view content [_] 7.124 + (with-color back-color 7.125 + (.fill *graphics* (Rectangle2D$Double. 0.0 0.0 *width* *height*))) 7.126 + (render! content)))) 7.127 + 7.128 +(defn hbox 7.129 + "Creates a view that draws the specified content views placing them 7.130 + horizontally." 7.131 + [& contents] 7.132 + (reify 7.133 + View 7.134 + (render! [_] 7.135 + (let [widths (map #(width (geometry %)) contents) 7.136 + xs (cons 0 (reductions + widths)) 7.137 + widths-sum (last xs) 7.138 + scale (/ *width* widths-sum)] 7.139 + (doseq [[c w x] (map vector contents widths xs)] 7.140 + (draw! x 0 w *height* c)))) 7.141 + (geometry [_] 7.142 + (reduce #(->Size (+ (width %1) (width %2)) 7.143 + (max (height %1) (height %2))) 7.144 + (->Size 0 0) 7.145 + (map geometry contents))))) 7.146 + 7.147 +(defn vbox 7.148 + "Creates a view that draws the specified content views placing them 7.149 + vertically." 7.150 + [& contents] 7.151 + (reify 7.152 + View 7.153 + (render! [_] 7.154 + (let [heights (map #(height (geometry %)) contents) 7.155 + ys (cons 0 (reductions + heights)) 7.156 + heights-sum (last ys) 7.157 + scale (/ *height* heights-sum)] 7.158 + (doseq [[c h y] (map vector contents heights ys)] 7.159 + (draw! 0 y *width* h c)))) 7.160 + (geometry [_] 7.161 + (reduce #(->Size (max (width %1) (width %2)) 7.162 + (+ (height %1) (height %2))) 7.163 + (->Size 0 0) 7.164 + (map geometry contents))))) 7.165 + 7.166 +(defn- re-split [^java.util.regex.Pattern re s] 7.167 + (seq (.split re s))) 7.168 + 7.169 +(def ^:private ^Cache text-layout-cache 7.170 + (-> (CacheBuilder/newBuilder) 7.171 + (.softValues) 7.172 + (.expireAfterAccess (long 1) TimeUnit/SECONDS) 7.173 + (.build))) 7.174 + 7.175 +(defn- get-text-layout 7.176 + [^String line ^Font font ^FontRenderContext font-context] 7.177 + (.get text-layout-cache [line font font-context] 7.178 + #(TextLayout. line font font-context))) 7.179 + 7.180 +(defn- layout-text 7.181 + [lines font font-context] 7.182 + (map #(get-text-layout % font font-context) lines)) 7.183 + 7.184 +(defn- text-width [layouts] 7.185 + (reduce (fn [w ^TextLayout tl] (max w (.getAdvance tl))) 0 layouts)) 7.186 + 7.187 +(defn- text-height [layouts] 7.188 + (reduce (fn [w ^TextLayout tl] 7.189 + (+ w (.getAscent tl) 7.190 + (.getDescent tl) 7.191 + (.getLeading tl))) 7.192 + 0 layouts)) 7.193 + 7.194 +(defn label 7.195 + "Creates a view to display multiline text." 7.196 + ([text] 7.197 + (label :left :top text)) 7.198 + ([h-align v-align text] 7.199 + (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" (str text))] 7.200 + (reify View 7.201 + (render! [view] 7.202 + (let [w *width* 7.203 + h *height* 7.204 + font (.getFont *graphics*) 7.205 + layouts (layout-text lines font (font-context)) 7.206 + y (align-y v-align (text-height layouts) h)] 7.207 + (loop [layouts layouts, y y] 7.208 + (when-first [^TextLayout layout layouts] 7.209 + (let [ascent (.getAscent layout) 7.210 + lh (+ ascent (.getDescent layout) (.getLeading layout)) 7.211 + x (align-x h-align (.getAdvance layout) w)] 7.212 + (.draw layout *graphics* x (+ y ascent)) 7.213 + (recur (next layouts) (+ y lh))))))) 7.214 + (geometry [view] 7.215 + (let [layouts (layout-text lines (:font *theme*) (font-context)) 7.216 + w (text-width layouts) 7.217 + h (text-height layouts)] 7.218 + (->Size w h))))))) 7.219 + 7.220 +(defn- ^ImageObserver image-observer [view] 7.221 + (reify 7.222 + ImageObserver 7.223 + (imageUpdate [this img infoflags x y width height] 7.224 + (update view) 7.225 + (zero? (bit-and infoflags 7.226 + (bit-or ImageObserver/ALLBITS 7.227 + ImageObserver/ABORT)))))) 7.228 + 7.229 +(defn image-view 7.230 + [image-or-uri] 7.231 + (let [^Image image (if (instance? Image image-or-uri) 7.232 + image-or-uri 7.233 + (.getImage (Toolkit/getDefaultToolkit) 7.234 + ^java.net.URL image-or-uri))] 7.235 + (.prepareImage (Toolkit/getDefaultToolkit) image -1 -1 nil) 7.236 + (reify 7.237 + View 7.238 + (render! [view] 7.239 + (repaint-on-update view) 7.240 + (.drawImage *graphics* image 0 0 (image-observer view))) 7.241 + (geometry [view] 7.242 + (let [observer (image-observer view) 7.243 + width (.getWidth image observer) 7.244 + height (.getHeight image observer) 7.245 + width (if (pos? width) width 1) 7.246 + height (if (pos? height) height 1)] 7.247 + (->Size width height)))))) 7.248 + 7.249 +(def ^:dynamic *miniature-thread-priority* 2) 7.250 + 7.251 +(defn ref-view 7.252 + [view-ref] 7.253 + (let [l (reify 7.254 + View 7.255 + (render! [l] 7.256 + (repaint-on-update l) 7.257 + (if-let [view @view-ref] 7.258 + (render! view))) 7.259 + (geometry [_] 7.260 + (if-let [view @view-ref] 7.261 + (geometry view) 7.262 + (->Size 1 1))))] 7.263 + (add-watch view-ref l (fn [_ _ _ _] (update l))) 7.264 + l)) 7.265 + 7.266 +;; 7.267 +;; View context decorators 7.268 +;; 7.269 + 7.270 +(defmacro handler [view & handlers] 7.271 + "Adds event handling to the view." 7.272 + `(let [view# ~view] 7.273 + (decorate-view view# [t#] 7.274 + (with-handlers t# 7.275 + (render! view#) 7.276 + ~@handlers)))) 7.277 + 7.278 +(defn themed [view & map-or-keyvals] 7.279 + (let [theme (if (== (count map-or-keyvals) 1) 7.280 + (first map-or-keyvals) 7.281 + (apply array-map map-or-keyvals))] 7.282 + (reify 7.283 + View 7.284 + (render! [_] 7.285 + (with-theme theme 7.286 + (render! view))) 7.287 + (geometry [_] 7.288 + (with-theme theme 7.289 + (geometry view)))))) 7.290 + 7.291 +(defn hinted [view & map-or-keyvals] 7.292 + (let [hints (if (== (count map-or-keyvals) 1) 7.293 + (first map-or-keyvals) 7.294 + (apply array-map map-or-keyvals))] 7.295 + (decorate-view view [_] 7.296 + (with-hints* hints render! view)))) 7.297 + 7.298 +;; 7.299 +;; Measuring time 7.300 +;; 7.301 + 7.302 +(def ^:dynamic *interval*) 7.303 + 7.304 +(defn interval-view 7.305 + "Creates a view that measures time between repaints ant draws it's 7.306 + content with the *interval* var bound to the measured time." 7.307 + [content] 7.308 + (let [last-time (atom nil)] 7.309 + (decorate-view content [_] 7.310 + (compare-and-set! last-time nil *time*) 7.311 + (let [lt @last-time] 7.312 + (binding [*interval* (if (compare-and-set! last-time lt *time*) 7.313 + (- *time* lt) 7.314 + 0)] ; already measured on parallel thread 7.315 + (render! content)))))) 7.316 + 7.317 +(defn- fps-label [text] 7.318 + (padding 5 (label :right :bottom text))) 7.319 + 7.320 +(defn fps-view 7.321 + "Creates a view that draws content and displays the 7.322 + frames per second rate." 7.323 + [content] 7.324 + (let [update-interval 2E8 ; 0.2 s in nanoseconds 7.325 + frames (ref 0) 7.326 + prev-time (ref nil) 7.327 + display (ref (fps-label "fps n/a"))] 7.328 + (decorate-view content [_] 7.329 + (draw! content) 7.330 + (draw! 7.331 + (dosync 7.332 + (alter frames inc) 7.333 + (if @prev-time 7.334 + (let [elapsed (- *time* @prev-time)] 7.335 + (when (> elapsed update-interval) 7.336 + (let [fps (/ @frames (/ elapsed 1E9))] 7.337 + (ref-set display (fps-label (format "%.1f" fps))) 7.338 + (ref-set frames 0) 7.339 + (ref-set prev-time *time*)))) 7.340 + (ref-set prev-time *time*)) 7.341 + @display))))) 7.342 + 7.343 +;; 7.344 +;; Overlays 7.345 +;; 7.346 + 7.347 +(def ^:private ^:dynamic *above*) 7.348 + 7.349 +(defn- overlay* [f & args] 7.350 + (var-set #'*above* (conj *above* (apply partial f args)))) 7.351 + 7.352 +(defn- ^Point2D to-graphics-coords 7.353 + [^AffineTransform transform x y] 7.354 + (let [p (Point2D$Double. x y)] 7.355 + (.transform transform p p) 7.356 + (.transform (.createInverse (.getTransform *graphics*)) p p) 7.357 + p)) 7.358 + 7.359 +(defn- draw-relative! 7.360 + ([transform x y view] 7.361 + (let [p (to-graphics-coords transform x y)] 7.362 + (draw! (.getX p) (.getY p) view))) 7.363 + ([transform x y w h view] 7.364 + (let [p (to-graphics-coords transform x y)] 7.365 + (draw! (.getX p) (.getY p) w h view)))) 7.366 + 7.367 +(defn- draw-relative-aligned! 7.368 + [transform h-align v-align x y view] 7.369 + (let [geom (geometry view) 7.370 + w (width geom) 7.371 + h (height geom) 7.372 + p (to-graphics-coords transform x y) 7.373 + x (- (.getX p) (anchor-x geom h-align w)) 7.374 + y (- (.getY p) (anchor-y geom v-align h))] 7.375 + (draw! x y w h view))) 7.376 + 7.377 +(defn overlay! 7.378 + "Draws view in the overlay context above the other views." 7.379 + ([view] 7.380 + (overlay* draw-relative! (.getTransform *graphics*) 0 0 view)) 7.381 + ([x y view] 7.382 + (overlay* draw-relative! (.getTransform *graphics*) x y view)) 7.383 + ([x y w h view] 7.384 + (overlay* draw-relative! (.getTransform *graphics*) x y w h view))) 7.385 + 7.386 +(defn overlay-aligned! [h-align v-align x y view] 7.387 + (overlay* draw-relative-aligned! 7.388 + (.getTransform *graphics*) 7.389 + h-align v-align x y 7.390 + view)) 7.391 + 7.392 +(defn with-overlays* [rec? f & args] 7.393 + (binding [*above* []] 7.394 + (apply f args) 7.395 + (if rec? 7.396 + (loop [above *above*] 7.397 + (when (seq above) 7.398 + (var-set #'*above* []) 7.399 + (doseq [f above] 7.400 + (f)) 7.401 + (recur *above*))) 7.402 + (doseq [of *above*] 7.403 + (of))))) 7.404 + 7.405 +(defmacro with-overlays [rec? & body] 7.406 + `(with-overlays* ~rec? (fn [] ~@body))) 7.407 + 7.408 +(defn layered 7.409 + ([content] 7.410 + (layered true content)) 7.411 + ([rec? content] 7.412 + (decorate-view content [_] 7.413 + (with-overlays* rec? render! content))))
8.1 --- a/src/net/kryshen/indyvon/async.clj Mon Apr 14 15:37:28 2014 +0400 8.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 8.3 @@ -1,178 +0,0 @@ 8.4 -;; 8.5 -;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> 8.6 -;; 8.7 -;; This file is part of Indyvon. 8.8 -;; 8.9 -;; Indyvon is free software: you can redistribute it and/or modify it 8.10 -;; under the terms of the GNU Lesser General Public License version 3 8.11 -;; only, as published by the Free Software Foundation. 8.12 -;; 8.13 -;; Indyvon is distributed in the hope that it will be useful, but 8.14 -;; WITHOUT ANY WARRANTY; without even the implied warranty of 8.15 -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 8.16 -;; Lesser General Public License for more details. 8.17 -;; 8.18 -;; You should have received a copy of the GNU Lesser General Public 8.19 -;; License along with Indyvon. If not, see 8.20 -;; <http://www.gnu.org/licenses/>. 8.21 -;; 8.22 - 8.23 -(ns net.kryshen.indyvon.async 8.24 - "Asynchronous drawing." 8.25 - (:use 8.26 - net.kryshen.indyvon.core) 8.27 - (:import 8.28 - java.awt.GraphicsConfiguration 8.29 - (java.awt Image AlphaComposite Transparency) 8.30 - (java.awt.image BufferedImage) 8.31 - (java.util.concurrent ThreadFactory ThreadPoolExecutor 8.32 - ThreadPoolExecutor$DiscardOldestPolicy 8.33 - ArrayBlockingQueue TimeUnit))) 8.34 - 8.35 -(defrecord Buffer [id image readers state]) 8.36 -;; Buffer states: 8.37 -;; :front, readers > 0 8.38 -;; being copied on screen 8.39 -;; :back 8.40 -;; being rendered to (offscreen) 8.41 -;; :fresh 8.42 -;; most recently updated 8.43 -;; :free 8.44 -;; not in use 8.45 - 8.46 -(defn- create-image [async-view ^GraphicsConfiguration device-conf] 8.47 - ;; TODO: support different image types. 8.48 - (.createCompatibleImage device-conf 8.49 - (:width async-view) 8.50 - (:height async-view) 8.51 - Transparency/TRANSLUCENT)) 8.52 - 8.53 -(defn- create-buffer [async-view device-conf] 8.54 - (Buffer. (Object.) (create-image async-view device-conf) 0 :free)) 8.55 - 8.56 -(defn- find-buffer 8.57 - "Find a buffer with the one of the specified states given 8.58 - in the order of preference." 8.59 - [buffers & states] 8.60 - (some identity 8.61 - (for [state states] 8.62 - (some #(if (= (:state %) state) % nil) buffers)))) 8.63 - 8.64 -(defn- replace-buffer [buffers buffer] 8.65 - (conj (remove #(= (:id %) (:id buffer)) buffers) 8.66 - buffer)) 8.67 - 8.68 -(defn- take-buffer [al type] 8.69 - (dosync 8.70 - (let [buffers @(:buffers al) 8.71 - b (case type 8.72 - :front (find-buffer buffers :front :fresh :free) 8.73 - :back (find-buffer buffers :free :fresh) 8.74 - (throw (IllegalArgumentException.))) 8.75 - readers (if (= type :front) 8.76 - (inc (:readers b)) 8.77 - (:readers b)) 8.78 - b (assoc b 8.79 - :state type 8.80 - :readers readers)] 8.81 - (alter (:buffers al) replace-buffer b) 8.82 - b))) 8.83 - 8.84 -(defn- release-buffer [al buffer] 8.85 - (dosync 8.86 - (let [state (:state buffer) 8.87 - readers (if (= state :front) 8.88 - (dec (:readers buffer)) 8.89 - (:readers buffer)) 8.90 - fresh (delay (find-buffer @(:buffers al) :fresh)) 8.91 - state (cond 8.92 - (pos? readers) :front 8.93 - (= :back state) :fresh 8.94 - @fresh :free 8.95 - :default :fresh)] 8.96 - (if (and (= state :fresh) @fresh) 8.97 - ;; Change state of the prefiously fresh buffer to :free. 8.98 - (alter (:buffers al) 8.99 - replace-buffer (assoc @fresh 8.100 - :state :free))) 8.101 - (alter (:buffers al) 8.102 - replace-buffer (assoc buffer 8.103 - :state state 8.104 - :readers readers))))) 8.105 - 8.106 -(defmacro with-buffer 8.107 - {:private true} 8.108 - [al type [name] & body] 8.109 - `(let [al# ~al 8.110 - ~name (take-buffer al# ~type)] 8.111 - (try 8.112 - ~@body 8.113 - (finally 8.114 - (release-buffer al# ~name))))) 8.115 - 8.116 -(defn- draw-offscreen [async-view] 8.117 - ;;(Thread/sleep 1000) 8.118 - (with-buffer async-view :back [b] 8.119 - (let [g (.createGraphics ^BufferedImage (:image b))] 8.120 - ;; Clear the buffer. 8.121 - (.setComposite g AlphaComposite/Clear) 8.122 - (.fillRect g 0 0 (:width async-view) (:height async-view)) 8.123 - (.setComposite g AlphaComposite/Src) 8.124 - (draw-scene! (:scene async-view) 8.125 - g 8.126 - (:width async-view) 8.127 - (:height async-view))) 8.128 - (update async-view))) 8.129 - 8.130 -(defn- draw-offscreen-async [async-view] 8.131 - (.execute ^ThreadPoolExecutor (:executor async-view) 8.132 - #(draw-offscreen async-view))) 8.133 - 8.134 -(defrecord AsyncView [scene width height executor buffers] 8.135 - View 8.136 - (render! [view] 8.137 - (repaint-on-update view) 8.138 - (add-context-observer scene (fn [_ _] (draw-offscreen-async view))) 8.139 - (when-not @buffers 8.140 - ;; TODO: dynamic size, recreate buffers when size increases. 8.141 - (let [device-conf (.getDeviceConfiguration *graphics*) 8.142 - new-buffers (repeatedly 2 8.143 - (partial create-buffer view device-conf))] 8.144 - (dosync 8.145 - (ref-set buffers new-buffers))) 8.146 - (draw-offscreen-async view)) 8.147 - (with-buffer view :front [b] 8.148 - (.drawImage *graphics* ^Image (:image b) 0 0 nil))) 8.149 - (geometry [view] 8.150 - (->Size width height))) 8.151 - 8.152 -(defn- create-thread-factory [priority] 8.153 - (reify 8.154 - ThreadFactory 8.155 - (newThread [_ runnable] 8.156 - (let [thread (Thread. runnable)] 8.157 - (when priority 8.158 - (.setPriority thread priority)) 8.159 - (.setDaemon thread true) 8.160 - thread)))) 8.161 - 8.162 -(defn- create-executor [priority] 8.163 - (doto (ThreadPoolExecutor. 8.164 - (int 1) (int 1) 8.165 - (long 0) TimeUnit/SECONDS 8.166 - (ArrayBlockingQueue. 1) 8.167 - (ThreadPoolExecutor$DiscardOldestPolicy.)) 8.168 - (.setThreadFactory (create-thread-factory priority)))) 8.169 - 8.170 -(defn async-view 8.171 - "Creates a View that draws the content asynchronously using an 8.172 - offscreen buffer." 8.173 - ([width height content] 8.174 - (async-view width height nil content)) 8.175 - ([width height priority content] 8.176 - ;; TODO: use operational event dispatcher. 8.177 - (->AsyncView (make-scene content) 8.178 - width 8.179 - height 8.180 - (create-executor priority) 8.181 - (ref nil))))
9.1 --- a/src/net/kryshen/indyvon/component.clj Mon Apr 14 15:37:28 2014 +0400 9.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 9.3 @@ -1,69 +0,0 @@ 9.4 -;; 9.5 -;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> 9.6 -;; 9.7 -;; This file is part of Indyvon. 9.8 -;; 9.9 -;; Indyvon is free software: you can redistribute it and/or modify it 9.10 -;; under the terms of the GNU Lesser General Public License version 3 9.11 -;; only, as published by the Free Software Foundation. 9.12 -;; 9.13 -;; Indyvon is distributed in the hope that it will be useful, but 9.14 -;; WITHOUT ANY WARRANTY; without even the implied warranty of 9.15 -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9.16 -;; Lesser General Public License for more details. 9.17 -;; 9.18 -;; You should have received a copy of the GNU Lesser General Public 9.19 -;; License along with Indyvon. If not, see 9.20 -;; <http://www.gnu.org/licenses/>. 9.21 -;; 9.22 - 9.23 -(ns net.kryshen.indyvon.component 9.24 - "Integrating Indyvon into AWT and Swing components." 9.25 - (:use 9.26 - net.kryshen.indyvon.core) 9.27 - (:import 9.28 - (java.awt Graphics Component Dimension Color) 9.29 - (java.awt.geom Rectangle2D$Double) 9.30 - (javax.swing JFrame JPanel JOptionPane))) 9.31 - 9.32 -(defn- paint-component [^Component c ^Graphics g scene] 9.33 - (let [w (.getWidth c) 9.34 - h (.getHeight c)] 9.35 - (.setColor g (:back-color *theme*)) 9.36 - (.fillRect g 0 0 w h) 9.37 - (draw-scene! scene g w h))) 9.38 - 9.39 -(defn- preferred-size [^Component c scene] 9.40 - (let [geom (scene-geometry scene)] 9.41 - (Dimension. (width geom) (height geom)))) 9.42 - 9.43 -(defn ^JPanel make-jpanel 9.44 - ([view] 9.45 - (make-jpanel view (root-event-dispatcher))) 9.46 - ([view event-dispatcher] 9.47 - (let [panel (proxy [JPanel] []) 9.48 - scene (make-scene 9.49 - view event-dispatcher panel 9.50 - (.getDesktopProperty (java.awt.Toolkit/getDefaultToolkit) 9.51 - "awt.font.desktophints"))] 9.52 - (update-proxy 9.53 - panel 9.54 - {"paintComponent" #(paint-component %1 %2 scene) 9.55 - "getPreferredSize" #(preferred-size % scene)}) 9.56 - (.setBackground panel (:back-color *theme*)) 9.57 - (add-observer panel scene (fn [w _] 9.58 - ;; Use the first observer argument 9.59 - ;; instead of closing over panel to 9.60 - ;; allow the panel and associated 9.61 - ;; observer to be gc'd. 9.62 - (.repaint ^Component w))) 9.63 - (listen! event-dispatcher panel) 9.64 - panel))) 9.65 - 9.66 -(defn ^JFrame make-jframe [^String title view] 9.67 - (doto (JFrame. title) 9.68 - (.. (getContentPane) (add (make-jpanel view))) 9.69 - (.pack))) 9.70 - 9.71 -(defn message [m] 9.72 - (JOptionPane/showMessageDialog (:component *scene*) m))
10.1 --- a/src/net/kryshen/indyvon/core.clj Mon Apr 14 15:37:28 2014 +0400 10.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 10.3 @@ -1,881 +0,0 @@ 10.4 -;; 10.5 -;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> 10.6 -;; 10.7 -;; This file is part of Indyvon. 10.8 -;; 10.9 -;; Indyvon is free software: you can redistribute it and/or modify it 10.10 -;; under the terms of the GNU Lesser General Public License version 3 10.11 -;; only, as published by the Free Software Foundation. 10.12 -;; 10.13 -;; Indyvon is distributed in the hope that it will be useful, but 10.14 -;; WITHOUT ANY WARRANTY; without even the implied warranty of 10.15 -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10.16 -;; Lesser General Public License for more details. 10.17 -;; 10.18 -;; You should have received a copy of the GNU Lesser General Public 10.19 -;; License along with Indyvon. If not, see 10.20 -;; <http://www.gnu.org/licenses/>. 10.21 -;; 10.22 - 10.23 -(ns net.kryshen.indyvon.core 10.24 - (:import 10.25 - (java.awt Graphics2D RenderingHints Component Color Font Shape 10.26 - Rectangle Cursor EventQueue) 10.27 - (java.awt.geom AffineTransform Point2D$Double Rectangle2D$Double Area) 10.28 - (java.awt.event MouseListener MouseMotionListener 10.29 - MouseWheelListener MouseWheelEvent) 10.30 - (java.awt.font FontRenderContext) 10.31 - java.util.concurrent.ConcurrentMap 10.32 - com.google.common.collect.MapMaker)) 10.33 - 10.34 -;; 10.35 -;; View context 10.36 -;; 10.37 - 10.38 -(def ^:dynamic ^Graphics2D *graphics*) 10.39 - 10.40 -(def ^:dynamic ^FontRenderContext *font-context* 10.41 - "FontRenderContext to use when Graphics2D is not available." 10.42 - (FontRenderContext. 10.43 - nil 10.44 - RenderingHints/VALUE_TEXT_ANTIALIAS_DEFAULT 10.45 - RenderingHints/VALUE_FRACTIONALMETRICS_DEFAULT)) 10.46 - 10.47 -(def ^:dynamic *width* 10.48 - "Width of the rendering area.") 10.49 - 10.50 -(def ^:dynamic *height* 10.51 - "Height of the rendering area.") 10.52 - 10.53 -(def ^:dynamic ^Shape *clip*) 10.54 - 10.55 -(def ^:dynamic ^Shape *input-clip* 10.56 - "Clipping area used for dispatching pointer events (intersected with 10.57 - *clip*). If nil, *clip* will be used.") 10.58 - 10.59 -(def ^:dynamic *time* 10.60 - "Timestamp of the current frame (in nanoseconds).") 10.61 - 10.62 -(def ^:dynamic *scene* 10.63 - "Encloses state that should be retained between repaints.") 10.64 - 10.65 -(def ^:dynamic *states* 10.66 - "Transient scene states, a map.") 10.67 - 10.68 -(def ^:dynamic *event-dispatcher*) 10.69 - 10.70 -(def ^:dynamic ^AffineTransform *initial-transform* 10.71 - "Initial transform associated with the graphics context.") 10.72 - 10.73 -(def ^:dynamic ^AffineTransform *inverse-initial-transform* 10.74 - "Inversion of the initial transform associated with the graphics 10.75 - context.") 10.76 - 10.77 -(defrecord Theme [fore-color back-color alt-back-color border-color 10.78 - shadow-color font]) 10.79 - 10.80 -;; REMIND: use system colors, see java.awt.SystemColor. 10.81 -(defn default-theme [] 10.82 - (Theme. Color/BLACK 10.83 - Color/WHITE 10.84 - (Color. 0xDD 0xDD 0xDD) 10.85 - (Color. 0 0 0xCC) 10.86 - (Color. 0x44 0x44 0x44) 10.87 - (Font. "Sans" Font/PLAIN 12))) 10.88 - 10.89 -(def ^:dynamic *theme* (default-theme)) 10.90 - 10.91 -;; 10.92 -;; Core protocols and types 10.93 -;; 10.94 - 10.95 -(defprotocol View 10.96 - "Basic UI element." 10.97 - (render! [view] 10.98 - "Draws the view in the current *graphics* context.") 10.99 - (geometry [view] 10.100 - "Returns the preferred Geometry for the view.")) 10.101 - 10.102 -(defprotocol Geometry 10.103 - "Describes geometry of a View. Prefer using the available 10.104 - implementations (Size, FixedGeometry and NestedGeometry) over 10.105 - extending this protocol directly as it is likely to be changed in 10.106 - the future versions." 10.107 - (width [geom] [geom height]) 10.108 - (height [geom] [geom width]) 10.109 - (anchor-x [geom h-align width] 10.110 - "Returns the x coordinate of the anchor point for the specified 10.111 - horizontal alignment and width, h-align could be :left, :center 10.112 - or :right.") 10.113 - (anchor-y [geom v-align height] 10.114 - "Returns the y coordinate of the anchor point for the specified 10.115 - vertical alignment and height, v-align could be :top, :center 10.116 - or :bottom.")) 10.117 - 10.118 -(defn- emit-align-xy [align size first center last] 10.119 - `(case ~align 10.120 - ~first 0 10.121 - ~center (/ ~size 2) 10.122 - ~last ~size)) 10.123 - 10.124 -;; Define as macro to avoid unnecessary calculation of width or height. 10.125 -(defmacro align-x 10.126 - ([align inner outer] 10.127 - `(align-x ~align (- ~outer ~inner))) 10.128 - ([align width] 10.129 - (emit-align-xy align width :left :center :right))) 10.130 - 10.131 -(defmacro align-y 10.132 - ([align inner outer] 10.133 - `(align-y ~align (- ~outer ~inner))) 10.134 - ([align height] 10.135 - (emit-align-xy align height :top :center :bottom))) 10.136 - 10.137 -(defrecord Size [width height] 10.138 - Geometry 10.139 - (width [_] width) 10.140 - (width [_ _] width) 10.141 - (height [_] height) 10.142 - (height [_ _] height) 10.143 - (anchor-x [_ h-align width] 10.144 - (align-x h-align width)) 10.145 - (anchor-y [_ v-align height] 10.146 - (align-y v-align height))) 10.147 - 10.148 -(defrecord FixedGeometry [ax ay width height] 10.149 - Geometry 10.150 - (width [_] width) 10.151 - (width [_ _] width) 10.152 - (height [_] height) 10.153 - (height [_ _] height) 10.154 - (anchor-x [_ _ _] ax) 10.155 - (anchor-y [_ _ _] ay)) 10.156 - 10.157 -(defrecord NestedGeometry [geometry top left bottom right] 10.158 - Geometry 10.159 - (width [_] 10.160 - (+ left right (width geometry))) 10.161 - (width [_ h] 10.162 - (+ left right (width geometry (- h top bottom)))) 10.163 - (height [_] 10.164 - (+ top bottom (height geometry))) 10.165 - (height [_ w] 10.166 - (+ top bottom (height geometry (- w left right)))) 10.167 - (anchor-x [_ h-align w] 10.168 - (+ left (anchor-x geometry h-align (- w left right)))) 10.169 - (anchor-y [_ v-align h] 10.170 - (+ top (anchor-y geometry v-align (- h top bottom))))) 10.171 - 10.172 -(defrecord ScaledGeometry [geometry sx sy] 10.173 - Geometry 10.174 - (width [_] 10.175 - (* sx (width geometry))) 10.176 - (width [_ h] 10.177 - (* sx (width geometry (/ h sy)))) 10.178 - (height [_] 10.179 - (* sy (height geometry))) 10.180 - (height [_ w] 10.181 - (* sy (height geometry (/ w sx)))) 10.182 - (anchor-x [_ h-align w] 10.183 - (* sx (anchor-x geometry h-align (/ w sx)))) 10.184 - (anchor-y [_ v-align h] 10.185 - (* sy (anchor-y geometry v-align (/ h sy))))) 10.186 - 10.187 -;; (defn ^:private to-integer 10.188 -;; ^long [align x] 10.189 -;; (if (integer? x) 10.190 -;; x 10.191 -;; (let [x (double x)] 10.192 -;; (Math/round 10.193 -;; (case align 10.194 -;; (:top :left) (Math/floor x) 10.195 -;; :center x 10.196 -;; (:bottom :right) (Math/ceil x)))))) 10.197 - 10.198 -;; (defrecord IntegerGeometry [geometry] 10.199 -;; Geometry 10.200 -;; (width [_] 10.201 -;; (to-integer :right (width geometry))) 10.202 -;; (width [_ h] 10.203 -;; (to-integer :right (width geometry h))) 10.204 -;; (height [_] 10.205 -;; (to-integer :bottom (height geometry))) 10.206 -;; (height [_ w] 10.207 -;; (to-integer :bottom (height geometry w))) 10.208 -;; (anchor-x [_ h-align w] 10.209 -;; (to-integer h-align (anchor-x geometry h-align w))) 10.210 -;; (anchor-y [_ v-align h] 10.211 -;; (to-integer v-align (anchor-y geometry v-align h)))) 10.212 - 10.213 -;; TODO: modifiers 10.214 -(defrecord MouseEvent [id when x y x-on-screen y-on-screen button 10.215 - wheel-rotation transform component]) 10.216 - 10.217 -;; TODO: KeyEvent 10.218 - 10.219 -(defprotocol EventDispatcher 10.220 - (listen! [this component] 10.221 - "Listen for events on the specified AWT Component.") 10.222 - (create-dispatcher [this handle handlers] 10.223 - "Returns new event dispatcher associated with the specified event 10.224 - handlers (an event-id -> handler-fn map). Handle is used to 10.225 - match the contexts between commits.") 10.226 - (commit [this] 10.227 - "Apply the registered handlers for event processing.") 10.228 - (handle-picked? [this handle] 10.229 - "Returns true if the specified handle received the :mouse-pressed 10.230 - event and have not yet received :moused-released.") 10.231 - (handle-hovered? [this handle] 10.232 - "Returns true if the specified handle received the :mouse-entered 10.233 - event and have not yet received :mouse-exited.")) 10.234 - 10.235 -(defn- assoc-cons [m key val] 10.236 - (->> (get m key) (cons val) (assoc m key))) 10.237 - 10.238 -;; 10.239 -;; Observers 10.240 -;; The mechanism used by views to request repaints 10.241 -;; 10.242 - 10.243 -(def ^ConcurrentMap observers 10.244 - (-> (MapMaker.) (.weakKeys) (.makeMap))) 10.245 - 10.246 -(defn- cm-replace! 10.247 - "Wrap ConcurrentMap replace method to treat nil value as absent 10.248 - mapping. Use with maps that does not support nil values." 10.249 - [^ConcurrentMap cmap key old new] 10.250 - (if (nil? old) 10.251 - (nil? (.putIfAbsent cmap key new)) 10.252 - (.replace cmap key old new))) 10.253 - 10.254 -(defn- cm-swap! 10.255 - "Atomically swaps the value associated with key in ConcurrentMap 10.256 - to be (apply f current-value args). Returns the new value." 10.257 - [^ConcurrentMap cmap key f & args] 10.258 - (loop [] 10.259 - (let [old (.get cmap key) 10.260 - new (apply f old args)] 10.261 - (if (cm-replace! cmap key old new) 10.262 - new 10.263 - (recur))))) 10.264 - 10.265 -(defn add-observer 10.266 - "Add observer fn for the target. Watcher identifies the group of 10.267 - observers and could be used to remove the group. Watcher is weakly 10.268 - referenced, all associated observers will be removed when the 10.269 - wathcer is removed by gc. The observer fn will be called with 10.270 - watcher and target arguments and any additional arguments specified 10.271 - in update call." 10.272 - [watcher target f] 10.273 - (cm-swap! observers watcher assoc-cons target f) 10.274 - nil) 10.275 - 10.276 -(defn remove-observers 10.277 - "Remove group of observers associated with the specified watcher." 10.278 - [watcher] 10.279 - (.remove observers watcher) 10.280 - nil) 10.281 - 10.282 -(defn- replace-observers-watcher 10.283 - [old-watcher new-watcher] 10.284 - (if-let [old (.remove observers old-watcher)] 10.285 - (.put observers new-watcher old)) 10.286 - nil) 10.287 - 10.288 -(defn update 10.289 - "Notify observers." 10.290 - [target & args] 10.291 - (doseq [entry observers 10.292 - f (get (val entry) target)] 10.293 - (apply f (key entry) target args))) 10.294 - 10.295 -(defn add-context-observer 10.296 - "Observer registered with this function will be automatically 10.297 - removed after the next repaint is complete." 10.298 - [target f] 10.299 - (add-observer *scene* target f)) 10.300 - 10.301 -(defn repaint-on-update 10.302 - "Trigger repaint of the current scene when the target updates." 10.303 - [target] 10.304 - (let [scene *scene*] 10.305 - (if-not (identical? scene target) 10.306 - (add-observer scene target (fn [w _] (update w)))))) 10.307 - 10.308 -(defn repaint 10.309 - "Requests repaint of the current scene. If handle and state are 10.310 - specified, the handle will be associated with the state in the 10.311 - *states* map for the next paint iteration." 10.312 - ([] 10.313 - (update *scene*)) 10.314 - ([handle state] 10.315 - (let [scene *scene*] 10.316 - (swap! (:next-state scene) assoc handle state) 10.317 - (update scene)))) 10.318 - 10.319 -;; 10.320 -;; Rendering 10.321 -;; 10.322 - 10.323 -(defn ^FontRenderContext font-context 10.324 - "Returns FontRenderContext for the current view context." 10.325 - [] 10.326 - (if (bound? (var *graphics*)) 10.327 - (.getFontRenderContext *graphics*) 10.328 - *font-context*)) 10.329 - 10.330 -(defn ^AffineTransform relative-transform 10.331 - "Returns AffineTransform: view context -> AWT component." 10.332 - [] 10.333 - (let [tr (.getTransform *graphics*)] 10.334 - (.preConcatenate tr *inverse-initial-transform*) 10.335 - tr)) 10.336 - 10.337 -(defn ^AffineTransform inverse-relative-transform 10.338 - "Returns AffineTransform: AWT component -> view context." 10.339 - [] 10.340 - (let [tr (.getTransform *graphics*)] 10.341 - (.invert tr) ; absolute -> view 10.342 - (.concatenate tr *initial-transform*) ; component -> absolute 10.343 - tr)) 10.344 - 10.345 -(defn transform-point [^AffineTransform tr ^double x ^double y] 10.346 - (let [p (Point2D$Double. x y)] 10.347 - (.transform tr p p) 10.348 - [(.x p) (.y p)])) 10.349 - 10.350 -(defn inverse-transform-point [^AffineTransform tr ^double x ^double y] 10.351 - (let [p (Point2D$Double. x y)] 10.352 - (.inverseTransform tr p p) 10.353 - [(.x p) (.y p)])) 10.354 - 10.355 -;; (defn- clip 10.356 -;; "Intersect clipping area with the specified shape or bounds. 10.357 -;; Returns new clip (Shape or nil if empty)." 10.358 -;; ([x y w h] 10.359 -;; (clip (Rectangle2D$Double. x y w h))) 10.360 -;; ([shape] 10.361 -;; (let [a1 (Area. shape) 10.362 -;; a2 (if (instance? Area *clip*) *clip* (Area. *clip*))] 10.363 -;; (.transform a1 (relative-transform)) 10.364 -;; (.intersect a1 a2) 10.365 -;; (if (.isEmpty a1) 10.366 -;; nil 10.367 -;; a1)))) 10.368 - 10.369 -;; Use faster clipping calculation provided by Graphics2D. 10.370 -(defn- clip 10.371 - "Intersect clipping area with the specified Shape in current 10.372 - transform coordinates. Returns new clip in the AWT component 10.373 - coordinates (Shape or nil if empty)." 10.374 - [^Shape shape] 10.375 - (let [^Graphics2D clip-g (.create *graphics*)] 10.376 - (try 10.377 - (doto clip-g 10.378 - (.setClip shape) 10.379 - (.setTransform *initial-transform*) 10.380 - (.clip *clip*)) 10.381 - (if (.isEmpty (.getClipBounds clip-g)) 10.382 - nil 10.383 - (.getClip clip-g)) 10.384 - (finally 10.385 - (.dispose clip-g))))) 10.386 - 10.387 -(defn- ^Graphics2D apply-theme 10.388 - "Set graphics' color and font to match theme. 10.389 - Modifies and returns the first argument." 10.390 - ([] 10.391 - (apply-theme *graphics* *theme*)) 10.392 - ([^Graphics2D graphics theme] 10.393 - (doto graphics 10.394 - (.setColor (:fore-color theme)) 10.395 - (.setFont (:font theme))))) 10.396 - 10.397 -(defn- ^Graphics2D create-graphics 10.398 - ([] 10.399 - (apply-theme (.create *graphics*) *theme*)) 10.400 - ([^long x ^long y ^long w ^long h] 10.401 - (apply-theme (.create *graphics* x y w h) *theme*))) 10.402 - 10.403 -(defn- with-bounds-noclip* 10.404 - [x y w h f & args] 10.405 - (let [graphics (create-graphics)] 10.406 - (try 10.407 - (.translate graphics (double x) (double y)) 10.408 - (binding [*width* w 10.409 - *height* h 10.410 - *input-clip* (Rectangle2D$Double. 0.0 0.0 w h) 10.411 - *graphics* graphics] 10.412 - (apply f args)) 10.413 - (finally 10.414 - (.dispose graphics))))) 10.415 - 10.416 -(defn with-bounds* 10.417 - [x y w h f & args] 10.418 - (let [x (double x) 10.419 - y (double y) 10.420 - bounds (Rectangle2D$Double. x y w h)] 10.421 - (when-let [clip (clip bounds)] 10.422 - (let [^Graphics2D graphics (create-graphics)] 10.423 - (try 10.424 - (.clip graphics bounds) 10.425 - (.translate graphics x y) 10.426 - (binding [*width* w 10.427 - *height* h 10.428 - *clip* clip 10.429 - *input-clip* nil 10.430 - *graphics* graphics] 10.431 - (apply f args)) 10.432 - (finally 10.433 - (.dispose graphics))))))) 10.434 - 10.435 -(defmacro with-bounds 10.436 - [x y w h & body] 10.437 - `(with-bounds* ~x ~y ~w ~h (fn [] ~@body))) 10.438 - 10.439 -(defmacro with-theme 10.440 - [theme & body] 10.441 - `(binding [*theme* (merge *theme* ~theme)] 10.442 - ~@body)) 10.443 - 10.444 -(defmacro with-color [color-or-key & body] 10.445 - `(let [color# ~color-or-key 10.446 - color# (get *theme* color# color#) 10.447 - g# *graphics* 10.448 - old-color# (.getColor g#)] 10.449 - (try 10.450 - (.setColor g# color#) 10.451 - ~@body 10.452 - (finally 10.453 - (.setColor g# old-color#))))) 10.454 - 10.455 -(defmacro with-stroke [stroke & body] 10.456 - `(let [g# *graphics* 10.457 - old-stroke# (.getStroke g#)] 10.458 - (try 10.459 - (.setStroke g# ~stroke) 10.460 - ~@body 10.461 - (finally 10.462 - (.setStroke g# old-stroke#))))) 10.463 - 10.464 -(defmacro with-hints 10.465 - [hints & body] 10.466 - `(let [h# ~hints 10.467 - g# *graphics* 10.468 - old# (.getRenderingHints g#)] 10.469 - (try 10.470 - (.addRenderingHints g# h#) 10.471 - ~@body 10.472 - (finally 10.473 - (.setRenderingHints g# old#))))) 10.474 - 10.475 -(defn with-hints* [hints f & args] 10.476 - (with-hints hints 10.477 - (apply f args))) 10.478 - 10.479 -;; TODO: constructor for AffineTransform. 10.480 -;; (transform :scale 0.3 0.5 10.481 -;; :translate 5 10 10.482 -;; :rotate (/ Math/PI 2)) 10.483 - 10.484 -(defmacro with-transform [transform & body] 10.485 - `(let [g# *graphics* 10.486 - old-t# (.getTransform g#)] 10.487 - (try 10.488 - (.transform g# ~transform) 10.489 - ~@body 10.490 - (finally 10.491 - (.setTransform g# old-t#))))) 10.492 - 10.493 -(defmacro with-rotate [theta ax ay & body] 10.494 - `(let [transform# (AffineTransform/getRotateInstance ~theta ~ax ~ay)] 10.495 - (with-transform transform# ~@body))) 10.496 - 10.497 -(defmacro with-translate [x y & body] 10.498 - `(let [x# ~x 10.499 - y# ~y 10.500 - g# *graphics*] 10.501 - (try 10.502 - (.translate g# x# y#) 10.503 - ~@body 10.504 - (finally 10.505 - (.translate g# (- x#) (- y#)))))) 10.506 - 10.507 -(defn draw! 10.508 - "Draws the View." 10.509 - ([view] 10.510 - (let [graphics (create-graphics)] 10.511 - (try 10.512 - (binding [*graphics* graphics] 10.513 - (render! view)) 10.514 - (finally 10.515 - (.dispose graphics))))) 10.516 - ([x y view] 10.517 - (draw! x y true view)) 10.518 - ([x y clip? view] 10.519 - (let [geom (geometry view)] 10.520 - (draw! x y (width geom) (height geom) clip? view))) 10.521 - ([x y width height view] 10.522 - (draw! x y width height true view)) 10.523 - ([x y width height clip? view] 10.524 - (if clip? 10.525 - (with-bounds* x y width height render! view) 10.526 - (with-bounds-noclip* x y width height render! view)))) 10.527 - 10.528 -(defn draw-aligned! 10.529 - "Draws the View. Location is relative to the view's anchor point 10.530 - for the specified alignment." 10.531 - ([h-align v-align x y view] 10.532 - (let [geom (geometry view) 10.533 - w (width geom) 10.534 - h (height geom)] 10.535 - (draw! (- x (anchor-x geom h-align w)) 10.536 - (- y (anchor-y geom v-align h)) 10.537 - w h 10.538 - view))) 10.539 - ([h-align v-align x y w h view] 10.540 - (let [geom (geometry view)] 10.541 - (draw! (- x (anchor-x geom h-align w)) 10.542 - (- y (anchor-y geom v-align h)) 10.543 - w h 10.544 - view)))) 10.545 - 10.546 -;; 10.547 -;; Event handling. 10.548 -;; 10.549 - 10.550 -(defn with-handlers* 10.551 - [handle handlers f & args] 10.552 - (binding [*event-dispatcher* (create-dispatcher 10.553 - *event-dispatcher* handle handlers)] 10.554 - (apply f args))) 10.555 - 10.556 -(defmacro with-handlers 10.557 - "specs => (:event-id name & handler-body)* 10.558 - 10.559 - Execute form with the specified event handlers." 10.560 - [handle form & specs] 10.561 - `(with-handlers* ~handle 10.562 - ~(reduce (fn [m spec] 10.563 - (assoc m (first spec) 10.564 - `(fn [~(second spec)] 10.565 - ~@(nnext spec)))) {} 10.566 - specs) 10.567 - (fn [] ~form))) 10.568 - 10.569 -(defn picked? [handle] 10.570 - (handle-picked? *event-dispatcher* handle)) 10.571 - 10.572 -(defn hovered? [handle] 10.573 - (handle-hovered? *event-dispatcher* handle)) 10.574 - 10.575 -;; 10.576 -;; EventDispatcher implementation 10.577 -;; 10.578 - 10.579 -(def awt-events 10.580 - {java.awt.event.MouseEvent/MOUSE_CLICKED :mouse-clicked 10.581 - java.awt.event.MouseEvent/MOUSE_DRAGGED :mouse-dragged 10.582 - java.awt.event.MouseEvent/MOUSE_ENTERED :mouse-entered 10.583 - java.awt.event.MouseEvent/MOUSE_EXITED :mouse-exited 10.584 - java.awt.event.MouseEvent/MOUSE_MOVED :mouse-moved 10.585 - java.awt.event.MouseEvent/MOUSE_PRESSED :mouse-pressed 10.586 - java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released 10.587 - java.awt.event.MouseEvent/MOUSE_WHEEL :mouse-wheel}) 10.588 - 10.589 -(def dummy-event-dispatcher 10.590 - (reify EventDispatcher 10.591 - (listen! [_ _]) 10.592 - (create-dispatcher [this _ _] this) 10.593 - (commit [_]) 10.594 - (handle-picked? [_ _]) 10.595 - (handle-hovered? [_ _]))) 10.596 - 10.597 -;; Not using defrecord to avoid unacceptable overhead of recursive 10.598 -;; hash code calculation. 10.599 -(deftype DispatcherNode [handle handlers parent 10.600 - ^Shape clip ^AffineTransform transform 10.601 - bindings] 10.602 - EventDispatcher 10.603 - (listen! [this component] 10.604 - (listen! parent component)) 10.605 - (create-dispatcher [this handle handlers] 10.606 - (create-dispatcher parent handle handlers)) 10.607 - (commit [this] 10.608 - (commit parent)) 10.609 - (handle-picked? [this handle] 10.610 - (handle-picked? parent handle)) 10.611 - (handle-hovered? [this handle] 10.612 - (handle-hovered? parent handle))) 10.613 - 10.614 -(defn- make-node [handle handlers] 10.615 - (let [clip (if *input-clip* 10.616 - (clip *input-clip*) 10.617 - *clip*) 10.618 - bindings (-> (get-thread-bindings) 10.619 - (dissoc (var *graphics*)) 10.620 - (assoc (var *font-context*) (font-context)))] 10.621 - (DispatcherNode. handle handlers *event-dispatcher* clip 10.622 - (relative-transform) 10.623 - bindings))) 10.624 - 10.625 -(defn- add-node [tree ^DispatcherNode node] 10.626 - (assoc-cons tree (.parent node) node)) 10.627 - 10.628 -(defn- nodes [tree] 10.629 - (apply concat (vals tree))) 10.630 - 10.631 -(defn- under-cursor 10.632 - "Returns a vector of child nodes under cursor." 10.633 - [node tree ^long x ^long y] 10.634 - (some (fn [^DispatcherNode n] 10.635 - (if (and (.clip n) (.contains ^Shape (.clip n) x y)) 10.636 - (conj (vec (under-cursor n tree x y)) n))) 10.637 - (get tree node))) 10.638 - 10.639 -(defn- translate-mouse-event [^java.awt.event.MouseEvent event 10.640 - ^AffineTransform tr id] 10.641 - (let [[x y] (inverse-transform-point tr (.getX event) (.getY event)) 10.642 - rotation (if (instance? MouseWheelEvent event) 10.643 - (.getWheelRotation ^MouseWheelEvent event) 10.644 - nil)] 10.645 - (->MouseEvent id (.getWhen event) x y 10.646 - (.getXOnScreen event) (.getYOnScreen event) 10.647 - (.getButton event) 10.648 - rotation 10.649 - tr 10.650 - (.getComponent event)))) 10.651 - 10.652 -(defn- translate-and-dispatch 10.653 - ([nodes first-only ^java.awt.event.MouseEvent event] 10.654 - (translate-and-dispatch nodes first-only 10.655 - event (awt-events (.getID event)))) 10.656 - ([nodes first-only event id] 10.657 - (if-let [^DispatcherNode node (first nodes)] 10.658 - (let [handlers (.handlers node) 10.659 - handler (get handlers id)] 10.660 - (if handler 10.661 - (do 10.662 - (with-bindings* (.bindings node) 10.663 - handler 10.664 - (translate-mouse-event event (.transform node) id)) 10.665 - (when-not first-only 10.666 - (recur (rest nodes) false event id))) 10.667 - (when-not (and (= id :mouse-dragged) 10.668 - (or (contains? handlers :mouse-pressed) 10.669 - (contains? handlers :mouse-released))) 10.670 - (recur (rest nodes) first-only event id))))))) 10.671 - 10.672 -(defn- process-mouse-event 10.673 - [dispatcher ^java.awt.event.MouseEvent source-event] 10.674 - (let [{active-ref :active 10.675 - hovered-ref :hovered 10.676 - picked-ref :picked 10.677 - last-ref :last-motion 10.678 - tree-ref :tree} dispatcher 10.679 - pressed (and source-event 10.680 - (== (.getID source-event) 10.681 - java.awt.event.MouseEvent/MOUSE_PRESSED)) 10.682 - released (and source-event 10.683 - (== (.getID source-event) 10.684 - java.awt.event.MouseEvent/MOUSE_RELEASED)) 10.685 - ^java.awt.event.MouseEvent last-event @last-ref 10.686 - ^java.awt.event.MouseEvent event (or source-event last-event)] 10.687 - (when event 10.688 - (let [x (.getX event) 10.689 - y (.getY event) 10.690 - active @active-ref 10.691 - active (if (and active 10.692 - source-event 10.693 - (== (.getX last-event) x) 10.694 - (== (.getY last-event) y)) 10.695 - active 10.696 - (ref-set active-ref 10.697 - (under-cursor dispatcher @tree-ref x y))) 10.698 - acted (cond 10.699 - pressed (ref-set picked-ref active) 10.700 - released (let [picked @picked-ref] 10.701 - (ref-set picked-ref nil) 10.702 - picked) 10.703 - :else active) 10.704 - picked (seq @picked-ref) 10.705 - pred #(= (.handle ^DispatcherNode %1) (.handle ^DispatcherNode %2)) 10.706 - hovered (if picked 10.707 - (filter #(some (partial pred %) picked) active) 10.708 - active) 10.709 - remove-all (fn [c1 c2] 10.710 - (filter #(not (some (partial pred %) c2)) c1)) 10.711 - old-hovered @hovered-ref 10.712 - exited (remove-all old-hovered hovered) 10.713 - entered (remove-all hovered old-hovered) 10.714 - moved (or picked (remove-all hovered entered))] 10.715 - (ref-set hovered-ref hovered) 10.716 - (ref-set last-ref event) 10.717 - [exited entered moved acted event])))) 10.718 - 10.719 -(defn- dispatch-mouse-event 10.720 - [dispatcher source-event button?] 10.721 - (when-let [[exited 10.722 - entered 10.723 - moved 10.724 - acted 10.725 - event] (dosync (process-mouse-event dispatcher source-event))] 10.726 - (when button? 10.727 - (translate-and-dispatch acted true event)) 10.728 - (translate-and-dispatch exited false event :mouse-exited) 10.729 - (translate-and-dispatch entered false event :mouse-entered) 10.730 - (when-not button? 10.731 - (translate-and-dispatch moved true source-event)))) 10.732 - 10.733 -(defrecord RootEventDispatcher [tree-r ;; register 10.734 - tree ;; dispatch 10.735 - active ;; nodes under cursor 10.736 - hovered ;; mouse entered 10.737 - picked ;; mouse pressed 10.738 - last-motion] 10.739 - EventDispatcher 10.740 - (listen! [dispatcher component] 10.741 - (doto ^Component component 10.742 - (.addMouseListener dispatcher) 10.743 - (.addMouseWheelListener dispatcher) 10.744 - (.addMouseMotionListener dispatcher))) 10.745 - (create-dispatcher [dispatcher handle handlers] 10.746 - (let [node (make-node handle handlers)] 10.747 - (dosync (alter tree-r add-node node)) 10.748 - node)) 10.749 - (commit [dispatcher] 10.750 - (let [[exited 10.751 - entered 10.752 - _ _ 10.753 - event] (dosync 10.754 - ;; TODO: retain contexts that do 10.755 - ;; not intersect graphics 10.756 - ;; clipping area in tree. 10.757 - (ref-set tree @tree-r) 10.758 - (ref-set tree-r {}) 10.759 - (process-mouse-event dispatcher nil))] 10.760 - ;; Send mouse entered and exited events if necessary due to 10.761 - ;; updated layout. 10.762 - (translate-and-dispatch exited false event :mouse-exited) 10.763 - (translate-and-dispatch entered false event :mouse-entered))) 10.764 - (handle-picked? [dispatcher handle] 10.765 - (some #(= handle (.handle ^DispatcherNode %)) @picked)) 10.766 - (handle-hovered? [dispatcher handle] 10.767 - (some #(= handle (.handle ^DispatcherNode %)) @hovered)) 10.768 - MouseListener 10.769 - (mouseEntered [dispatcher event] 10.770 - (dispatch-mouse-event dispatcher event false)) 10.771 - (mouseExited [dispatcher event] 10.772 - (dispatch-mouse-event dispatcher event false)) 10.773 - (mouseClicked [dispatcher event] 10.774 - (dispatch-mouse-event dispatcher event true)) 10.775 - (mousePressed [dispatcher event] 10.776 - (dispatch-mouse-event dispatcher event true)) 10.777 - (mouseReleased [dispatcher event] 10.778 - (dispatch-mouse-event dispatcher event true)) 10.779 - MouseWheelListener 10.780 - (mouseWheelMoved [dispatcher event] 10.781 - (dispatch-mouse-event dispatcher event true)) 10.782 - MouseMotionListener 10.783 - (mouseDragged [dispatcher event] 10.784 - (dispatch-mouse-event dispatcher event false)) 10.785 - (mouseMoved [dispatcher event] 10.786 - (dispatch-mouse-event dispatcher event false))) 10.787 - 10.788 -(defn root-event-dispatcher [] 10.789 - (->RootEventDispatcher 10.790 - (ref {}) (ref {}) ;; trees 10.791 - (ref nil) (ref nil) (ref nil) ;; node states 10.792 - (ref nil))) ;; last event 10.793 - 10.794 -;; 10.795 -;; Scene 10.796 -;; 10.797 - 10.798 -(defrecord Scene [view 10.799 - event-dispatcher 10.800 - component 10.801 - rendering-hints 10.802 - next-state]) 10.803 - 10.804 -;; Define rendering hints that affect font metrics to make sure that 10.805 -;; Graphics and Scene FontRenderContexts are consistent. 10.806 -(def ^:private default-rendering-hints 10.807 - {RenderingHints/KEY_TEXT_ANTIALIASING 10.808 - RenderingHints/VALUE_TEXT_ANTIALIAS_DEFAULT, 10.809 - RenderingHints/KEY_FRACTIONALMETRICS 10.810 - RenderingHints/VALUE_FRACTIONALMETRICS_DEFAULT}) 10.811 - 10.812 -(defn make-scene 10.813 - ([view] 10.814 - (make-scene view dummy-event-dispatcher nil)) 10.815 - ([view event-dispatcher] 10.816 - (make-scene view event-dispatcher nil)) 10.817 - ([view event-dispatcher ^Component component] 10.818 - (make-scene view event-dispatcher component nil)) 10.819 - ([view event-dispatcher ^Component component hints] 10.820 - (let [hints (merge default-rendering-hints hints)] 10.821 - (->Scene view 10.822 - event-dispatcher 10.823 - component 10.824 - hints 10.825 - (atom nil))))) 10.826 - 10.827 -(defn- get-and-set! 10.828 - "Atomically sets the value of atom to newval and returns the old 10.829 - value." 10.830 - [atom newval] 10.831 - (loop [v @atom] 10.832 - (if (compare-and-set! atom v newval) 10.833 - v 10.834 - (recur @atom)))) 10.835 - 10.836 -(defn draw-scene! 10.837 - [scene ^Graphics2D graphics width height] 10.838 - (.addRenderingHints graphics (:rendering-hints scene)) 10.839 - (binding [*states* (get-and-set! (:next-state scene) nil) 10.840 - *scene* scene 10.841 - *graphics* graphics 10.842 - *initial-transform* (.getTransform graphics) 10.843 - *inverse-initial-transform* (-> graphics 10.844 - .getTransform 10.845 - .createInverse) 10.846 - *event-dispatcher* (:event-dispatcher scene) 10.847 - *width* width 10.848 - *height* height 10.849 - *clip* (Rectangle2D$Double. 0.0 0.0 width height) 10.850 - *input-clip* nil 10.851 - *time* (System/nanoTime)] 10.852 - (apply-theme) 10.853 - (let [tmp-watcher (Object.)] 10.854 - ;; Keep current context observers until the rendering is 10.855 - ;; complete. Some observers may be invoked twice if they 10.856 - ;; appear in both groups until tmp-watcher is removed. 10.857 - (replace-observers-watcher scene tmp-watcher) 10.858 - (try 10.859 - (render! (:view scene)) 10.860 - (finally 10.861 - (remove-observers tmp-watcher) 10.862 - (commit (:event-dispatcher scene))))))) 10.863 - 10.864 -(defn- scene-font-context [scene] 10.865 - (let [hints (:rendering-hints scene) 10.866 - ^Component c (:component scene) 10.867 - t (if c (->> c 10.868 - .getFont 10.869 - (.getFontMetrics c) 10.870 - .getFontRenderContext 10.871 - .getTransform))] 10.872 - (FontRenderContext. 10.873 - t 10.874 - (get hints RenderingHints/KEY_TEXT_ANTIALIASING) 10.875 - (get hints RenderingHints/KEY_FRACTIONALMETRICS)))) 10.876 - 10.877 -(defn scene-geometry [scene] 10.878 - (binding [*scene* scene 10.879 - *font-context* (scene-font-context scene)] 10.880 - (geometry (:view scene)))) 10.881 - 10.882 -(defn set-cursor! [^Cursor cursor] 10.883 - (when-let [^Component component (:component *scene*)] 10.884 - (EventQueue/invokeLater #(.setCursor component cursor))))
11.1 --- a/src/net/kryshen/indyvon/demo.clj Mon Apr 14 15:37:28 2014 +0400 11.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 11.3 @@ -1,223 +0,0 @@ 11.4 -;; 11.5 -;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> 11.6 -;; 11.7 -;; This file is part of Indyvon. 11.8 -;; 11.9 -;; Indyvon is free software: you can redistribute it and/or modify it 11.10 -;; under the terms of the GNU Lesser General Public License version 3 11.11 -;; only, as published by the Free Software Foundation. 11.12 -;; 11.13 -;; Indyvon is distributed in the hope that it will be useful, but 11.14 -;; WITHOUT ANY WARRANTY; without even the implied warranty of 11.15 -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11.16 -;; Lesser General Public License for more details. 11.17 -;; 11.18 -;; You should have received a copy of the GNU Lesser General Public 11.19 -;; License along with Indyvon. If not, see 11.20 -;; <http://www.gnu.org/licenses/>. 11.21 -;; 11.22 - 11.23 -(ns net.kryshen.indyvon.demo 11.24 - "Indyvon demo and experiments." 11.25 - (:gen-class) 11.26 - (:use 11.27 - (net.kryshen.indyvon core views viewport component)) 11.28 - (:import 11.29 - (java.awt Color) 11.30 - (javax.swing JFrame))) 11.31 - 11.32 -(defn draw-button! 11.33 - "Draws a button immediately (but uses callback for the action unlike 11.34 - IMGUI)." 11.35 - [id content callback & args] 11.36 - (with-handlers id 11.37 - (let [shadow-offset 2 11.38 - padding 4 11.39 - border-width 1 11.40 - offset (if (picked? id) (/ shadow-offset 2) 0) 11.41 - ^Color color (:alt-back-color *theme*) 11.42 - color (if (hovered? id) (.brighter color) color) 11.43 - width (- *width* shadow-offset) 11.44 - height (- *height* shadow-offset)] 11.45 - (with-color (:shadow-color *theme*) 11.46 - (.fillRect *graphics* shadow-offset shadow-offset width height)) 11.47 - (with-color color 11.48 - (.fillRect *graphics* offset offset width height)) 11.49 - (draw! offset offset width height 11.50 - (border border-width padding content))) 11.51 - ;; Event handlers 11.52 - (:mouse-entered _ (repaint)) 11.53 - (:mouse-exited _ (repaint)) 11.54 - (:mouse-pressed _ (repaint)) 11.55 - (:mouse-released _ (repaint)) 11.56 - (:mouse-clicked _ (apply callback args)))) 11.57 - 11.58 -(defn combine-colors 11.59 - "Returns color between color1 and color2. When c (0 <= c <= 1.0) is 11.60 - closer to 0 the returned сolor is closer to color1." 11.61 - [^Color color1 ^Color color2 c] 11.62 - (case c 11.63 - 0.0 color1 11.64 - 1.0 color2 11.65 - (let [rgb1 (.getRGBComponents color1 nil) 11.66 - rgb2 (.getRGBComponents color2 nil) 11.67 - rgb (float-array (map #(+ (* (- 1 c) %1) (* c %2)) rgb1 rgb2))] 11.68 - (Color. (aget rgb 0) (aget rgb 1) (aget rgb 2) (aget rgb 3))))) 11.69 - 11.70 -(defn animate 11.71 - "Changes the value of atom according to the specified range, speed, 11.72 - and current frame interval. Invokes repaint if change happens." 11.73 - [atom from to speed] 11.74 - (let [prev @atom 11.75 - state (cond 11.76 - (zero? speed) :stop 11.77 - (= prev from) (if (pos? speed) :start :stop) 11.78 - (= prev to) (if (neg? speed) :start :stop) 11.79 - :default :continue)] 11.80 - (if (= state :stop) 11.81 - prev 11.82 - (let [interval (if (= state :start) 1 *interval*) 11.83 - step (* speed interval 1E-9) 11.84 - val (swap! atom #(-> % (+ step) (max from) (min to)))] 11.85 - (repaint) 11.86 - val)))) 11.87 - 11.88 -(defn animated-button 11.89 - "Creates an animated button." 11.90 - [content callback & args] 11.91 - (let [padding 4 11.92 - border-width 1 11.93 - shadow-offset 2 11.94 - face (border padding border-width content) 11.95 - highlight (atom 0) 11.96 - animation-speed (atom 0)] 11.97 - (interval-view 11.98 - (reify 11.99 - View 11.100 - (render! [button] 11.101 - (with-handlers button 11.102 - (let [hovered (hovered? button) 11.103 - offset (if (picked? button) (/ shadow-offset 2) 0) 11.104 - color (combine-colors 11.105 - (:alt-back-color *theme*) Color/WHITE 11.106 - (animate highlight 0.0 1.0 @animation-speed)) 11.107 - width (- *width* shadow-offset) 11.108 - height (- *height* shadow-offset)] 11.109 - (with-color (:shadow-color *theme*) 11.110 - (.fillRect *graphics* 11.111 - shadow-offset shadow-offset 11.112 - width height)) 11.113 - (with-color color 11.114 - (.fillRect *graphics* offset offset width height)) 11.115 - (draw! offset offset width height 11.116 - (border border-width padding content))) 11.117 - ;; Event handlers 11.118 - (:mouse-entered _ 11.119 - (reset! animation-speed 4) 11.120 - (repaint)) 11.121 - (:mouse-exited _ 11.122 - (reset! animation-speed -2) 11.123 - (repaint)) 11.124 - (:mouse-pressed _ (repaint)) 11.125 - (:mouse-released _ (repaint)) 11.126 - (:mouse-clicked _ (apply callback args)))) 11.127 - (geometry [button] 11.128 - (let [face-geom (geometry face)] 11.129 - (->Size (+ (width face-geom) shadow-offset) 11.130 - (+ (height face-geom) shadow-offset)))))))) 11.131 - 11.132 -(def button1 (animated-button (label "Animated button 1") 11.133 - println "Animated button 1 clicked")) 11.134 - 11.135 -(def button2 (animated-button (label "Animated button 2") 11.136 - println "Animated button 2 clicked")) 11.137 - 11.138 -(def test-view1 11.139 - (reify 11.140 - View 11.141 - (render! [view] 11.142 - (with-handlers view 11.143 - (with-color (if (hovered? view) Color/ORANGE Color/RED) 11.144 - (.fillRect *graphics* 0 0 *width* *height*)) 11.145 - (:mouse-entered e 11.146 - (repaint) 11.147 - (println e)) 11.148 - (:mouse-exited e 11.149 - (repaint) 11.150 - (println e)) 11.151 - (:mouse-moved e 11.152 - (println e)))) 11.153 - (geometry [view] 11.154 - (->Size 30 20)))) 11.155 - 11.156 -(def test-view1b (border 2 3 test-view1)) 11.157 - 11.158 -(def test-view2 11.159 - (reify 11.160 - View 11.161 - (render! [view] 11.162 - (doto *graphics* 11.163 - (.setColor Color/YELLOW) 11.164 - (.fillRect 0 0 *width* *height*)) 11.165 - (with-rotate 0.5 0 0 11.166 - (draw! 30 25 test-view1b)) 11.167 - (draw! 55 5 test-view1)) 11.168 - (geometry [view] 11.169 - (->Size 70 65)))) 11.170 - 11.171 -(def test-view2m (miniature 30 30 test-view2)) 11.172 - 11.173 -(def test-view3 (border (label :right :bottom "Sample\ntext"))) 11.174 - 11.175 -(def root 11.176 - (reify 11.177 - View 11.178 - (render! [view] 11.179 - ;;(repaint) 11.180 - (doto *graphics* 11.181 - (.drawLine 0 0 *width* *height*) 11.182 - (.drawLine *width* 0 0 *height*) 11.183 - ;; Random color to see when repaint happens. 11.184 - (.setColor (rand-nth [Color/BLACK Color/BLUE Color/RED])) 11.185 - (.fillOval 5 5 20 20)) 11.186 - (draw! 30 20 test-view2) 11.187 - (draw! 120 50 test-view2m) 11.188 - (draw! 100 100 80 50 test-view3) 11.189 - (draw! 50 160 button1) 11.190 - (with-rotate (/ Math/PI 6) 250 200 11.191 - (draw! 210 140 button1)) 11.192 - (draw! 100 200 button2) 11.193 - (with-bounds 180 240 140 30 11.194 - (draw-button! :button 11.195 - (label :center :center "Immediate button") 11.196 - #(println "Button clicked!")))) 11.197 - (geometry [view] 11.198 - (->Size 400 300)))) 11.199 - 11.200 -;; Main viewport 11.201 -(def vp (viewport root)) 11.202 - 11.203 -;; Miniature (rendered asynchronously) 11.204 -(def vp-miniature (->> vp (viewport-miniature 100 75) border shadow)) 11.205 - 11.206 -;; Main scene 11.207 -(def scene 11.208 - (fps-view 11.209 - (decorate-view vp [_] 11.210 - (draw! vp) 11.211 - (draw-aligned! 11.212 - :left :bottom 5 (- *height* 5) 11.213 - (label (str "Drag mouse to pan," \newline 11.214 - "use mouse wheel to zoom."))) 11.215 - (draw! (- *width* 105) 5 vp-miniature)))) 11.216 - 11.217 -(defn show-frame [view] 11.218 - (doto (make-jframe "Test" view) 11.219 - (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE) 11.220 - (.setVisible true))) 11.221 - 11.222 -(defn -main [] 11.223 - (show-frame scene)) 11.224 - 11.225 -(comment 11.226 - (show-frame (viewport-miniature 200 150 vp)))
12.1 --- a/src/net/kryshen/indyvon/viewport.clj Mon Apr 14 15:37:28 2014 +0400 12.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 12.3 @@ -1,238 +0,0 @@ 12.4 -;; 12.5 -;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> 12.6 -;; 12.7 -;; This file is part of Indyvon. 12.8 -;; 12.9 -;; Indyvon is free software: you can redistribute it and/or modify it 12.10 -;; under the terms of the GNU Lesser General Public License version 3 12.11 -;; only, as published by the Free Software Foundation. 12.12 -;; 12.13 -;; Indyvon is distributed in the hope that it will be useful, but 12.14 -;; WITHOUT ANY WARRANTY; without even the implied warranty of 12.15 -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12.16 -;; Lesser General Public License for more details. 12.17 -;; 12.18 -;; You should have received a copy of the GNU Lesser General Public 12.19 -;; License along with Indyvon. If not, see 12.20 -;; <http://www.gnu.org/licenses/>. 12.21 -;; 12.22 - 12.23 -(ns net.kryshen.indyvon.viewport 12.24 - "Scrollable viewport and miniature." 12.25 - (:use 12.26 - (net.kryshen.indyvon core async views)) 12.27 - (:import 12.28 - java.awt.Cursor 12.29 - java.awt.geom.AffineTransform)) 12.30 - 12.31 -;;(defn- translate [^AffineTransform transform ^double x ^double y] 12.32 -;; (doto ^AffineTransform (.clone transform) 12.33 -;; (.translate x y))) 12.34 - 12.35 -(defn- scale [^AffineTransform transform ^double sx ^double sy] 12.36 - (doto ^AffineTransform (.clone transform) 12.37 - (.scale sx sy))) 12.38 - 12.39 -(defn- pre-translate [^AffineTransform transform ^double x ^double y] 12.40 - (if (== 0.0 x y) 12.41 - transform 12.42 - (doto (AffineTransform/getTranslateInstance x y) 12.43 - (.concatenate transform)))) 12.44 - 12.45 -(def ^:dynamic *viewport-scaling-step* (double 3/4)) 12.46 -(def ^:dynamic *viewport-min-scale* 1E-6) 12.47 -(def ^:dynamic *viewport-max-scale* 1E6) 12.48 - 12.49 -(def ^:dynamic *viewport* nil) 12.50 -(def ^:dynamic ^AffineTransform *viewport-transform*) 12.51 - 12.52 -(declare scale-viewport!) 12.53 - 12.54 -(defrecord ViewportState [transform 12.55 - fix-x fix-y 12.56 - last-width last-height 12.57 - last-anchor-x last-anchor-y]) 12.58 - 12.59 -(defn- update-viewport [state content-geom h-align v-align] 12.60 - (let [w *width* 12.61 - h *height* 12.62 - cw (width content-geom) 12.63 - ch (height content-geom) 12.64 - ax (anchor-x content-geom h-align cw) 12.65 - ay (anchor-y content-geom v-align ch) 12.66 - ax1 (align-x h-align (:last-width state) w) 12.67 - ay1 (align-y v-align (:last-height state) h) 12.68 - ax2 (- (:last-anchor-x state) ax) 12.69 - ay2 (- (:last-anchor-y state) ay) 12.70 - transform (:transform state) 12.71 - transform (if (and (zero? ax1) (zero? ay1) 12.72 - (zero? ax2) (zero? ay2)) 12.73 - transform 12.74 - (doto 12.75 - (AffineTransform/getTranslateInstance ax1 ay1) 12.76 - (.concatenate transform) 12.77 - (.translate ax2 ay2)))] 12.78 - (assoc state 12.79 - :last-width w 12.80 - :last-height h 12.81 - :last-anchor-x ax 12.82 - :last-anchor-y ay 12.83 - :transform transform))) 12.84 - 12.85 -(defrecord Viewport [content h-align v-align state] 12.86 - View 12.87 - (render! [view] 12.88 - (repaint-on-update view) 12.89 - (with-handlers view 12.90 - (let [geom (geometry content) 12.91 - new-state (swap! state update-viewport geom h-align v-align) 12.92 - transform (:transform new-state)] 12.93 - ;; TODO: notify observers when size changes. 12.94 - (binding [*viewport* view 12.95 - *viewport-transform* transform] 12.96 - (with-transform transform 12.97 - (draw! 0 0 (width geom) (height geom) false content)))) 12.98 - (:mouse-pressed e 12.99 - (swap! state assoc 12.100 - :fix-x (:x-on-screen e) 12.101 - :fix-y (:y-on-screen e)) 12.102 - (set-cursor! (Cursor. Cursor/MOVE_CURSOR))) 12.103 - (:mouse-released e 12.104 - (set-cursor! (Cursor. Cursor/DEFAULT_CURSOR))) 12.105 - (:mouse-dragged e 12.106 - (swap! state 12.107 - (fn [s] 12.108 - (assoc s 12.109 - :transform (pre-translate 12.110 - (:transform s) 12.111 - (- (:x-on-screen e) (:fix-x s)) 12.112 - (- (:y-on-screen e) (:fix-y s))) 12.113 - :fix-x (:x-on-screen e) 12.114 - :fix-y (:y-on-screen e)))) 12.115 - (update view)) 12.116 - (:mouse-wheel e 12.117 - (scale-viewport! 12.118 - view 12.119 - (Math/pow *viewport-scaling-step* (:wheel-rotation e)) 12.120 - true (:x e) (:y e))))) 12.121 - (geometry [_] 12.122 - (geometry content))) 12.123 - 12.124 -(def ^:private viewport-initial-state 12.125 - (->ViewportState 12.126 - (AffineTransform.) ; transform 12.127 - 0 0 ; fix-x fix-y 12.128 - 0 0 ; last-width last-height 12.129 - 0 0)) 12.130 - 12.131 -(defn viewport 12.132 - "Creates scrollable viewport view." 12.133 - ([content] 12.134 - (viewport :left :top content)) 12.135 - ([h-align v-align content] 12.136 - (->Viewport content h-align v-align (atom viewport-initial-state)))) 12.137 - 12.138 -(defn- scale-viewport [state vp s relative? x y] 12.139 - (let [^AffineTransform tr (:transform state) 12.140 - sx (if relative? s (/ s (.getScaleX tr))) 12.141 - sy (if relative? s (/ s (.getScaleY tr))) 12.142 - x (or x (align-x (:h-align vp) (:last-width state))) 12.143 - y (or y (align-y (:v-align vp) (:last-height state))) 12.144 - x (- x (* x sx)) 12.145 - y (- y (* y sy)) 12.146 - scaled (doto (AffineTransform/getTranslateInstance x y) 12.147 - (.scale sx sy) 12.148 - (.concatenate tr)) 12.149 - sx (.getScaleX scaled) 12.150 - sy (.getScaleY scaled)] 12.151 - (if (<= *viewport-min-scale* 12.152 - (min sx sy) 12.153 - (max sx sy) 12.154 - *viewport-max-scale*) 12.155 - (assoc state 12.156 - :transform scaled) 12.157 - state))) 12.158 - 12.159 -(defn scale-viewport! 12.160 - ([viewport s] 12.161 - (scale-viewport! viewport s true)) 12.162 - ([viewport s relative?] 12.163 - (scale-viewport! viewport s relative? nil nil)) 12.164 - ([viewport s relative? x y] 12.165 - (swap! (:state viewport) scale-viewport viewport s relative? x y) 12.166 - (update viewport))) 12.167 - 12.168 -(defn reset-viewport! [viewport] 12.169 - (reset! (:state viewport) viewport-initial-state) 12.170 - (update viewport)) 12.171 - 12.172 -(defn ^AffineTransform viewport-transform [viewport] 12.173 - (:transform @(:state viewport))) 12.174 - 12.175 -(defn- scaling 12.176 - [width height max-width max-height] 12.177 - (min (/ max-width width) 12.178 - (/ max-height height))) 12.179 - 12.180 -(defn miniature 12.181 - "Creates a view that asynchronously renders the content view scaled to 12.182 - the specified size." 12.183 - [mw mh content] 12.184 - (async-view 12.185 - mw mh *miniature-thread-priority* 12.186 - (reify 12.187 - View 12.188 - (render! [this] 12.189 - (let [geom (geometry content) 12.190 - cw (width geom) 12.191 - ch (height geom) 12.192 - s (scaling cw ch mw mh)] 12.193 - (.scale *graphics* s s) 12.194 - (draw! (align-x :center cw (/ mw s)) 12.195 - (align-y :center ch (/ mh s)) 12.196 - cw ch 12.197 - content))) 12.198 - (geometry [_] 12.199 - (->Size mw mh))))) 12.200 - 12.201 -(defn viewport-miniature 12.202 - "Creates miniature view of the viewport's contents." 12.203 - [m-width m-height viewport] 12.204 - (let [miniature (miniature m-width m-height (:content viewport))] 12.205 - (decorate-view miniature [l] 12.206 - (repaint-on-update viewport) 12.207 - (let [geom (geometry (:content viewport)) 12.208 - s (scaling (width geom) (height geom) m-width m-height) 12.209 - vp-state @(:state viewport) 12.210 - {:keys [transform last-width last-height]} @(:state viewport) 12.211 - ox (align-x :center (width geom) (/ m-width s)) 12.212 - oy (align-y :center (height geom) (/ m-height s)) 12.213 - inverse (.createInverse ^AffineTransform transform) 12.214 - transform (doto (AffineTransform.) 12.215 - (.scale s s) 12.216 - (.translate ox oy) 12.217 - (.concatenate inverse)) 12.218 - move-vp (fn [state x y] 12.219 - (let [x (- (/ x s) ox) 12.220 - y (- (/ y s) oy) 12.221 - tr (:transform state) 12.222 - [x y] (transform-point tr x y) 12.223 - x (- x (/ (:last-width state) 2)) 12.224 - y (- y (/ (:last-height state) 2))] 12.225 - (assoc state 12.226 - :transform (pre-translate tr (- x) (- y))))) 12.227 - move-vp! (fn [x y] 12.228 - (swap! (:state viewport) move-vp x y) 12.229 - (update viewport))] 12.230 - (with-color :alt-back-color 12.231 - (.fillRect *graphics* 0 0 *width* *height*)) 12.232 - (with-transform transform 12.233 - (with-color :back-color 12.234 - (.fillRect *graphics* 0 0 last-width last-height))) 12.235 - (with-handlers l 12.236 - (draw! miniature) 12.237 - (:mouse-pressed e (move-vp! (:x e) (:y e))) 12.238 - (:mouse-dragged e (move-vp! (:x e) (:y e)))) 12.239 - (with-transform transform 12.240 - (with-color :border-color 12.241 - (.drawRect *graphics* 0 0 last-width last-height)))))))
13.1 --- a/src/net/kryshen/indyvon/views.clj Mon Apr 14 15:37:28 2014 +0400 13.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 13.3 @@ -1,410 +0,0 @@ 13.4 -;; 13.5 -;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> 13.6 -;; 13.7 -;; This file is part of Indyvon. 13.8 -;; 13.9 -;; Indyvon is free software: you can redistribute it and/or modify it 13.10 -;; under the terms of the GNU Lesser General Public License version 3 13.11 -;; only, as published by the Free Software Foundation. 13.12 -;; 13.13 -;; Indyvon is distributed in the hope that it will be useful, but 13.14 -;; WITHOUT ANY WARRANTY; without even the implied warranty of 13.15 -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13.16 -;; Lesser General Public License for more details. 13.17 -;; 13.18 -;; You should have received a copy of the GNU Lesser General Public 13.19 -;; License along with Indyvon. If not, see 13.20 -;; <http://www.gnu.org/licenses/>. 13.21 -;; 13.22 - 13.23 -(ns net.kryshen.indyvon.views 13.24 - "Implementations of the View protocol." 13.25 - (:use 13.26 - (net.kryshen.indyvon core async)) 13.27 - (:import 13.28 - (java.awt Font Image Toolkit) 13.29 - java.awt.image.ImageObserver 13.30 - (java.awt.geom Area AffineTransform Rectangle2D$Double Point2D 13.31 - Point2D$Double) 13.32 - (java.awt.font FontRenderContext TextLayout) 13.33 - java.util.concurrent.TimeUnit 13.34 - (com.google.common.cache Cache CacheBuilder CacheLoader))) 13.35 - 13.36 -(defmacro decorate-view 13.37 - "Decorate the view replacing render! implementation." 13.38 - [view & render-tail] 13.39 - `(let [view# ~view] 13.40 - (reify 13.41 - View 13.42 - (render! ~@render-tail) 13.43 - (geometry [t#] (geometry view#))))) 13.44 - 13.45 -(defrecord Empty [] 13.46 - View 13.47 - (render! [_]) 13.48 - (geometry [_] 13.49 - (->Size 0 0))) 13.50 - 13.51 -(def empty-view (->Empty)) 13.52 - 13.53 -;; TODO: change argument order for decorators, content should be the 13.54 -;; last. 13.55 - 13.56 -(defn padding 13.57 - "Adds padding to the content view." 13.58 - ([distance content] 13.59 - (padding distance distance distance distance content)) 13.60 - ([top left bottom right content] 13.61 - (if (== 0 top left bottom right) 13.62 - content 13.63 - (reify 13.64 - View 13.65 - (render! [l] 13.66 - (draw! left top 13.67 - (- *width* left right) 13.68 - (- *height* top bottom) 13.69 - false 13.70 - content)) 13.71 - (geometry [l] 13.72 - (->NestedGeometry (geometry content) top left bottom right)))))) 13.73 - 13.74 -(defn border 13.75 - "Adds a border to the content view." 13.76 - ([content] 13.77 - (border 1 content)) 13.78 - ([thickness content] 13.79 - (border thickness 0 content)) 13.80 - ([thickness gap content] 13.81 - (let [view (padding (+ thickness gap) content) 13.82 - t (double thickness)] 13.83 - (decorate-view view [_] 13.84 - (render! view) 13.85 - (with-color :border-color 13.86 - (let [w (double *width*) 13.87 - h (double *height*) 13.88 - outer (Area. (Rectangle2D$Double. 0.0 0.0 w h)) 13.89 - inner (Area. (Rectangle2D$Double. t t (- w t t) (- h t t)))] 13.90 - (.subtract outer inner) 13.91 - (.fill *graphics* outer))))))) 13.92 - 13.93 -;; TODO: opacity and blur. 13.94 -(defn shadow 13.95 - "Adds a shadow to the content view." 13.96 - ([content] 13.97 - (shadow 1 1 content)) 13.98 - ([x-offset y-offset content] 13.99 - (let [x (if (neg? x-offset) (- x-offset) 0) 13.100 - y (if (neg? y-offset) (- y-offset) 0) 13.101 - abs-x (if (neg? x-offset) (- x-offset) x-offset) 13.102 - abs-y (if (neg? y-offset) (- y-offset) y-offset) 13.103 - shadow-x (+ x-offset x) 13.104 - shadow-y (+ y-offset y)] 13.105 - (reify 13.106 - View 13.107 - (render! [_] 13.108 - (let [w (- *width* abs-x) 13.109 - h (- *height* abs-y)] 13.110 - (with-color :shadow-color 13.111 - (.fillRect *graphics* shadow-x shadow-y w h)) 13.112 - (draw! x y w h content))) 13.113 - (geometry [_] 13.114 - (->NestedGeometry (geometry content) 13.115 - y x shadow-y shadow-x)))))) 13.116 - 13.117 -(defn panel 13.118 - "An opaque view using theme's alt-back-color or a custom background 13.119 - color." 13.120 - ([content] 13.121 - (panel :alt-back-color content)) 13.122 - ([back-color content] 13.123 - (decorate-view content [_] 13.124 - (with-color back-color 13.125 - (.fill *graphics* (Rectangle2D$Double. 0.0 0.0 *width* *height*))) 13.126 - (render! content)))) 13.127 - 13.128 -(defn hbox 13.129 - "Creates a view that draws the specified content views placing them 13.130 - horizontally." 13.131 - [& contents] 13.132 - (reify 13.133 - View 13.134 - (render! [_] 13.135 - (let [widths (map #(width (geometry %)) contents) 13.136 - xs (cons 0 (reductions + widths)) 13.137 - widths-sum (last xs) 13.138 - scale (/ *width* widths-sum)] 13.139 - (doseq [[c w x] (map vector contents widths xs)] 13.140 - (draw! x 0 w *height* c)))) 13.141 - (geometry [_] 13.142 - (reduce #(->Size (+ (width %1) (width %2)) 13.143 - (max (height %1) (height %2))) 13.144 - (->Size 0 0) 13.145 - (map geometry contents))))) 13.146 - 13.147 -(defn vbox 13.148 - "Creates a view that draws the specified content views placing them 13.149 - vertically." 13.150 - [& contents] 13.151 - (reify 13.152 - View 13.153 - (render! [_] 13.154 - (let [heights (map #(height (geometry %)) contents) 13.155 - ys (cons 0 (reductions + heights)) 13.156 - heights-sum (last ys) 13.157 - scale (/ *height* heights-sum)] 13.158 - (doseq [[c h y] (map vector contents heights ys)] 13.159 - (draw! 0 y *width* h c)))) 13.160 - (geometry [_] 13.161 - (reduce #(->Size (max (width %1) (width %2)) 13.162 - (+ (height %1) (height %2))) 13.163 - (->Size 0 0) 13.164 - (map geometry contents))))) 13.165 - 13.166 -(defn- re-split [^java.util.regex.Pattern re s] 13.167 - (seq (.split re s))) 13.168 - 13.169 -(def ^:private ^Cache text-layout-cache 13.170 - (-> (CacheBuilder/newBuilder) 13.171 - (.softValues) 13.172 - (.expireAfterAccess (long 1) TimeUnit/SECONDS) 13.173 - (.build))) 13.174 - 13.175 -(defn- get-text-layout 13.176 - [^String line ^Font font ^FontRenderContext font-context] 13.177 - (.get text-layout-cache [line font font-context] 13.178 - #(TextLayout. line font font-context))) 13.179 - 13.180 -(defn- layout-text 13.181 - [lines font font-context] 13.182 - (map #(get-text-layout % font font-context) lines)) 13.183 - 13.184 -(defn- text-width [layouts] 13.185 - (reduce (fn [w ^TextLayout tl] (max w (.getAdvance tl))) 0 layouts)) 13.186 - 13.187 -(defn- text-height [layouts] 13.188 - (reduce (fn [w ^TextLayout tl] 13.189 - (+ w (.getAscent tl) 13.190 - (.getDescent tl) 13.191 - (.getLeading tl))) 13.192 - 0 layouts)) 13.193 - 13.194 -(defn label 13.195 - "Creates a view to display multiline text." 13.196 - ([text] 13.197 - (label :left :top text)) 13.198 - ([h-align v-align text] 13.199 - (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" (str text))] 13.200 - (reify View 13.201 - (render! [view] 13.202 - (let [w *width* 13.203 - h *height* 13.204 - font (.getFont *graphics*) 13.205 - layouts (layout-text lines font (font-context)) 13.206 - y (align-y v-align (text-height layouts) h)] 13.207 - (loop [layouts layouts, y y] 13.208 - (when-first [^TextLayout layout layouts] 13.209 - (let [ascent (.getAscent layout) 13.210 - lh (+ ascent (.getDescent layout) (.getLeading layout)) 13.211 - x (align-x h-align (.getAdvance layout) w)] 13.212 - (.draw layout *graphics* x (+ y ascent)) 13.213 - (recur (next layouts) (+ y lh))))))) 13.214 - (geometry [view] 13.215 - (let [layouts (layout-text lines (:font *theme*) (font-context)) 13.216 - w (text-width layouts) 13.217 - h (text-height layouts)] 13.218 - (->Size w h))))))) 13.219 - 13.220 -(defn- ^ImageObserver image-observer [view] 13.221 - (reify 13.222 - ImageObserver 13.223 - (imageUpdate [this img infoflags x y width height] 13.224 - (update view) 13.225 - (zero? (bit-and infoflags 13.226 - (bit-or ImageObserver/ALLBITS 13.227 - ImageObserver/ABORT)))))) 13.228 - 13.229 -(defn image-view 13.230 - [image-or-uri] 13.231 - (let [^Image image (if (instance? Image image-or-uri) 13.232 - image-or-uri 13.233 - (.getImage (Toolkit/getDefaultToolkit) 13.234 - ^java.net.URL image-or-uri))] 13.235 - (.prepareImage (Toolkit/getDefaultToolkit) image -1 -1 nil) 13.236 - (reify 13.237 - View 13.238 - (render! [view] 13.239 - (repaint-on-update view) 13.240 - (.drawImage *graphics* image 0 0 (image-observer view))) 13.241 - (geometry [view] 13.242 - (let [observer (image-observer view) 13.243 - width (.getWidth image observer) 13.244 - height (.getHeight image observer) 13.245 - width (if (pos? width) width 1) 13.246 - height (if (pos? height) height 1)] 13.247 - (->Size width height)))))) 13.248 - 13.249 -(def ^:dynamic *miniature-thread-priority* 2) 13.250 - 13.251 -(defn ref-view 13.252 - [view-ref] 13.253 - (let [l (reify 13.254 - View 13.255 - (render! [l] 13.256 - (repaint-on-update l) 13.257 - (if-let [view @view-ref] 13.258 - (render! view))) 13.259 - (geometry [_] 13.260 - (if-let [view @view-ref] 13.261 - (geometry view) 13.262 - (->Size 1 1))))] 13.263 - (add-watch view-ref l (fn [_ _ _ _] (update l))) 13.264 - l)) 13.265 - 13.266 -;; 13.267 -;; View context decorators 13.268 -;; 13.269 - 13.270 -(defmacro handler [view & handlers] 13.271 - "Adds event handling to the view." 13.272 - `(let [view# ~view] 13.273 - (decorate-view view# [t#] 13.274 - (with-handlers t# 13.275 - (render! view#) 13.276 - ~@handlers)))) 13.277 - 13.278 -(defn themed [view & map-or-keyvals] 13.279 - (let [theme (if (== (count map-or-keyvals) 1) 13.280 - (first map-or-keyvals) 13.281 - (apply array-map map-or-keyvals))] 13.282 - (reify 13.283 - View 13.284 - (render! [_] 13.285 - (with-theme theme 13.286 - (render! view))) 13.287 - (geometry [_] 13.288 - (with-theme theme 13.289 - (geometry view)))))) 13.290 - 13.291 -(defn hinted [view & map-or-keyvals] 13.292 - (let [hints (if (== (count map-or-keyvals) 1) 13.293 - (first map-or-keyvals) 13.294 - (apply array-map map-or-keyvals))] 13.295 - (decorate-view view [_] 13.296 - (with-hints* hints render! view)))) 13.297 - 13.298 -;; 13.299 -;; Measuring time 13.300 -;; 13.301 - 13.302 -(def ^:dynamic *interval*) 13.303 - 13.304 -(defn interval-view 13.305 - "Creates a view that measures time between repaints ant draws it's 13.306 - content with the *interval* var bound to the measured time." 13.307 - [content] 13.308 - (let [last-time (atom nil)] 13.309 - (decorate-view content [_] 13.310 - (compare-and-set! last-time nil *time*) 13.311 - (let [lt @last-time] 13.312 - (binding [*interval* (if (compare-and-set! last-time lt *time*) 13.313 - (- *time* lt) 13.314 - 0)] ; already measured on parallel thread 13.315 - (render! content)))))) 13.316 - 13.317 -(defn- fps-label [text] 13.318 - (padding 5 (label :right :bottom text))) 13.319 - 13.320 -(defn fps-view 13.321 - "Creates a view that draws content and displays the 13.322 - frames per second rate." 13.323 - [content] 13.324 - (let [update-interval 2E8 ; 0.2 s in nanoseconds 13.325 - frames (ref 0) 13.326 - prev-time (ref nil) 13.327 - display (ref (fps-label "fps n/a"))] 13.328 - (decorate-view content [_] 13.329 - (draw! content) 13.330 - (draw! 13.331 - (dosync 13.332 - (alter frames inc) 13.333 - (if @prev-time 13.334 - (let [elapsed (- *time* @prev-time)] 13.335 - (when (> elapsed update-interval) 13.336 - (let [fps (/ @frames (/ elapsed 1E9))] 13.337 - (ref-set display (fps-label (format "%.1f" fps))) 13.338 - (ref-set frames 0) 13.339 - (ref-set prev-time *time*)))) 13.340 - (ref-set prev-time *time*)) 13.341 - @display))))) 13.342 - 13.343 -;; 13.344 -;; Overlays 13.345 -;; 13.346 - 13.347 -(def ^:private ^:dynamic *above*) 13.348 - 13.349 -(defn- overlay* [f & args] 13.350 - (var-set #'*above* (conj *above* (apply partial f args)))) 13.351 - 13.352 -(defn- ^Point2D to-graphics-coords 13.353 - [^AffineTransform transform x y] 13.354 - (let [p (Point2D$Double. x y)] 13.355 - (.transform transform p p) 13.356 - (.transform (.createInverse (.getTransform *graphics*)) p p) 13.357 - p)) 13.358 - 13.359 -(defn- draw-relative! 13.360 - ([transform x y view] 13.361 - (let [p (to-graphics-coords transform x y)] 13.362 - (draw! (.getX p) (.getY p) view))) 13.363 - ([transform x y w h view] 13.364 - (let [p (to-graphics-coords transform x y)] 13.365 - (draw! (.getX p) (.getY p) w h view)))) 13.366 - 13.367 -(defn- draw-relative-aligned! 13.368 - [transform h-align v-align x y view] 13.369 - (let [geom (geometry view) 13.370 - w (width geom) 13.371 - h (height geom) 13.372 - p (to-graphics-coords transform x y) 13.373 - x (- (.getX p) (anchor-x geom h-align w)) 13.374 - y (- (.getY p) (anchor-y geom v-align h))] 13.375 - (draw! x y w h view))) 13.376 - 13.377 -(defn overlay! 13.378 - "Draws view in the overlay context above the other views." 13.379 - ([view] 13.380 - (overlay* draw-relative! (.getTransform *graphics*) 0 0 view)) 13.381 - ([x y view] 13.382 - (overlay* draw-relative! (.getTransform *graphics*) x y view)) 13.383 - ([x y w h view] 13.384 - (overlay* draw-relative! (.getTransform *graphics*) x y w h view))) 13.385 - 13.386 -(defn overlay-aligned! [h-align v-align x y view] 13.387 - (overlay* draw-relative-aligned! 13.388 - (.getTransform *graphics*) 13.389 - h-align v-align x y 13.390 - view)) 13.391 - 13.392 -(defn with-overlays* [rec? f & args] 13.393 - (binding [*above* []] 13.394 - (apply f args) 13.395 - (if rec? 13.396 - (loop [above *above*] 13.397 - (when (seq above) 13.398 - (var-set #'*above* []) 13.399 - (doseq [f above] 13.400 - (f)) 13.401 - (recur *above*))) 13.402 - (doseq [of *above*] 13.403 - (of))))) 13.404 - 13.405 -(defmacro with-overlays [rec? & body] 13.406 - `(with-overlays* ~rec? (fn [] ~@body))) 13.407 - 13.408 -(defn layered 13.409 - ([content] 13.410 - (layered true content)) 13.411 - ([rec? content] 13.412 - (decorate-view content [_] 13.413 - (with-overlays* rec? render! content))))