Getting Started with ScalaFX and resizable UIs

When I was a school kid oh so long ago, the most permanent subject of my programming hobby was an AI for “Connect Four” (besides lots of genetic algorithm and neural network stuff, sigh). I remember very well the thousands of lines of code and the weeks I spent trying to get that right (using Turbo Pascal), and to get it to be able to beat me at the game. However, I got better at the game faster than my AI, I’m afraid.

Today I know that “Connect Four” is a solved problem. Still, I think it’s a nice programming playground. And as I wanted to find out how far I’ve come with my development skills since I was a school kid, I decided to ignore that research paper and use “Connect Four” as a playground for trying out Akka and ScalaFX for a bit over the holidays – if you want to skip ahead, you’ll find the results at Bitbucket.

So, here’s a blog about how I started out with ScalaFX in  that little project.

The first steps to getting started with ScalaFX were not immediately clear to me. There’s a tutorial about how to get started with ScalaFX within Eclipse alright, but for using it from SBT, I fortunately remembered a Herbstcampus talk by @phdoerfler. You can find the (German) slides and an SBT build file here. Turns out it’s quite easy.

The first thing you need to know about ScalaFX is that it’s unfortunately not published anywhere. That means that you have to publish it locally in order to be able to use it as a library.

Doing this means installing Mercurial if you haven’t got it already, and then cloning the ScalaFX repository:

hg clone https://code.google.com/p/scalafx/

Next, you have to package and publish ScalaFX. If you’ve got git installed, too, then this should do the trick:

sbt publish-local

However, if you don’t have git installed, you have to remove the project/plugins/project/PluginBuild.scala file first, which references an sbt plugin (used for the test target) cloned from git. This plugin is not really required for locally publishing ScalaFX, anyway.

Now you’re set for referencing ScalaFX in your own SBT build – just add ScalaFX as a library dependency, and also add the JavaFX runtime (coming from your local Java 7 installation, if it’s up-to-date as it should be) as a dependency:

libraryDependencies += "org.scalafx" % "scalafx" % "1.0-SNAPSHOT"

unmanagedJars in Compile += Attributed.blank(
    file(scala.util.Properties.javaHome) / "lib" / "jfxrt.jar")

fork in run := true

As you notice, I have also told the run command to fork in order to avoid “UnsatisfiedLinkError“s due to various classloader “magics”…

On to coding: For my Connect Four board, I wanted to start with a pane within a resizable window where I just do the layout myself, basically drawing the board with the help of ScalaFX shapes instead of using Graphics2D. At first I thought that that would be easy, but then I spend a lot of time on one single detail: Namely, how to get the ScalaFX UI to resize with the window. Incidentally, this is what spawned this blog post.

My main class started out quite simple, as any ScalaFX application does:

import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.stage.Stage

object Main extends JFXApp {
  stage = new Stage {
    title = "Akka Connect Four"
    width = 800
    height = 600
  }
}

This sets the stage, and that’s all. – Next, you have to add the scene graph to the stage. Normally, you’d do this by simply adding scene = new Scene { ... } to the stage. But that’s exactly what broke my neck when trying to make the scene resize together with the window. And the reason for this is that ScalaFX internally puts a JavaFX “group” as the root of the scene here, instead of a single component.

Using a single component as root for the ScalaFX scene graph unfortunately doesn’t seem to be well supported by ScalaFX. The only way I was able to make it work is by doing it like this, directly creating a scene via JavaFX:

import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.stage.Stage
import scalafx.scene.Scene
import scalafx.scene.layout.BorderPane

object Main extends JFXApp {
  stage = new Stage {
    title = "Akka Connect Four"
    width = 800
    height = 600

    scene = new Scene(new javafx.scene.Scene(root))
  }

  lazy val root = new BorderPane {}
}

I’m using a BorderPane here as a container so that I can arrange additional stuff around my Connect Four board later on.

Next, let’s add the basic board rectangle to the center of the root pane.

import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.stage.Stage
import scalafx.scene.Scene
import scalafx.scene.layout.{ Pane, BorderPane }
import scalafx.scene.shape.Rectangle
import javafx.scene.paint.{ Color => JFXColor }

object Main extends JFXApp {
  stage = new Stage {
    title = "Akka Connect Four"
    width = 800
    height = 600

    scene = new Scene(new javafx.scene.Scene(root))
  }

