Mercurial > hg > indyvon
changeset 26:1237f7555029
Rearranged namespaces.
Mouse events represented by a record.
Added alignment args to anchor.
author | Mikhail Kryshen <mikhail@kryshen.net> |
---|---|
date | Mon, 21 Jun 2010 04:00:45 +0400 |
parents | 07ee065cbb3e |
children | 61bc04f94d61 4cb70c5a6e0d |
files | project.clj src/indyvon/component.clj src/indyvon/core.clj src/indyvon/event.clj src/indyvon/layers.clj |
diffstat | 5 files changed, 302 insertions(+), 271 deletions(-) [+] |
line diff
1.1 --- a/project.clj Mon Jun 21 01:18:50 2010 +0400 1.2 +++ b/project.clj Mon Jun 21 04:00:45 2010 +0400 1.3 @@ -1,8 +1,8 @@ 1.4 (defproject indyvon "1.0.0-SNAPSHOT" 1.5 - :description "FIXME: write" 1.6 + :description "INteractive DYnamic VisualizatiON library" 1.7 :dependencies [[org.clojure/clojure "1.2.0-master-SNAPSHOT"] 1.8 [org.clojure/clojure-contrib "1.2.0-SNAPSHOT"]] 1.9 :dev-dependencies [[leiningen/lein-swank "1.2.0-SNAPSHOT"]] 1.10 :namespaces [indyvon.core 1.11 - indyvon.event 1.12 + indyvon.layers 1.13 indyvon.component])
2.1 --- a/src/indyvon/component.clj Mon Jun 21 01:18:50 2010 +0400 2.2 +++ b/src/indyvon/component.clj Mon Jun 21 04:00:45 2010 +0400 2.3 @@ -5,10 +5,8 @@ 2.4 ;; 2.5 2.6 (ns indyvon.component 2.7 - (:use indyvon.core) 2.8 - (:require (indyvon [event :as event])) 2.9 + (:use indyvon.core indyvon.layers) 2.10 (:import (java.awt Component Dimension Color) 2.11 - (java.awt.event MouseEvent) 2.12 (javax.swing JFrame JPanel))) 2.13 2.14 (defn- font-context [component] 2.15 @@ -35,7 +33,7 @@ 2.16 2.17 (defn make-jpanel 2.18 ([layer] 2.19 - (make-jpanel layer (event/make-event-dispatcher))) 2.20 + (make-jpanel layer (make-event-dispatcher))) 2.21 ([layer event-dispatcher] 2.22 (let [context (default-context) 2.23 context (assoc context :dispatcher event-dispatcher) 2.24 @@ -53,9 +51,6 @@ 2.25 (do 2.26 (def frame (JFrame. "Test")) 2.27 2.28 - (defn handler [event context] 2.29 - (println (:layer context) (.paramString event))) 2.30 - 2.31 (def layer1 2.32 (reify 2.33 Layer 2.34 @@ -65,7 +60,7 @@ 2.35 (size [this context] [30 20]) 2.36 MouseHandler 2.37 (handle-mouse [this context event] 2.38 - (println "layer1" (.paramString event))))) 2.39 + (println "layer1" event)))) 2.40 2.41 (def layer1b (border-layer layer1 2 3)) 2.42 2.43 @@ -80,7 +75,7 @@ 2.44 (size [this context] [70 65]) 2.45 MouseHandler 2.46 (handle-mouse [this context event] 2.47 - (println "layer2" (.paramString event))))) 2.48 + (println "layer2" event)))) 2.49 2.50 (def layer3 2.51 (border-layer (text-layer "Sample\ntext" :right :center)))
3.1 --- a/src/indyvon/core.clj Mon Jun 21 01:18:50 2010 +0400 3.2 +++ b/src/indyvon/core.clj Mon Jun 21 04:00:45 2010 +0400 3.3 @@ -5,15 +5,17 @@ 3.4 ;; 3.5 3.6 (ns indyvon.core 3.7 - (:import (java.awt Color Font Cursor) 3.8 - (java.awt.font FontRenderContext TextLayout) 3.9 - (java.awt.event MouseEvent))) 3.10 + (:import (java.awt Color Font) 3.11 + (java.awt.event MouseListener MouseMotionListener))) 3.12 3.13 (defprotocol Layer 3.14 "Basic UI element." 3.15 (render! [this context graphics]) 3.16 (size [this context])) 3.17 3.18 +;; TODO: modifiers 3.19 +(defrecord MouseEvent [id when x y x-on-screen y-on-screen button]) 3.20 + 3.21 (defprotocol MouseHandler 3.22 "Layers that also satisfy this protocol will recieve mouse events." 3.23 (handle-mouse [this context event])) 3.24 @@ -25,6 +27,29 @@ 3.25 (hovered? [this layer]) 3.26 (picked? [this layer])) 3.27 3.28 +(defprotocol Anchored 3.29 + "Provide anchor point for Layers. Used by viewport." 3.30 + (anchor [this context h-align v-align] 3.31 + "Anchor point: [x y], h-align could be :left, :center 3.32 + or :right, v-align is :top, :center or :bottom")) 3.33 + 3.34 +;; Default implementation of Anchored for any Layer. 3.35 +(extend-protocol Anchored 3.36 + indyvon.core.Layer 3.37 + (anchor [this context h-align v-align] 3.38 + (if (and (= h-align :left) 3.39 + (= v-align :top)) 3.40 + [0 0] 3.41 + (let [size (size this context)] 3.42 + [(case h-align 3.43 + :top 0 3.44 + :center (/ (size 0) 2) 3.45 + :right (size 0)) 3.46 + (case v-align 3.47 + :left 0 3.48 + :center (/ (size 1) 2) 3.49 + :bottom (size 1))])))) 3.50 + 3.51 (defrecord Theme [fore-color back-color border-color font]) 3.52 3.53 (defn default-theme [] 3.54 @@ -81,134 +106,151 @@ 3.55 (.dispose graphics)))))) 3.56 3.57 ;; 3.58 -;; Layer implementations. 3.59 +;; EventDispatcher implementation 3.60 ;; 3.61 3.62 -(defn border-layer 3.63 - "Decorate layer with a border." 3.64 - ([content] 3.65 - (border-layer content 1)) 3.66 - ([content width] 3.67 - (border-layer content width 0)) 3.68 - ([content width gap] 3.69 - (let [offset (+ width gap)] 3.70 - (reify Layer 3.71 - (render! [l c g] 3.72 - (let [w (:width c) 3.73 - h (:height c)] 3.74 - (.setColor g (-> c :theme :border-color)) 3.75 - (doseq [i (range 0 width)] 3.76 - (.drawRect g i i (- w 1 i i) (- h 1 i i))) 3.77 - (draw! content c g offset offset (- w offset offset) 3.78 - (- h offset offset)))) 3.79 - (size [l c] 3.80 - (let [s (size content c)] 3.81 - [(+ (s 0) offset offset) 3.82 - (+ (s 1) offset offset)])))))) 3.83 +(def awt-events 3.84 + {java.awt.event.MouseEvent/MOUSE_CLICKED :mouse-clicked 3.85 + java.awt.event.MouseEvent/MOUSE_DRAGGED :mouse-dragged 3.86 + java.awt.event.MouseEvent/MOUSE_ENTERED :mouse-entered 3.87 + java.awt.event.MouseEvent/MOUSE_EXITED :mouse-exited 3.88 + java.awt.event.MouseEvent/MOUSE_MOVED :mouse-moved 3.89 + java.awt.event.MouseEvent/MOUSE_PRESSED :mouse-pressed 3.90 + java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released}) 3.91 3.92 -;; Define as macro to avoid unnecessary calculation of inner and outer 3.93 -;; sizes in the first case. 3.94 -(defmacro align-xy [inner outer align first center last] 3.95 - `(case ~align 3.96 - ~first 0 3.97 - ~center (/ (- ~outer ~inner) 2) 3.98 - ~last (- ~outer ~inner))) 3.99 +(defn- registered-parent 3.100 + "Returns first context parent registered for event processing." 3.101 + [context-tree context] 3.102 + (let [parent (:parent context)] 3.103 + (cond 3.104 + (nil? parent) nil 3.105 + (contains? context-tree parent) parent 3.106 + :default (recur context-tree parent)))) 3.107 3.108 -(defmacro align-x [inner outer align] 3.109 - `(align-xy ~inner ~outer ~align :left :center :right)) 3.110 +(defn- add-context 3.111 + [context-tree context] 3.112 + (let [parent (registered-parent context-tree context)] 3.113 + (assoc context-tree parent (cons context (context-tree parent)) 3.114 + context nil))) 3.115 3.116 -(defmacro align-y [inner outer align] 3.117 - `(align-xy ~inner ~outer ~align :top :center :bottom)) 3.118 +(defn- inside? 3.119 + ([x y context] 3.120 + (inside? x y (:x context) (:y context) 3.121 + (:width context) (:height context))) 3.122 + ([px py x y w h] 3.123 + (and (>= px x) 3.124 + (>= py y) 3.125 + (< px (+ x w)) 3.126 + (< py (+ y h))))) 3.127 3.128 -(defn- re-split [re s] 3.129 - (seq (.split re s))) 3.130 +(defn- under-cursor 3.131 + "Returns a sequence of contexts under cursor." 3.132 + ([context-tree x y] 3.133 + (under-cursor context-tree x y nil)) 3.134 + ([context-tree x y context] 3.135 + (some #(if (inside? x y %) 3.136 + (conj (under-cursor context-tree x y %) %)) 3.137 + (context-tree context)))) 3.138 3.139 -(defn- layout-text [lines font font-context] 3.140 - (map #(TextLayout. % font font-context) lines)) 3.141 +(defn- remove-all [coll1 coll2 pred] 3.142 + (filter #(not (some (partial pred %) coll2)) coll1)) 3.143 3.144 -(defn- text-width [layouts] 3.145 - (reduce #(max %1 (.getAdvance %2)) 0 layouts)) 3.146 +;; (defn- translate-mouse-event 3.147 +;; [event x y id] 3.148 +;; (proxy [MouseEvent] [(.getComponent event) 3.149 +;; id 3.150 +;; (.getWhen event) 3.151 +;; (.getModifiers event) 3.152 +;; (- (.getX event) x) 3.153 +;; (- (.getY event) y) 3.154 +;; (.getClickCount event) 3.155 +;; (.isPopupTrigger event)] 3.156 +;; (getXOnScreen [] (.getXOnScreen event)) 3.157 +;; (getYOnScreen [] (.getYOnScreen event)))) 3.158 3.159 -(defn- text-height [layouts] 3.160 - (reduce #(+ %1 (.getAscent %2) (.getDescent %2) (.getLeading %2)) 3.161 - 0 layouts)) 3.162 +(defn- translate-mouse-event 3.163 + [event x y id] 3.164 + (MouseEvent. id (.getWhen event) 3.165 + (- (.getX event) x) (- (.getY event) y) 3.166 + (.getXOnScreen event) (.getYOnScreen event) 3.167 + (.getButton event))) 3.168 3.169 -(defn text-layer 3.170 - "Creates a layer to display multiline text." 3.171 - ([text] 3.172 - (text-layer text :left :top)) 3.173 - ([text h-align v-align] 3.174 - (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" text)] 3.175 - (reify Layer 3.176 - (render! [l c g] 3.177 - (let [w (:width c) 3.178 - h (:height c) 3.179 - font (.getFont g) 3.180 - font-context (:font-context c) 3.181 - layouts (layout-text lines font font-context) 3.182 - y (align-y (text-height layouts) h v-align)] 3.183 - (loop [layouts layouts, y y] 3.184 - (when-first [layout layouts] 3.185 - (let [ascent (.getAscent layout) 3.186 - lh (+ ascent (.getDescent layout) (.getLeading layout)) 3.187 - x (align-x (.getAdvance layout) w h-align)] 3.188 - (.draw layout g x (+ y ascent)) 3.189 - (recur (next layouts) (+ y lh))))))) 3.190 - (size [l c] 3.191 - (let [layouts (layout-text lines 3.192 - (-> c :theme :font) 3.193 - (:font-context c)) 3.194 - width (text-width layouts) 3.195 - height (text-height layouts)] 3.196 - [width height])))))) 3.197 +(defn- translate-and-dispatch 3.198 + ([contexts event] 3.199 + (translate-and-dispatch contexts event (awt-events (.getID event)))) 3.200 + ([contexts event id] 3.201 + (doseq [context contexts] 3.202 + (handle-mouse 3.203 + (:layer context) context 3.204 + (translate-mouse-event event (:x context) (:y context) id))) 3.205 + id)) 3.206 3.207 -(defprotocol Anchored 3.208 - "Provide anchor point for Layers. Used by viewport." 3.209 - (anchor [this context] "Anchor point: [x y]")) 3.210 +(defn- dispatch-mouse-motion* 3.211 + "Dispatches mouse motion events. Returns a new set of contexts which 3.212 + currently are under cursor." 3.213 + [hovered context-tree event] 3.214 + (let [x (.getX event) 3.215 + y (.getY event) 3.216 + hovered2 (under-cursor context-tree x y) 3.217 + pred #(= (:layer %1) (:layer %2)) 3.218 + exited (remove-all hovered hovered2 pred) 3.219 + entered (remove-all hovered2 hovered pred) 3.220 + moved (remove-all hovered2 entered pred)] 3.221 + (translate-and-dispatch exited event :mouse-exited) 3.222 + (translate-and-dispatch entered event :mouse-entered) 3.223 + (translate-and-dispatch moved event :mouse-moved) 3.224 + hovered2)) 3.225 3.226 -;; Default implementation of Anchored for any Layer. 3.227 -(extend-protocol Anchored 3.228 - indyvon.core.Layer 3.229 - (anchor [this context] [0 0])) 3.230 +(defn- dispatch-mouse-motion 3.231 + [hovered-ref context-tree event] 3.232 + (dosync 3.233 + (alter hovered-ref dispatch-mouse-motion* context-tree event))) 3.234 3.235 -(defn viewport 3.236 - "Creates scrollable viewport layer." 3.237 - ([content] (viewport content :left :top)) 3.238 - ([content h-align v-align] 3.239 - (let [x (ref 0) 3.240 - y (ref 0) 3.241 - fix-x (ref 0) 3.242 - fix-y (ref 0) 3.243 - last-width (ref 0) 3.244 - last-height (ref 0)] 3.245 +(defn- dispatch-mouse-button* 3.246 + "Dispatches mouse button events. Returns a new set of contexts which 3.247 + currently are picked with a pressed button." 3.248 + [picked hovered event] 3.249 + (if (= (translate-and-dispatch hovered event) :mouse-pressed) 3.250 + hovered 3.251 + nil)) 3.252 + 3.253 +(defn- dispatch-mouse-button 3.254 + [picked-ref hovered-ref event] 3.255 + (dosync 3.256 + (alter picked-ref dispatch-mouse-button* @hovered-ref event))) 3.257 + 3.258 +(defn make-event-dispatcher [] 3.259 + (let [context-tree-r (ref {}) ; register 3.260 + context-tree (ref {}) ; dispatch 3.261 + hovered (ref '()) 3.262 + picked (ref '())] 3.263 (reify 3.264 - Layer 3.265 - (render! [layer c g] 3.266 - (let [anchor (anchor content c) 3.267 - width (:width c) 3.268 - height (:height c)] 3.269 - (dosync 3.270 - (alter x + (align-x width @last-width h-align)) 3.271 - (alter y + (align-y height @last-height v-align)) 3.272 - (ref-set last-width width) 3.273 - (ref-set last-height height)) 3.274 - (draw! content c g 3.275 - (- 0 @x (anchor 0)) 3.276 - (- 0 @y (anchor 1))))) 3.277 - (size [layer c] (size content c)) 3.278 - MouseHandler 3.279 - (handle-mouse [layer c e] 3.280 - (when (= (.getID e) MouseEvent/MOUSE_PRESSED) 3.281 - (dosync 3.282 - (ref-set fix-x (.getXOnScreen e)) 3.283 - (ref-set fix-y (.getYOnScreen e))) 3.284 - (->> Cursor/MOVE_CURSOR Cursor. (.setCursor (:target c)))) 3.285 - (when (= (.getID e) MouseEvent/MOUSE_RELEASED) 3.286 - (->> Cursor/DEFAULT_CURSOR Cursor. (.setCursor (:target c)))) 3.287 - (when (= (.getID e) MouseEvent/MOUSE_DRAGGED) 3.288 - (dosync 3.289 - (alter x + (- @fix-x (.getXOnScreen e))) 3.290 - (alter y + (- @fix-y (.getYOnScreen e))) 3.291 - (ref-set fix-x (.getXOnScreen e)) 3.292 - (ref-set fix-y (.getYOnScreen e))) 3.293 - (update c))))))) 3.294 + EventDispatcher 3.295 + (listen! [this component] 3.296 + (doto component 3.297 + (.addMouseListener this) 3.298 + (.addMouseMotionListener this))) 3.299 + (register [this context] 3.300 + (when (satisfies? MouseHandler (:layer context)) 3.301 + (dosync (alter context-tree-r add-context context)))) 3.302 + (commit [this] 3.303 + (dosync (ref-set context-tree @context-tree-r) 3.304 + (ref-set context-tree-r {}))) 3.305 + (picked? [this layer] false) 3.306 + (hovered? [this layer] false) 3.307 + MouseListener 3.308 + (mouseEntered [this event] 3.309 + (dispatch-mouse-motion hovered @context-tree event)) 3.310 + (mouseExited [this event] 3.311 + (dispatch-mouse-motion hovered @context-tree event)) 3.312 + (mouseClicked [this event] 3.313 + (dispatch-mouse-button picked hovered event)) 3.314 + (mousePressed [this event] 3.315 + (dispatch-mouse-button picked hovered event)) 3.316 + (mouseReleased [this event] 3.317 + (dispatch-mouse-button picked hovered event)) 3.318 + MouseMotionListener 3.319 + (mouseDragged [this event] 3.320 + (translate-and-dispatch @picked event)) 3.321 + (mouseMoved [this event] 3.322 + (dispatch-mouse-motion hovered @context-tree event)))))
4.1 --- a/src/indyvon/event.clj Mon Jun 21 01:18:50 2010 +0400 4.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 4.3 @@ -1,140 +0,0 @@ 4.4 -;; 4.5 -;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net> 4.6 -;; 4.7 -;; This file is part of Indyvon. 4.8 -;; 4.9 - 4.10 -(ns indyvon.event 4.11 - (:use indyvon.core) 4.12 - (:import (java.awt.event MouseEvent MouseListener MouseMotionListener) 4.13 - java.lang.ref.WeakReference)) 4.14 - 4.15 -(defn- registered-parent 4.16 - "Returns first context parent registered for event processing." 4.17 - [context-tree context] 4.18 - (let [parent (:parent context)] 4.19 - (cond 4.20 - (nil? parent) nil 4.21 - (contains? context-tree parent) parent 4.22 - :default (recur context-tree parent)))) 4.23 - 4.24 -(defn- add-context 4.25 - [context-tree context] 4.26 - (let [parent (registered-parent context-tree context)] 4.27 - (assoc context-tree parent (cons context (context-tree parent)) 4.28 - context nil))) 4.29 - 4.30 -(defn- inside? 4.31 - ([x y context] 4.32 - (inside? x y (:x context) (:y context) 4.33 - (:width context) (:height context))) 4.34 - ([px py x y w h] 4.35 - (and (>= px x) 4.36 - (>= py y) 4.37 - (< px (+ x w)) 4.38 - (< py (+ y h))))) 4.39 - 4.40 -(defn- under-cursor 4.41 - "Returns a sequence of contexts under cursor." 4.42 - ([context-tree x y] 4.43 - (under-cursor context-tree x y nil)) 4.44 - ([context-tree x y context] 4.45 - (some #(if (inside? x y %) 4.46 - (conj (under-cursor context-tree x y %) %)) 4.47 - (context-tree context)))) 4.48 - 4.49 -(defn- remove-all [coll1 coll2 pred] 4.50 - (filter #(not (some (partial pred %) coll2)) coll1)) 4.51 - 4.52 -(defn- translate-mouse-event 4.53 - [event x y id] 4.54 - (proxy [MouseEvent] [(.getComponent event) 4.55 - id 4.56 - (.getWhen event) 4.57 - (.getModifiers event) 4.58 - (- (.getX event) x) 4.59 - (- (.getY event) y) 4.60 - (.getClickCount event) 4.61 - (.isPopupTrigger event)] 4.62 - (getXOnScreen [] (.getXOnScreen event)) 4.63 - (getYOnScreen [] (.getYOnScreen event)))) 4.64 - 4.65 -(defn- translate-and-dispatch 4.66 - ([contexts event] 4.67 - (translate-and-dispatch contexts event (.getID event))) 4.68 - ([contexts event id] 4.69 - (doseq [context contexts] 4.70 - (handle-mouse 4.71 - (:layer context) context 4.72 - (translate-mouse-event event (:x context) (:y context) id))))) 4.73 - 4.74 -(defn- dispatch-mouse-motion* 4.75 - "Dispatches mouse motion events. Returns a new set of contexts which 4.76 - currently are under cursor." 4.77 - [hovered context-tree event] 4.78 - (let [x (.getX event) 4.79 - y (.getY event) 4.80 - hovered2 (under-cursor context-tree x y) 4.81 - pred #(= (:layer %1) (:layer %2)) 4.82 - exited (remove-all hovered hovered2 pred) 4.83 - entered (remove-all hovered2 hovered pred) 4.84 - moved (remove-all hovered2 entered pred)] 4.85 - (translate-and-dispatch exited event MouseEvent/MOUSE_EXITED) 4.86 - (translate-and-dispatch entered event MouseEvent/MOUSE_ENTERED) 4.87 - (translate-and-dispatch moved event MouseEvent/MOUSE_MOVED) 4.88 - hovered2)) 4.89 - 4.90 -(defn- dispatch-mouse-motion 4.91 - [hovered-ref context-tree event] 4.92 - (dosync 4.93 - (alter hovered-ref dispatch-mouse-motion* context-tree event))) 4.94 - 4.95 -(defn- dispatch-mouse-button* 4.96 - "Dispatches mouse button events. Returns a new set of contexts which 4.97 - currently are picked with a pressed button." 4.98 - [picked hovered event] 4.99 - (translate-and-dispatch hovered event) 4.100 - (if (= (.getID event) MouseEvent/MOUSE_PRESSED) 4.101 - hovered 4.102 - nil)) 4.103 - 4.104 -(defn- dispatch-mouse-button 4.105 - [picked-ref hovered-ref event] 4.106 - (dosync 4.107 - (alter picked-ref dispatch-mouse-button* @hovered-ref event))) 4.108 - 4.109 -(defn make-event-dispatcher [] 4.110 - (let [context-tree-r (ref {}) ; register 4.111 - context-tree (ref {}) ; dispatch 4.112 - hovered (ref '()) 4.113 - picked (ref '())] 4.114 - (reify 4.115 - EventDispatcher 4.116 - (listen! [this component] 4.117 - (doto component 4.118 - (.addMouseListener this) 4.119 - (.addMouseMotionListener this))) 4.120 - (register [this context] 4.121 - (when (satisfies? MouseHandler (:layer context)) 4.122 - (dosync (alter context-tree-r add-context context)))) 4.123 - (commit [this] 4.124 - (dosync (ref-set context-tree @context-tree-r) 4.125 - (ref-set context-tree-r {}))) 4.126 - (picked? [this layer] false) 4.127 - (hovered? [this layer] false) 4.128 - MouseListener 4.129 - (mouseEntered [this event] 4.130 - (dispatch-mouse-motion hovered @context-tree event)) 4.131 - (mouseExited [this event] 4.132 - (dispatch-mouse-motion hovered @context-tree event)) 4.133 - (mouseClicked [this event] 4.134 - (dispatch-mouse-button picked hovered event)) 4.135 - (mousePressed [this event] 4.136 - (dispatch-mouse-button picked hovered event)) 4.137 - (mouseReleased [this event] 4.138 - (dispatch-mouse-button picked hovered event)) 4.139 - MouseMotionListener 4.140 - (mouseDragged [this event] 4.141 - (translate-and-dispatch @picked event)) 4.142 - (mouseMoved [this event] 4.143 - (dispatch-mouse-motion hovered @context-tree event)))))
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/src/indyvon/layers.clj Mon Jun 21 04:00:45 2010 +0400 5.3 @@ -0,0 +1,134 @@ 5.4 +;; 5.5 +;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net> 5.6 +;; 5.7 +;; This file is part of Indyvon. 5.8 +;; 5.9 + 5.10 +(ns indyvon.layers 5.11 + (:use indyvon.core) 5.12 + (:import (java.awt Cursor) 5.13 + (java.awt.font FontRenderContext TextLayout))) 5.14 + 5.15 +;; Define as macro to avoid unnecessary calculation of inner and outer 5.16 +;; sizes in the first case. 5.17 +(defmacro align-xy [inner outer align first center last] 5.18 + `(case ~align 5.19 + ~first 0 5.20 + ~center (/ (- ~outer ~inner) 2) 5.21 + ~last (- ~outer ~inner))) 5.22 + 5.23 +(defmacro align-x [inner outer align] 5.24 + `(align-xy ~inner ~outer ~align :left :center :right)) 5.25 + 5.26 +(defmacro align-y [inner outer align] 5.27 + `(align-xy ~inner ~outer ~align :top :center :bottom)) 5.28 + 5.29 +(defn border-layer 5.30 + "Decorate layer with a border." 5.31 + ([content] 5.32 + (border-layer content 1)) 5.33 + ([content width] 5.34 + (border-layer content width 0)) 5.35 + ([content width gap] 5.36 + (let [offset (+ width gap)] 5.37 + (reify Layer 5.38 + (render! [l c g] 5.39 + (let [w (:width c) 5.40 + h (:height c)] 5.41 + (.setColor g (-> c :theme :border-color)) 5.42 + (doseq [i (range 0 width)] 5.43 + (.drawRect g i i (- w 1 i i) (- h 1 i i))) 5.44 + (draw! content c g offset offset (- w offset offset) 5.45 + (- h offset offset)))) 5.46 + (size [l c] 5.47 + (let [s (size content c)] 5.48 + [(+ (s 0) offset offset) 5.49 + (+ (s 1) offset offset)])))))) 5.50 + 5.51 +(defn- re-split [re s] 5.52 + (seq (.split re s))) 5.53 + 5.54 +(defn- layout-text [lines font font-context] 5.55 + (map #(TextLayout. % font font-context) lines)) 5.56 + 5.57 +(defn- text-width [layouts] 5.58 + (reduce #(max %1 (.getAdvance %2)) 0 layouts)) 5.59 + 5.60 +(defn- text-height [layouts] 5.61 + (reduce #(+ %1 (.getAscent %2) (.getDescent %2) (.getLeading %2)) 5.62 + 0 layouts)) 5.63 + 5.64 +(defn text-layer 5.65 + "Creates a layer to display multiline text." 5.66 + ([text] 5.67 + (text-layer text :left :top)) 5.68 + ([text h-align v-align] 5.69 + (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" text)] 5.70 + (reify Layer 5.71 + (render! [l c g] 5.72 + (let [w (:width c) 5.73 + h (:height c) 5.74 + font (.getFont g) 5.75 + font-context (:font-context c) 5.76 + layouts (layout-text lines font font-context) 5.77 + y (align-y (text-height layouts) h v-align)] 5.78 + (loop [layouts layouts, y y] 5.79 + (when-first [layout layouts] 5.80 + (let [ascent (.getAscent layout) 5.81 + lh (+ ascent (.getDescent layout) (.getLeading layout)) 5.82 + x (align-x (.getAdvance layout) w h-align)] 5.83 + (.draw layout g x (+ y ascent)) 5.84 + (recur (next layouts) (+ y lh))))))) 5.85 + (size [l c] 5.86 + (let [layouts (layout-text lines 5.87 + (-> c :theme :font) 5.88 + (:font-context c)) 5.89 + width (text-width layouts) 5.90 + height (text-height layouts)] 5.91 + [width height])))))) 5.92 + 5.93 +(defn viewport 5.94 + "Creates scrollable viewport layer." 5.95 + ([content] (viewport content :left :top)) 5.96 + ([content h-align v-align] 5.97 + (let [x (ref 0) 5.98 + y (ref 0) 5.99 + fix-x (ref 0) 5.100 + fix-y (ref 0) 5.101 + last-width (ref 0) 5.102 + last-height (ref 0)] 5.103 + (reify 5.104 + Layer 5.105 + (render! [layer c g] 5.106 + (let [anchor (anchor content c h-align v-align) 5.107 + width (:width c) 5.108 + height (:height c)] 5.109 + (dosync 5.110 + (alter x + (align-x width @last-width h-align)) 5.111 + (alter y + (align-y height @last-height v-align)) 5.112 + (ref-set last-width width) 5.113 + (ref-set last-height height)) 5.114 + (draw! content c g 5.115 + (- 0 @x (anchor 0)) 5.116 + (- 0 @y (anchor 1))))) 5.117 + (size [layer c] (size content c)) 5.118 + MouseHandler 5.119 + (handle-mouse [layer c e] 5.120 + (case (:id e) 5.121 + :mouse-pressed 5.122 + (do 5.123 + (dosync 5.124 + (ref-set fix-x (:x-on-screen e)) 5.125 + (ref-set fix-y (:y-on-screen e))) 5.126 + (->> Cursor/MOVE_CURSOR Cursor. (.setCursor (:target c)))) 5.127 + :mouse-released 5.128 + (->> Cursor/DEFAULT_CURSOR Cursor. (.setCursor (:target c))) 5.129 + :mouse-dragged 5.130 + (do 5.131 + (dosync 5.132 + (alter x + (- @fix-x (:x-on-screen e))) 5.133 + (alter y + (- @fix-y (:y-on-screen e))) 5.134 + (ref-set fix-x (:x-on-screen e)) 5.135 + (ref-set fix-y (:y-on-screen e))) 5.136 + (update c)) 5.137 + nil))))))