Presentation on Scala and JavaFX given at Scala Days. Shows how the ScalaFX API can be used to write cleaner and more maintainable code for your JavaFX applications in the Scala language. Also goes over implementation details that may be useful to other Scala DSL creators and has some quotes from Stephen Coulbourne to "lighten" things up.
1. JavaFX and Scala – Like Milk and Cookies
Stephen Chin Luc Duponcheel
JavaFX Evangelist, Oracle Independent, ImagineJ
steveonjava@gmail.com luc.duponcheel@gmail.com
tweet: @steveonjava tweet: @LucDup
2. Meet the Presenters
Stephen Chin Luc Duponcheel
Family Man
Motorcyclist BeJUG
Researcher
3. JavaFX 2.0 Platform
Immersive Application Experience
> Cross-platform
Animation, Video, Charting
> Integrate Java, JavaScript, and
HTML5 in the same application
> New graphics stack takes
advantage of hardware
acceleration for 2D and 3D
applications
> Use your favorite IDE:
NetBeans, Eclipse, IntelliJ, etc.
6. JavaFX in Java
> JavaFX API uses an enhanced JavaBeans
pattern
> Similar in feel to other UI toolkits (Swing,
Pivot, etc.)
> Uses builder pattern to minimize boilerplate
8. Application Skeleton
public class VanishingCircles extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Vanishing Circles");
Group root = new Group();
Scene scene = new Scene(root, 800, 600, Color.BLACK);
[create the circles…]
root.getChildren().addAll(circles);
primaryStage.setScene(scene);
primaryStage.show();
[begin the animation…]
}
}
9. Create the Circles
List<Circle> circles = new ArrayList<Circle>();
for (int i = 0; i < 50; i++) {
final Circle circle = new Circle(150);
circle.setCenterX(Math.random() * 800);
circle.setCenterY(Math.random() * 600);
circle.setFill(new Color(Math.random(), Math.random(),
Math.random(), .2));
circle.setEffect(new BoxBlur(10, 10, 3));
circle.setStroke(Color.WHITE);
[setup binding…]
[setup event listeners…]
circles.add(circle);
}
9
14. Java vs. Scala DSL
public class VanishingCircles extends Application { object VanishingCircles extends JFXApp {
var circles: Seq[Circle] = null
public static void main(String[] args) { stage = new Stage {
Application.launch(args); title = "Vanishing Circles"
} width = 800
height = 600
@Override scene = new Scene {
public void start(Stage primaryStage) { fill = BLACK
primaryStage.setTitle("Vanishing Circles"); circles = for (i <- 0 until 50) yield new Circle {
Group root = new Group(); centerX = random * 800
Scene scene = new Scene(root, 800, 600, Color.BLACK); centerY = random * 600
List<Circle> circles = new ArrayList<Circle>(); radius = 150
for (int i = 0; i < 50; i++) { fill = color(random, random, random, .2)
final Circle circle = new Circle(150); effect = new BoxBlur(10, 10, 3)
40 Lines
circle.setCenterX(Math.random() * 800);
circle.setCenterY(Math.random() * 600);
circle.setFill(new Color(Math.random(), Math.random(), Math.random(), .2));
circle.setEffect(new BoxBlur(10, 10, 3));
33 Lines
strokeWidth <== when (hover) then 4 otherwise 0
stroke = WHITE
onMouseClicked = {
Timeline(at (3 s) {radius -> 0}).play()
1299 Characters
circle.addEventHandler(MouseEvent.MOUSE_CLICKED, new
EventHandler<MouseEvent>() {
public void handle(MouseEvent t) {
KeyValue collapse = new KeyValue(circle.radiusProperty(), 0); }
}
}
591 Characters
content = circles
new Timeline(new KeyFrame(Duration.seconds(3), collapse)).play(); }
}
}); new Timeline {
circle.setStroke(Color.WHITE); cycleCount = INDEFINITE
circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty()) autoReverse = true
.then(4) keyFrames = for (circle <- circles) yield at (40 s) {
.otherwise(0)); Set(
circles.add(circle); circle.centerX -> random * stage.width,
} circle.centerY -> random * stage.height
root.getChildren().addAll(circles); )
primaryStage.setScene(scene); }
primaryStage.show(); }.play();
}
Timeline moveCircles = new Timeline();
for (Circle circle : circles) {
KeyValue moveX = new KeyValue(circle.centerXProperty(), Math.random() *
800);
KeyValue moveY = new KeyValue(circle.centerYProperty(), Math.random() *
600);
moveCircles.getKeyFrames().add(new KeyFrame(Duration.seconds(40), moveX,
moveY));
}
moveCircles.play();
}
}
14
15. object VanishingCircles extends JFXApp {
stage = new Stage {
title = "Disappearing Circles"
width = 800
height = 600
scene = new Scene {
fill = BLACK
content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800
centerY = random * 600
radius = 150
fill = color(random, random, random, 0.2)
effect = new BoxBlur(10, 10, 3)
}
}
}
}
15
16. object VanishingCircles extends JFXApp {
stage = new Stage {
title = "Disappearing Circles"
width = 800
height = 600 class for JavaFX
Base
applications
scene = new Scene {
fill = BLACK
content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800
centerY = random * 600
radius = 150
fill = color(random, random, random, 0.2)
effect = new BoxBlur(10, 10, 3)
}
}
}
}
16
17. object VanishingCircles extends JFXApp {
stage = new Stage {
title = "Disappearing Circles"
width = 800
height = 600 Declarative Stage
scene = new Scene {
fill = BLACK
definition
content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800
centerY = random * 600
radius = 150
fill = color(random, random, random, 0.2)
effect = new BoxBlur(10, 10, 3)
}
}
}
}
17
18. object VanishingCircles extends JFXApp {
stage = new Stage {
title = "Disappearing Circles"
width = 800
Inline property
height = 600 definitions
scene = new Scene {
fill = BLACK
content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800
centerY = random * 600
radius = 150
fill = color(random, random, random, 0.2)
effect = new BoxBlur(10, 10, 3)
}
}
}
}
18
19. object VanishingCircles extends JFXApp {
stage = new Stage {
title = "Disappearing Circles"
width = 800 Sequence Creation Via
height = 600 Loop
scene = new Scene {
fill = BLACK
content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800
centerY = random * 600
radius = 150
fill = color(random, random, random, 0.2)
effect = new BoxBlur(10, 10, 3)
}
}
}
}
19
20. Binding in Scala
Infix Addition/Subtraction/Multiplication/Division:
height <== rect1.height + rect2.height
Aggregate Operators:
width <== max(rect1.width, rect2.width,
rect3.width)
Conditional Expressions:
strokeWidth <== when (hover) then 4 otherwise 0
Compound Expressions:
text <== when (rect.hover || circle.hover &&
!disabled) then textField.text + " is enabled"
otherwise "disabled"
20
21. Animation in Scala
val timeline = new Timeline {
cycleCount = INDEFINITE
autoReverse = true
keyFrames = for (circle <- circles) yield
at (40 s) {
Set(
circle.centerX -> random * stage.width,
circle.centerY -> random * stage.height
)
}
}
timeline.play();
21
22. JavaFX Script-like animation
Animation in Scala syntax: at (duration) {keyframes}
val timeline = new Timeline {
cycleCount = INDEFINITE
autoReverse = true
keyFrames = for (circle <- circles) yield at (40 s) {
Set(
circle.centerX -> random * stage.width,
circle.centerY -> random * stage.height
)
}
}
timeline.play();
22
23. Animation in Scala
val timeline = new Timeline {
cycleCount = INDEFINITE
autoReverse = true
keyFrames = for (circle <- circles) yield at (40 s) {
Set(
circle.centerX -> random * stage.width,
circle.centerY -> random * stage.height
)
}
}
timeline.play();
Operator overloading for
animation syntax
23
24. Animation in Scala
val timeline = new Timeline {
cycleCount = INDEFINITE
autoReverse = true
keyFrames = for (circle <- circles) yield at (40 s) {
Set(
circle.centerX -> random * stage.width tween EASE_BOTH,
circle.centerY -> random * stage.height tween EASE_IN
)
}
}
timeline.play();
Optional
tween syntax
24
25. Event Listeners in Scala
> Supported using the built-in Closure syntax
> Arguments for event objects
> 100% type-safe
onMouseClicked = { (e: MouseEvent) =>
Timeline(at(3 s){radius->0}).play()
}
25
26. Event Listeners in Scala
> Supported using the built-in Closure syntax
> Arguments for event objects Optional event
> 100% type-safe parameter
{(event) => body}
onMouseClicked = { (e: MouseEvent) =>
Timeline(at(3 s){radius->0}).play()
}
26
28. Jumping Frogs Puzzle
> My first ScalaFX program
> Similar to (but more concise as) my version using
JavaFX 1.2
> After a few JavaFX 1.2 to ScalaFX translation
iterations it just worked
> After some MVC rethinking and a few refactoring
iterations it worked even better (e.g. it is possible
to click on a jumping frog)
28
29. Application
object JumpingFrogsPuzzle extends JFXApp {
stage = new Stage {
title = TITLE
scene = new Scene {
content =
theShapes.canvasShape ::
theShapes.stoneShapes :::
theView.frogShapes
}
}
}
29
34. View (control)
private val control: Unit => Unit = { _ =>
view.frogShapes.foreach {
frogShape => frogShape.onMouseClicked = {
(_: MouseEvent) =>
val frog = frogShape.frog
if (model.canMoveOneRight(frog)) {
view.jumpOneRight(frogShape)
model.moveOneRight(frog)
} else if (model.canMoveTwoRight(frog)) {
view.jumpTwoRight(frogShape)
model.moveTwoRight(frog)
34
35. ScalaFX Internals
a.k.a. How to Write Your Own Scala DSL
With quotes from Stephen Colebourne (@jodastephen) to help
us keep our sanity!
Disclaimer: Statements taken from http://blog.joda.org and may not accurately reflect his opinion or viewpoint.
35
36. Application Initialization
> JavaFX Requires all UI code executed on the
Application Thread
> But our ScalaFX Application has no start method:
object VanishingCircles extends JFXApp {
stage = new Stage {
…
}
}
How Does This Code Work?!?
36
37. DelayedInit
> Introduced in Scala 2.9
> How to Use It:
1. Extend a special trait called DelayedInit
2. Implement a method of type:
def delayedInit(x: => Unit): Unit
3. Store off the init closure and call it on the
Application Thread
Joda says…
For me, Scala didn't throw enough away and added too much - a lethal
combination.
37
38. Hierarchical Implicit Conversions
> ScalaFX defines a set of proxies that mirror the
JavaFX hierarchy
> JavaFX classes are "implicitly" wrapped when you
call a ScalaFX API
> But Scala implicit priority ignores type hierarchy!
JFXNode SFXNode
JFXShape ? SFXShape
JFXCircle ! SFXCircle
38
39. N-Level Implicit Precedence
> Scala throws an exception if two implicits have the
same precedence
> Classes that are extended have 1 lower
precedence:
object HighPriorityIncludes extends LowerPriorityIncludes {…}
trait LowerPriorityIncludes {…}
> You can stack extended traits n-levels deep to
reduce precision by n
Joda says…
Well, it may be type safe, but its also silent and very deadly.
39
40. Properties
> JavaFX supports properties of type Boolean,
Integer, Long, Float, Double, String, and Object
> Properties use Generics for type safety
> But generics don't support primitives…
> JavaFX solves this with 20 interfaces and 44
classes for all the type/readable/writable
combinations.
> Can we do better?
40
41. @specialized
> Special annotation that generates primitive
variants of the class
> Improves performance by avoiding
boxing/unboxing
trait ObservableValue[@specialized(Int, Long,
Float, Double, Boolean) T, J]
> Cuts down on code duplication (ScalaFX only has
18 property/value classes total)
Joda says…
Whatever the problem, the type system is bound to be part of the solution.
41
42. Bindings
> How does Scala know what order to evaluate this
in?
text <== when (rect.hover || circle.hover
&& !disabled) then textField.text + " is
enabled" otherwise "disabled
And why the funky bind operator?!?
42
43. Operator Precedence Rules
> First Character Determines Precedence
Lowest Precedence
10. (all letters) Exception Assignment
9. |
Operators, which are
8. ^ even lower…
7. &
6. < > 11. Assignment Operators
5. = !
end with equal
4. :
> But don't start with equal
3. + *
> And cannot be one of:
<=
2. / %
>=
1. (all other special
!=
characters)
Highest Precedence 43
44. Operator Precedence
text <== when (rect.hover || circle.hover
11 10 9
&& !disabled) then textField.text + " is
7 5 10 3
enabled" otherwise "disabled"
10
Joda says…
Personally, I find the goal of the open and flexible syntax (arbitrary DSLs) to
be not worth the pain
44
45. Conclusion
> You can use Scala and JavaFX together.
> ScalaFX provides cleaner APIs that are tailor
designed for Scala.
> Try using ScalaFX today and help contribute APIs
for our upcoming 1.0 release!
http://code.google.com/p/scalafx/
46. Stephen Chin
steveonjava@gmail.com
tweet: @steveonjava
Pro JavaFX 2 Platform Available Now!
46