Restructured code. Improved 'compile'. Added more wrappers.

This commit is contained in:
Daniel Ziltener 2015-01-16 21:26:46 +00:00
parent c10b14359b
commit 94968a61aa
5 changed files with 200 additions and 74 deletions

View file

@ -9,11 +9,11 @@ This is in a very early state, so there isn't much yet, except one thing.
### Declarative UI programming ### Declarative UI programming
```clojure ```clojure
(compile [:VBox {:id "TopLevelVBox" (compile [VBox {:id "TopLevelVBox"
:children [:Label {:text "Hi!"} :children [Label {:text "Hi!"}
:Label {:text "I'm ClojureFX!"} Label {:text "I'm ClojureFX!"}
:HBox {:id "HorizontalBox" HBox {:id "HorizontalBox"
:children [:Button {:text "Alright."}]}]}]) :children [Button {:text "Alright."}]}]}])
``` ```
## TODO ## TODO

View file

@ -3,6 +3,7 @@
:dependencies '[[org.clojure/clojure "1.7.0-alpha4"] :dependencies '[[org.clojure/clojure "1.7.0-alpha4"]
[com.taoensso/timbre "3.3.1" :exclusions [com.taoensso/carmine]] [com.taoensso/timbre "3.3.1" :exclusions [com.taoensso/carmine]]
[org.clojure/core.typed "0.2.77"] [org.clojure/core.typed "0.2.77"]
[clojure-jsr-223 "0.1.0"]
[boot-deps "0.1.2" :scope "test"] [boot-deps "0.1.2" :scope "test"]
[midje "1.6.3" :scope "test"] [midje "1.6.3" :scope "test"]
@ -12,14 +13,14 @@
(require '[zilti.boot-midje :refer [midje]] (require '[zilti.boot-midje :refer [midje]]
'[zilti.boot-typed :refer [typed]]) '[zilti.boot-typed :refer [typed]])
(def +version+ "0.0.1-SNAPSHOT") (def +version+ "0.0.5-SNAPSHOT")
(task-options! (task-options!
pom {:project 'ClojureFX pom {:project 'clojurefx
:version +version+ :version +version+
:description "A Clojure JavaFX wrapper." :description "A Clojure JavaFX wrapper."
:url "https://bitbucket.com/zilti/ClojureFX" :url "https://bitbucket.com/zilti/clojurefx"
:scm {:url "https://bitbucket.com/zilti/ClojureFX"} :scm {:url "https://bitbucket.com/zilti/clojurefx"}
:license {:name "GNU Lesser General Public License 3.0" :license {:name "GNU Lesser General Public License 3.0"
:url "http://www.gnu.org/licenses/lgpl-3.0.txt"}} :url "http://www.gnu.org/licenses/lgpl-3.0.txt"}}
midje {:test-paths #{"test"} midje {:test-paths #{"test"}

View file

@ -4,17 +4,46 @@
[clojure.core.typed.unsafe :refer [ignore-with-unchecked-cast]] [clojure.core.typed.unsafe :refer [ignore-with-unchecked-cast]]
[taoensso.timbre :as timbre] [taoensso.timbre :as timbre]
[clojure.java.io :as io] [clojure.java.io :as io]
[clojurefx.protocols :refer :all])) [clojurefx.protocols :refer :all]
[clojure.java.io :refer :all]))
(defonce force-toolkit-init (javafx.embed.swing.JFXPanel.)) (defonce force-toolkit-init (javafx.embed.swing.JFXPanel.))
;; ## Threading helpers
(defn run-later*"
Simple wrapper for Platform/runLater. You should use run-later.
" [f]
(javafx.application.Platform/runLater f))
(defmacro run-later [& body]
`(run-later* (fn [] ~@body)))
(defn run-now*"
A modification of run-later waiting for the running method to return. You should use run-now.
" [f]
(if (javafx.application.Platform/isFxApplicationThread)
(apply f [])
(let [result (promise)]
(run-later
(deliver result (try (f) (catch Throwable e e))))
@result)))
(defmacro run-now "
Runs the code on the FX application thread and waits until the return value is delivered.
" [& body]
`(run-now* (fn [] ~@body)))
(tc-ignore (timbre/refer-timbre)) (tc-ignore (timbre/refer-timbre))
(import (javafx.scene.control Label TextField TextArea CheckBox ComboBox Menu MenuItem MenuBar (import (javafx.scene.control Label TextField TextArea CheckBox ComboBox Menu MenuItem MenuBar
MenuButton ContextMenu ToolBar SplitPane ScrollPane Accordion MenuButton ContextMenu ToolBar SplitPane ScrollPane Accordion
TitledPane TabPane Tab TableColumnBase Labeled) TitledPane TabPane Tab TableColumnBase Labeled)
(javafx.scene Node) (javafx.scene Node Scene Parent)
(javafx.scene.layout Pane VBox) (javafx.scene.layout Pane VBox)
(javafx.stage Stage)
(javafx.collections FXCollections ObservableList) (javafx.collections FXCollections ObservableList)
(javafx.css Styleable)
(java.util Collection)) (java.util Collection))
;; TODO This belongs elsewhere. ;; TODO This belongs elsewhere.
@ -68,18 +97,9 @@
(tc-ignore (tc-ignore
(extend-protocol FXId (extend-protocol FXId
Node Styleable
(get-id [this] (.getId ^Node this)) (get-id [this] (.getId ^Styleable this))
(set-id! [this id] (tc-assert String id) (.setId ^Node this ^String id) this) (set-id! [this id] (tc-assert String id) (.setId ^Styleable this ^String id) this)))
Tab
(get-id [this] (.getId ^Tab this))
(set-id! [this id] (tc-assert String id) (.setId ^Tab this ^String id) this)
TableColumnBase
(get-id [this] (.getId ^TableColumnBase this))
(set-id! [this id] (tc-assert String id) (.setId ^TableColumnBase this ^String id) this)
MenuItem
(get-id [this] (.getId ^MenuItem this))
(set-id! [this id] (tc-assert String id) (.setId ^MenuItem this ^String id) this)))
(tc-ignore (tc-ignore
(extend-protocol FXParent (extend-protocol FXParent
@ -132,58 +152,39 @@
(get-graphic [this] (.getGraphic ^Menu this)) (get-graphic [this] (.getGraphic ^Menu this))
(set-graphic! [this graphic] (.setGraphic ^Menu this ^Node graphic)))) (set-graphic! [this graphic] (.setGraphic ^Menu this ^Node graphic))))
(tc-ignore
(extend-protocol clojure.lang.IObj
Node
(meta [this] (.getUserData ^Node this))
(withMeta [this metadata] (.setUserData ^Node this metadata) this)
MenuItem
(meta [this] (.getUserData ^MenuItem this))
(withMeta [this metadata] (.setUserData ^MenuItem this metadata) this)))
;; TODO Code below probably also belongs somewhere else (extend-protocol FXStyleSetter
(def getter first) Node
(def setter second) (set-style! [this style] (.setStyle ^Node this ^String style) this)
MenuItem
(set-style! [this style] (.setStyle ^MenuItem this ^String style) this))
(def translation-map (extend-type Styleable
(atom {:text (with-meta [#'get-value #'set-value!] {:argument String :parent FXValue}) FXStyleable
:value (with-meta [#'get-value #'set-value!] {:argument Object :parent FXValue}) (get-css-meta [this] (.getCssMetaData ^Styleable this))
:id (with-meta [#'get-id #'set-id!] {:argument String :parent FXId}) (get-pseudo-class-styles [this] (.getPseudoClassStyles ^Styleable this))
:graphic (with-meta [#'get-graphic #'set-graphic!] {:argument Node :parent FXGraphic}) (get-style [this] (.getStyle ^Styleable this))
:content (with-meta [#'get-content #'set-content!] {:argument Node :parent FXContainer}) (get-style-classes [this] (.getStyleClass ^Styleable this))
:children (with-meta [#'get-subnodes #'set-subnodes!] {:argument java.util.List :parent FXParent})})) (set-style-classes! [this classes] (.setAll ^ObservableList (.getStyleClass ^Styleable this) classes) this)
(get-styleable-parent [this] (.getStyleableParent ^Styleable this))
(get-type-selector [this] (.getTypeSelector ^Styleable this)))
(declare compile-o-matic) (extend-type Stage
(ann build-node [Any (Map Keyword Any) -> Any]) FXStage
(defn build-node [object props] (get-title [this] (.getTitle ^Stage this))
(debug "build-node:" object props) (set-title! [this title] (.setTitle ^Stage this ^String title))
(let [obj (eval `(new ~object))] (get-scene [this] (.getScene ^Stage this))
(doseq [[k v] props] (set-scene! [this scene] (.setScene ^Stage this ^Scene scene)))
(let [translation (get @translation-map k)
{:keys [argument parent]} (meta translation)
v (compile-o-matic v)]
(trace "Key:" k " " (type k) "Value:" v " " (type v))
(when (nil? translation)
(throw (Exception. (str "Property" k "not available in translation map."))))
;; (when-not ((pred-substitute argument) v)
;; (throw (Exception. (str "Input type" v "is not compatible with expected type for" k))))
;; (when-not ((pred-substitute parent) obj)
;; (throw (Exception. (str "Property" k "not available for class" (class obj)))))
((setter translation) obj v)))
obj))
(ann resolv-o-matic [(U String Keyword Symbol Class) -> Class]) (extend-type Scene
(defn resolv-o-matic [thing] FXScene
(cond (get-root [this] (.getRoot ^Scene this))
(symbol? thing) (ns-resolve (the-ns 'clojurefx.clojurefx) thing) (set-root! [this root] (.setRoot ^Scene this ^Parent root) this))
(keyword? thing) (recur (name thing))
(string? thing) (recur (symbol thing))
:else thing))
(ann compile [(Vec Any) -> Any])
(defn compile [[obj params & other]]
(assert (map? params))
(let [obj (build-node (resolv-o-matic obj) params)]
(if (empty? other)
obj
(flatten (conj (list obj) (compile other))))))
(ann compile-o-matic [Any -> Any])
(defn compile-o-matic [thing]
(if (instance? java.util.List thing)
(if (and (not (coll? (first thing))) (map? (second thing)))
(compile thing)
thing)
thing))

92
src/clojurefx/factory.clj Normal file
View file

@ -0,0 +1,92 @@
(ns clojurefx.factory
(:refer-clojure :exclude [atom doseq let fn defn ref dotimes defprotocol loop for send compile])
(:require [clojure.core.typed :refer :all]
[clojure.core.typed.unsafe :refer [ignore-with-unckecked-cast]]
[taoensso.timbre :as timbre]
[clojure.java.io :as io]
[clojurefx.clojurefx :refer :all]
[clojurefx.protocols :refer :all]))
(tc-ignore (timbre/refer-timbre))
;;## FXMLLoader
(defn load-fxml [filename :- String] :- javafx.scene.Node
(let [loader (new javafx.fxml.FXMLLoader)]
(.setLocation loader (io/resource ""))
(.load loader (-> filename io/resource io/input-stream))))
;;## VectorBuilder
(def getter first)
(def setter second)
(def translation-map
(atom {:text (with-meta [#'get-value #'set-value!] {:argument String :parent FXValue})
:value (with-meta [#'get-value #'set-value!] {:argument Object :parent FXValue})
:id (with-meta [#'get-id #'set-id!] {:argument String :parent FXId})
:graphic (with-meta [#'get-graphic #'set-graphic!] {:argument Node :parent FXGraphic})
:content (with-meta [#'get-content #'set-content!] {:argument Node :parent FXContainer})
:children (with-meta [#'get-subnodes #'set-subnodes!] {:argument java.util.List :parent FXParent})
:title (with-meta [#'get-title #'set-title!] {:argument String :parent FXStage})
:scene (with-meta [#'get-scene #'set-scene!] {:argument Scene :parent FXStage})
:root (with-meta [#'get-root #'set-root!] {:argument Parent :parent FXScene})}))
(def mandatory-constructor-args
(atom {javafx.scene.Scene [:root]}))
(declare compile-o-matic)
(ann apply-props-to-node [Any (Map Keyword Any) -> Any])
(defn apply-props-to-node [node props]
(doseq [[k v] props]
(let [translation (get @translation-map k)
{:keys [argument parent]} (meta translation)
v (compile-o-matic v)]
(trace "Key:" k " " (type k) "Value:" v " " (type v))
(when (nil? translation)
(throw (Exception. (str "Property" k "not available in translation map."))))
;; (when-not ((pred-substitute argument) v)
;; (throw (Exception. (str "Input type" v "is not compatible with expected type for" k))))
;; (when-not ((pred-substitute parent) node)
;; (throw (Exception. (str "Property" k "not available for class" (class node)))))
((setter translation) node v)))
node)
(ann build-node [Any (Map Keyword Any) -> Any])
(defn build-node [object props]
(debug "build-node:" object props)
(let [mandatory (get mandatory-constructor-args object)
form `(~object new)]
(apply-props-to-node
(-> (reduce (fn [form mandatory]
(if-let [entry (get props mandatory)]
(cons entry form)
form)) form mandatory)
reverse
eval)
(apply dissoc props mandatory))))
(ann resolv-o-matic [(U String Keyword Symbol Class) -> Class])
(defn resolv-o-matic [thing]
(cond
(symbol? thing) (ns-resolve (the-ns 'clojurefx.clojurefx) thing)
(keyword? thing) (recur (name thing))
(string? thing) (recur (symbol thing))
:else thing))
(ann compile [(Vec Any) -> Any])
(defn compile [[obj params & other]]
(assert (map? params))
(let [obj (build-node (resolv-o-matic obj) params)]
(if (empty? other)
obj
(flatten (conj (list obj) (compile other))))))
(ann compile-o-matic [Any -> Any])
(defn compile-o-matic [thing]
(if (instance? java.util.List thing)
(if (and (not (coll? (first thing))) (map? (second thing)))
(compile thing)
thing)
thing))

View file

@ -35,3 +35,35 @@
FXGraphic FXGraphic
(get-graphic [this :- A] :- B) (get-graphic [this :- A] :- B)
(set-graphic! [this :- A graphic :- B] :- A)) (set-graphic! [this :- A graphic :- B] :- A))
(defprotocol [[A :variance :covariant :< javafx.css.Styleable]
[B :variance :covariant :< javafx.css.Styleable]]
FXStyleable
"http://download.java.net/jdk8/jfxdocs/javafx/css/Styleable.html"
(get-css-meta [this :- A] :- (java.util.List javafx.css.CssMetaData)) ;; TODO
(get-pseudo-class-styles [this :- A] :- (javafx.collections.ObservableSet javafx.css.PseudoClass))
(get-style [this :- A] :- String)
(get-style-classes [this :- A] :- (javafx.collections.ObservableList String))
(set-style-classes! [this :- A classes :- java.util.Collection] :- A)
(get-styleable-parent [this :- A] :- (U nil B))
(get-type-selector [this :- A] :- String))
(defprotocol [[A :variance :covariant]]
FXStyleSetter
(set-style! [this :- A style :- String] :- A))
(defalias FXStyled (U FXStyleable FXStyleSetter))
(defprotocol [[A :variance :covariant :< javafx.stage.Stage]
[B :variance :covariant :< javafx.scene.Scene]]
FXStage
(get-title [this :- A] :- String)
(set-title! [this :- A title :- String] :- A)
(get-scene [this :- A] :- B)
(set-scene! [this :- A scene :- B] :- A))
(defprotocol [[A :variance :covariant :< javafx.scene.Scene]
[B :variance :covariant :< javafx.scene.Parent]]
FXScene
(get-root [this :- A] :- B)
(set-root! [this :- A root :- B] :- A))