Skip to content

Commit

Permalink
Moved most stuff over to the wiki. README was getting unwieldy.
Browse files Browse the repository at this point in the history
  • Loading branch information
daveray committed May 17, 2011
1 parent 122684e commit e5e53e5
Showing 1 changed file with 2 additions and 329 deletions.
331 changes: 2 additions & 329 deletions README.md
Expand Up @@ -4,6 +4,8 @@ _Seesaw's experimental and subject to radical change_

Seesaw's an experiment to see what I can do to make Swing funner in Clojure. It's kinda 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.

*See [the Seesaw Wiki] (https://github.com/daveray/seesaw/wiki) for more detailed docs*

## TL;DR

Here's how you use Seesaw with [Leiningen] (https://github.com/technomancy/leiningen)
Expand Down Expand Up @@ -50,336 +52,7 @@ Now run it:
* Some kind of ToModel protocol for auto-converting Clojure data-structures to Swing models.
* Investigate how a framework like [cljque] (https://github.com/stuartsierra/cljque) might fit in with Seesaw

## Usage
See tests and src/seesaw/examples. Seriously, there are a lot of tests and they're 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 (see Widget Coercion below) ...

There are several examples at the moment. They're all in the `src/seesaw/examples` and can be run with `lein` like this:

$ lein deps
$ lein run -m seesaw.examples.<name-of-example>

To run the tests:

$ java -cp "src:test:classes:lib/*:lib/dev/*" lazytest.watch src test

Hopefully you see a nice wall of green.

### Native Look and Feel
Call the `(native!)` function early in your program (like before any other Swing or Seesaw function is called) to get a more "native" behavior. This includes correct menu bar placement in OSX, etc.

### 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 :handler 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.

New coercions can be added by extending the `ToWidget` protocol. See the `to-widget` example.

### 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>:id</code></td><td>A unique id for the widget for use with `(select)` (see below).</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>:enabled?</code></td><td>Whether the widget is enabled or not.</td></tr>
<tr><td><code>:minimum-size</code></td><td>Minimum size of component, set with a `java.awt.Dimension` or a vector of the form `[width :by height]`, for example `[50 :by 50]`. Note that in Swing, some containers don't honor minimum size.</td></tr>
<tr><td><code>:maximum-size</code></td><td>Same as `:minimum-size`, but maximum. Note that in Swing, some containers don't honor maximum size.</td></tr>
<tr><td><code>:preferred-size</code></td><td>Same as `:minimum-size`, but preferred size.</td></tr>
<tr><td><code>:size</code></td><td>Set `:minimum-size`, `:maximum-size`, and `:preferred-size` all at once.</td></tr>
<tr><td><code>:location</code></td><td>Set the location of the widget with a `[x y]` vector or a `java.awt.Point`. _Note this is only useful when there is no layout on the container._</td></tr>
<tr><td><code>:bounds</code></td><td>Set the bounds (location and size) of the widget with a `[x y width height]` vector or a `java.awt.Rectangle`. _Note this is only useful when there is no layout on the container._</td></tr>
<tr><td><code>:listen</code></td><td>List of event listeners with same format as args to `(listen)` function (see below).</td></tr>
<tr><td><code>:popup</code></td><td>A JPopupMenu or function that generates the items of a popup menu to display. In this case, Seesaw ensures that the popup is shown on the correct mouse event for the platform. See `src/seesaw/examples/popup.clj`</td></tr>
</table>

... and many more. See code and tests for details.

Note that these properties can also be used with the `(config!)` function which applies them to an existing widget or widgets:

(config! (select root [:#my-widget]) :enabled? false :text "I'm disabled.")

`(config!)` can be applied to a single widget, or list of widgets, or things that can be turned into widgets.

### Selectors

I hope to one day support general CSS-style selectors for finding and styling widgets in an app. The `(select)` function supports locating a widget by `:id` as set at creation time:

(button :id :the-button :text "Push me")

... later ...

(listen (select root [:#the-button])
:action (fn [e] ... do something ...))

Note that the first argument to `(select)` is always the root of the widget hierarchy to search from and the second argument is always a vector containing the selector. I wish the root wasn't necessary, but not requiring it makes it very difficult to support multiple instances of the same frame in one app. Suggestions welcome.

The "all" selector is also supported which will match everything in a sub-tree including the root. For example to disable an entire sub-tree:

(config! (select my-panel [:*]) :enabled? false)

At the moment, I'm planning on following the selector conventions established by [Enlive] (https://github.com/cgrand/enlive). See also, the apparently defunct [Java CSS] (http://weblogs.java.net/blog/2008/07/17/introducing-java-css) project to get an idea where this may lead.

_Of all the areas of Seesaw, I think this is the least stable. You may be best off just binding widgets to variables and passing them around._

### Creating a Frame

A typical Swing app will stick everything inside an instance of [JFrame] (http://download.oracle.com/javase/6/docs/api/javax/swing/JFrame.html). Create a `JFrame` in Seesaw with the `(frame)` function:

(frame :title "An example", :on-close :exit, :content "Some Content")

`(frame)` takes an number of options (see the code and tests), but two very important options are `:on-close` and `:content`.

`:on-close` sets the default behavior when the frame is closed by the user. Valid values are `:exit`, `:hide`, `:dispose`, and `:nothing`. Note that `:exit` will cause the entire JVM to exit when the frame is closed, which may be reasonable for a standalone app, but probably isn't for use in the REPL. The default value is `:hide`.

`:content` sets the content of the frame which can be any widget (see Widget Coercion above), but is usually a panel of some sort.

### A Note on Threading

As noted [here] (http://download.oracle.com/javase/6/docs/api/javax/swing/package-summary.html#threading) Swing is single threaded nearly all UI operations should be executed on the Swing UI dispatch thread. To facilitate this, Seesaw includes the `(invoke-now)` and `(invoke-later)` macros. The former executes forms on the UI thread and waits for their completion, while the latter simply schedules the forms for execution sometime in the future.

A typical use for `(invoke-later)` is to get things going in an app:

(defn -main [& args]
(invoke-later
(frame :title "Hello" :content (button :text "Push me"))))

### 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 :handler 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` with labels at each position:

(border-panel :hgap 10 :vgap 10 :center "CENTER" :north "NORTH" :south "SOUTH" :east "EAST" :west "WEST")

There's also `(mig-panel)` which uses [MigLayout] (http://www.miglayout.com/), `(vertical-panel)`, `(horizontal-panel)`, `(border-panel)`, etc.

### 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"
:listen [:item-state-changed handler]))

Event handlers are installed with the `(listen)` function. Its first argument is a widget, or seq of widgets, and then one or more event specs of the form event-name/function. For the most part, the name of the event is the name of a Swing event listener method. Here's an example that listens for some mouse events, assuming that `p` is bound to a widget:

(listen p
:mouse-clicked (fn [e] ... do something ...)
:mouse-entered (fn [e] ... do something ...)
:mouse-exited (fn [e] ... do something ...))

Note that these same arguments can be given to the `:listen` property when the widget is constructed.

`(listen)` returns a function which, when called, will remove all listeners installed by the `(listen)` call. There is no "remove-listener" function.

See `seesaw.events/listen` for more details.

### Actions
It's typical in Swing apps to use actions for menus, buttons, etc. An action needs a handler function and some properties. Here's an example of creating an action and adding it to a toolbar:

(use 'seesaw.core)
(let [open-action (action
:handler (fn [e] (alert "I should open a new something."))
:name "Open ..."
:key "menu O"
:tip "Open a new something something.")
exit-action (action
:handler (fn [e] (.dispose (to-frame e)))
:name "Exit"
:tip "Close this window")]
(frame
:title "Toolbar action test"
:content (border-panel
:north (toolbar :items [open-action exit-action])
:center "Insert content here")))

`(action)` also supports an `:icon` property which can be a `javax.swing.Icon`, a `java.net.URL` or something that looks like a file or URL after `(str)` has been applied to it. See `seesaw/action.clj` for an accurate list of options.

The `:key` property takes an argument which is passed to `seesaw.keystroke/keystroke`. This sets the accelerator key for the action.

Like widgets, actions can be modified with the `(config!)` function:

(def a (action :name "Fire Missiles" :enabled? false))

(config! a :name "Fire Missiles!!!" :enabled? true :handler (fn [e] (println "FIRE")))

### Interacting with the User
The `(input)` function asks the user for some input. The simplest case just gets a string:

(input "Bang the keyboard like a monkey")

... but there are many options including choosing from a set of Clojure objects:

(input "Pick a city"
:choices [{ :name "New York" :population 8000000 }
{ :name "Ann Arbor" :population 100000 }
{ :name "Twin Peaks" :population 5201 }]
:to-string :name)

Please see the doc for `(seesaw.core/input)` for all the options.

To tell the user something, use `(seesaw.core/alert)`:

(alert "Something terrible has happened")

Please see the doc for `(seesaw.core/alert)` for all the options.

Note that both functions take an optional first argument, passed through `(to-widget)` which is used as the parent component for the dialog. This ensures that the dialog is positioned correctly.

### Menus
Here's how you can make a menu bar full of menus:

(frame :title "MENUS!"
:menubar
(menubar :items
[(menu :text "File" :items [new-action open-action save-action exit-action])
(menu :text "Edit" :items [copy-action paste-action])))

_Note that calling the `(native!)` function at startup will ensure that the menu bar goes in the right spot on OSX._

`(menubar)` has a list of `(menus)`, while each `(menu)` has text and a list of actions, or items. Note that in addition to using Actions as menu items, you can also use `(menu-item)`, `(checkbox-menu-item)`, and `(radio-menu-item)`, each of which has the exact same behavior (and options) as a button.

Popup menus (context/right-click) can be easily added to a widget with the `:popup` property. Just give it a single-argument function that returns a list of actions or menu items. The function will be called each time the menu needs to be displayed. For example,

(listbox :popup (fn [e] [action1 action2 ...]))

Seesaw takes care of registering the mouse handler and showing the popup at the right time depending on the platform. See `src/seesaw/examples/popup.clj`.

### Selection Handling
The `(selection)` and `(selection!)` function handles the details of selection management for listboxes, checkboxes, toggle buttons, combo boxes, etc. To get the current selection, just pass a widget (or something convertible to a widget) to `(selection`). It will always return the selected value, or `nil` if there is no selection:

(if-let [s (selection my-widget)]
(println "Current selection is " s)
(println "No selection"))

For multi-selection, `(selection)` takes an options map:

(doseq [s (selection {:multi? true} my-list)]
(println "Selected: " s))

Note that you can apply `(selection)` to event objects as well:

(listen (select [:#my-list]) :selection
(fn [e]
(println "Current selection of my-list is: " (selection e))))

The `(selection!)` function will set the current selection:

(let [my-list (listbox :model ["jim" "bob" "al"])]
(selection! my-list "bob"))

Pass `nil` to clear the selection. Like with `(selection)`, use the `multi?` option to interpret the new selection value as a list of values to select.

### 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")

### Graphics

Seesaw has some basic support for Java2D drawing, using the `(canvas)` function to create a paintable panel. See `src/seesaw/graphics.clj` and `src/seesaw/examples/canvas.clj`.

## License

Expand Down

0 comments on commit e5e53e5

Please sign in to comment.