Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

readme and various cleanup

  • Loading branch information...
commit cdb4045ace8e47238bd1829f50e0056ac6b0ce9d 1 parent 7ae2afd
@daveray authored
@@ -1,13 +0,0 @@
-# seesaw
-A primordial Swing DSL in Clojure
-## Usage
-See tests and src/seesaw/examples.
-## License
-Copyright (C) 2011 Dave Ray
-Distributed under the Eclipse Public License, the same as Clojure.
@@ -0,0 +1,149 @@
+# Seesaw: a Clojure/Swing experiment
+Seesaw's a *primordial* experiment to see what I can do to make Swing funner in Clojure. It's maybe inspired by [Shoes](, [Stuart Sierra's Swing posts](, etc. [clojure.contrib.swing-utils]( is useful, but minimal and still means a lot of "Java-in-Clojure" coding.
+## Usage
+See tests and src/seesaw/examples. Seriously, the tests are pretty descriptive of how things work.
+Let's create a `JFrame`:
+ (frame :title "Hello" :content "Hi there")
+This will create a `JFrame` with title "Hello" and a single label "Hi there". The `:content` property expects something that can be turned into a widget and uses it as the content pane of the frame. Any place where a widget is expected, one will be created depending on the argument...
+### Widget Coercion
+ <tr><td>Input</td><td>Result</td></tr>
+ <tr><td>java.awt.Component</td><td>return argument unchanged</td></tr>
+ <tr><td>java.awt.Dimension</td><td>return Box/createRigidArea</td></tr>
+ <tr><td>java.swing.Action</td><td>return a button using the action</td></tr>
+ <tr><td>java.util.EventObject (for example in an event handler)</td><td>return the event source</td></tr>
+ <tr><td>:fill-h</td><td>Box/createHorizontalGlue</td></tr>
+ <tr><td>:fill-v</td><td>Box/createVerticalGlue</td></tr>
+ <tr><td>[:fill-h n], e.g. <code>[:fill-h 99]<code></td><td>Box/createHorizontalStrut with width n</td></tr>
+ <tr><td>[:fill-v n]</td><td>Box/createVerticalStrut with height n</td></tr>
+ <tr><td>[width :by height]</td><td>create rigid area with given dimensions</td></tr>
+ <tr><td>A URL</td><td>a label with the image located at the url</td></tr>
+ <tr><td>A non-url string</td><td>a label with the given text</td></tr>
+Most of Seesaw's container functions (`flow-panel`, `grid-panel`, etc) take an `:items` property which is a list of these widget-able values. For example:
+ (let [choose (fn [e] (alert "I should open a file chooser"))]
+ (flow-panel
+ :items ["File" [:fill-h 5]
+ (text (System/getProperty "user.dir")) [:fill-h 5]
+ (action choose :name "...")]))
+creates a panel with a "File" label, a text entry field initialized to the current working directory and a button that doesn't do much. Each component is separated by 5 pixel padding.
+### Default Properties
+All of Seesaw's widget creation functions (`label`, `text`, `horizontal-panel`, etc) support a base set of properties:
+ <tr><td>Property</td><td>Description</td></tr>
+ <tr><td><code>:opaque</code></td><td>(boolean) Set whether the background of the widget is opaque.</td></tr>
+ <tr><td><code>:background</code></td><td>Background color by coercing into a Color (see below)</td></tr>
+ <tr><td><code>:foreground</code></td><td>Foreground color by coercing into a Color (see below)</td></tr>
+ <tr><td><code>:border</code></td><td>Set the border of the widget by coercing into a Border. See below.</td></tr>
+ <tr><td><code>:font</code></td><td>Set the font of the widget by coercing into a Font. See below.</td></tr>
+ <tr><td><code>:on-action</code></td><td>Set the widget's default action handler by coercing into an action. See below.</td></tr>
+ <tr><td><code>:on-mouse-clicked</code>, <code>:on-mouse-entered</code>,<code>:on-mouse-exited</code> </td><td>Handle mouse events using the given event handler functions.</td></tr>
+### Containers
+There are container creation functions which basically create `JPanel` instances with particular layouts. Here are some examples. Any place that a widget or list of widgets is expected, the widget coercion rules described above apply.
+A `FlowLayout` with some items:
+ (flow-panel
+ :align :left
+ :hgap 20
+ :items ["Label" (action alert "Button") "Another label"])
+A `GridLayout` with 2 columns and a titled border:
+ (grid-panel
+ :border "Properties"
+ :columns 2
+ :items ["Name" (text "Frank")
+ "Address" (text "123 Main St")])
+A `BorderLayout`:
+ (border-panel :hgap 10 :vgap 10 :center "CENTER" :north "NORTH" :south "SOUTH" :east "EAST" :west "WEST")
+### Event Handlers
+Event handler functions are single-argument functions that take an event object whose type depends on the event being fired, e.g. `MouseEvent`. For example, we can execute a function when a checkbox is checked:
+ (let [handler (fn [e] (alert (.. (.getSource e) (isSelected))))]
+ (checkbox :text "Check me" :on-selection-changed handler))
+### Color Coercion
+Colors can be specified in the following ways (using the `:foreground` property as an example):
+ :foreground java.awt.Color/BLACK (a raw color object)
+ :foreground (color 255 255 224) (RGB bytes)
+ :foreground (color 255 255 224 128) (RGBA bytes)
+ :foreground "#FFEEDD" (hex color string)
+ :foreground (color "#FFEEDD" 128) (hex color string + alpha)
+Here's a label with blue text and a red background:
+ (label :text "Hideous"
+ :opaque true
+ :foreground (color 0 0 255)
+ :background "#FF0000")
+Of course, a raw `Color` object can also be used.
+### Font Coercion
+Fonts can be specified in the following ways (using the `:font` property as an example):
+ :font "ARIAL-BOLD-18" (Swing-style font spec string)
+ :font {:name "ARIAL" :style :bold :size 18} (using a properties hash)
+ :font (font :name "ARIAL" :style :bold :size 18) (using properties with font function)
+So, you could make a monospaced text area like this:
+ (text :text "Type some code here"
+ :multi-line? true
+ :font {:name :monospaced :size 15})
+Of course, a raw `Font` object can also be used.
+### Border Coercion
+Widget borders can be passed to the `:border` property to create many border styles:
+ :border "Title" (creates a plain title border)
+ :border 10 (creates an empty 10 pixel border)
+ :border [10 "Title" 5] (compound empty/title/empty border)
+ :border (line-border :thickness 3 :color "#FF0000") (red, 3 pixel border)
+ :border (line-border :top 5 :left 5) (5 pixel black border on top and left)
+Of course, a raw `Border` object can also be used.
+### Scrolling
+Use the `(scrollable)` function to make a widget scrollable:
+ (scrollable (text :multi-line? true))
+### Splitters
+Use the `(top-bottom-split)` or `(left-right-split)` functions to make a splitter each takes two widget args:
+ (top-bottom-split "Top" "Bottom")
+ (left-right-split "Top" "Bottom")
+## License
+Copyright (C) 2011 Dave Ray
+Distributed under the Eclipse Public License, the same as Clojure.
51 src/seesaw/core.clj
@@ -99,11 +99,26 @@
(apply-selection-changed-handler opts))))
(defn to-widget [v]
+ "Try to convert the input argument to a widget based on the following rules:
+ nil -> nil
+ java.awt.Component -> return argument unchanged
+ java.awt.Dimension -> return Box/createRigidArea
+ java.swing.Action -> return a button using the action
+ java.util.EventObject -> return the event source
+ :fill-h -> Box/createHorizontalGlue
+ :fill-v -> Box/createVerticalGlue
+ [:fill-h n] -> Box/createHorizontalStrut with width n
+ [:fill-v n] -> Box/createVerticalStrut with height n
+ [width :by height] -> create rigid area with given dimensions
+ A URL -> a label with the image located at the url
+ A non-url string -> a label with the given text
+ "
(let [vs (when (coll? v) (seq v))]
(nil? v) nil
- (instance? java.awt.Dimension v) (Box/createRigidArea v)
(instance? java.awt.Component v) v
+ (instance? java.awt.Dimension v) (Box/createRigidArea v)
(instance? javax.swing.Action v) (JButton. v)
(instance? java.util.EventObject) (try-cast java.awt.Component (.getSource v))
(= v :fill-h) (Box/createHorizontalGlue)
@@ -270,11 +285,20 @@
(defn text
- [& {:keys [text multi-line?] :as opts}]
- (let [t (if multi-line? (JTextArea.) (apply-text-alignment (JTextField.) opts))
- w (apply-text-opts t opts)]
- (when text (.setText w (str text)))
- w))
+ "Create a text field or area. Given a single argument, creates a JTextField using the argument as the initial text value. Otherwise, supports the following properties:
+ :text Initial text content
+ :multi-line? If true, a JTextArea is created (default false)
+ :on-changed Event handler function called when the content is changed.
+ :editable If false, the text is read-only (default true)
+ "
+ [& args]
+ (if-not (next args)
+ (apply text :text args)
+ (let [{:keys [text multi-line?] :as opts} args]
+ (let [t (if multi-line? (JTextArea.) (apply-text-alignment (JTextField.) opts))
+ w (apply-text-opts t opts)]
+ w))))
@@ -299,12 +323,23 @@
; Frame
(defn frame
+ "Create a JFrame. Options:
+ :title the title of the window
+ :pack true/false whether JFrame/pack should be called (default true)
+ :width initial width if :pack is false
+ :height initial height if :pack is true
+ :content passed through (to-widget) and used as the frame's content-pane
+ :visible whether frame should be initially visible (default true)
+ returns the new frame."
[& {:keys [title width height content visible pack]
:or {width 100 height 100 visible true pack true}
:as opts}]
(let [f (JFrame.)]
- (when title (.setTitle f title))
- (when content (.setContentPane f content))
+ (when title (.setTitle f (str title)))
+ (when content (.setContentPane f (to-widget content)))
(doto f
(.setSize width height)
(.setVisible visible))
7 src/seesaw/examples/crazy.clj
@@ -1,10 +1,11 @@
(ns seesaw.examples.crazy
+ (:require
(:use seesaw.core)
(:use seesaw.border)
(:import (javax.swing JFrame JLabel)
(java.awt Color)))
-(def rss-url "")
+(def rss-url ( "seesaw/examples/rss.gif"))
(def redditor "")
(defn crazy-app []
@@ -14,7 +15,7 @@
:hgap 12 :vgap 15
:background Color/ORANGE
- :border [10 "Hello there" (empty-border :thickness 15)]
+ :border [10 "This is a border layout" (empty-border :thickness 15)]
:north (horizontal-panel
:items [(action
#(println "FOO" %)
@@ -60,7 +61,7 @@
:text "Check me"
:on-selection-changed (fn [e] (println (.. (.getSource e) (isSelected)))))]))
- :border [10 "Here's a grid" 10]
+ :border [10 "Here's a grid layout with 3 columns" 10]
:hgap 10
:vgap 10
:columns 3
BIN  src/seesaw/examples/rss.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 src/seesaw/util.clj
@@ -9,7 +9,9 @@
(catch ClassCastException e nil)))
(defn to-url [s]
- "Try to parse (str s) as a URL. Returns new on success, nil otherwise."
+ "Try to parse (str s) as a URL. Returns new on success, nil
+ otherwise. This is different from in that it doesn't
+ throw an exception and it uses (str) on the input."
(URL. (str s))
(catch MalformedURLException e nil)))
4 test/seesaw/test/core.clj
@@ -151,6 +151,10 @@
(= SwingConstants/BOTTOM (.getVerticalAlignment (label :valign :bottom)))))
(describe text
+ (it "should create a text field given a string argument"
+ (let [t (text "HI")]
+ (expect (= JTextField (class t)))
+ (expect (= "HI" (.getText t)))))
(it "should create a text field by default"
(let [t (text :text "HI")]
(expect (= JTextField (class t)))
1  test/seesaw/test/resource-url.txt
@@ -0,0 +1 @@
Please sign in to comment.
Something went wrong with that request. Please try again.