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.
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:
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.
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()
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.
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:
val yesno: Boolean = askYesNo("Do you like this?")It looks like this:
Finally, we can ask the user to enter a string:
val name: String = inputString("What is your name?")It looks like this:
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.