Some stuff
This commit is contained in:
parent
0441e6cbec
commit
56e94758c2
6 changed files with 261 additions and 143 deletions
|
@ -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">• <a href="#Installation-and-deployment" accesskey="1">Installation and deployment</a>:</td><td> </td><td align="left" valign="top">adding ClojureFX and probably tools.jar to your build tool.
|
<tr><td align="left" valign="top">• <a href="#Installation-and-deployment" accesskey="1">Installation and deployment</a>:</td><td> </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 "0.4.0"]</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 "0.5.0"]</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’t, the OpenJDK wiki has an article <a href="https://wiki.openjdk.java.net/display/OpenJFX/Building+OpenJFX">“Building OpenJFX”</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’t, the OpenJDK wiki has an article <a href="https://wiki.openjdk.java.net/display/OpenJFX/Building+OpenJFX">“Building OpenJFX”</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 ‘<code>javafx.application.Application</code>’ 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 [& args]
|
(defn -main [& 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>
|
||||||
<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 & 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 & 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’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’s reflection API. Let’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 "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."}]}]}]}])
|
||||||
</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: </th><td><a class="summary-letter" href="#Index_fn_letter-C"><b>C</b></a>
|
<table><tr><th valign="top">Jump to: </th><td><a class="summary-letter" href="#Index_fn_letter-C"><b>C</b></a>
|
||||||
|
|
||||||
|
<a class="summary-letter" href="#Index_fn_letter-F"><b>F</b></a>
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
<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> </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> </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> </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> </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> </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> </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> </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> </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: </th><td><a class="summary-letter" href="#Index_fn_letter-C"><b>C</b></a>
|
<table><tr><th valign="top">Jump to: </th><td><a class="summary-letter" href="#Index_fn_letter-C"><b>C</b></a>
|
||||||
|
|
||||||
|
<a class="summary-letter" href="#Index_fn_letter-F"><b>F</b></a>
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
<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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -5,36 +5,55 @@
|
||||||
[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."
|
||||||
|
@ -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,6 +113,10 @@
|
||||||
(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* "
|
||||||
|
@ -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,11 +180,12 @@
|
||||||
(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)]
|
||||||
|
@ -169,7 +194,8 @@
|
||||||
(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))
|
||||||
|
|
||||||
|
|
|
@ -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))
|
|
||||||
|
|
|
@ -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)))
|
Loading…
Reference in a new issue