Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome or Safari browser. Firefox 10 (to be released soon) will also handle it.

Building User Interfaces with Seesaw

Clojure/West 2012

Dave Ray (@darevay)

So, about Swing ...

So, about Swing ...

So, about Swing ...

      (some #{:easy :simple} swing)
      ;=> nil
      

So, about Swing ...

Seesaw's Goals

Make Me a Widget

Java + Swing

JLabel myLabel = new JLabel("Hiya");
myLabel.setBackground(new Color(136, 136, 136));
myLabel.setOpaque(true);
myLabel.setForeground(Color.BLUE);

JFrame myFrame = new JFrame("Why Swing, why?");
myFrame.setContent(myLabel);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.pack();
myFrame.setVisible(true);
      

Clojure + Swing

(doto (JFrame. "Why Swing, why?")
  (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
  (.setContent (doto (JLabel. "Hiya")
                  (.setBackground (Color. 136 136 136))
                  (.setOpaque true)
                  (.setForeground Color/BLUE)))
  .pack
  (.setVisible true))

Seesaw

(-> (frame :title    "Why Swing, why?"
           :on-close :exit
           :content  (label :text       "Hiya"
                            :border     5
                            :background "#888"
                            :foreground :blue))
    pack!
    show!)
      

Shortcuts/Color

:red
"#F00"
"#FF0000"
:my-app.core/my-color-resource

Shortcuts/Font

"ARIAL-BOLD-18"
{:name "ARIAL" :style :bold :size 18}
(seesaw.font/font :name "ARIAL"
                  :style :bold
                  :size 18)
      
:my-app.core/my-font-resource

Shortcuts/Icons

"http://example.com/icon.png"
(clojure.java.io/resource "path/to/icon.png")
:my-app.core/my-icon-resource

Shortcuts/Misc

; Keystroke
"ctrl C"
      
; Dimensions
[640 :by 480]
      
; Borders
[5 "Compound" 10]
      

Tell a Widget

(config! my-label :background :red
                  :foreground "#00FF00")
;=> my-label
      
(defprotocol Config)

Ask a Widget

(config my-label :background)
;=> #<Color java.awt.Color[r=255,g=0,b=0]>
      
(defprotocol Config)

What's a Widget Worth?

Inspired completely by Stathis Sideris' Clarity

What's a Widget Worth?

(value (textbox :text "HI"))
;=> "HI"
      
(value (slider :min 0 :max 11 :value 6))
;=> 6
      
(-> (combobox :model [1 2 3 4])
  (selection! 3)
  value)
;=> 3
      
(defprotocol Value)
(def form
  (grid-panel :columns 2
    :items ["First Name" (text :id :first-name)
            "Last Name"  (text :id :last-name)
            "Sex"        (combobox :id :sex
                              :model ["Female" "Male"])
            "Age"        (spinner  :id :age)]))
      
(-> (dialog :content form) pack! show!)
;=> :success
      
(value form)
=> {:first-name "Philip",
    :last-name  "Glass",
    :sex        "Male",
    :age        75}
      
(defprotocol Value)

Events: What Just Happened?

Events/Java+Swing

public interface MouseListener {
  void mouseClicked(MouseEvent e);
  void mouseEntered(MouseEvent e);
  void mouseExited(MouseEvent e);
  void mousePressed(MouseEvent e);
  void mouseReleased(MouseEvent e);
}

JLabel label = new JLabel();
label.addMouseListener(new MouseListener() {...});

      

Events/Clojure+Swing

(.addMouseListener my-panel
  (proxy [MouseAdapter] []
    (mouseClicked [this e]
      ... do something ...)))

(.addMouseMotionListener my-panel
  (proxy [MouseMotionAdapter] []
    (mouseMoved [this e]
      ... do something ...)))

(.addComponentListener my-panel
  (proxy [ComponentAdapter] []
    (componentResized [e]
      ... do something ...)))
      

Events/Seesaw

(listen my-panel
  :mouse-clicked     (fn [e] ... do something ...)
  :mouse-moved       (fn [e] ... do something ...)
  :component-resized (fn [e] ... do something))
      
(defmulti listen-for-named-event)

Events/:selection

// List

JList myList = ...;
myList.addListSelectionListener(...);
      
(listen my-list :selection (fn [e] ...))
      

Events/:selection

// Tree

JTree myTree = ...;
myTree.addTreeSelectionListener(...);

// seriously?
      
(listen my-tree :selection (fn [e] ...))
      

Events/:selection

// Table

JTable myTable = ...;
myTable.getListSelectionModel()
       .addListSelectionListener(...);

// oh, come on.
      
(listen my-table :selection (fn [e] ...))
      

Events: Selection

// Group of mutually exclusive radio buttons
Enumeration<AbstractButton> bs =
  myButtons.getElements();

for(; bs.hasMoreElements();) {
  bs.nextElement().addItemListener(...);
}

// WTF, eh?
      
(listen buttons :selection (fn [e] ...))
      

Binding

Binding

(require '[seesaw.bind :as b])

(def safety        (checkbox :text "Safety"
                             :selected? false))

(def fire-missiles (button :text "Fire!"
                           :enabled? false))

(b/bind
  safety
  (property fire-missiles :enabled?))
      
(defprotocol Bindable)

Binding: tee + transform

...

(def status        (label :text "Safe"))

(b/bind
  safety
  (b/tee
    (property fire-missiles :enabled?)
    (b/bind
        (b/transform #(if % :red :green))
        (property status :background))
    (b/bind
        (b/transform #(if % "Armed" "Safe"))
        (property status :text))))
      
(defprotocol Bindable)

Binding: (inc)

(def volume-control (slider :min 0 :max 11 :value 5))

(def volume-atom (atom 5))

(b/bind volume-control
        volume-atom
        volume-control)
      
(defprotocol Bindable)

What About Those :ids?

How do you separate layout from behavior?

Selectors: Enlive + Swing

Select by :id

(select panel [:#first-name])
;=> JTextField
      

Select by :class

(select panel [:.dangerous])
;=> (JButton JButton JButton ...)
      

Select all children of widget with class :foo

 (select root [:.foo :> :*])
      

... and many, more

Selectors: Enlive + Swing

Plays nicely with "vectorized" config!

; Disable everything
(config! (select panel [:*]) :enabled? false)
      

Plays nicely with "vectorized" listen

; Add action handler to all widgets
; with class :dangerous
(listen (select panel [:.dangerous])
        :action (fn [e] ...))
      

Selectors: Enlive + Swing

Separate layout from behavior

(button   :id :open-doors :text "Open Pod Bay Doors")
(checkbox :id :safety :text "Safety")
(button   :id :launch-missiles :class ...)
(button   :id :fire-torpedoes :class :dangerous ...)

  ...
      
(defn add-behaviors [root]

  (listen (select root [:#open-doors]) :action
    (fn [_] ...))

  (listen (select root [:#safety]) :selection
    (fn [e]
      (config! (select root [:.dangerous])
               :enabled? (not (selection e)))))

  ...
"Seesaw is amazing and wonderful, but it still doesn't do much good for people who don't know Java/Swing already"
Anthony Grimes (aka Raynes)
user=> (doc button)
-------------------------
seesaw.core/button
([& args])
  Construct a generic button. In addition to default widget options, supports
  the following:

      :halign    Horizontal alignment. One of :left, :right, :leading, :trailing,
                 :center

  ... snip ...

  Resources and i18n:

    A button's base properties can be set from a resource prefix, i.e. a namespace-
    qualified keyword that refers to a resource bundle loadable by j18n.

  Examples:

    ; Create a button with text "Next" with alt-N mnemonic shortcut that shows
    ; an alert when clicked.
    (button :text "Next"
            :mnemonic \N
            :listen [:action #(alert "NEXT!")])

  See:
    http://download.oracle.com/javase/6/docs/api/javax/swing/JButton.html
    (seesaw.core/button-group)

      
        (use 'seesaw.dev)
        
user=> (show-options (label))
             Option  Notes/Examples
-------------------  --------------
        :background  :aliceblue
                     "#f00"
                     "#FF0000"
                     (seesaw.color/color 255 0 0 0 224)
            :border  5
                     "Border Title"
                     [5 "Compound" 10]
                     See (seesaw.border/*)
            :bounds  :preferred
                     [x y w h]
                     Use :* to leave component unchanged:

... snip a lot here...

         :user-data  Anything.
                     Associate arbitrary user-data with a widget.
                     See (seesaw.core/user-data)
   :v-text-position  :bottom
                     :center
                     :top
            :valign  :bottom
                     :center
                     :top
          :visible?  boolean

        
user=> (show-events (text))
:action [java.awt.event.ActionListener]
  :action-performed
:caret [javax.swing.event.CaretListener]
  :caret-update
:component [java.awt.event.ComponentListener]
  :component-hidden
  :component-moved
  :component-resized
  :component-shown

... snip a lot here ...

:mouse-motion [java.awt.event.MouseMotionListener]
  :mouse-dragged
  :mouse-moved
:mouse-wheel [java.awt.event.MouseWheelListener]
  :mouse-wheel-moved
:property-change [java.beans.PropertyChangeListener]
  :property-change
:selection [javax.swing.event.CaretListener]
  :caret-update
        

But wait, there's more!

Test Suite, i18n, Graphics, Improved layout, SwingX, Timers, DnD, Full screen, ...

All new for Summer 2012! Upshot

Seesaw's JavaFX-based Cousin

https://github.com/daveray/upshot

Recap

; Construct a widget
(def my-text (text :id :my-text :text "Hi" :font ...))
;=> #<JLabel ...>

; Change a widget
(config! my-text :enabled? false)
;=> #<JLabel ...>
      
; Get a widget's value
(value my-text)
;=> "Hi"
      
; Find out when things happen
(listen my-text :focus-gained (fn [e] ...))

; Bind widgets together
(bind my-text (property my-label :text))
      
; Find a widget
(select root [:#my-text])
;=> <JLabel ...>
      
; Explore a widget
(show-options my-text)

(show-events my-text)

      

Thanks!

Use a spacebar arrow keys to navigate