Some stuff

This commit is contained in:
zilti 2018-10-30 10:11:36 +00:00
parent 0441e6cbec
commit 56e94758c2
6 changed files with 261 additions and 143 deletions

View file

@ -3,9 +3,10 @@
<html> <html>
<!-- This manual is for ClojureFX, version 0.4.0. <!-- This manual is for ClojureFX, version 0.4.0.
Copyright (C) 2017 Daniel Ziltener. --> Copyright (C) 2016-2018 Daniel Ziltener. -->
<!-- Created by GNU Texinfo 6.4, http://www.gnu.org/software/texinfo/ --> <!-- Created by GNU Texinfo 6.5, http://www.gnu.org/software/texinfo/ -->
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>ClojureFX Manual</title> <title>ClojureFX Manual</title>
<meta name="description" content="ClojureFX Manual"> <meta name="description" content="ClojureFX Manual">
@ -13,7 +14,6 @@ Copyright (C) 2017 Daniel Ziltener. -->
<meta name="resource-type" content="document"> <meta name="resource-type" content="document">
<meta name="distribution" content="global"> <meta name="distribution" content="global">
<meta name="Generator" content="makeinfo"> <meta name="Generator" content="makeinfo">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link href="#Top" rel="start" title="Top"> <link href="#Top" rel="start" title="Top">
<link href="#Index" rel="index" title="Index"> <link href="#Index" rel="index" title="Index">
<link href="#SEC_Contents" rel="contents" title="Table of Contents"> <link href="#SEC_Contents" rel="contents" title="Table of Contents">
@ -92,7 +92,7 @@ Next: <a href="#Installation-and-deployment" accesskey="n" rel="next">Installati
<a name="ClojureFX"></a> <a name="ClojureFX"></a>
<h1 class="top">ClojureFX</h1> <h1 class="top">ClojureFX</h1>
<p>This is the documentation to ClojureFX, version 0.4.0. <p>This is the documentation to ClojureFX, version 0.5.0.
</p> </p>
<table class="menu" border="0" cellspacing="0"> <table class="menu" border="0" cellspacing="0">
<tr><td align="left" valign="top">&bull; <a href="#Installation-and-deployment" accesskey="1">Installation and deployment</a>:</td><td>&nbsp;&nbsp;</td><td align="left" valign="top">adding ClojureFX and probably tools.jar to your build tool. <tr><td align="left" valign="top">&bull; <a href="#Installation-and-deployment" accesskey="1">Installation and deployment</a>:</td><td>&nbsp;&nbsp;</td><td align="left" valign="top">adding ClojureFX and probably tools.jar to your build tool.
@ -120,9 +120,9 @@ Next: <a href="#Getting-started" accesskey="n" rel="next">Getting started</a>, P
<a name="Installation-and-deployment-1"></a> <a name="Installation-and-deployment-1"></a>
<h2 class="chapter">1 Installation and deployment</h2> <h2 class="chapter">1 Installation and deployment</h2>
<p>The first, straightforward part of this is to add the dependency to your <samp>project.clj</samp> or <samp>build.boot</samp>, which consists simply of adding <code>[clojurefx &quot;0.4.0&quot;]</code>. <p>The first, straightforward part of this is to add the dependency to your <samp>project.clj</samp> or <samp>build.boot</samp>, which consists simply of adding <code>[clojurefx &quot;0.5.0&quot;]</code>.
</p> </p>
<p>For the users of <em>OpenJDK</em> 7 and 8, <em>OpenJFX</em>, the opensource implementation of JavaFX, is not included yet (it will be starting with OpenJDK 9). Luckily, many Linux distributions ship a separate OpenJFX package by now, but for those that don&rsquo;t, the OpenJDK wiki has an article <a href="https://wiki.openjdk.java.net/display/OpenJFX/Building+OpenJFX">&ldquo;Building OpenJFX&rdquo;</a>. Alternatively, you can of course install the Oracle JDK manually. <p>For the users of <em>OpenJDK</em> 7 and 8, <em>OpenJFX</em>, the opensource implementation of JavaFX, is not included. Luckily, many Linux distributions ship a separate OpenJFX package by now, but for those that don&rsquo;t, the OpenJDK wiki has an article <a href="https://wiki.openjdk.java.net/display/OpenJFX/Building+OpenJFX">&ldquo;Building OpenJFX&rdquo;</a>. Starting with OpenJDK 9, JavaFX is available as a library. Alternatively, you can of course install the Oracle JDK manually.
</p> </p>
<hr> <hr>
<a name="Getting-started"></a> <a name="Getting-started"></a>
@ -141,25 +141,24 @@ Next: <a href="#Coding-a-scenegraph" accesskey="n" rel="next">Coding a scenegrap
</p> </p>
<p><code>(defonce force-toolkit-init (javafx.embed.swing.JFXPanel.))</code> <p><code>(defonce force-toolkit-init (javafx.embed.swing.JFXPanel.))</code>
</p> </p>
<p>Subclassing &lsquo;<code>javafx.application.Application</code>&rsquo; is a tad more work and requires you to aot-compile the namespace: <p>Alternatively, and preferredly, you can use <code>start-app</code>:
</p> </p>
<div class="lisp"> <div class="lisp">
<pre class="lisp">(ns example.core <pre class="lisp">(ns example.core
(:require [clojurefx.clojurefx :as fx]) (:require [clojurefx.clojurefx :as fx])
(:gen-class :main true (:gen-class))
:extends javafx.application.Application))
(defn -init [this] (defn init []
nil) nil)
(defn -start [this ^javafx.stage.Stage stage] (defn start [^javafx.stage.Stage stage]
(.show stage)) (.show stage))
(defn -stop [this] (defn stop []
nil) nil)
(defn -main [&amp; args] (defn -main [&amp; args]
(javafx.application.Application/launch example.core args)) (fx/start-app init start stop))
</pre></div> </pre></div>
<table class="menu" border="0" cellspacing="0"> <table class="menu" border="0" cellspacing="0">
@ -186,6 +185,26 @@ Up: <a href="#Getting-started" accesskey="u" rel="up">Getting started</a> &nbsp;
<dd><p>This macro runs the code given on the JavaFX thread and immediately returns. Prefixing the s-exp with an @ has the same effect as using <code>run-now</code>. <dd><p>This macro runs the code given on the JavaFX thread and immediately returns. Prefixing the s-exp with an @ has the same effect as using <code>run-now</code>.
</p></dd></dl> </p></dd></dl>
<dl>
<dt><a name="index-fi"></a>clojurefx.clojurefx: <strong>fi</strong> <em>interface args &amp; code</em></dt>
<dd><p>This macro is used to use a Clojure function as a functional interface. The interface name is needed: <code>(fx/fi javafx.event.Event [event] eventhandling-code)</code>
</p></dd></dl>
<dl>
<dt><a name="index-connect"></a>clojurefx.clojurefx: <strong>connect</strong> <em>instance function args &amp; code</em></dt>
<dd><p>This macro is used to use a Clojure function as a functional interface. The function must be written in kebab case: <code>(fx/connect btn set-on-action [event] (do-something-with event))</code>
</p></dd></dl>
<dl>
<dt><a name="index-find_002dchild_002dby_002dclass"></a>clojurefx.clojurefx: <strong>find-child-by-class</strong> <em>node clazz</em></dt>
<dd><p>With this function, you can find an element / elements in a scenegraph by one of its CSS classes.
</p></dd></dl>
<dl>
<dt><a name="index-find_002dchild_002dby_002did"></a>clojurefx.clojurefx: <strong>find-child-by-id</strong> <em>node id</em></dt>
<dd><p>Analogous to find-child-by-class, but to find an element by fx:id.
</p></dd></dl>
<hr> <hr>
<a name="Coding-a-scenegraph"></a> <a name="Coding-a-scenegraph"></a>
<div class="header"> <div class="header">
@ -195,15 +214,19 @@ Next: <a href="#FXML-and-controllers" accesskey="n" rel="next">FXML and controll
<a name="Coding-a-scenegraph-1"></a> <a name="Coding-a-scenegraph-1"></a>
<h2 class="chapter">3 Coding a scenegraph</h2> <h2 class="chapter">3 Coding a scenegraph</h2>
<p><strong>This part of the library has not been tested for a long time; I will get to it eventually, but expect things to be somewhat broken.</strong> <p>You can write a scenegraph in-code using the <code>compile</code> function. It is straightforward, alternating between the class name and the element&rsquo;s properties. Make sure though to either use <code>start-app</code> function or first initialize the toolkit:
</p>
<p><code>(defonce force-toolkit-init (javafx.embed.swing.JFXPanel.))</code>
</p>
<p>There are no further dependencies for this, since the calls are made using Clojure&rsquo;s reflection API. Let&rsquo;s look at an example:
</p> </p>
<div class="lisp"> <div class="lisp">
<pre class="lisp">(require '[clojurefx.clojure :refer [compile]]) <pre class="lisp">(require '[clojurefx.clojure :refer [compile]])
(compile [VBox {:id &quot;TopLevelVBox&quot; (compile [Scene {:root [VBox {:id &quot;TopLevelVBox&quot;
:children [Label {:text &quot;Hi!&quot;} :children [Label {:text &quot;Hi!&quot;}
Label {:text &quot;I'm ClojureFX!&quot;} Label {:text &quot;I'm ClojureFX!&quot;}
HBox {:id &quot;HorizontalBox&quot; HBox {:id &quot;HorizontalBox&quot;
:children [Button {:text &quot;Alright.&quot;}]}]}]) :children [Button {:text &quot;Alright.&quot;}]}]}]}])
</pre></div> </pre></div>
<table class="menu" border="0" cellspacing="0"> <table class="menu" border="0" cellspacing="0">
@ -363,6 +386,8 @@ Previous: <a href="#Roadmap" accesskey="p" rel="prev">Roadmap</a>, Up: <a href="
<table><tr><th valign="top">Jump to: &nbsp; </th><td><a class="summary-letter" href="#Index_fn_letter-C"><b>C</b></a> <table><tr><th valign="top">Jump to: &nbsp; </th><td><a class="summary-letter" href="#Index_fn_letter-C"><b>C</b></a>
&nbsp; &nbsp;
<a class="summary-letter" href="#Index_fn_letter-F"><b>F</b></a>
&nbsp;
<a class="summary-letter" href="#Index_fn_letter-G"><b>G</b></a> <a class="summary-letter" href="#Index_fn_letter-G"><b>G</b></a>
&nbsp; &nbsp;
<a class="summary-letter" href="#Index_fn_letter-L"><b>L</b></a> <a class="summary-letter" href="#Index_fn_letter-L"><b>L</b></a>
@ -375,6 +400,12 @@ Previous: <a href="#Roadmap" accesskey="p" rel="prev">Roadmap</a>, Up: <a href="
<tr><td colspan="4"> <hr></td></tr> <tr><td colspan="4"> <hr></td></tr>
<tr><th><a name="Index_fn_letter-C">C</a></th><td></td><td></td></tr> <tr><th><a name="Index_fn_letter-C">C</a></th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-compile"><code>compile</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#Scenegraph-API">Scenegraph API</a></td></tr> <tr><td></td><td valign="top"><a href="#index-compile"><code>compile</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#Scenegraph-API">Scenegraph API</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-connect"><code>connect</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#Core-API">Core API</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th><a name="Index_fn_letter-F">F</a></th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-fi"><code>fi</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#Core-API">Core API</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-find_002dchild_002dby_002dclass"><code>find-child-by-class</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#Core-API">Core API</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-find_002dchild_002dby_002did"><code>find-child-by-id</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#Core-API">Core API</a></td></tr>
<tr><td colspan="4"> <hr></td></tr> <tr><td colspan="4"> <hr></td></tr>
<tr><th><a name="Index_fn_letter-G">G</a></th><td></td><td></td></tr> <tr><th><a name="Index_fn_letter-G">G</a></th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-generate_002dcontroller"><code>generate-controller</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#FXML-API">FXML API</a></td></tr> <tr><td></td><td valign="top"><a href="#index-generate_002dcontroller"><code>generate-controller</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#FXML-API">FXML API</a></td></tr>
@ -390,6 +421,8 @@ Previous: <a href="#Roadmap" accesskey="p" rel="prev">Roadmap</a>, Up: <a href="
</table> </table>
<table><tr><th valign="top">Jump to: &nbsp; </th><td><a class="summary-letter" href="#Index_fn_letter-C"><b>C</b></a> <table><tr><th valign="top">Jump to: &nbsp; </th><td><a class="summary-letter" href="#Index_fn_letter-C"><b>C</b></a>
&nbsp; &nbsp;
<a class="summary-letter" href="#Index_fn_letter-F"><b>F</b></a>
&nbsp;
<a class="summary-letter" href="#Index_fn_letter-G"><b>G</b></a> <a class="summary-letter" href="#Index_fn_letter-G"><b>G</b></a>
&nbsp; &nbsp;
<a class="summary-letter" href="#Index_fn_letter-L"><b>L</b></a> <a class="summary-letter" href="#Index_fn_letter-L"><b>L</b></a>

