Here is a quick introduction to style your Clojure JavaFX application via CSS using the garden library.

Intro

I am still working on a desktop application built with Clojure and fn-fx and I am making good progress, pretty happy with it so far. When I gave a talk about it at the Clojure meetup Berlin, I was asked about how it looks. Well it might not be spectacular, but I was actually quite happy with the default look of it. Yeah, despite being a Java app! It might be hard to be believe when you still remember the stuff from the 90s and 00s, but check it out yourself:

JavaFX default visuals, except for the plot symbols
JavaFX default visuals, except for the plot symbols

One thing I want to customise already though are the plots. Later I might also have to touch some of the other UI elements. So how to do that with Clojure and fn-fx setup I am using?

I am going to show you how to use CSS in you fn-fx application and how to generate CSS with the full power of Clojure using the garden library.

Using CSS with fn-fx

We can simply declare a CSS files to be used on a scene of our application:

(defui MainScene
  (render [this {:keys [::data ::options] :as state}]
    (controls/scene
     :stylesheets (::style-sheets options)
     :root ...)))

We are getting the file from the application state dynamically, so that we can swap it if we want to. This would for example allow to allow the user to load different themes (and is helpful for the REPL, see below). The stylesheet can be set and updated in the application state atom:

(swap! data-state assoc-in [::options ::style-sheets] ["main.css"])

We are just passing the filename to the JavaFX Scene object as a string. The object needs to be able to find that file, I recommend you put it in the resources subfolder of your project but you could of course also have a different setup.

There is one minor annoyance with the described approach though: As we are using a simple vector of strings, which is used in the fn-fx component, this won’t trigger a UI update when the file contents change. In a final build of the application you might not want that at all anyways but for development, we want to be able to update the CSS from the REPL dynamically. Luckily we put the filename in our application state, so for development I just use a simple hack to trigger an update:

(swap! data-state assoc-in [::options ::style-sheets] ["invalidate"])
(swap! data-state assoc-in [::options ::style-sheets] ["main.css"])

This will first swap another filename in (which does not need to actually exist on the file system, it will simply be ignored then) and then back to the actual file main.css. This will then trigger an UI update of the scene and reload the CSS file.

You could just put the CSS file in place and be done, great! But wait, you don’t want to write CSS, LESS & Co. do you? Let’s use Clojure instead, read ahead!

Using garden

garden is a nice library which allows generating CSS from Clojure. If you know hiccup, it is pretty much like that. If you are a Clojure web developer you might be using garden already, otherwise give it a try. We are going to use it for our desktop application though!

Let’s jump straight in an create a couple of rules to change the design of JavaFX charts:

 (let [series-colors ["#00A0B0" "#6A4A3C" "#CC333F" "#EB6841" "#EDC951"]]
      (garden/css {:output-to "resources/main.css"}
                  (for [n (range 5)]
                    [(keyword (str ".chart-symbol.series" n))
                     {:-fx {:background-color (str (nth series-colors n) ", white")
                            :background-insets "0, 2"
                            :opacity 0.6}}
                     [:&:hover {:-fx {:opacity 1
                                      :scale-x 2
                                      :scale-y 2}}]])
                  [:.chart
                   {:-fx {:background-color :white
                          :vertical-grid-lines-visible false
                          :horizontal-grid-lines-visible false
                                        ; :rotate 45
                                        ; :opacity 0.6
                          }}
                   [:.chart-plot-background {:-fx {:background-color :white}}]]))

( Color palette by DESIGNJUNKEE)

Oh, so much code? Well most of it is just the CSS rules. You should be able to follow along easily. Please pay attention that we can really make excellent use of Clojure via let-bindings, for list comprehensions, string concatenation, etc. to build the CSS rules. The function garden/css (you need to (require '[garden.core :as garden])) returns the compiled CSS. We are also providing the optional {:output-to "resources/main.css} argument so that the file is written straight away and we do not need to do that manually. You might not want to do that in your final build and you could have a look at the Boot and Leiningen plugins for garden.

The JavaFX CSS Reference Guide has all the specifications you need to figure out the classes and available properties for the UI elements you would like to style. The JavaFX properties start with -fx-, for example: -fx-opacity garden allows to define those prefixes for a whole property map, that is why you see those {:-fx {....}} constructs. If you prefer you could of course always write the full property names instead.

In the example I am using a list comprehension to create rules for the selectors .chart-symbol.series0 to .chart-symbol.series4. Those classes determine the style of the symbols used in the charts. I am also adding a :hover effect, remove the grid lines from the chart area and change the background colours. You can see the effects in this image series:

Did you recognise the :rotate and :opacity rules on the .chart selector? They were commented out but if this floats you boat, you can go wild:

Rotated plots with dropshadows? Sure!
Rotated plots with dropshadows? Sure!

I hope to have convinced you that a JavaFX application can look good. If you are better with CSS than I am you probably can make them even great! Additionally you can have a nice, idiomatic Clojure setup and CSS rule handling. My JavaFX journey keeps being surprisingly pleasant!

Please let me know if you have comments, have any questions or run into issues or just want to have a chat about the stuff.

Thanks for reading and cheers

Nils