Last time, we examined an idiomatic "Hello, world" application written using Clojure Desktop Toolkit.
This episode, we'll rewrite "Hello, world" but start by using the naked Clojure Desktop Toolkit user interface engine and none of the helpers that Clojure Desktop Toolkit automatically generates from SWT's API. From there, we'll rebuild the sugared version step-by-step.
As a result, you will:
Here's the "Hello, world" application we examined last time:
(defn hello []
(application
(shell SWT/SHELL_TRIM
"Hello application"
:layout (FillLayout.)
(label "Hello, world"))))
Here is the same application, with all syntactic sugar removed:
(defn hello-desugared []
(application
(fn [props parent]
(let [child (Shell. parent SWT/SHELL_TRIM)]
(doall
(map (fn [initfn] (initfn props child))
[(fn [_props parent] (.setText parent "Hello application"))
(fn [_props parent] (.setLayout parent (FillLayout.)))
(fn [props parent]
(let [child (Label. parent SWT/NONE)]
(doall
(map (fn [initfn] (initfn props child))
[(fn [_props parent] (.setText parent "Hello, world"))]))
child))]))
(.open child)
child))))
What are the the lessons?
(fn [props parent] ,,,,)
(What is this props
parameter? It's an atom containing a map where one can set and retrieve state throughout user interface construction and later bind data into the user interface elements stored there. We'll see more of this later.)
Init functions that construct their own widgets (such as Shell and Label in this example) must also accept a parameter list of init functions for setting their own properties and for constructing their own children. These "constructor" init functions are responsible to run these child inits as well. Constructor init functions must return the thing that they constructed.
Regarding point #3 above, in our example, the "constructor" init functions don't accept parameter lists at all, but hard-code their child init functions into the map
functiion call. How does Clojure Desktop Toolkit do this?
Clojure Desktop Toolkit's own special-purpose constructor init functions like shell
and label
are functions that return init functions.
Let's use a macro to illustrate the general idea:
(defmacro widget
"Add a child widget to specified parent and run the functions in its arglist"
[clazz style-bits & initfns]
`(fn [props# parent#]
(let [child# (new ~clazz parent# ~style-bits)]
(doall (map (fn [initfn#] (initfn# props# child#)) [~@initfns]))
child#)))
(defn hello-desugared-with-widget-helper []
(application
(widget Shell SWT/SHELL_TRIM
(fn [_props parent] (.setText parent "Hello application"))
(fn [_props parent] (.setLayout parent (FillLayout.)))
(widget Label SWT/NONE
(fn [_props parent] (.setText parent "Hello, world")))
(fn [_props parent] (.open parent)))))
In this revised "desugared" Hello program, we use a macro to rewrite the hello-desugared-with-widget-helper
function body to almost exactly what hello-desugared
was.
Now we know enough to understand how the syntax sugar works.
Returning to our original "Hello, world":
(defn hello []
(application
(shell SWT/SHELL_TRIM
"Hello application"
:layout (FillLayout.)
(label "Hello, world"))))
Question: How does Clojure Desktop Toolkit support all of this sugar? Clearly, the argument lists in Clojure Desktop Toolkit (like those above) aren't expecting each argument to be a function with the init function signature.
Answer: For arguments that aren't already init functions, Clojure Desktop Toolkit first translates (or wraps) those arguments into init functions.
To illustrate, here is the actual code Clojure Desktop Toolkit uses to construct a Shell
:
(defn shell
"org.eclipse.swt.widgets.Shell"
[& args]
(let [[style
args] (i/extract-style-from-args args)
style (nothing->identity SWT/SHELL_TRIM style)
style (if (= style SWT/DEFAULT)
SWT/SHELL_TRIM
style)
init (i/widget* Shell style (or args []))]
(fn [props disp]
(let [sh (init props disp)]
(.open sh)
sh))))
In the code above, the i
alias points to a namespace with utilities for building init functions from argument lists. The i/widget*
macro builds an all-in-one init function that constructs the Shell
and also runs the list of inits constructed from the shell
function's arguments against the shell.
NOTE: In Clojure Desktop Toolkit,
shell
is a special case, because theShell
requires special style bit processing, and because theShell
itself has to be opened after construction.In contrast, the other widget classes' initialization functions can be (and are) generated mechanically. These expand to init functions that are nearly identical to what we wrote above by hand.
(fn [props parent] ,,,,)
.props
is an atom containing a map. It can store user interface widget instances or any other state needed to implement a desired UX.widget
macro above is a simple example. shell
is a built-in example. That's all there is to extending Clojure Desktop Toolkit's basic vocabulary and behavior!There's one more bit of "magic" happening when you assign to widget properties:
We'll talk about this in the next chapter.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close