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
(defmacro fi (defn fi* [interface args code]
"This macro is used to make use of functional interfaces. The class name of the functional interface has to be given." (debug "fi*")
[interface args & code]
(let [iface-ref (reflect/type-reflect interface) (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) (eval `(proxy [~interface] []
(~method-sym ~args
~@code)))))
(defmacro fi
"This macro is used to make use of functional interfaces. The class name of the functional interface has to be given."
[interface args & code]
(debug "fi")
;;`(fi* ~interface '~args '~code)
(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))]
(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)))
@ -20,3 +20,29 @@
(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)))