A Concise Introduction to Scala GUIs
Copyright © 2013 David Matuszek

GUI Program Example

package simpleGUI

import scala.swing._
import scala.swing.BorderPanel.Position._
import event._
import java.awt.{ Color, Graphics2D }
import scala.util.Random

object SimpleGUI extends SimpleSwingApplication {

  def top = new MainFrame { // top is a required method
    title = "A Sample Scala Swing GUI"

    // declare Components here
    val label = new Label {
      text = "I'm a big label!."
      font = new Font("Ariel", java.awt.Font.ITALIC, 24)
    }
    val button = new Button {
      text = "Throw!"
      foreground = Color.blue
      background = Color.red
      borderPainted = true
      enabled = true
      tooltip = "Click to throw a dart"
    }
    val toggle = new ToggleButton { text = "Toggle" }
    val checkBox = new CheckBox { text = "Check me" }
    val textField = new TextField {
      columns = 10
      text = "Click on the target!"
    }
    val textArea = new TextArea {
      text = "initial text\nline two"
      background = Color.green
    }
    val canvas = new Canvas {
      preferredSize = new Dimension(100, 100)
    }
    val gridPanel = new GridPanel(1, 2) {
      contents += checkBox
      contents += label
      contents += textArea
    }

    // choose a top-level Panel and put components in it
    // Components may include other Panels
    contents = new BorderPanel {
      layout(gridPanel) = North
      layout(button) = West
      layout(canvas) = Center
      layout(toggle) = East
      layout(textField) = South
    }
    size = new Dimension(300, 200)
    menuBar = new MenuBar {
      contents += new Menu("File") {
        contents += new MenuItem(Action("Exit") {
          sys.exit(0)
        })
      }
    }

    // specify which Components produce events of interest
    listenTo(button)
    listenTo(toggle)
    listenTo(canvas.mouse.clicks)

    // react to events
    reactions += {
      case ButtonClicked(component) if component == button =>
        val x = Random.nextInt(100)
        val y = Random.nextInt(100)
        val c = new Color(Random.nextInt(Int.MaxValue))
        canvas.throwDart(new Dart(x, y, c))
        textField.text = s"Dart thrown at $x, $y"
      case ButtonClicked(component) if component == toggle =>
        toggle.text = if (toggle.selected) "On" else "Off"
      case MouseClicked(_, point, _, _, _) =>
        canvas.throwDart(new Dart(point.x, point.y, Color.black))
        textField.text = (s"You clicked in the Canvas at x=${point.x}, y=${point.y}.") 
    }
  }
}
package simpleGUI case class Dart(val x: Int, val y: Int, val color: java.awt.Color)
package simpleGUI import scala.swing.Panel import java.awt.{ Graphics2D, Color } class Canvas extends Panel { var centerColor = Color.yellow var darts = List[Dart]() override def paintComponent(g: Graphics2D) { // Start by erasing this Canvas g.clearRect(0, 0, size.width, size.height) // Draw background here g.setColor(Color.blue) g.fillOval(0, 0, 100, 100) g.setColor(Color.red) g.fillOval(20, 20, 60, 60) g.setColor(centerColor) g.fillOval(40, 40, 20, 20) // Draw things that change on top of background for (dart <- darts) { g.setColor(dart.color) g.fillOval(dart.x, dart.y, 10, 10) } } /** Add a "dart" to list of things to display */ def throwDart(dart: Dart) { darts = darts :+ dart // Tell Scala that the display should be repainted repaint() } }

Visible Components

Components are added to the contents buffer (see examples above). They have fields that can be read and written, for example, Labels and Buttons both have a text field. Here are some components with their most important fields.

Actions

Most GUI elements should be treated as "passive"--they accept input, but don't themselves cause any action to be taken. For example, nothing should happen immediately when a checkbox is checked; but it should affect the later behavior of the program. "Active" elements, such as buttons and some menu items, are those which immediately cause the program to do something. Whenever the user chooses an active element, something on the display should change, to indicate that the action has occurred.

A lot of events are occuring in the Scala system, but most of them are ignored.

In addition to telling Scala to listenTo various components, you must tell Scala what to do when each interesting Event occurs, by adding it to the reactions buffer:

Organizational components

Components are added to Frames (top-level windows) and Panels (which occur inside Frames or other Panels). The most important Panel types are

 BorderLayout

BorderPanel

Components are added to the contents buffer with
  layout(component) = Area
where Area is one of North, South, East, West, Center

This is assuming you have done
 import scala.swing.BorderPanel.Position._
if not, you must qualify each Area with this.

FlowLayout 

FlowPanel

Components added to the contents buffer by
  contents += component
Components will be placed left to right, top to bottom.  

GridLayout

GridPanel(nRows, nColumns)

Components added to the contents buffer by
  contents += component
Components will be placed left to right, top to bottom.    

Drawing

To do any drawing, the program should create an object of a class that extends Panel (in the example this is the Canvas class), and put an instance of this class into the GUI . The class should define an override def paintComponent(g: Graphics2D) method to do the drawing. The Graphics2D holds settings, such as color and font, which may be changed by the program. Changes will affect subsequent drawing operations.

Unlike the above Swing-supplied "widgets," changes made to a drawing will not occur until the Panel object issues a redraw() request, which will cause paintComponent to be called. The program should not call paintComponent directly.

In the following, the program variable g is assumed to hold the Graphics2D.

g.setColor(color)
Sets the color for subsequent drawings.
g.setFont(font)
Sets the font for subsequent calls to drawString.
g.drawRect(x, y, width, height)
Draw the outline of a rectangle starting at x, y, with the given width and height.
g.fillRect(x, y, width, height)
Draw a solid rectangle or square starting at x, y, with the given width and height.
g.drawOval(x, y, width, height)
Draw the outline of an ellipse or circle included in the rectangle starting at x, y, with the given width and height.
g.fillOval(x, y, width, height)
Draw a solid ellipse or circle included in the rectangle starting at x, y, with the given width and height.
g.drawArc(x, y, width, height, startAngle, degrees)
Draw an arc of a circle whose center is the center of the circle included in the rectangle starting at x, y, with the given width and height. The arc starts at startAngle (0 is the 3 o'clock position) and continues counterclockwise for the specified number of degrees.
g.fillArc(x, y, width, height, startAngle, degrees)
Draw a sector (wedge) of a circle whose center is the center of the circle included in the rectangle starting at x, y, with the given width and height. The sector starts at startAngle (0 is the 3 o'clock position) and continues counterclockwise for the specified number of degrees.
g.drawRoundRect(x, y, width, height, arcWidth, arcHeight)
Draw the outline of a rectangle with rounded corners starting at x, y, with the given width and height. The amount of "roundedness" is given by arcWidth and arcHeight.
g.fillRoundRect(x, y, width, height, arcWidth, arcHeight)
Draw a solid rectangle with rounded corners starting at x, y, with the given width and height. The amount of "roundedness" is given by arcWidth and arcHeight.
g.drawLine(x1, y1, x2, y2)
Draw a line from the point (x1, y1) to the point (x2, y2).
g.drawString("Some quoted string", x, y)
Draws the characters inside quotes starting at the position (x, y). Unlike the other methods, the point (x, y) is the bottom left corner of the first letter.
g.drawPolygon(xArray, yArray, n)
Draws the outline of a polygon. The sequence of points is given by the n corresponding values in xArray and yArray. For example, the command
    g.drawPolygon(Array(250, 290, 210), Array(210, 290, 290), 3)
will draw a triangle using the 3 points (250, 210), (290, 290), and (210, 290).
g.fillPolygon(xArray, yArray, n)
Draws a solid polygon. The sequence of points is given by the n corresponding values in xArray and yArray.When the edges of the polygon cross, some areas may be considered to be "outside" the polygon, hence not colored in.