Home

The CS109UI module

The CS109UI module

I have written a simple module called CS109UI that makes it possible to write programs with a simple graphical user interface without learning about the Swing library or worrying about event-based programming. We use this module for several projects in CS109.

Basic use

To use the module, you need to install it as explained in the installation page.

The module creates a window. You can fill this window with something interesting by drawing an image (using the operations explained here), and then calling show.

The setTitle command sets the caption of the window.

Here is a basic example (uitest1.scala). The function draw draws to a java.awt.image.BufferedImage (it's the same drawing code as in the drawing section).

import org.otfried.cs109.UI._

import java.awt.image.BufferedImage
import java.awt.{Graphics2D,Color,Font,BasicStroke}
import java.awt.geom._

def draw(canvas: BufferedImage) {
  // get Graphics2D for the image
  val g = canvas.createGraphics()

  // clear background
  g.setColor(Color.WHITE)
  g.fillRect(0, 0, canvas.getWidth, canvas.getHeight)
  
  // enable anti-aliased rendering (prettier lines and circles)
  // Comment it out to see what this does!
  g.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, 
		     java.awt.RenderingHints.VALUE_ANTIALIAS_ON)

  // draw two filled circles
  g.setColor(Color.RED)
  g.fill(new Ellipse2D.Double(30.0, 30.0, 40.0, 40.0))
  g.fill(new Ellipse2D.Double(230.0, 380.0, 40.0, 40.0))
  
  // draw an unfilled circle with a pen of width 3
  g.setColor(Color.MAGENTA)
  g.setStroke(new BasicStroke(3f))
  g.draw(new Ellipse2D.Double(400.0, 35.0, 30.0, 30.0))
  
  // draw a filled and an unfilled Rectangle
  g.setColor(Color.CYAN)
  g.fill(new Rectangle2D.Double(20.0, 400.0, 50.0, 20.0))
  g.draw(new Rectangle2D.Double(400.0, 400.0, 50.0, 20.0))
  
  // draw a line
  g.setStroke(new BasicStroke())  // reset to default
  g.setColor(new Color(0, 0, 255)) // same as Color.BLUE
  g.draw(new Line2D.Double(50.0, 50.0, 250.0, 400.0))
  
  // draw some text
  g.setColor(new Color(0, 128, 0)) // a darker green
  g.setFont(new Font("Batang", Font.PLAIN, 20))
  g.drawString("Hello World!", 155, 225)
  g.drawString("안녕 하세요", 175, 245)
  
  // done with drawing
  g.dispose()
}

def main() {
  setTitle("CS109 UI Test #1")
  val canvas = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB)
  draw(canvas)
  show(canvas)
}

main()

We run the script normally:

$ scala uitest1.scala 
CS109 UI version 2015/03/31

The script starts, prints the version message of cs109ui, and then opens a new window like this:

Screenshot of uitest1.scala

Note that your script has not yet terminated, even though the main function has already returned. To end the script, you have to close the window manually using the mouse.

Updating the display

You can change the contents of the window by drawing a new image and calling show again. (You can draw to the same image as before or use a new one.)

By waiting some time in between changes to the window, you can perform some simple animation, like letting objects blink or move inside the window.

Here is a simple example that shows a blinking square. First the square appears for one second in red, then for one second in blue. Five seconds later the program terminates automatically (uitest2.scala):

import org.otfried.cs109.UI._

import java.awt.image.BufferedImage
import java.awt.{Graphics2D,Color,Font,BasicStroke}
import java.awt.geom._

def draw(canvas: BufferedImage, color: Color) {
  val g = canvas.createGraphics()
  g.setColor(Color.WHITE)
  g.fillRect(0, 0, canvas.getWidth, canvas.getHeight)
  g.setColor(color)
  g.fillRect(100, 100, 300, 300)
  g.dispose()
}

def showSleep(canvas: BufferedImage, color: Color, ms: Int) {
  draw(canvas, color)       // draw rectangle
  show(canvas)
  Thread.sleep(ms)          // wait ms milliseconds
}

