1 of 40

Bringing Clojure to the Desktop

with cljfx

2 of 40

The problem: complex stateful applications

3 of 40

The problem: complex stateful applications

4 of 40

The problem: a place for tooling (rant)

5 of 40

The problem: a place for tooling (rant)

6 of 40

The problem: a place for tooling (rant)

(require 'clojure.inspector)

7 of 40

The problem: a place for tooling (rant)

(require 'clojure.inspector)

(clojure.inspector/inspect-table

[{:a 1 :b 2}

{:a 3 :b 4}])

(clojure.inspector/inspect-tree

{:a 1

:b {:a 1

:b 2}})

8 of 40

The problem: a place for tooling (rant)

9 of 40

The problem: a place for tooling (rant)

10 of 40

The problem: a place for tooling (rant)

11 of 40

Solution

12 of 40

Somewhat unbiased comparison to Electron

clj + cljfx

cljs + electron

Memory use

high

high

CPU use

high

high

Language semantics

powerful

great but limited

Concurrency ergonomics

great

great

Performance

great

good

Ecosystem size

huge

huge

Ecosystem maturity

high

low

Industry support

low

high

Typesetting

bad

good

13 of 40

Getting started: component description

{:fx/type :text-field

:text "Hello world"

:on-text-changed println}

14 of 40

Getting started: component creation

(require '[cljfx.api :as fx])

(def component

(fx/create-component

{:fx/type :text-field

:text "Hello world"

:on-text-changed println}))

(.getText (fx/instance component))

=> "Hello world"

15 of 40

Getting started: component creation

(doto (TextField.)

(.setText "Hello world")

(-> .textProperty

(.addListener (reify ChangeListener

(changed [_ _ _ value]

(println value))))))

16 of 40

Getting started: component prop evolution

(def component-2

(fx/advance-component

component

{:fx/type :text-field

:text "Hello cljfx"

:on-text-changed println}))

(.getText (fx/instance component-2))

=> "Hello cljfx"

(identical? (fx/instance component) (fx/instance component-2))

=> true

17 of 40

Getting started: component type evolution

(def component-3

(fx/advance-component

component-2

{:fx/type :text-area

:text "Hello cljfx"

:on-text-changed println}))

(class (fx/instance component-2))

=> javafx.scene.control.TextField

(class (fx/instance component-3))

=> javafx.scene.control.TextArea

18 of 40

Getting started: function components

(defn username-input [{:keys [username on-username-changed]}]

{:fx/type :text-field

:text username

:on-text-changed on-username-changed})

(def username-component

(fx/create-component

{:fx/type username-input

:username "vlaaad"

:on-username-changed println}))

(class (fx/instance username-component))

=> javafx.scene.control.TextField

19 of 40

Getting started: renderer

(def renderer

(fx/create-renderer))

(renderer

{:fx/type :stage

:showing true

:scene {:fx/type :scene

:root {:fx/type :text-field

:text "Hello world"

:on-text-changed println}}})

20 of 40

Getting started: renderer

(renderer

{:fx/type :stage

:showing true

:scene {:fx/type :scene

:root {:fx/type :text-field

:text "Hello cljfx"

:on-text-changed println}}})

21 of 40

Getting started: renderer

(renderer

{:fx/type :stage

:showing true

:scene {:fx/type :scene

:root {:fx/type :v-box

:children [{:fx/type :label

:text "Username"}

{:fx/type username-input

:username ""

:on-username-changed println}]}}})

22 of 40

Getting started: event handling

=> v

23 of 40

Getting started: event handling

=> v

=> vl

24 of 40

Getting started: event handling

=> v

=> vl

=> vla

25 of 40

Getting started: event handling

=> v

=> vl

=> vla

=> vlaa

26 of 40

Getting started: event handling

=> v

=> vl

=> vla

=> vlaa

=> vlaaa

27 of 40

Getting started: event handling

=> v

=> vl

=> vla

=> vlaa

=> vlaaa

=> vlaaad

28 of 40

Getting started: state

(def *state (atom {:username "vlaaad"}))

(defn root [{:keys [username]}]

{:fx/type :stage

:showing true

:scene {:fx/type :scene

:root

{:fx/type :v-box

:children

[{:fx/type :label

:text "Username"}

{:fx/type username-input

:username username

:on-username-changed #(swap! *state assoc :username %)}]}}})

29 of 40

Getting started: state

(renderer (root @*state))

30 of 40

Getting started: state

(def renderer

(fx/create-renderer

:middleware (fx/wrap-map-desc root)))

(fx/mount-renderer *state renderer)

31 of 40

There is more: map events

(def renderer

(fx/create-renderer :opts {:fx.opt/map-event-handler prn}))

(renderer

{:fx/type :stage

:showing true

:scene {:fx/type :scene

:root {:fx/type username-input

:username ""

:on-username-changed {::event ::set-username}}}})

=> {::event ::set-username, :fx/event "cljfx"}

32 of 40

There is more: pure event handling

(defn- my-event-handler [{:keys [state fx/event]}]

(case (::event event)

::add-todo {:set-state (update state :todos conj {:text (:text event)

:done false})}))

(-> my-event-handler

(fx/wrap-co-effects {:state (fx/make-deref-co-effect *state)})

(fx/wrap-effects {:set-state (fx/make-reset-effect *state)}))

(my-event-handler {:state {:todos []}

:fx/event {::event ::add-todo

:text "Play Animal Crossing"}})

=> {:set-state {:todos [{:text "Play Animal Crossing"

:done false}]}}

33 of 40

There is more: many examples

34 of 40

There is more: cljfx/css

(require '[cljfx.css :as css])

(def background-color "#ffc")

(def text-color "#400")

(def style

(css/register ::style

{".my-label" {:-fx-padding 20

:-fx-text-fill text-color}

".my-root" {:-fx-background-color background-color}}))

35 of 40

There is more: cljfx/css

(renderer

{:fx/type :stage

:showing true

:scene {:fx/type :scene

:stylesheets [(::css/url style)]

:root {:fx/type :v-box

:style-class "my-root"

:children [{:fx/type :label

:style-class "my-label"

:effect {:fx/type :drop-shadow

:color text-color

:offset-y 2}

:text "hello styling"}]}}})

36 of 40

There is more: special keys

(def todos

[{:id 1 :done false :text "Buy groceries"}

{:id 2 :done true :text "Play Minecraft"}

{:id 3 :done false :text "Prepare talk"}])

{:fx/type :v-box

:children (for [todo (sort-by :done todos)]

{:fx/type :check-box

:fx/key (:id todo)

:v-box/margin 10

:selected (:done todo)

:text (:text todo)})}

37 of 40

There is more: packaging with Java 14

$ jpackage --input dist \

--main-jar uberjar.jar \

--main-class my.app.core \

--name my-app

38 of 40

There is more: advanced stuff

;; re-frame-like subscriptions

(fx/sub-ctx context sorted-todos)

;; extension lifecycles

{:fx/type fx/ext-instance-factory

:create #(Duration/valueOf "10ms")}

;; tools to create custom props

(def ext-with-tooltip

(fx/make-ext-with-props

{:tooltip (fx.prop/make ...)}))

39 of 40

Summary

40 of 40

Thank you!

Any questions?