View file

@ -5,7 +5,7 @@
@copying @copying
This manual is for ClojureFX, version 0.4.0. This manual is for ClojureFX, version 0.4.0.
Copyright @copyright{} 2017 Daniel Ziltener. Copyright @copyright{} 2016-2018 Daniel Ziltener.
@end copying @end copying
@titlepage @titlepage
@ -24,7 +24,7 @@ Copyright @copyright{} 2017 Daniel Ziltener.
@node Top @node Top
@top ClojureFX @top ClojureFX
This is the documentation to ClojureFX, version 0.4.0. This is the documentation to ClojureFX, version 0.5.0.
@c@end ifnottex @c@end ifnottex
@menu @menu
@ -40,9 +40,9 @@ This is the documentation to ClojureFX, version 0.4.0.
@node Installation and deployment @node Installation and deployment
@chapter Installation and deployment @chapter Installation and deployment
The first, straightforward part of this is to add the dependency to your @file{project.clj} or @file{build.boot}, which consists simply of adding @code{[clojurefx "0.4.0"]}. The first, straightforward part of this is to add the dependency to your @file{project.clj} or @file{build.boot}, which consists simply of adding @code{[clojurefx "0.5.0"]}.
For the users of @emph{OpenJDK} 7 and 8, @emph{OpenJFX}, the opensource implementation of JavaFX, is not included yet (it will be starting with OpenJDK 9). Luckily, many Linux distributions ship a separate OpenJFX package by now, but for those that don't, the OpenJDK wiki has an article @uref{https://wiki.openjdk.java.net/display/OpenJFX/Building+OpenJFX, ``Building OpenJFX''}. Alternatively, you can of course install the Oracle JDK manually. For the users of @emph{OpenJDK} 7 and 8, @emph{OpenJFX}, the opensource implementation of JavaFX, is not included. Luckily, many Linux distributions ship a separate OpenJFX package by now, but for those that don't, the OpenJDK wiki has an article @uref{https://wiki.openjdk.java.net/display/OpenJFX/Building+OpenJFX, ``Building OpenJFX''}. Starting with OpenJDK 9, JavaFX is available as a library. Alternatively, you can of course install the Oracle JDK manually.
@node Getting started @node Getting started
@chapter Getting started @chapter Getting started
@ -55,25 +55,24 @@ For the ``nasty hack'', you have to add a @code{defonce} @emph{before} you impor
@code{(defonce force-toolkit-init (javafx.embed.swing.JFXPanel.))} @code{(defonce force-toolkit-init (javafx.embed.swing.JFXPanel.))}
Subclassing @indicateurl{javafx.application.Application} is a tad more work and requires you to aot-compile the namespace: Alternatively, and preferredly, you can use @code{start-app}:
@lisp @lisp
(ns example.core (ns example.core
(:require [clojurefx.clojurefx :as fx]) (:require [clojurefx.clojurefx :as fx])
(:gen-class :main true (:gen-class))
:extends javafx.application.Application))
(defn -init [this] (defn init []
nil) nil)
(defn -start [this ^javafx.stage.Stage stage] (defn start [^javafx.stage.Stage stage]
(.show stage)) (.show stage))
(defn -stop [this] (defn stop []
nil) nil)
(defn -main [& args] (defn -main [& args]
(javafx.application.Application/launch example.core args)) (fx/start-app init start stop))
@end lisp @end lisp
@node Core API @node Core API
@ -87,18 +86,38 @@ This macro runs the code given on the JavaFX thread and blocks the current threa
This macro runs the code given on the JavaFX thread and immediately returns. Prefixing the s-exp with an @@ has the same effect as using @code{run-now}. This macro runs the code given on the JavaFX thread and immediately returns. Prefixing the s-exp with an @@ has the same effect as using @code{run-now}.
@end deffn @end deffn
@deffn clojurefx.clojurefx fi interface args & code
This macro is used to use a Clojure function as a functional interface. The interface name is needed: @code{(fx/fi javafx.event.Event [event] eventhandling-code)}
@end deffn
@deffn clojurefx.clojurefx connect instance function args & code
This macro is used to use a Clojure function as a functional interface. The function must be written in kebab case: @code{(fx/connect btn set-on-action [event] (do-something-with event))}
@end deffn
@deffn clojurefx.clojurefx find-child-by-class node clazz
With this function, you can find an element / elements in a scenegraph by one of its CSS classes.
@end deffn
@deffn clojurefx.clojurefx find-child-by-id node id
Analogous to find-child-by-class, but to find an element by fx:id.
@end deffn
@node Coding a scenegraph @node Coding a scenegraph
@chapter Coding a scenegraph @chapter Coding a scenegraph
@strong{This part of the library is not yet completed; mainly, the problem is that some objects can have children while not being a Parent.} You can write a scenegraph in-code using the @code{compile} function. It is straightforward, alternating between the class name and the element's properties. Make sure though to either use @code{start-app} function or first initialize the toolkit:
@code{(defonce force-toolkit-init (javafx.embed.swing.JFXPanel.))}
There are no further dependencies for this, since the calls are made using Clojure's reflection API. Let's look at an example:
@lisp @lisp
(require '[clojurefx.clojure :refer [compile]]) (require '[clojurefx.clojure :refer [compile]])
(compile [VBox @{:id "TopLevelVBox" (compile [Scene @{:root [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."@}]@}]@}]@}])
@end lisp @end lisp
@node Scenegraph API @node Scenegraph API

View file

@ -5,8 +5,9 @@
:signing {:gpg-key "68484437"} :signing {:gpg-key "68484437"}
:dependencies [[org.clojure/clojure "1.9.0"] :dependencies [[org.clojure/clojure "1.9.0"]
[org.clojure/core.async "0.4.474" :scope "test"] [org.clojure/core.async "0.4.474" :scope "test"]
[org.openjfx/javafx-fxml "11-ea+25"] [org.clojars.tristefigure/shuriken "0.14.28" :scope "test"]
[org.openjfx/javafx-swing "11-ea+25"] [org.openjfx/javafx-fxml "11-ea+25" :scope "test"]
[org.openjfx/javafx-swing "11-ea+25" :scope "test"]
[swiss-arrows "1.0.0"] [swiss-arrows "1.0.0"]
[camel-snake-kebab "0.4.0"] [camel-snake-kebab "0.4.0"]
[com.taoensso/timbre "4.10.0" :exclusions [com.taoensso/carmine]] [com.taoensso/timbre "4.10.0" :exclusions [com.taoensso/carmine]]
@ -16,7 +17,8 @@
[clojure-jsr-223 "0.1.0"] [clojure-jsr-223 "0.1.0"]
] ]
:profiles {:test {:source-paths ["test"] :profiles {:test {:source-paths ["test"]
:resource-paths ["test-resources"]} :resource-paths ["test-resources"]
:aot :all}
:uberjar {:aot :all}} :uberjar {:aot :all}}
:aot :all :aot :all
:omit-source true :omit-source true

View file

@ -5,42 +5,61 @@
[clojure.reflect :as reflect] [clojure.reflect :as reflect]
[clojure.string :as str] [clojure.string :as str]
[swiss.arrows :refer :all] [swiss.arrows :refer :all]
[clojure.spec.alpha :as s]) [clojure.spec.alpha :as s]
[clojure.pprint :refer :all]
[shuriken.macro :as sm])
(:import (javafx.scene.layout Region) (:import (javafx.scene.layout Region)
(javafx.scene.shape Rectangle) (javafx.scene.shape Rectangle)
(clojurefx.ApplicationInitializer))) (clojurefx.ApplicationInitializer)
(java.lang.reflect Method)
(javafx.scene.control Button)))
(timbre/refer-timbre) (timbre/refer-timbre)
;; ## Specs ;; ## Specs
(s/def ::node (fn [x] (or (instance? javafx.scene.Node x) (instance? javafx.scene.Scene x)))) (s/def ::node (partial instance? javafx.scene.Node))
(s/def ::scene (partial instance? javafx.scene.Scene))
(s/def ::stage (partial instance? javafx.stage.Stage))
(s/def ::scenegraph (s/or :node ::node
:scene ::scene
:stage ::stage))
(s/def ::args (s/coll-of symbol?))
(s/def ::code (s/coll-of any?))
;; ## Functional interfaces ;; ## Functional interfaces
(defn fi* [interface args code]
(debug "fi*")
(let [iface-ref (reflect/type-reflect interface)
methods (filter #(instance? clojure.reflect.Method %) (:members iface-ref))
functional-method (filter (fn [x] (some #(= % :abstract) (:flags x))) methods)
method-sym (:name (first functional-method))]
(eval `(proxy [~interface] []
(~method-sym ~args
~@code)))))
(defmacro fi (defmacro fi
"This macro is used to make use of functional interfaces. The class name of the functional interface has to be given." "This macro is used to make use of functional interfaces. The class name of the functional interface has to be given."
[interface args & code] [interface args & code]
(let [iface-ref (reflect/type-reflect interface) (debug "fi")
;;`(fi* ~interface '~args '~code)
(let [iface-ref (reflect/type-reflect interface)
methods (filter #(instance? clojure.reflect.Method %) (:members iface-ref)) methods (filter #(instance? clojure.reflect.Method %) (:members iface-ref))
functional-method (filter (fn [x] (some #(= % :abstract) (:flags x))) methods) functional-method (filter (fn [x] (some #(= % :abstract) (:flags x))) methods)
method-sym (:name (first functional-method))] method-sym (:name (first functional-method))]
(debug "method-sym:" method-sym)
(when-not (= (count functional-method) 1) (when-not (= (count functional-method) 1)
(throw (new Exception (str "can't take an interface with more than one method:" (pr-str functional-method))))) (throw (new Exception (str "can't take an interface with more than one method:" (pr-str functional-method)))))
(debug "Writing proxy.")
(debug (pr-str `(proxy [~interface] []
(~method-sym ~args ~@code))))
`(proxy [~interface] [] `(proxy [~interface] []
(~method-sym ~args (~method-sym ~args
~@code)))) ~@code)))
)
(defn- map2 (defn- map2
"Like map, but takes two elements at a time." "Like map, but takes two elements at a time."
([fun a b] (list (fun a b))) ([fun a b] (list (fun a b)))
([fun [a b & coll]] ([fun [a b & coll]]
(cons (fun a b) (map2 fun coll)))) (cons (fun a b) (map2 fun coll))))
(defn typematcher (defn typematcher
[arg-types methods] [arg-types methods]
@ -54,28 +73,32 @@
:else :else
(recur arg-types (rest methods))))) (recur arg-types (rest methods)))))
(declare camelcase-low)
(def instancecache (atom {}))
(defmacro objwrap [obj sym]
(do
(swap! instancecache assoc sym obj)
`(let [obj# (get @instancecache ~sym)]
(debug "Calling back object" obj# "with hash" ~sym)
(swap! instancecache dissoc ~sym)
obj#)))
(defmacro connect (defmacro connect
"This macro is used to make use of functional interfaces. The args list has to be provided with the arg types, like in Java: [Type name1 Type name2]." [instance method args & code]
[instance args & code] `(let [instance# ~instance
(let [class-ref (reflect/type-reflect (class instance)) dbg# (debug "instance#" instance#)
ifaces (flatten (map reflect/type-reflect (into #{} (:bases class-ref)))) method# '~(if (instance? clojure.lang.Cons method) (second method) method)
methods (filter #(instance? clojure.reflect.Method %) (:members ifaces)) dbg# (debug "method#" method#)
functional-methods (filter (fn [x] (some #(= % :abstract) (:flags x))) methods) functional-method# (first (clojure.lang.Reflector/getMethods (class instance#) 1 (camelcase-low (name method#)) false))
arg-types (map2 (fn [a _] a) args) dbg# (debug "functional-method#" functional-method#)
method (typematcher arg-types functional-methods)] functional-para# (symbol (.getName (first (.getParameterTypes ^Method functional-method#))))
(debug "method-sym:" (:name method)) dbg# (debug "functional-para#" functional-para#)
code# '~(if (= 1 (count code)) (first code) code)
`(proxy [~(:declaring-class method)] [] dbg# (debug "code#" code#)]
(~(:name method) ~(map2 (fn [_ b] b) args) (eval `(~(symbol (str "." (camelcase-low (name method#)))) ~`(objwrap ~instance# ~(hash instance#))
~@code)) (fi ~functional-para# ~'~args '~@code#))))
)) )
(defmacro handle
""
[obj prop fun]
(let [argument (->> fun (drop 1) first)
code (drop 2 fun)]
`(.setValue (~(symbol (str (name obj) "/" (name prop)))) (fi javafx.event.ActionEvent ~argument ~@code))))
(defn start-app [app-init app-start app-stop] (defn start-app [app-init app-start app-stop]
(clojurefx.ApplicationInitializer/initApp app-init app-start app-stop)) (clojurefx.ApplicationInitializer/initApp app-init app-start app-stop))
@ -90,9 +113,13 @@
(let [splitted (str/split (name kebabcase) #"-")] (let [splitted (str/split (name kebabcase) #"-")]
(reduce #(str %1 (str/capitalize %2)) "" splitted))) (reduce #(str %1 (str/capitalize %2)) "" splitted)))
(defn camelcase-low [kebabcase]
(let [splitted (str/split (name kebabcase) #"-")]
(reduce #(str %1 (str/capitalize %2)) (first splitted) (rest splitted))))
;; ## Threading helpers ;; ## Threading helpers
(defn run-later*" (defn run-later* "
Simple wrapper for Platform/runLater. You should use run-later. Simple wrapper for Platform/runLater. You should use run-later.
" [f] " [f]
(assert (instance? Runnable f)) (assert (instance? Runnable f))
@ -139,7 +166,6 @@
first)) first))
(defn invoke-constructor [clazz args] (defn invoke-constructor [clazz args]
(info "Constructing" clazz "with" (first args))
(clojure.lang.Reflector/invokeConstructor clazz (into-array args))) (clojure.lang.Reflector/invokeConstructor clazz (into-array args)))
;; ## Scene graph walker ;; ## Scene graph walker
@ -147,8 +173,6 @@
(and (not (nil? node)) (not (empty? (clojure.lang.Reflector/getMethods (class node) 0 method false))))) (and (not (nil? node)) (not (empty? (clojure.lang.Reflector/getMethods (class node) 0 method false)))))
(defn- graph-node-has-children? [node] (defn- graph-node-has-children? [node]
;{:pre [(s/valid? ::node node)]
; :post [boolean?]}
(or (has-method? node "getChildren") (or (has-method? node "getChildren")
(has-method? node "getGraphic") (has-method? node "getGraphic")
(has-method? node "getMenus") (has-method? node "getMenus")
@ -156,20 +180,22 @@
(has-method? node "getContent") (has-method? node "getContent")
(has-method? node "getTabs") (has-method? node "getTabs")
(has-method? node "getItems") (has-method? node "getItems")
(has-method? node "getRoot")) (has-method? node "getRoot")
(has-method? node "getScene"))
) )
(defn- graph-node-get-children [node] (defn- graph-node-get-children [node]
{:pre [(s/valid? ::node node)] {:pre [(s/valid? ::scenegraph node)]
:post [coll?]} :post [coll?]}
(cond (has-method? node "getChildren") (.getChildren node) (cond (has-method? node "getChildren") (.getChildren node)
(has-method? node "getGraphic") [(.getGraphic node)] (has-method? node "getGraphic") [(.getGraphic node)]
(has-method? node "getMenus") (.getMenus node) (has-method? node "getMenus") (.getMenus node)
(has-method? node "getContent") [(.getContent node)] (has-method? node "getContent") [(.getContent node)]
(has-method? node "getTabs") (.getTabs node) (has-method? node "getTabs") (.getTabs node)
(has-method? node "getColumns") (.getColumns node) (has-method? node "getColumns") (.getColumns node)
(has-method? node "getItems") (.getItems node) (has-method? node "getItems") (.getItems node)
(has-method? node "getRoot") [(.getRoot node)]) (has-method? node "getRoot") [(.getRoot node)]
(has-method? node "getScene") [(.getScene node)])
) )
;; (def struct (compile [Scene {:root [VBox {:children [Label {:text "Hi!" :style-class ["test"]}]}]}])) ;; (def struct (compile [Scene {:root [VBox {:children [Label {:text "Hi!" :style-class ["test"]}]}]}]))
@ -182,32 +208,37 @@
(zip/node zipper) (zip/node zipper)
(lazy-seq (cons (zip/node zipper) (flat-zipper (zip/next zipper)))))) (lazy-seq (cons (zip/node zipper) (flat-zipper (zip/next zipper))))))
(defn- has-id? [node id]
{:pre [(s/valid? ::scenegraph node) (string? id)]
:post [boolean?]}
(if (s/valid? ::node node)
(= id (.getId node))
false))
(defn find-child-by-id [node id] (defn find-child-by-id [node id]
{:pre [(s/valid? ::node node) {:pre [(s/valid? ::scenegraph node)
(string? id)] (string? id)]
:post [#(or (s/valid? ::node node) nil?)]} :post [#(or (s/valid? ::scenegraph node) nil?)]}
(let [zipper (scenegraph-zipper node)] (let [zipper (scenegraph-zipper node)]
(filter #(= id (.getId %)) (flat-zipper zipper)))) (first (filter #(has-id? % id) (flat-zipper zipper)))))
(defn- contains-class? [node clazz] (defn- contains-class? [node clazz]
{:pre [(s/valid? ::node node) (string? clazz)] {:pre [(s/valid? ::scenegraph node) (string? clazz)]
:post [boolean?]} :post [boolean?]}
(s/valid? ::node node) (if (s/valid? ::node node)
(if (instance? javafx.scene.Scene node) (< 0 (count (filter #(= % clazz) (.getStyleClass node))))
false false))
(< 0 (count (filter #(= % clazz) (.getStyleClass node))))))
(defn find-child-by-class [node clazz] (defn find-child-by-class [node clazz]
{:pre [(s/valid? ::node node) {:pre [(s/valid? ::scenegraph node)
(string? clazz)] (string? clazz)]
:post [#(or (s/valid? ::node node) nil?)]} :post [#(or (s/valid? ::scenegraph node) nil?)]}
(let [zipper (scenegraph-zipper node)] (let [zipper (scenegraph-zipper node)]
(filter #(contains-class? % clazz) (flat-zipper zipper)))) (filter #(contains-class? % clazz) (flat-zipper zipper))))
;; ## Properties ;; ## Properties
(defn find-property [obj prop] (defn find-property [obj prop]
(info obj prop)
(clojure.lang.Reflector/invokeInstanceMethod obj (str (camelcase prop) "Property") (into-array []))) (clojure.lang.Reflector/invokeInstanceMethod obj (str (camelcase prop) "Property") (into-array [])))
(defn get-property-value (defn get-property-value
@ -216,30 +247,39 @@
(defn set-property-value (defn set-property-value
([obj prop val] ([obj prop val]
(info obj ": Setting property" prop "to" val)
(clojure.lang.Reflector/invokeInstanceMethod obj (str "set" (camelcase prop)) (into-array [val])))) (clojure.lang.Reflector/invokeInstanceMethod obj (str "set" (camelcase prop)) (into-array [val]))))
;; ## In-code scenegraph ;; ## In-code scenegraph
(declare compile-o-matic) (declare compile-o-matic)
(defn- apply-connects [nodeobj [[method args & code] & conncoll]]
{:pre [(s/valid? ::node nodeobj)
(symbol? method)
(s/valid? ::args args)
(s/valid? ::code code)]}
(eval `(connect ~`(objwrap ~nodeobj ~(hash nodeobj)) '~method '~args ~code))
(debug "Heyy!")
(if (not (or (nil? conncoll) (empty? conncoll)))
(recur nodeobj conncoll)))
(defn- apply-props-to-node [nodeobj propmap] (defn- apply-props-to-node [nodeobj propmap]
(doseq [[k v] propmap] (doseq [[k v] propmap]
(case k (cond
:children (.add (.getChildren nodeobj) (compile-o-matic v)) (= k :children) (.add (.getChildren nodeobj) (compile-o-matic v))
:style-class (.addAll (.getStyleClass nodeobj) (compile-o-matic v)) (= k :style-class) (.addAll (.getStyleClass nodeobj) (compile-o-matic v))
(set-property-value nodeobj k (compile-o-matic v)))) (= k :connect) (apply-connects nodeobj v)
:else (set-property-value nodeobj k (compile-o-matic v)))
)
nodeobj) nodeobj)
(defn- propmap-splitter [clazz propmap] (defn- propmap-splitter [clazz propmap]
(let [constructor-args (keys (get @constructor-args clazz))] (let [constructor-args (keys (get @constructor-args clazz))]
(info "Constructor args for" clazz "are" constructor-args)
[(map propmap constructor-args) (apply dissoc propmap constructor-args)])) [(map propmap constructor-args) (apply dissoc propmap constructor-args)]))
(defn- build-node [clazz propmap] (defn- build-node [clazz propmap]
(let [[cargs props] (propmap-splitter clazz propmap) (let [[cargs props] (propmap-splitter clazz propmap)
nodeobj (invoke-constructor clazz (map compile-o-matic cargs))] nodeobj (invoke-constructor clazz (map compile-o-matic cargs))]
(info cargs " " props)
(apply-props-to-node nodeobj props) (apply-props-to-node nodeobj props)
nodeobj)) nodeobj))

View file

@ -3,39 +3,37 @@
[clojure.core.async :as async :refer [<! >! chan go go-loop]] [clojure.core.async :as async :refer [<! >! chan go go-loop]]
[clojure.test :as t] [clojure.test :as t]
[clojure.java.io :as io] [clojure.java.io :as io]
[taoensso.timbre :as timbre])) [taoensso.timbre :as timbre
;(timbre/refer-timbre) :refer [debug]]))
; ;
;(defonce force-toolkit-init (javafx.embed.swing.JFXPanel.)) (go (defonce force-toolkit-init (javafx.embed.swing.JFXPanel.)))
; (Thread/sleep 500)
;(def test1-fxml (io/resource "fxml/exampleWindow.fxml"))
; (def test1-fxml (io/resource "fxml/exampleWindow.fxml"))
;(t/deftest fxml-loading
; (debug "FXML loading") (t/deftest fxml-loading
; (t/is (instance? javafx.scene.Node (sut/load-fxml test1-fxml)))) (t/is (instance? javafx.scene.Node (sut/load-fxml test1-fxml))))
;
;(def test2-fxml (io/resource "fxml/exampleControllerWindow.fxml")) (def test2-fxml (io/resource "fxml/exampleControllerWindow.fxml"))
;
;(t/deftest controller-generation (t/deftest controller-generation
; (t/is (instance? java.lang.Class (sut/generate-controller test2-fxml "a.b/c")))) (t/is (instance? java.lang.Class (sut/generate-controller test2-fxml "a.b/c"))))
;
; (def instance (atom nil))
; (def clicked (atom false))
;(def instance (atom nil))
;(def clicked (atom false)) (defn initialize [inst]
; (reset! instance inst))
;(defn initialize [inst]
; (reset! instance inst)) (defn test-1-click [_ e]
; (reset! clicked true))
;(defn test-1-click [_ e]
; (reset! clicked true)) (sut/load-fxml-with-controller test2-fxml "clojurefx.fxml-test/initialize")
;
;(sut/load-fxml-with-controller test2-fxml "clojurefx.fxml-test/initialize") (t/deftest proper-init
; (t/is (instance? ch.lyrion.Test1 @instance)))
;(t/deftest proper-init
; (t/is (instance? ch.lyrion.Test1 @instance))) (.fire (.simpleButton @instance))
;
;(.fire (.simpleButton @instance)) (t/deftest testfire-result
; (t/is @clicked))
;(t/deftest testfire-result
; (t/is @clicked))

View file

@ -5,7 +5,7 @@
[clojure.test :as t] [clojure.test :as t]
[taoensso.timbre :as timbre [taoensso.timbre :as timbre
:refer [info]]) :refer [info]])
(:import (javafx.scene.control Label) (:import (javafx.scene.control Label Button)
(javafx.scene Scene) (javafx.scene Scene)
(javafx.scene.layout VBox))) (javafx.scene.layout VBox)))
@ -19,4 +19,30 @@
(t/is (instance? Label (t/is (instance? Label
(first (find-child-by-class (compile [Scene {:root [VBox {:children [Label {:text "Hi!" :style-class ["test"]}]}]}]) (first (find-child-by-class (compile [Scene {:root [VBox {:children [Label {:text "Hi!" :style-class ["test"]}]}]}])
"test")) "test"))
))) )))
(t/deftest test-find-child-by-id
(t/is (instance? Label
(find-child-by-id (compile [Scene {:root [VBox {:children [Label {:text "Hi!" :id "test"}]}]}])
"test")
)))
(t/deftest functional-interfaces-fi
(let [fired (atom false)
btn (Button.)]
(.setOnAction btn (fi javafx.event.EventHandler [event] (reset! fired true)))
(.fire btn)
(t/is @fired)))
(t/deftest functional-interfaces-connect
(let [fired (atom false)
btn (Button.)]
(connect btn set-on-action [event] (reset! fired true))
(.fire btn)
(t/is @fired)))
(t/deftest connect-in-compile
(let [fired (atom false)
graph (compile [Scene {:root [VBox {:children [Button {:id "Button" :connect ['(set-on-action [event] (reset! fired true))]}]}]}])]
(.fire (find-child-by-id graph "Button"))
(t/is @fired)))