def main() {
  setTitle("CS109 UI Blinking Rectangle")

  val canvas = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB)

  showSleep(canvas, Color.WHITE, 500) // 0.5 sec white picture
  showSleep(canvas, Color.RED, 1000)  // 1 sec red rectangle
  showSleep(canvas, Color.WHITE, 500) // 0.5 sec white picture
  showSleep(canvas, Color.BLUE, 1000) // 1 sec blue rectangle
  showSleep(canvas, Color.WHITE, 5000) // 5 secs white picture  
  
  close() // close window and terminate program
}

main()

Again, you can run this using

$ scala uitest2.scala 
CS109 UI version 2015/03/31

You can now write interesting programs, such as the Simon project: You can use the terminal for text output and text input, and the window for graphical output.

Animation

By updating the window very quickly, we can program some simple smooth animations. Here is an example that lets a red ball move smoothly over the screen (uitest-animation.scala):

import org.otfried.cs109.UI._
import java.awt.image.BufferedImage
import java.awt.{Graphics2D,Color,Font,BasicStroke}
import java.awt.geom._

def draw(canvas: BufferedImage, x: Int, y: Int) {
  val g = canvas.createGraphics()
  g.setColor(Color.WHITE)
  g.fillRect(0, 0, canvas.getWidth, canvas.getHeight)
  g.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, 
		     java.awt.RenderingHints.VALUE_ANTIALIAS_ON)
  g.setColor(Color.RED)
  g.fill(new Ellipse2D.Double(x, y, 40.0, 40.0))
  g.dispose()
}

def main() {
  setTitle("CS109 UI Animation test")

  val canvas = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB)

  var x = 30
  var y = 30
  while (x < 500) {
    draw(canvas, x, y)
    x += 2
    y += 1
    show(canvas)
    Thread.sleep(10)
  }
}

main()

Keyboard input

If we want to get a bit more advanced, we can perform all interaction with the user through the window. We can use the drawString function to show text in our window. The next step is to allow the user to control the program by pressing keys in the window.

This is done using the waitKey function. It waits until the user has pressed a key, and then returns the character pressed. Here is a simple test program (uitest3.scala):

import org.otfried.cs109.UI._

import java.awt.image.BufferedImage
import java.awt.{Graphics2D,Color,Font,BasicStroke}
import java.awt.geom._

def draw(canvas: BufferedImage, color: Color) {
  val g = canvas.createGraphics()
  g.setColor(Color.WHITE)
  g.fillRect(0, 0, canvas.getWidth, canvas.getHeight)
  g.setColor(color)
  g.fillRect(100, 100, 300, 300)
  g.dispose()
}

def main() {
  setTitle("CS109 UI Keyboard Input Test")

  val canvas = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB)

  draw(canvas, Color.RED)
  show(canvas)

  println("Now press some keys inside the CS109 UI windows")
  println("Pressing 'q' will terminate the program")

  while (true) {
    val ch = waitKey()
    printf("Got character %c\n", ch)
    if (ch == 'q')
      close()  // close window and terminate program
  }
}

main()

After the window has appeared, try to type keys with the focus on the window. You should see the message Got character on your terminal. Pressing the 'q' key will terminate the program.

Dialogs

We can make our programs more professional by using additional pop-up windows, usually called "dialogs".

The simplest one just shows a message to the user. The user has to press "Ok" to continue the program. For instance, with this code

  showMessage("This is a message")
we would get this pop-up window:

showMessage window

Slightly more interesting, we can ask a Yes/No-question, and the user can decide by pressing one of the two buttons:
  val yesno: Boolean = askYesNo("Do you like this?")
It looks like this:

askYesNo window

The askYesNo function returns the user's choice as a Boolean value.

Finally, we can ask the user to enter a string:

  val name: String = inputString("What is your name?")
It looks like this:

inputString window

and again the function returns the string entered by the user. (If the user presses "Cancel" or closes the popup-window, the empty string is returned.)

Advanced possibilities

There are a few more possibilities in the module: receiving mouse button clicks from the window, two more dialogs, and setting a time-out on user input. I will document these when they are needed for projects, but you can always look at the example code.