Skip to content

HTTPS clone URL

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
View
13 README
@@ -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.
View
149 README.md
@@ -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](http://shoesrb.com/), [Stuart Sierra's Swing posts](http://stuartsierra.com/tag/swing), etc. [clojure.contrib.swing-utils](http://richhickey.github.com/clojure-contrib/swing-utils-api.html) 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
+
+<table>
+ <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>
+</table>
+
+
+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:
+
+<table>
+ <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>
+</table>
+
+### 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.
View
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))]
(cond
(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 @@
w*))
(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))
View
7 src/seesaw/examples/crazy.clj
@@ -1,10 +1,11 @@
(ns seesaw.examples.crazy
+ (:require clojure.java.io)
(:use seesaw.core)
(:use seesaw.border)
(:import (javax.swing JFrame JLabel)
(java.awt Color)))
-(def rss-url "http://ir.netflix.com/images/toolbar/rss_2.gif")
+(def rss-url (clojure.java.io/resource "seesaw/examples/rss.gif"))
(def redditor "http://static.reddit.com/reddit.com.header.png")
(defn crazy-app []
@@ -14,7 +15,7 @@
(border-panel
: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)))))]))
(grid-panel
- :border [10 "Here's a grid" 10]
+ :border [10 "Here's a grid layout with 3 columns" 10]
:hgap 10
:vgap 10
:columns 3
View
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.
View
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 java.net.URL on success, nil otherwise."
+ "Try to parse (str s) as a URL. Returns new java.net.URL on success, nil
+ otherwise. This is different from clojure.java.io/as-url in that it doesn't
+ throw an exception and it uses (str) on the input."
(try
(URL. (str s))
(catch MalformedURLException e nil)))
View
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)))
View
1  test/seesaw/test/resource-url.txt
@@ -0,0 +1 @@
+TESTING
Please sign in to comment.
Something went wrong with that request. Please try again.