Mercurial > hg > indyvon
changeset 151:cb108c6fa079
Layers are now called Views.
author | Mikhail Kryshen <mikhail@kryshen.net> |
---|---|
date | Mon, 07 Apr 2014 15:23:58 +0400 |
parents | 86d0358d38c7 |
children | 9997ac717c3c |
files | 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/layers.clj src/net/kryshen/indyvon/viewport.clj src/net/kryshen/indyvon/views.clj |
diffstat | 7 files changed, 542 insertions(+), 541 deletions(-) [+] |
line wrap: on
line diff
--- a/src/net/kryshen/indyvon/async.clj Mon Apr 07 14:24:16 2014 +0400 +++ b/src/net/kryshen/indyvon/async.clj Mon Apr 07 15:23:58 2014 +0400 @@ -1,5 +1,5 @@ ;; -;; Copyright 2010, 2011 Mikhail Kryshen <mikhail@kryshen.net> +;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> ;; ;; This file is part of Indyvon. ;; @@ -40,15 +40,15 @@ ;; :free ;; not in use -(defn- create-image [async-layer ^GraphicsConfiguration device-conf] +(defn- create-image [async-view ^GraphicsConfiguration device-conf] ;; TODO: support different image types. (.createCompatibleImage device-conf - (:width async-layer) - (:height async-layer) + (:width async-view) + (:height async-view) Transparency/TRANSLUCENT)) -(defn- create-buffer [async-layer device-conf] - (Buffer. (Object.) (create-image async-layer device-conf) 0 :free)) +(defn- create-buffer [async-view device-conf] + (Buffer. (Object.) (create-image async-view device-conf) 0 :free)) (defn- find-buffer "Find a buffer with the one of the specified states given @@ -110,40 +110,40 @@ (finally (release-buffer al# ~name))))) -(defn- draw-offscreen [async-layer] +(defn- draw-offscreen [async-view] ;;(Thread/sleep 1000) - (with-buffer async-layer :back [b] + (with-buffer async-view :back [b] (let [g (.createGraphics ^BufferedImage (:image b))] ;; Clear the buffer. (.setComposite g AlphaComposite/Clear) - (.fillRect g 0 0 (:width async-layer) (:height async-layer)) + (.fillRect g 0 0 (:width async-view) (:height async-view)) (.setComposite g AlphaComposite/Src) - (draw-scene! (:scene async-layer) + (draw-scene! (:scene async-view) g - (:width async-layer) - (:height async-layer))) - (update async-layer))) + (:width async-view) + (:height async-view))) + (update async-view))) -(defn- draw-offscreen-async [async-layer] - (.execute ^ThreadPoolExecutor (:executor async-layer) - #(draw-offscreen async-layer))) +(defn- draw-offscreen-async [async-view] + (.execute ^ThreadPoolExecutor (:executor async-view) + #(draw-offscreen async-view))) -(defrecord AsyncLayer [scene width height executor buffers] - Layer - (render! [layer] - (repaint-on-update layer) - (add-context-observer scene (fn [_ _] (draw-offscreen-async layer))) +(defrecord AsyncView [scene width height executor buffers] + View + (render! [view] + (repaint-on-update view) + (add-context-observer scene (fn [_ _] (draw-offscreen-async view))) (when-not @buffers ;; TODO: dynamic size, recreate buffers when size increases. (let [device-conf (.getDeviceConfiguration *graphics*) new-buffers (repeatedly 2 - (partial create-buffer layer device-conf))] + (partial create-buffer view device-conf))] (dosync (ref-set buffers new-buffers))) - (draw-offscreen-async layer)) - (with-buffer layer :front [b] + (draw-offscreen-async view)) + (with-buffer view :front [b] (.drawImage *graphics* ^Image (:image b) 0 0 nil))) - (geometry [layer] + (geometry [view] (->Size width height))) (defn- create-thread-factory [priority] @@ -164,15 +164,15 @@ (ThreadPoolExecutor$DiscardOldestPolicy.)) (.setThreadFactory (create-thread-factory priority)))) -(defn async-layer - "Creates layer that draws the content asynchronously using +(defn async-view + "Creates a View that draws the content asynchronously using an offscreen buffer." ([content width height] - (async-layer content width height nil)) + (async-view content width height nil)) ([content width height priority] ;; TODO: use operational event dispatcher. - (->AsyncLayer (make-scene content) - width - height - (create-executor priority) - (ref nil)))) + (->AsyncView (make-scene content) + width + height + (create-executor priority) + (ref nil))))
--- a/src/net/kryshen/indyvon/component.clj Mon Apr 07 14:24:16 2014 +0400 +++ b/src/net/kryshen/indyvon/component.clj Mon Apr 07 15:23:58 2014 +0400 @@ -1,5 +1,5 @@ ;; -;; Copyright 2010, 2011, 2012 Mikhail Kryshen <mikhail@kryshen.net> +;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> ;; ;; This file is part of Indyvon. ;; @@ -38,12 +38,12 @@ (Dimension. (width geom) (height geom)))) (defn ^JPanel make-jpanel - ([layer] - (make-jpanel layer (root-event-dispatcher))) - ([layer event-dispatcher] + ([view] + (make-jpanel view (root-event-dispatcher))) + ([view event-dispatcher] (let [panel (proxy [JPanel] []) scene (make-scene - layer event-dispatcher panel + view event-dispatcher panel (.getDesktopProperty (java.awt.Toolkit/getDefaultToolkit) "awt.font.desktophints"))] (update-proxy @@ -60,9 +60,9 @@ (listen! event-dispatcher panel) panel))) -(defn ^JFrame make-jframe [^String title layer] +(defn ^JFrame make-jframe [^String title view] (doto (JFrame. title) - (.. (getContentPane) (add (make-jpanel layer))) + (.. (getContentPane) (add (make-jpanel view))) (.pack))) (defn message [m]
--- a/src/net/kryshen/indyvon/core.clj Mon Apr 07 14:24:16 2014 +0400 +++ b/src/net/kryshen/indyvon/core.clj Mon Apr 07 15:23:58 2014 +0400 @@ -1,5 +1,5 @@ ;; -;; Copyright 2010, 2011, 2012, 2013 Mikhail Kryshen <mikhail@kryshen.net> +;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> ;; ;; This file is part of Indyvon. ;; @@ -29,7 +29,7 @@ com.google.common.collect.MapMaker)) ;; -;; Layer context +;; View context ;; (def ^:dynamic ^Graphics2D *graphics*) @@ -89,15 +89,15 @@ ;; Core protocols and types ;; -(defprotocol Layer +(defprotocol View "Basic UI element." - (render! [layer] - "Draws layer in the current *graphics* context.") - (geometry [layer] - "Returns the preferred layer Geometry.")) + (render! [view] + "Draws the view in the current *graphics* context.") + (geometry [view] + "Returns the preferred Geometry for the view.")) (defprotocol Geometry - "Describes geometry of a Layer. Prefer using the available + "Describes geometry of a View. Prefer using the available implementations (Size, FixedGeometry and NestedGeometry) over extending this protocol directly as it is likely to be changed in the future versions." @@ -234,7 +234,7 @@ ;; ;; Observers -;; The mechanism used by layers to request repaints +;; The mechanism used by views to request repaints ;; (def ^ConcurrentMap observers @@ -318,24 +318,24 @@ ;; (defn ^FontRenderContext font-context - "Returns FontRenderContext for the current Layer context." + "Returns FontRenderContext for the current view context." [] (if (bound? (var *graphics*)) (.getFontRenderContext *graphics*) *font-context*)) (defn ^AffineTransform relative-transform - "Returns AffineTransform: layer context -> AWT component." + "Returns AffineTransform: view context -> AWT component." [] (let [tr (.getTransform *graphics*)] (.preConcatenate tr *inverse-initial-transform*) tr)) (defn ^AffineTransform inverse-relative-transform - "Returns AffineTransform: AWT component -> layer context." + "Returns AffineTransform: AWT component -> view context." [] (let [tr (.getTransform *graphics*)] - (.invert tr) ; absolute -> layer + (.invert tr) ; absolute -> view (.concatenate tr *initial-transform*) ; component -> absolute tr)) @@ -502,40 +502,40 @@ (.translate g# (- x#) (- y#)))))) (defn draw! - "Draws layer." - ([layer] + "Draws the View." + ([view] (let [graphics (create-graphics)] (try (binding [*graphics* graphics] - (render! layer)) + (render! view)) (finally (.dispose graphics))))) - ([layer x y] - (draw! layer x y true)) - ([layer x y clip?] - (let [geom (geometry layer)] - (draw! layer x y (width geom) (height geom) clip?))) - ([layer x y width height] - (draw! layer x y width height true)) - ([layer x y width height clip?] + ([view x y] + (draw! view x y true)) + ([view x y clip?] + (let [geom (geometry view)] + (draw! view x y (width geom) (height geom) clip?))) + ([view x y width height] + (draw! view x y width height true)) + ([view x y width height clip?] (if clip? - (with-bounds* x y width height render! layer) - (with-bounds-noclip* x y width height render! layer)))) + (with-bounds* x y width height render! view) + (with-bounds-noclip* x y width height render! view)))) (defn draw-aligned! - "Draws layer. Location is relative to the layer's anchor point for - the specified alignment." - ([layer h-align v-align x y] - (let [geom (geometry layer) + "Draws the View. Location is relative to the view's anchor point + for the specified alignment." + ([view h-align v-align x y] + (let [geom (geometry view) w (width geom) h (height geom)] - (draw! layer + (draw! view (- x (anchor-x geom h-align w)) (- y (anchor-y geom v-align h)) w h))) - ([layer h-align v-align x y w h] - (let [geom (geometry layer)] - (draw! layer + ([view h-align v-align x y w h] + (let [geom (geometry view)] + (draw! view (- x (anchor-x geom h-align w)) (- y (anchor-y geom v-align h)) w h)))) @@ -792,7 +792,7 @@ ;; Scene ;; -(defrecord Scene [layer +(defrecord Scene [view event-dispatcher component rendering-hints @@ -807,15 +807,15 @@ RenderingHints/VALUE_FRACTIONALMETRICS_DEFAULT}) (defn make-scene - ([layer] - (make-scene layer dummy-event-dispatcher nil)) - ([layer event-dispatcher] - (make-scene layer event-dispatcher nil)) - ([layer event-dispatcher ^Component component] - (make-scene layer event-dispatcher component nil)) - ([layer event-dispatcher ^Component component hints] + ([view] + (make-scene view dummy-event-dispatcher nil)) + ([view event-dispatcher] + (make-scene view event-dispatcher nil)) + ([view event-dispatcher ^Component component] + (make-scene view event-dispatcher component nil)) + ([view event-dispatcher ^Component component hints] (let [hints (merge default-rendering-hints hints)] - (->Scene layer + (->Scene view event-dispatcher component hints @@ -853,7 +853,7 @@ ;; appear in both groups until tmp-watcher is removed. (replace-observers-watcher scene tmp-watcher) (try - (render! (:layer scene)) + (render! (:view scene)) (finally (remove-observers tmp-watcher) (commit (:event-dispatcher scene))))))) @@ -874,7 +874,7 @@ (defn scene-geometry [scene] (binding [*scene* scene *font-context* (scene-font-context scene)] - (geometry (:layer scene)))) + (geometry (:view scene)))) (defn set-cursor! [^Cursor cursor] (when-let [^Component component (:component *scene*)]
--- a/src/net/kryshen/indyvon/demo.clj Mon Apr 07 14:24:16 2014 +0400 +++ b/src/net/kryshen/indyvon/demo.clj Mon Apr 07 15:23:58 2014 +0400 @@ -21,14 +21,14 @@ "Indyvon demo and experiments." (:gen-class) (:use - (net.kryshen.indyvon core layers viewport component)) + (net.kryshen.indyvon core views viewport component)) (:import (java.awt Color) (javax.swing JFrame))) (defn draw-button! - "Draws button immediately (but uses callback for button action - unlike IMGUI)." + "Draws a button immediately (but uses callback for the action unlike + IMGUI)." [id content callback & args] (with-handlers id (let [shadow-offset 2 @@ -65,8 +65,8 @@ (Color. (aget rgb 0) (aget rgb 1) (aget rgb 2) (aget rgb 3))))) (defn animate - "Changes atom value according to specified range, speed, and current - frame interval. Invokes repaint if change happens." + "Changes the value of atom according to the specified range, speed, + and current frame interval. Invokes repaint if change happens." [atom from to speed] (let [prev @atom state (cond @@ -83,7 +83,7 @@ val)))) (defn animated-button - "Create animated button layer." + "Creates an animated button." [content callback & args] (let [padding 4 border-width 1 @@ -91,9 +91,9 @@ face (border content padding border-width) highlight (atom 0) animation-speed (atom 0)] - (interval-layer + (interval-view (reify - Layer + View (render! [button] (with-handlers button (let [hovered (hovered? button) @@ -132,12 +132,12 @@ (def button2 (animated-button (label "Animated button 2") println "Animated button 2 clicked")) -(def test-layer1 +(def test-view1 (reify - Layer - (render! [layer] - (with-handlers layer - (with-color (if (hovered? layer) Color/ORANGE Color/RED) + View + (render! [view] + (with-handlers view + (with-color (if (hovered? view) Color/ORANGE Color/RED) (.fillRect *graphics* 0 0 *width* *height*)) (:mouse-entered e (repaint) @@ -147,32 +147,32 @@ (println e)) (:mouse-moved e (println e)))) - (geometry [layer] + (geometry [view] (->Size 30 20)))) -(def test-layer1b (border test-layer1 2 3)) +(def test-view1b (border test-view1 2 3)) -(def test-layer2 +(def test-view2 (reify - Layer - (render! [layer] + View + (render! [view] (doto *graphics* (.setColor Color/YELLOW) (.fillRect 0 0 *width* *height*)) (with-rotate 0.5 0 0 - (draw! test-layer1b 30 25)) - (draw! test-layer1 55 5)) - (geometry [layer] + (draw! test-view1b 30 25)) + (draw! test-view1 55 5)) + (geometry [view] (->Size 70 65)))) -(def test-layer2m (miniature test-layer2 30 30)) +(def test-view2m (miniature test-view2 30 30)) -(def test-layer3 (border (label "Sample\ntext" :right :bottom))) +(def test-view3 (border (label "Sample\ntext" :right :bottom))) (def root (reify - Layer - (render! [layer] + View + (render! [view] ;;(repaint) (doto *graphics* (.drawLine 0 0 *width* *height*) @@ -180,9 +180,9 @@ ;; Random color to see when repaint happens. (.setColor (rand-nth [Color/BLACK Color/BLUE Color/RED])) (.fillOval 5 5 20 20)) - (draw! test-layer2 30 20) - (draw! test-layer2m 120 50) - (draw! test-layer3 100 100 80 50) + (draw! test-view2 30 20) + (draw! test-view2m 120 50) + (draw! test-view3 100 100 80 50) (draw! button1 50 160) (with-rotate (/ Math/PI 6) 250 200 (draw! button1 210 140)) @@ -191,7 +191,7 @@ (draw-button! :button (label "Immediate button" :center :center) #(println "Button clicked!")))) - (geometry [layer] + (geometry [view] (->Size 400 300)))) ;; Main viewport @@ -202,8 +202,8 @@ ;; Main scene (def scene - (fps-layer - (decorate-layer vp [_] + (fps-view + (decorate-view vp [_] (draw! vp) (draw-aligned! (label (str "Drag mouse to pan," \newline @@ -211,8 +211,8 @@ :left :bottom 5 (- *height* 5)) (draw! vp-miniature (- *width* 105) 5)))) -(defn show-frame [layer] - (doto (make-jframe "Test" layer) +(defn show-frame [view] + (doto (make-jframe "Test" view) (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE) (.setVisible true)))
--- a/src/net/kryshen/indyvon/layers.clj Mon Apr 07 14:24:16 2014 +0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,408 +0,0 @@ -;; -;; Copyright 2010, 2011, 2012, 2013 Mikhail Kryshen <mikhail@kryshen.net> -;; -;; This file is part of Indyvon. -;; -;; Indyvon is free software: you can redistribute it and/or modify it -;; under the terms of the GNU Lesser General Public License version 3 -;; only, as published by the Free Software Foundation. -;; -;; Indyvon is distributed in the hope that it will be useful, but -;; WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -;; Lesser General Public License for more details. -;; -;; You should have received a copy of the GNU Lesser General Public -;; License along with Indyvon. If not, see -;; <http://www.gnu.org/licenses/>. -;; - -(ns net.kryshen.indyvon.layers - "Implementations of Layer protocol." - (:use - (net.kryshen.indyvon core async)) - (:import - (java.awt Font Image Toolkit) - java.awt.image.ImageObserver - (java.awt.geom Area AffineTransform Rectangle2D$Double Point2D - Point2D$Double) - (java.awt.font FontRenderContext TextLayout) - java.util.concurrent.TimeUnit - (com.google.common.cache Cache CacheBuilder CacheLoader))) - -(defmacro decorate-layer - "Decorate Layer replacing render! implementation." - [layer & render-tail] - `(let [layer# ~layer] - (reify - Layer - (render! ~@render-tail) - (geometry [t#] (geometry layer#))))) - -(defrecord Empty [] - Layer - (render! [_]) - (geometry [_] - (->Size 0 0))) - -(def empty-layer (->Empty)) - -;; TODO: change argument order for decorators, content should be the -;; last. - -(defn padding - "Decorates layer adding padding." - ([content pad] - (padding content pad pad pad pad)) - ([content top left bottom right] - (if (== 0 top left bottom right) - content - (reify - Layer - (render! [l] - (draw! content - left top - (- *width* left right) - (- *height* top bottom) - false)) - (geometry [l] - (->NestedGeometry (geometry content) top left bottom right)))))) - -(defn border - "Decorate layer with a border." - ([content] - (border content 1)) - ([content thikness] - (border content thikness 0)) - ([content thikness gap] - (let [layer (padding content (+ thikness gap)) - t (double thikness)] - (decorate-layer layer [_] - (render! layer) - (with-color :border-color - (let [w (double *width*) - h (double *height*) - outer (Area. (Rectangle2D$Double. 0.0 0.0 w h)) - inner (Area. (Rectangle2D$Double. t t (- w t t) (- h t t)))] - (.subtract outer inner) - (.fill *graphics* outer))))))) - -;; 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 - 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))) - (geometry [_] - (->NestedGeometry (geometry content) - y x shadow-y shadow-x)))))) - -(defn panel - "Opaque layer using theme's alt-back-color or a custom background color." - ([content] - (panel :alt-back-color content)) - ([back-color content] - (decorate-layer content [_] - (with-color back-color - (.fill *graphics* (Rectangle2D$Double. 0.0 0.0 *width* *height*))) - (render! content)))) - -(defn hbox - "Creates layer that draws the specified content layers placing them - horizontally." - [& contents] - (reify - Layer - (render! [_] - (let [widths (map #(width (geometry %)) 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*)))) - (geometry [_] - (reduce #(->Size (+ (width %1) (width %2)) - (max (height %1) (height %2))) - (->Size 0 0) - (map geometry contents))))) - -(defn vbox - "Creates layer that draws the specified content layers placing them - vertically." - [& contents] - (reify - Layer - (render! [_] - (let [heights (map #(height (geometry %)) 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)))) - (geometry [_] - (reduce #(->Size (max (width %1) (width %2)) - (+ (height %1) (height %2))) - (->Size 0 0) - (map geometry contents))))) - -(defn- re-split [^java.util.regex.Pattern re s] - (seq (.split re s))) - -(def ^:private ^Cache text-layout-cache - (-> (CacheBuilder/newBuilder) - (.softValues) - (.expireAfterAccess (long 1) TimeUnit/SECONDS) - (.build))) - -(defn- get-text-layout - [^String line ^Font font ^FontRenderContext font-context] - (.get text-layout-cache [line font font-context] - #(TextLayout. line font font-context))) - -(defn- layout-text - [lines font font-context] - (map #(get-text-layout % font font-context) lines)) - -(defn- text-width [layouts] - (reduce (fn [w ^TextLayout tl] (max w (.getAdvance tl))) 0 layouts)) - -(defn- text-height [layouts] - (reduce (fn [w ^TextLayout tl] - (+ w (.getAscent tl) - (.getDescent tl) - (.getLeading tl))) - 0 layouts)) - -(defn label - "Creates a layer to display multiline text." - ([text] - (label text :left :top)) - ([text h-align v-align] - (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" (str text))] - (reify Layer - (render! [layer] - (let [w *width* - h *height* - font (.getFont *graphics*) - layouts (layout-text lines font (font-context)) - y (align-y v-align (text-height layouts) h)] - (loop [layouts layouts, y y] - (when-first [^TextLayout layout layouts] - (let [ascent (.getAscent layout) - lh (+ ascent (.getDescent layout) (.getLeading layout)) - x (align-x h-align (.getAdvance layout) w)] - (.draw layout *graphics* x (+ y ascent)) - (recur (next layouts) (+ y lh))))))) - (geometry [layer] - (let [layouts (layout-text lines (:font *theme*) (font-context)) - w (text-width layouts) - h (text-height layouts)] - (->Size w h))))))) - -(defn- ^ImageObserver image-observer [layer] - (reify - ImageObserver - (imageUpdate [this img infoflags x y width height] - (update layer) - (zero? (bit-and infoflags - (bit-or ImageObserver/ALLBITS - ImageObserver/ABORT)))))) - -(defn image-layer - [image-or-uri] - (let [^Image image (if (instance? Image image-or-uri) - image-or-uri - (.getImage (Toolkit/getDefaultToolkit) - ^java.net.URL image-or-uri))] - (.prepareImage (Toolkit/getDefaultToolkit) image -1 -1 nil) - (reify - Layer - (render! [layer] - (repaint-on-update layer) - (.drawImage *graphics* image 0 0 (image-observer layer))) - (geometry [layer] - (let [observer (image-observer layer) - width (.getWidth image observer) - height (.getHeight image observer) - width (if (pos? width) width 1) - height (if (pos? height) height 1)] - (->Size width height)))))) - -(def ^:dynamic *miniature-thread-priority* 2) - -(defn ref-layer - [layer-ref] - (let [l (reify - Layer - (render! [l] - (repaint-on-update l) - (if-let [layer @layer-ref] - (render! layer))) - (geometry [_] - (if-let [layer @layer-ref] - (geometry layer) - (->Size 1 1))))] - (add-watch layer-ref l (fn [_ _ _ _] (update l))) - l)) - -;; -;; Layer context decorators. -;; - -(defmacro handler [layer & handlers] - "Decorate layer to handle events." - `(let [layer# ~layer] - (decorate-layer layer# [t#] - (with-handlers t# - (render! layer#) - ~@handlers)))) - -(defn themed [layer & map-or-keyvals] - (let [theme (if (== (count map-or-keyvals) 1) - (first map-or-keyvals) - (apply array-map map-or-keyvals))] - (reify - Layer - (render! [_] - (with-theme theme - (render! layer))) - (geometry [_] - (with-theme theme - (geometry layer)))))) - -(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 ^:dynamic *interval*) - -(defn interval-layer - "Creates layer that measures time between repaints ant draws it's - content with the *interval* var bound to the measured time." - [content] - (let [last-time (atom nil)] - (decorate-layer content [_] - (compare-and-set! last-time nil *time*) - (let [lt @last-time] - (binding [*interval* (if (compare-and-set! last-time lt *time*) - (- *time* lt) - 0)] ; already measured on parallel thread - (render! content)))))) - -(defn- fps-label [text] - (padding (label text :right :bottom) 5)) - -(defn fps-layer - "Creates layer that draws content and displays - the frames per seconds rate." - [content] - (let [update-interval 2E8 ; 0.2 s in nanoseconds - frames (ref 0) - prev-time (ref nil) - display (ref (fps-label "fps n/a"))] - (decorate-layer content [_] - (draw! content) - (draw! - (dosync - (alter frames inc) - (if @prev-time - (let [elapsed (- *time* @prev-time)] - (when (> elapsed update-interval) - (let [fps (/ @frames (/ elapsed 1E9))] - (ref-set display (fps-label (format "%.1f" fps))) - (ref-set frames 0) - (ref-set prev-time *time*)))) - (ref-set prev-time *time*)) - @display))))) - -;; -;; Overlayer. -;; - -(def ^:private ^:dynamic *above*) - -(defn- overlay* [f & args] - (var-set #'*above* (conj *above* (apply partial f args)))) - -(defn- ^Point2D to-graphics-coords - [^AffineTransform transform x y] - (let [p (Point2D$Double. 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 (.getX p) (.getY p)))) - ([layer transform x y w h] - (let [p (to-graphics-coords transform x y)] - (draw! layer (.getX p) (.getY p) w h)))) - -(defn- draw-relative-aligned! - [layer transform h-align v-align x y] - (let [geom (geometry layer) - w (width geom) - h (height geom) - p (to-graphics-coords transform x y) - x (- (.getX p) (anchor-x geom h-align w)) - y (- (.getY p) (anchor-y geom v-align h))] - (draw! layer x y 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 overlay-aligned! [layer h-align v-align x y] - (overlay* draw-relative-aligned! - layer (.getTransform *graphics*) - h-align v-align x y)) - -(defn with-overlays* [rec? f & args] - (binding [*above* []] - (apply f args) - (if rec? - (loop [above *above*] - (when (seq above) - (var-set #'*above* []) - (doseq [f above] - (f)) - (recur *above*))) - (doseq [of *above*] - (of))))) - -(defmacro with-overlays [rec? & body] - `(with-overlays* ~rec? (fn [] ~@body))) - -(defn overlayer - ([content] - (overlayer content true)) - ([content rec?] - (decorate-layer content [_] - (with-overlays* rec? render! content))))
--- a/src/net/kryshen/indyvon/viewport.clj Mon Apr 07 14:24:16 2014 +0400 +++ b/src/net/kryshen/indyvon/viewport.clj Mon Apr 07 15:23:58 2014 +0400 @@ -1,5 +1,5 @@ ;; -;; Copyright 2010, 2011, 2012, 2013 Mikhail Kryshen <mikhail@kryshen.net> +;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> ;; ;; This file is part of Indyvon. ;; @@ -20,7 +20,7 @@ (ns net.kryshen.indyvon.viewport "Scrollable viewport and miniature." (:use - (net.kryshen.indyvon core async layers)) + (net.kryshen.indyvon core async views)) (:import java.awt.Cursor java.awt.geom.AffineTransform)) @@ -80,15 +80,15 @@ :transform transform))) (defrecord Viewport [content h-align v-align state] - Layer - (render! [layer] - (repaint-on-update layer) - (with-handlers layer + View + (render! [view] + (repaint-on-update view) + (with-handlers view (let [geom (geometry content) new-state (swap! state update-viewport geom h-align v-align) transform (:transform new-state)] ;; TODO: notify observers when size changes. - (binding [*viewport* layer + (binding [*viewport* view *viewport-transform* transform] (with-transform transform (draw! content 0 0 (width geom) (height geom) false)))) @@ -109,10 +109,10 @@ (- (:y-on-screen e) (:fix-y s))) :fix-x (:x-on-screen e) :fix-y (:y-on-screen e)))) - (update layer)) + (update view)) (:mouse-wheel e (scale-viewport! - layer + view (Math/pow *viewport-scaling-step* (:wheel-rotation e)) true (:x e) (:y e))))) (geometry [_] @@ -126,7 +126,7 @@ 0 0)) (defn viewport - "Creates scrollable viewport layer." + "Creates scrollable viewport view." ([content] (viewport content :left :top)) ([content h-align v-align] @@ -175,12 +175,12 @@ (/ max-height height))) (defn miniature - "Creates layer that asynchronously renders view of the content - scaled to the specified size." + "Creates a view that asynchronously renders the content view scaled to + the specified size." [content mw mh] - (async-layer + (async-view (reify - Layer + View (render! [this] (let [geom (geometry content) cw (width geom) @@ -199,7 +199,7 @@ "Creates miniature view of the viewport's contents." [viewport m-width m-height] (let [miniature (miniature (:content viewport) m-width m-height)] - (decorate-layer miniature [l] + (decorate-view miniature [l] (repaint-on-update viewport) (let [geom (geometry (:content viewport)) s (scaling (width geom) (height geom) m-width m-height)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/kryshen/indyvon/views.clj Mon Apr 07 15:23:58 2014 +0400 @@ -0,0 +1,409 @@ +;; +;; Copyright 2010-2014 Mikhail Kryshen <mikhail@kryshen.net> +;; +;; This file is part of Indyvon. +;; +;; Indyvon is free software: you can redistribute it and/or modify it +;; under the terms of the GNU Lesser General Public License version 3 +;; only, as published by the Free Software Foundation. +;; +;; Indyvon is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; Lesser General Public License for more details. +;; +;; You should have received a copy of the GNU Lesser General Public +;; License along with Indyvon. If not, see +;; <http://www.gnu.org/licenses/>. +;; + +(ns net.kryshen.indyvon.views + "Implementations of the View protocol." + (:use + (net.kryshen.indyvon core async)) + (:import + (java.awt Font Image Toolkit) + java.awt.image.ImageObserver + (java.awt.geom Area AffineTransform Rectangle2D$Double Point2D + Point2D$Double) + (java.awt.font FontRenderContext TextLayout) + java.util.concurrent.TimeUnit + (com.google.common.cache Cache CacheBuilder CacheLoader))) + +(defmacro decorate-view + "Decorate the view replacing render! implementation." + [view & render-tail] + `(let [view# ~view] + (reify + View + (render! ~@render-tail) + (geometry [t#] (geometry view#))))) + +(defrecord Empty [] + View + (render! [_]) + (geometry [_] + (->Size 0 0))) + +(def empty-view (->Empty)) + +;; TODO: change argument order for decorators, content should be the +;; last. + +(defn padding + "Adds padding to the content view." + ([content pad] + (padding content pad pad pad pad)) + ([content top left bottom right] + (if (== 0 top left bottom right) + content + (reify + View + (render! [l] + (draw! content + left top + (- *width* left right) + (- *height* top bottom) + false)) + (geometry [l] + (->NestedGeometry (geometry content) top left bottom right)))))) + +(defn border + "Adds a border to the content view." + ([content] + (border content 1)) + ([content thikness] + (border content thikness 0)) + ([content thikness gap] + (let [view (padding content (+ thikness gap)) + t (double thikness)] + (decorate-view view [_] + (render! view) + (with-color :border-color + (let [w (double *width*) + h (double *height*) + outer (Area. (Rectangle2D$Double. 0.0 0.0 w h)) + inner (Area. (Rectangle2D$Double. t t (- w t t) (- h t t)))] + (.subtract outer inner) + (.fill *graphics* outer))))))) + +;; TODO: opacity and blur. +(defn shadow + "Adds a shadow to the content view." + ([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 + View + (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))) + (geometry [_] + (->NestedGeometry (geometry content) + y x shadow-y shadow-x)))))) + +(defn panel + "An opaque view using theme's alt-back-color or a custom background + color." + ([content] + (panel :alt-back-color content)) + ([back-color content] + (decorate-view content [_] + (with-color back-color + (.fill *graphics* (Rectangle2D$Double. 0.0 0.0 *width* *height*))) + (render! content)))) + +(defn hbox + "Creates a view that draws the specified content views placing them + horizontally." + [& contents] + (reify + View + (render! [_] + (let [widths (map #(width (geometry %)) 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*)))) + (geometry [_] + (reduce #(->Size (+ (width %1) (width %2)) + (max (height %1) (height %2))) + (->Size 0 0) + (map geometry contents))))) + +(defn vbox + "Creates a view that draws the specified content views placing them + vertically." + [& contents] + (reify + View + (render! [_] + (let [heights (map #(height (geometry %)) 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)))) + (geometry [_] + (reduce #(->Size (max (width %1) (width %2)) + (+ (height %1) (height %2))) + (->Size 0 0) + (map geometry contents))))) + +(defn- re-split [^java.util.regex.Pattern re s] + (seq (.split re s))) + +(def ^:private ^Cache text-layout-cache + (-> (CacheBuilder/newBuilder) + (.softValues) + (.expireAfterAccess (long 1) TimeUnit/SECONDS) + (.build))) + +(defn- get-text-layout + [^String line ^Font font ^FontRenderContext font-context] + (.get text-layout-cache [line font font-context] + #(TextLayout. line font font-context))) + +(defn- layout-text + [lines font font-context] + (map #(get-text-layout % font font-context) lines)) + +(defn- text-width [layouts] + (reduce (fn [w ^TextLayout tl] (max w (.getAdvance tl))) 0 layouts)) + +(defn- text-height [layouts] + (reduce (fn [w ^TextLayout tl] + (+ w (.getAscent tl) + (.getDescent tl) + (.getLeading tl))) + 0 layouts)) + +(defn label + "Creates a view to display multiline text." + ([text] + (label text :left :top)) + ([text h-align v-align] + (let [lines (re-split #"\r\n|\n|\r|\u0085|\u2028|\u2029" (str text))] + (reify View + (render! [view] + (let [w *width* + h *height* + font (.getFont *graphics*) + layouts (layout-text lines font (font-context)) + y (align-y v-align (text-height layouts) h)] + (loop [layouts layouts, y y] + (when-first [^TextLayout layout layouts] + (let [ascent (.getAscent layout) + lh (+ ascent (.getDescent layout) (.getLeading layout)) + x (align-x h-align (.getAdvance layout) w)] + (.draw layout *graphics* x (+ y ascent)) + (recur (next layouts) (+ y lh))))))) + (geometry [view] + (let [layouts (layout-text lines (:font *theme*) (font-context)) + w (text-width layouts) + h (text-height layouts)] + (->Size w h))))))) + +(defn- ^ImageObserver image-observer [view] + (reify + ImageObserver + (imageUpdate [this img infoflags x y width height] + (update view) + (zero? (bit-and infoflags + (bit-or ImageObserver/ALLBITS + ImageObserver/ABORT)))))) + +(defn image-view + [image-or-uri] + (let [^Image image (if (instance? Image image-or-uri) + image-or-uri + (.getImage (Toolkit/getDefaultToolkit) + ^java.net.URL image-or-uri))] + (.prepareImage (Toolkit/getDefaultToolkit) image -1 -1 nil) + (reify + View + (render! [view] + (repaint-on-update view) + (.drawImage *graphics* image 0 0 (image-observer view))) + (geometry [view] + (let [observer (image-observer view) + width (.getWidth image observer) + height (.getHeight image observer) + width (if (pos? width) width 1) + height (if (pos? height) height 1)] + (->Size width height)))))) + +(def ^:dynamic *miniature-thread-priority* 2) + +(defn ref-view + [view-ref] + (let [l (reify + View + (render! [l] + (repaint-on-update l) + (if-let [view @view-ref] + (render! view))) + (geometry [_] + (if-let [view @view-ref] + (geometry view) + (->Size 1 1))))] + (add-watch view-ref l (fn [_ _ _ _] (update l))) + l)) + +;; +;; View context decorators +;; + +(defmacro handler [view & handlers] + "Adds event handling to the view." + `(let [view# ~view] + (decorate-view view# [t#] + (with-handlers t# + (render! view#) + ~@handlers)))) + +(defn themed [view & map-or-keyvals] + (let [theme (if (== (count map-or-keyvals) 1) + (first map-or-keyvals) + (apply array-map map-or-keyvals))] + (reify + View + (render! [_] + (with-theme theme + (render! view))) + (geometry [_] + (with-theme theme + (geometry view)))))) + +(defn hinted [view & map-or-keyvals] + (let [hints (if (== (count map-or-keyvals) 1) + (first map-or-keyvals) + (apply array-map map-or-keyvals))] + (decorate-view view [_] + (with-hints* hints render! view)))) + +;; +;; Measuring time +;; + +(def ^:dynamic *interval*) + +(defn interval-view + "Creates a view that measures time between repaints ant draws it's + content with the *interval* var bound to the measured time." + [content] + (let [last-time (atom nil)] + (decorate-view content [_] + (compare-and-set! last-time nil *time*) + (let [lt @last-time] + (binding [*interval* (if (compare-and-set! last-time lt *time*) + (- *time* lt) + 0)] ; already measured on parallel thread + (render! content)))))) + +(defn- fps-label [text] + (padding (label text :right :bottom) 5)) + +(defn fps-view + "Creates a view that draws content and displays the + frames per second rate." + [content] + (let [update-interval 2E8 ; 0.2 s in nanoseconds + frames (ref 0) + prev-time (ref nil) + display (ref (fps-label "fps n/a"))] + (decorate-view content [_] + (draw! content) + (draw! + (dosync + (alter frames inc) + (if @prev-time + (let [elapsed (- *time* @prev-time)] + (when (> elapsed update-interval) + (let [fps (/ @frames (/ elapsed 1E9))] + (ref-set display (fps-label (format "%.1f" fps))) + (ref-set frames 0) + (ref-set prev-time *time*)))) + (ref-set prev-time *time*)) + @display))))) + +;; +;; Overlays +;; + +(def ^:private ^:dynamic *above*) + +(defn- overlay* [f & args] + (var-set #'*above* (conj *above* (apply partial f args)))) + +(defn- ^Point2D to-graphics-coords + [^AffineTransform transform x y] + (let [p (Point2D$Double. x y)] + (.transform transform p p) + (.transform (.createInverse (.getTransform *graphics*)) p p) + p)) + +(defn- draw-relative! + ([view transform x y] + (let [p (to-graphics-coords transform x y)] + (draw! view (.getX p) (.getY p)))) + ([view transform x y w h] + (let [p (to-graphics-coords transform x y)] + (draw! view (.getX p) (.getY p) w h)))) + +(defn- draw-relative-aligned! + [view transform h-align v-align x y] + (let [geom (geometry view) + w (width geom) + h (height geom) + p (to-graphics-coords transform x y) + x (- (.getX p) (anchor-x geom h-align w)) + y (- (.getY p) (anchor-y geom v-align h))] + (draw! view x y w h))) + +(defn overlay! + "Draws view in the overlay context above the other views." + ([view] + (overlay* draw-relative! view (.getTransform *graphics*) 0 0)) + ([view x y] + (overlay* draw-relative! view (.getTransform *graphics*) x y)) + ([view x y w h] + (overlay* draw-relative! view (.getTransform *graphics*) x y w h))) + +(defn overlay-aligned! [view h-align v-align x y] + (overlay* draw-relative-aligned! + view (.getTransform *graphics*) + h-align v-align x y)) + +(defn with-overlays* [rec? f & args] + (binding [*above* []] + (apply f args) + (if rec? + (loop [above *above*] + (when (seq above) + (var-set #'*above* []) + (doseq [f above] + (f)) + (recur *above*))) + (doseq [of *above*] + (of))))) + +(defmacro with-overlays [rec? & body] + `(with-overlays* ~rec? (fn [] ~@body))) + +(defn layered + ([content] + (layered content true)) + ([content rec?] + (decorate-view content [_] + (with-overlays* rec? render! content))))