  lazy val root = new BorderPane {
    center = gamePane
  }

  lazy val gamePane: Pane = new Pane {
    content = showBoard(784.0, 562.0)
  }

  def showBoard(paneWidth: Double, paneHeight: Double) = {
    val offX = 50.0
    val offY = 50.0
    val boardWidth = paneWidth - offX * 2
    val boardHeight = paneHeight - offY * 2
    
    new Rectangle {
      x = offX
      y = offY
      width = boardWidth
      height = boardHeight
      fill = JFXColor.DEEPSKYBLUE
    }
  }
}

This way, we get a nice centered blue rectangle (I won’t add more details to it for the purpose of this blog post). However, you may have already noticed that the board still doesn’t resize itself when you resize the window. At least the pane itself does resize itself thanks to our trick with the root component above (you can check that by setting the background color, for example, or by using Scenic View – which is an awesome tool for debugging JavaFX UIs, by the way).

In order to get a resizing board, we still have to bind the width and height of the game pane to redrawing our board. Now, if there’s something that’s really awesome in ScalaFX, then it’s how simple this can be done, using “bind expressions”. In our case, we just want to show an updated board every time the width or height of the window changes. Therefore, we want to set width.onChange() and height.onChange().

Here’s what I did:

import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.stage.Stage
import scalafx.scene.Scene
import scalafx.scene.layout.{ Pane, BorderPane }
import scalafx.scene.shape.Rectangle
import javafx.scene.paint.{ Color => JFXColor }

object Main extends JFXApp {
  stage = new Stage {
    title = "Akka Connect Four"
    width = 800
    height = 600

    scene = new Scene(new javafx.scene.Scene(root))

    width onChange show
    height onChange show
  }

  lazy val root = new BorderPane {
    center = gamePane
  }

  lazy val gamePane: Pane = new Pane {
    content = showBoard(784.0, 562.0)
  }

  def show: Unit = {
    gamePane.content = showBoard(gamePane.width.get, gamePane.height.get)
  }


  def showBoard(paneWidth: Double, paneHeight: Double) = {
    val offX = 50.0
    val offY = 50.0
    val boardWidth = paneWidth - offX * 2
    val boardHeight = paneHeight - offY * 2
    
    new Rectangle {
      x = offX
      y = offY
      width = boardWidth
      height = boardHeight
      fill = JFXColor.DEEPSKYBLUE
    }
  }
}

And that’s it already: A resizable game board for my Connect Four UI.

And as I’m still a bloody ScalaFX newbie: Should you know a way to do any of the above easier, please write a comment below!

You can also check out the source code of the Connect Four application at BitBucket. – That said, it’s not complete in any way (no parallelism at all yet, no permanent brain, nearly completely unoptimized for performance, for example).

However, I got it to being able to play on par with me within a few days of eating christmas cookies and casually hacking, and that’s awesome and depressing at the same time! :)

Posted in Scala by jmhofer at January 3rd, 2013.
Tags: , , , ,

3 Responses to “Getting Started with ScalaFX and resizable UIs”

  1. Philipp Dörfler says:

    Have you tried just binding the rectangle’s width and height to the stage’s width and height?

    board.width <== stage.width – (offX * 2) // untested

    Using an AnchorPane would be a viable alternative as it allows you to anchor your board rectangle's size to the parent's size (minus your offset).

    Happy new year and stuff!

  2. I would have written it a bit more concisely:

    {{{
    import scalafx.Includes._
    import scalafx.application.JFXApp
    import scalafx.stage.Stage
    import scalafx.scene.Scene
    import scalafx.scene.layout.{ Pane, BorderPane }
    import scalafx.scene.paint.Color
    import scalafx.scene.shape.Rectangle

    object Connect4 extends JFXApp {
    stage = new Stage(JFXApp.STAGE) {
    title = “Akka Connect Four”
    width = 800
    height = 600
    }
    stage.scene = new Scene {
    root = new BorderPane {
    center = new Pane {
    content = new Rectangle {
    x = 50.0
    y = 50.0
    width <== stage.width – x * 2
    height <== stage.height – y * 2
    fill = Color.DEEPSKYBLUE
    }
    }
    }
    }
    }

    }}}

    Nice post, by the way!

  3. ScalaFX is now available in the Maven Central Repository:
    http://search.maven.org/#search|ga|1|scalafx

Leave a Reply