view src/kryshen/indyvon/layers.clj @ 44:064b21604f74

Improved performance. Added image layer.
author Mikhail Kryshen <mikhail@kryshen.net>
date Mon, 19 Jul 2010 15:36:08 +0400
parents 7d67064f0880
children 180d7690d09a
line source
1 ;;
2 ;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
3 ;;
4 ;; This file is part of Indyvon.
5 ;;
7 (ns kryshen.indyvon.layers
8 (:use kryshen.indyvon.core)
9 (:import (kryshen.indyvon.core Size Location)
10 (java.lang.ref SoftReference)
11 (java.awt Font Cursor Image Toolkit)
12 (java.awt.image ImageObserver)
13 (java.awt.font FontRenderContext TextLayout)))
15 ;; Define as macro to avoid unnecessary calculation of inner and outer
16 ;; sizes in the first case.
17 (defmacro align-xy [inner outer align first center last]
18 `(case ~align
19 ~first 0
20 ~center (/ (- ~outer ~inner) 2)
21 ~last (- ~outer ~inner)))
23 (defmacro align-x [inner outer align]
24 `(align-xy ~inner ~outer ~align :left :center :right))
26 (defmacro align-y [inner outer align]
27 `(align-xy ~inner ~outer ~align :top :center :bottom))
29 (defmacro decorate-layer [layer & render-tail]
30 `(let [layer# ~layer]
31 (reify
32 Layer
33 (render! ~@render-tail)
34 (layer-size [t#] (layer-size layer#))
35 Anchored
36 (anchor [t# xa# ya#] (anchor layer# xa# ya#)))))
38 (defn padding
39 ([content pad]
40 (padding content pad pad pad pad))
41 ([content top left bottom right]
42 (if (== 0 top left bottom right)
43 content
44 (reify
45 Layer
46 (render! [l]
47 (draw! content
48 left top
49 (- (:width *bounds*) left right)
50 (- (:height *bounds*) top bottom)))
51 (layer-size [l]
52 (let [s (layer-size content)]
53 (Size. (+ (:width s) left right)
54 (+ (:height s) top bottom))))))))
56 (defn border
57 "Decorate layer with a border."
58 ([content]
59 (border content 1))
60 ([content width]
61 (border content width 0))
62 ([content width gap]
63 (let [layer (padding content (+ width gap))]
64 (decorate-layer layer [_]
65 (let [w (:width *bounds*)
66 h (:height *bounds*)]
67 (with-color (:border-color *theme*)
68 (doseq [i (range 0 width)]
69 (.drawRect *graphics* i i (- w 1 i i) (- h 1 i i))))
70 (render! layer))))))
72 (defn panel
73 "Opaque layer using theme's alt-back-color."
74 ([content]
75 (panel content 0))
76 ([content gap]
77 (let [layer (padding content gap)]
78 (decorate-layer layer [_]
79 (with-color (:alt-back-color *theme*)
80 (.fillRect *graphics* 0 0
81 (:width *bounds*) (:height *bounds*)))
82 (render! layer)))))
84 (defn- re-split [^java.util.regex.Pattern re s]
85 (seq (.split re s)))
87 (def text-layout-cache (atom {}))
89 (defn- get-text-layout
90 [^String line ^Font font ^FontRenderContext font-context]
91 (let [key [line font font-context]]
92 (or (if-let [softref (@text-layout-cache key)]
93 (.get softref)
94 (do (swap! text-layout-cache dissoc key)
95 false))
96 (let [layout (TextLayout. line font font-context)]
97 (println "text-layout-cache miss" line)
98 (swap! text-layout-cache assoc key (SoftReference. layout))
99 layout))))
101 (defn- layout-text
102 [lines ^Font font ^FontRenderContext font-context]
103 (map #(get-text-layout % font font-context) lines))
104 ;;(map #(TextLayout. ^String % font font-context) lines))
106 (defn- text-width [layouts]
107 (reduce (fn [w ^TextLayout tl] (max w (.getAdvance tl))) 0 layouts))
109 (defn- text-height [layouts]
110 (reduce (fn [w ^TextLayout tl] (+ w (.getAscent tl)
111 (.getDescent tl)
112 (.getLeading tl)))
113 0 layouts))
115 (defn text-layer
116 "Creates a layer to display multiline text."
117 ([text]
118 (text-layer text :left :top))
119 ([text h-align v-align]
120 (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" text)]
121 (reify Layer
122 (render! [layer]
123 (let [w (:width *bounds*)
124 h (:height *bounds*)
125 font (.getFont *graphics*)
126 layouts (layout-text lines font *font-context*)
127 y (align-y (text-height layouts) h v-align)]
128 (loop [layouts layouts, y y]
129 (when-first [^TextLayout layout layouts]
130 (let [ascent (.getAscent layout)
131 lh (+ ascent (.getDescent layout) (.getLeading layout))
132 x (align-x (.getAdvance layout) w h-align)]
133 (.draw layout *graphics* x (+ y ascent))
134 (recur (next layouts) (+ y lh)))))))
135 (layer-size [layer]
136 (let [layouts (layout-text lines (:font *theme*) *font-context*)
137 width (text-width layouts)
138 height (text-height layouts)]
139 (Size. width height)))))))
141 (defn- image-observer [update-fn]
142 (reify
143 ImageObserver
144 (imageUpdate [this img infoflags x y width height]
145 (update-fn)
146 (zero? (bit-and infoflags
147 (bit-or ImageObserver/ALLBITS
148 ImageObserver/ABORT))))))
150 (defn image-layer
151 [image-or-uri]
152 (let [^Image image (if (isa? image-or-uri Image)
153 image-or-uri
154 (.createImage (Toolkit/getDefaultToolkit)
155 ^java.net.URL image-or-uri))]
156 (.prepareImage (Toolkit/getDefaultToolkit) image -1 -1 nil)
157 (reify
158 Layer
159 (render! [layer]
160 (.drawImage *graphics* image 0 0 (image-observer *update*)))
161 (layer-size [layer]
162 (let [observer (image-observer *update*)
163 width (.getWidth image observer)
164 height (.getHeight image observer)
165 width (if (pos? width) width 1)
166 height (if (pos? height) height 1)]
167 (Size. width height))))))
169 (defn viewport
170 "Creates scrollable viewport layer."
171 ([content] (viewport content :left :top))
172 ([content h-align v-align]
173 (let [x (ref 0)
174 y (ref 0)
175 fix-x (ref 0)
176 fix-y (ref 0)
177 last-width (ref 0)
178 last-height (ref 0)]
179 (reify
180 Layer
181 (render! [layer]
182 (with-handlers layer
183 (let [anchor (anchor content h-align v-align)
184 width (:width *bounds*)
185 height (:height *bounds*)]
186 (dosync
187 (alter x + (align-x width @last-width h-align))
188 (alter y + (align-y height @last-height v-align))
189 (ref-set last-width width)
190 (ref-set last-height height))
191 (draw! content (- 0 @x (:x anchor)) (- 0 @y (:y anchor))))
192 (:mouse-pressed e
193 (dosync
194 (ref-set fix-x (:x-on-screen e))
195 (ref-set fix-y (:y-on-screen e)))
196 (->> Cursor/MOVE_CURSOR Cursor. (.setCursor *target*)))
197 (:mouse-released e
198 (->> Cursor/DEFAULT_CURSOR Cursor. (.setCursor *target*)))
199 (:mouse-dragged e
200 (dosync
201 (alter x + (- @fix-x (:x-on-screen e)))
202 (alter y + (- @fix-y (:y-on-screen e)))
203 (ref-set fix-x (:x-on-screen e))
204 (ref-set fix-y (:y-on-screen e)))
205 (*update*))))
206 (layer-size [layer] (layer-size content))))))
208 ;;
209 ;; Layer context decorators.
210 ;;
212 (defmacro handler [layer & handlers]
213 `(decorate-layer ~layer [t#]
214 (with-handlers t#
215 (render! ~layer)
216 ~@handlers)))
218 (defn theme [layer & map-or-keyvals]
219 (let [theme (if (== (count map-or-keyvals) 1)
220 map-or-keyvals
221 (apply array-map map-or-keyvals))]
222 (reify
223 Layer
224 (render! [t]
225 (with-theme theme
226 (render! layer)))
227 (layer-size [t]
228 (with-theme theme
229 (layer-size layer)))
230 Anchored
231 (anchor [t xa ya]
232 (with-theme theme
233 (anchor layer xa ya))))))