Mercurial > hg > indyvon
changeset 101:9874107e3e96
Clojure 1.3 compatibility, mouse wheel support, scalable viewport, additional layer implementations.
author | Mikhail Kryshen <mikhail@kryshen.net> |
---|---|
date | Wed, 18 May 2011 20:50:49 +0400 |
parents | f8c8abb84e99 |
children | fd8fb8a3ff5a |
files | project.clj src/net/kryshen/indyvon/component.clj src/net/kryshen/indyvon/core.clj src/net/kryshen/indyvon/demo.clj src/net/kryshen/indyvon/layers.clj |
diffstat | 5 files changed, 362 insertions(+), 107 deletions(-) [+] |
line wrap: on
line diff
--- a/project.clj Wed May 18 19:05:11 2011 +0400 +++ b/project.clj Wed May 18 20:50:49 2011 +0400 @@ -3,7 +3,7 @@ ;;:warn-on-reflection true :dependencies [[org.clojure/clojure "1.2.1"] [com.google.guava/guava "r09"]] - :dev-dependencies [[swank-clojure/swank-clojure "1.3.1"]] + :dev-dependencies [[swank-clojure "1.3.1"]] ;;:aot [net.kryshen.indyvon.core ;; net.kryshen.indyvon.async ;; net.kryshen.indyvon.layers
--- a/src/net/kryshen/indyvon/component.clj Wed May 18 19:05:11 2011 +0400 +++ b/src/net/kryshen/indyvon/component.clj Wed May 18 20:50:49 2011 +0400 @@ -1,5 +1,5 @@ ;; -;; Copyright 2010 Mikhail Kryshen <mikhail@kryshen.net> +;; Copyright 2010, 2011 Mikhail Kryshen <mikhail@kryshen.net> ;; ;; This file is part of Indyvon. ;; @@ -25,12 +25,18 @@ (net.kryshen.indyvon.core Size Bounds) (java.awt Graphics Component Dimension Color) (java.awt.geom Rectangle2D$Double) - (javax.swing JFrame JPanel))) + (javax.swing JFrame JPanel JOptionPane))) -(defn- font-context [^Component component] +(defn font-context [^Component component] (.getFontRenderContext (.getFontMetrics component (.getFont component)))) -(defn make-jpanel +(defmacro with-component [component & body] + `(let [c# ~component] + (binding [*target* c# + *font-context* (font-context c#)] + ~@body))) + +(defn ^JPanel make-jpanel ([layer] (make-jpanel layer (root-event-dispatcher))) ([layer event-dispatcher] @@ -59,3 +65,6 @@ (doto (JFrame. title) (.. (getContentPane) (add (make-jpanel layer))) (.pack))) + +(defn message [m] + (JOptionPane/showMessageDialog *target* m))
--- a/src/net/kryshen/indyvon/core.clj Wed May 18 19:05:11 2011 +0400 +++ b/src/net/kryshen/indyvon/core.clj Wed May 18 20:50:49 2011 +0400 @@ -21,7 +21,8 @@ (:import (java.awt Graphics2D RenderingHints Component Color Font Shape) (java.awt.geom AffineTransform Point2D$Double Rectangle2D$Double Area) - (java.awt.event MouseListener MouseMotionListener) + (java.awt.event MouseListener MouseMotionListener + MouseWheelListener MouseWheelEvent) (java.awt.font FontRenderContext) java.util.concurrent.ConcurrentMap com.google.common.collect.MapMaker)) @@ -30,48 +31,67 @@ ;; Layer context ;; -(def ^Graphics2D *graphics*) +(def ^{:dynamic true + :tag Graphics2D} + *graphics*) -(def ^FontRenderContext *font-context*) +(def ^{:dynamic true + :tag FontRenderContext} + *font-context*) -(def ^{:tag Component +(def ^{:dynamic true + :tag Component :doc "Target AWT component, may be nil if drawing off-screen."} - *target*) + *target*) -(def ^{:doc "Width of the rendering area."} - *width*) +(def ^{:dynamic true + :doc "Width of the rendering area."} + *width*) -(def ^{:doc "Height of the rendering area."} - *height*) +(def ^{:dynamic true + :doc "Height of the rendering area."} + *height*) -(def ^Shape *clip*) +(def ^{:dynamic true + :tag Shape} + *clip*) -(def ^{:doc "The root (background) layer of the scene."} - *root*) +(def ^{:dynamic true + :doc "The root (background) layer of the scene."} + *root*) -(def ^{:doc "Time in nanoseconds when the rendering of the current +(def ^{:dynamic true + :doc "Time in nanoseconds when the rendering of the current frame starts."} - *time*) + *time*) -(def *event-dispatcher*) +(def ^{:dynamic true} + *event-dispatcher*) -(def ^{:tag AffineTransform +(def ^{:dynamic true + :tag AffineTransform :doc "Initial transform associated with the graphics context."} - *initial-transform*) + *initial-transform*) -(def ^{:tag AffineTransform +(def ^{:dynamic true + :tag AffineTransform :doc "Inversion of the initial transform associated with the graphics context."} - *inverse-initial-transform*) + *inverse-initial-transform*) -(defrecord Theme [fore-color back-color alt-back-color border-color font]) +(defrecord Theme [fore-color back-color alt-back-color border-color + shadow-color font]) ;; REMIND: use system colors, see java.awt.SystemColor. (defn default-theme [] - (Theme. Color/BLACK Color/WHITE Color/LIGHT_GRAY - Color/BLUE (Font. "Sans" Font/PLAIN 12))) + (Theme. Color/BLACK + Color/WHITE + (Color. 0xC8 0xD2 0xD8) + (Color. 0 0 0xC8) + (Color. 0x44 0x44 0x44) + (Font. "Sans" Font/PLAIN 12))) -(def *theme* (default-theme)) +(def ^{:dynamic true} *theme* (default-theme)) (defrecord Location [x y]) (defrecord Size [width height]) @@ -87,12 +107,13 @@ (layer-size [this])) ;; TODO: modifiers -(defrecord MouseEvent [id when x y x-on-screen y-on-screen button]) +(defrecord MouseEvent [id when x y x-on-screen y-on-screen button + wheel-rotation]) ;; TODO: KeyEvent (defprotocol EventDispatcher - (listen! [this ^Component component] + (listen! [this component] "Listen for events on the specified AWT Component.") (create-dispatcher [this handle handlers] "Returns new event dispatcher associated with the specified event @@ -275,16 +296,28 @@ ([] (apply-theme *graphics* *theme*)) ([^Graphics2D graphics theme] - (doto graphics - (.setColor (:fore-color theme)) - (.setFont (:font theme))))) + (doto graphics + (.setColor (:fore-color theme)) + (.setFont (:font theme))))) (defn- ^Graphics2D create-graphics ([] - (create-graphics 0 0 *width* *height*)) + (apply-theme (.create *graphics*) *theme*)) ([x y w h] (apply-theme (.create *graphics* x y w h) *theme*))) +(defn- with-bounds-noclip* + [x y w h f & args] + (let [graphics (create-graphics)] + (try + (.translate graphics (int x) (int y)) + (binding [*width* w + *height* h + *graphics* graphics] + (apply f args)) + (finally + (.dispose graphics))))) + (defn with-bounds* [x y w h f & args] (when-let [clip (clip x y w h)] @@ -320,6 +353,23 @@ (finally (.setColor *graphics* old-color#)))))) +(defn with-hints* + [hints f & args] + (if hints + (let [g *graphics* + old (.getRenderingHints g)] + (try + (.addRenderingHints g hints) + (binding [*font-context* (.getFontRenderContext g)] + (apply f args)) + (finally + (.setRenderingHints g old)))) + (apply f args))) + +(defmacro with-hints + [hints & body] + `(with-hints ~hints (fn [] ~@body))) + ;; TODO: constructor for AffineTransform. ;; (transform :scale 0.3 0.5 ;; :translate 5 10 @@ -337,6 +387,15 @@ `(let [transform# (AffineTransform/getRotateInstance ~theta ~ax ~ay)] (with-transform transform# ~@body))) +(defmacro with-translate [x y & body] + `(let [x# ~x + y# ~y] + (try + (.translate *graphics* x# y#) + ~@body + (finally + (.translate *graphics* (- x#) (- y#)))))) + (defn draw! "Draws layer." ([layer] @@ -347,10 +406,16 @@ (finally (.dispose graphics))))) ([layer x y] + (draw! layer x y true)) + ([layer x y clip?] (let [size (layer-size layer)] - (draw! layer x y (:width size) (:height size)))) + (draw! layer x y (:width size) (:height size) clip?))) ([layer x y width height] - (with-bounds* x y width height render! layer))) + (draw! layer x y width height true)) + ([layer x y width height clip?] + (if clip? + (with-bounds* x y width height render! layer) + (with-bounds-noclip* x y width height render! layer)))) (defn draw-anchored! "Draws layer. Location is relative to the layer's anchor point for @@ -367,6 +432,18 @@ ([layer graphics width height event-dispatcher] (draw-root! layer graphics width height event-dispatcher nil)) ([layer ^Graphics2D graphics width height event-dispatcher target] + ;; (.setRenderingHint graphics + ;; RenderingHints/KEY_INTERPOLATION + ;; RenderingHints/VALUE_INTERPOLATION_BILINEAR) + ;; (.setRenderingHint graphics + ;; RenderingHints/KEY_ALPHA_INTERPOLATION + ;; RenderingHints/VALUE_ALPHA_INTERPOLATION_QUALITY) + ;; (.setRenderingHint graphics + ;; RenderingHints/KEY_ANTIALIASING + ;; RenderingHints/VALUE_ANTIALIAS_ON) + ;; (.setRenderingHint graphics + ;; RenderingHints/KEY_TEXT_ANTIALIASING + ;; RenderingHints/VALUE_TEXT_ANTIALIAS_ON) (binding [*root* layer *target* target *graphics* graphics @@ -379,15 +456,6 @@ *height* height *clip* (Rectangle2D$Double. 0 0 width height) *time* (System/nanoTime)] - ;; (.setRenderingHint graphics - ;; RenderingHints/KEY_INTERPOLATION - ;; RenderingHints/VALUE_INTERPOLATION_BILINEAR) - ;; (.setRenderingHint graphics - ;; RenderingHints/KEY_ALPHA_INTERPOLATION - ;; RenderingHints/VALUE_ALPHA_INTERPOLATION_QUALITY) - ;; (.setRenderingHint graphics - ;; RenderingHints/KEY_ANTIALIASING - ;; RenderingHints/VALUE_ANTIALIAS_ON) (apply-theme) (let [tmp-watcher (Object.)] ;; Keep current context observers until the rendering is @@ -438,7 +506,6 @@ (defn hovered? [handle] (handle-hovered? *event-dispatcher* handle)) - ;; ;; EventDispatcher implementation ;; @@ -450,7 +517,8 @@ java.awt.event.MouseEvent/MOUSE_EXITED :mouse-exited java.awt.event.MouseEvent/MOUSE_MOVED :mouse-moved java.awt.event.MouseEvent/MOUSE_PRESSED :mouse-pressed - java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released}) + java.awt.event.MouseEvent/MOUSE_RELEASED :mouse-released + java.awt.event.MouseEvent/MOUSE_WHEEL :mouse-wheel}) (def dummy-event-dispatcher (reify @@ -484,6 +552,9 @@ (defn- add-node [tree node] (assoc-cons tree (:parent node) node)) +(defn- nodes [tree] + (apply concat (vals tree))) + (defn- under-cursor "Returns a vector of child nodes under cursor." [x y tree node] @@ -496,22 +567,26 @@ (defn- translate-mouse-event [^java.awt.event.MouseEvent event ^AffineTransform tr id] - (let [[x y] (transform-point tr (.getX event) (.getY event))] + (let [[x y] (transform-point tr (.getX event) (.getY event)) + rotation (if (instance? MouseWheelEvent event) + (.getWheelRotation ^MouseWheelEvent event) + nil)] (MouseEvent. id (.getWhen event) x y (.getXOnScreen event) (.getYOnScreen event) - (.getButton event)))) + (.getButton event) + rotation))) (defn- translate-and-dispatch ([nodes first-only ^java.awt.event.MouseEvent event] (translate-and-dispatch nodes first-only - event (awt-events (.getID event)))) + event (awt-events (.getID event)))) ([nodes first-only event id] (if-let [node (first nodes)] (if-let [handler (get (:handlers node) id)] (do - (with-bindings* (:bindings node) - handler - (translate-mouse-event event (:transform node) id)) + (let [translated (translate-mouse-event event (:transform node) id)] + (with-bindings* (:bindings node) + handler translated)) (if-not first-only (recur (rest nodes) false event id))) (recur (rest nodes) first-only event id))))) @@ -556,14 +631,17 @@ (reify EventDispatcher (listen! [this component] - (doto component + (doto ^Component component (.addMouseListener this) + (.addMouseWheelListener this) (.addMouseMotionListener this))) (create-dispatcher [this handle handlers] (let [node (make-node handle handlers)] (dosync (alter tree-r add-node node)) node)) (commit [this] + ;; TODO: retain contexts that do not intersect graphics + ;; clipping area in tree. (dosync (ref-set tree @tree-r) (ref-set tree-r {}))) (handle-picked? [this handle] @@ -581,6 +659,9 @@ (dispatch-mouse-button picked hovered event)) (mouseReleased [this event] (dispatch-mouse-button picked hovered event)) + MouseWheelListener + (mouseWheelMoved [this event] + (dispatch-mouse-button picked hovered event)) MouseMotionListener (mouseDragged [this event] (translate-and-dispatch @picked true event))
--- a/src/net/kryshen/indyvon/demo.clj Wed May 18 19:05:11 2011 +0400 +++ b/src/net/kryshen/indyvon/demo.clj Wed May 18 20:50:49 2011 +0400 @@ -1,5 +1,5 @@ ;; -;; Copyright 2010 Mikhail Kryshen <mikhail@kryshen.net> +;; Copyright 2010, 2011 Mikhail Kryshen <mikhail@kryshen.net> ;; ;; This file is part of Indyvon. ;;
--- a/src/net/kryshen/indyvon/layers.clj Wed May 18 19:05:11 2011 +0400 +++ b/src/net/kryshen/indyvon/layers.clj Wed May 18 20:50:49 2011 +0400 @@ -24,8 +24,9 @@ (:import (net.kryshen.indyvon.core Size Location) (java.lang.ref SoftReference) - (java.awt Font Cursor Image Toolkit) - (java.awt.image ImageObserver) + (java.awt Font Cursor Image Toolkit Point) + java.awt.image.ImageObserver + (java.awt.geom AffineTransform Point2D$Double) (java.awt.font FontRenderContext TextLayout))) ;; Define as macro to avoid unnecessary calculation of inner and outer @@ -87,6 +88,32 @@ (- *height* 1 i i)))) (render! layer))))) +;; TODO: opacity and blur. +(defn shadow + "Add shadow to content layer." + ([content] + (shadow content 1 1)) + ([content x-offset y-offset] + (let [x (if (neg? x-offset) (- x-offset) 0) + y (if (neg? y-offset) (- y-offset) 0) + abs-x (if (neg? x-offset) (- x-offset) x-offset) + abs-y (if (neg? y-offset) (- y-offset) y-offset) + shadow-x (+ x-offset x) + shadow-y (+ y-offset y)] + (reify + ;; TODO: Anchored + Layer + (render! [_] + (let [w (- *width* abs-x) + h (- *height* abs-y)] + (with-color :shadow-color + (.fillRect *graphics* shadow-x shadow-y w h)) + (draw! content x y w h))) + (layer-size [_] + (let [s (layer-size content)] + (Size. (+ (:width s) abs-x) + (+ (:height s) abs-y)))))))) + (defn panel "Opaque layer using theme's alt-back-color." ([content] @@ -105,20 +132,41 @@ (reify Layer (render! [_] - ;; TODO: distribute space proportionally. - (let [w (/ *width* (count contents))] - (doseq [[i c] (map-indexed vector contents)] - (draw! c (* i w) 0 w *height*)))) + (let [widths (map #(:width (layer-size %)) contents) + xs (cons 0 (reductions + widths)) + widths-sum (last xs) + scale (/ *width* widths-sum)] + (doseq [[c w x] (map vector contents widths xs)] + (draw! c x 0 w *height*)))) (layer-size [_] (reduce #(Size. (+ (:width %1) (:width %2)) (max (:height %1) (:height %2))) (Size. 0 0) (map layer-size contents))))) +(defn vbox + "Creates layer that draws the specified content layers placing them + vertically." + [& contents] + (reify + Layer + (render! [_] + (let [heights (map #(:height (layer-size %)) contents) + ys (cons 0 (reductions + heights)) + heights-sum (last ys) + scale (/ *height* heights-sum)] + (doseq [[c h y] (map vector contents heights ys)] + (draw! c 0 y *width* h)))) + (layer-size [_] + (reduce #(Size. (max (:width %1) (:width %2)) + (+ (:height %1) (:height %2))) + (Size. 0 0) + (map layer-size contents))))) + (defn- re-split [^java.util.regex.Pattern re s] (seq (.split re s))) -(def text-layout-cache (atom {})) +(def ^:private text-layout-cache (atom {})) (defn- get-text-layout [^String line ^Font font ^FontRenderContext font-context] @@ -152,7 +200,7 @@ ([text] (label text :left :top)) ([text h-align v-align] - (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" text)] + (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" (str text))] (reify Layer (render! [layer] (let [w *width* @@ -184,7 +232,7 @@ (defn image-layer [image-or-uri] - (let [^Image image (if (isa? image-or-uri Image) + (let [^Image image (if (instance? Image image-or-uri) image-or-uri (.getImage (Toolkit/getDefaultToolkit) ^java.net.URL image-or-uri))] @@ -202,13 +250,13 @@ height (if (pos? height) height 1)] (Size. width height)))))) -(def *miniature-thread-priority* 2) +(def ^{:dynamic true} *miniature-thread-priority* 2) (defn- scaling [width height max-width max-height] (min (/ max-width width) (/ max-height height))) - + (defn miniature "Creates layer that asynchronously renders view of the content scaled to the specified size." @@ -231,25 +279,50 @@ ;; (Size. (* (:width size) s) (* (:height size) s))))) width height *miniature-thread-priority*)) +;;(defn- translate [^AffineTransform transform ^double x ^double y] +;; (doto ^AffineTransform (.clone transform) +;; (.translate x y))) + +(defn- scale [^AffineTransform transform ^double sx ^double sy] + (doto ^AffineTransform (.clone transform) + (.scale sx sy))) + +(defn- pre-translate [^AffineTransform transform ^double x ^double y] + (if (== 0.0 x y) + transform + (doto (AffineTransform/getTranslateInstance x y) + (.concatenate transform)))) + +(def ^{:dynamic true} *viewport-scaling-step* (double 3/4)) +(def ^{:dynamic true} *viewport-min-scale* 1E-6) +(def ^{:dynamic true} *viewport-max-scale* 1E6) + (defrecord Viewport [content h-align v-align ;; State (refs) - ;; TODO: group into data structures. - x y fix-x fix-y last-width last-height - vp-x vp-y] + transform + fix-x fix-y + last-width last-height last-anchor] Layer (render! [layer] (repaint-on-update layer) (with-handlers layer (let [anchor (anchor content h-align v-align)] (dosync - (alter x + (align-x *width* @last-width h-align)) - (alter y + (align-y *height* @last-height v-align)) + (let [ax1 (align-x @last-width *width* h-align) + ay1 (align-y @last-height *height* v-align) + ax2 (- (:x @last-anchor) (:x anchor)) + ay2 (- (:y @last-anchor) (:y anchor))] + (if-not (and (zero? ax1) (zero? ay1) (zero? ax2) (zero? ay2)) + (ref-set transform + (doto (AffineTransform/getTranslateInstance ax1 ay1) + (.concatenate @transform) + (.translate ax2 ay2))))) (ref-set last-width *width*) (ref-set last-height *height*) - (ref-set vp-x (+ @x (:x anchor))) - (ref-set vp-y (+ @y (:y anchor)))) + (ref-set last-anchor anchor)) ;; TODO: notify observers when size changes. - (draw! content (- @vp-x) (- @vp-y))) + (with-transform @transform + (draw! content 0 0 false))) (:mouse-pressed e (dosync (ref-set fix-x (:x-on-screen e)) @@ -261,29 +334,49 @@ (->> Cursor/DEFAULT_CURSOR Cursor. (.setCursor *target*)))) (:mouse-dragged e (dosync - (alter x + (- @fix-x (:x-on-screen e))) - (alter y + (- @fix-y (:y-on-screen e))) + (alter transform pre-translate + (- (:x-on-screen e) @fix-x) + (- (:y-on-screen e) @fix-y)) (ref-set fix-x (:x-on-screen e)) (ref-set fix-y (:y-on-screen e))) + (update layer)) + (:mouse-wheel e + (dosync + (let [s (Math/pow *viewport-scaling-step* (:wheel-rotation e)) + x (- (:x e) (* (:x e) s)) + y (- (:y e) (* (:y e) s)) + scaled (doto (AffineTransform/getTranslateInstance x y) + (.scale s s) + (.concatenate @transform)) + sx (.getScaleX scaled) + sy (.getScaleY scaled)] + (if (<= *viewport-min-scale* + (min sx sy) + (max sx sy) + *viewport-max-scale*) + (ref-set transform scaled)))) (update layer)))) (layer-size [layer] (layer-size content))) (defn viewport "Creates scrollable viewport layer." - ([content] (viewport content :left :top)) + ([content] + (viewport content :left :top)) ([content h-align v-align] (Viewport. content h-align v-align - (ref 0) (ref 0) ; x y - (ref 0) (ref 0) ; fix-x fix-y - (ref 0) (ref 0) ; last-width last-height - (ref 0) (ref 0)))) ; vp-x vp-y + (ref (AffineTransform.)) ; transform + (ref 0) (ref 0) ; fix-x fix-y + (ref 0) (ref 0) ; last-width last-height + (ref (Location. 0 0))))) ; last-anchor -(defn- viewport-visible-bounds - [vp] +(defn reset-viewport [viewport] (dosync - [@(:vp-x vp) @(:vp-y vp) - @(:last-width vp) @(:last-height vp)])) + (ref-set (:last-width viewport) 0) + (ref-set (:last-height viewport) 0) + (ref-set (:last-anchor viewport) (Location. 0 0)) + (ref-set (:transform viewport) (AffineTransform.))) + (update viewport)) (defn viewport-miniature "Creates miniature view of the viewport's contents." @@ -293,36 +386,60 @@ (repaint-on-update viewport) (let [size (layer-size (:content viewport)) s (scaling (:width size) (:height size) width height) - [x y w h] (viewport-visible-bounds viewport) + [vp-tr w h] (dosync + [@(:transform viewport) + @(:last-width viewport) + @(:last-height viewport)]) + vp-inverse (.createInverse ^AffineTransform vp-tr) ox (align-x (:width size) (/ width s) :center) oy (align-y (:height size) (/ height s) :center) - sx (* (+ x ox) s) - sy (* (+ y oy) s) - sw (* w s) - sh (* h s) + transform (doto (AffineTransform.) + (.scale s s) + (.translate ox oy) + (.concatenate vp-inverse)) move-vp (fn [x y] (dosync - (ref-set (:x viewport) - (- (/ x s) - (/ w 2) - ox - (- @(:vp-x viewport) @(:x viewport)))) - (ref-set (:y viewport) - (- (/ y s) - (/ h 2) - oy - (- @(:vp-y viewport) @(:y viewport))))) + (let [x (- (/ x s) ox) + y (- (/ y s) oy) + [x y] (transform-point @(:transform viewport) + x y) + x (- x (/ @(:last-width viewport) 2)) + y (- y (/ @(:last-height viewport) 2))] + (alter (:transform viewport) + pre-translate (- x) (- y)))) (update viewport))] (with-color :alt-back-color (.fillRect *graphics* 0 0 *width* *height*)) - (with-color :back-color - (.fillRect *graphics* sx sy sw sh)) + (with-transform transform + (with-color :back-color + (.fillRect *graphics* 0 0 w h))) (with-handlers l (draw! miniature) (:mouse-pressed e (move-vp (:x e) (:y e))) (:mouse-dragged e (move-vp (:x e) (:y e)))) - (with-color :border-color - (.drawRect *graphics* sx sy sw sh)))))) + (with-transform transform + (with-color :border-color + (.drawRect *graphics* 0 0 w h))))))) + +(defn ref-layer + [layer-ref] + (let [l (reify + Layer + (render! [l] + (repaint-on-update l) + (if-let [layer @layer-ref] + (render! layer))) + (layer-size [_] + (if-let [layer @layer-ref] + (layer-size layer) + (Size. 1 1))) + Anchored + (anchor [_ x-align y-align] + (if-let [layer @layer-ref] + (anchor layer x-align y-align) + (Location. 0 0))))] + (add-watch layer-ref l (fn [_ _ _ _] (update l))) + l)) ;; ;; Layer context decorators. @@ -336,9 +453,9 @@ (render! layer#) ~@handlers)))) -(defn theme [layer & map-or-keyvals] +(defn themed [layer & map-or-keyvals] (let [theme (if (== (count map-or-keyvals) 1) - map-or-keyvals + (first map-or-keyvals) (apply array-map map-or-keyvals))] (reify Layer @@ -353,11 +470,18 @@ (with-theme theme (anchor layer xa ya)))))) +(defn hinted [layer & map-or-keyvals] + (let [hints (if (== (count map-or-keyvals) 1) + (first map-or-keyvals) + (apply array-map map-or-keyvals))] + (decorate-layer layer [_] + (with-hints* hints render! layer)))) + ;; ;; Measuring time ;; -(def *interval*) +(def ^{:dynamic true} *interval*) (defn interval-layer "Creates layer that measures time between repaints ant draws it's @@ -397,3 +521,44 @@ (ref-set prev-time *time*)))) (ref-set prev-time *time*)) @display))))) + +;; +;; Overlayer. +;; + +(def ^{:private true :dynamic true} *above*) + +(defn- overlay* [f & args] + (var-set #'*above* (conj *above* (apply partial f args)))) + +(defn- ^Point to-graphics-coords + [^AffineTransform transform x y] + (let [p (Point. x y)] + (.transform transform p p) + (.transform (.createInverse (.getTransform *graphics*)) p p) + p)) + +(defn- draw-relative! + ([layer transform x y] + (let [p (to-graphics-coords transform x y)] + (draw! layer (.x p) (.y p)))) + ([layer transform x y w h] + (let [p (to-graphics-coords transform x y)] + (draw! layer (.x p) (.y p) w h)))) + +(defn overlay! + "Draws layer in the overlayer context above the other layers." + ([layer] + (overlay* draw-relative! layer (.getTransform *graphics*) 0 0)) + ([layer x y] + (overlay* draw-relative! layer (.getTransform *graphics*) x y)) + ([layer x y w h] + (overlay* draw-relative! layer (.getTransform *graphics*) x y w h))) + +(defn overlayer + [content] + (decorate-layer content [_] + (binding [*above* []] + (render! content) + (doseq [f *above*] + (f)))))