JavaFX 2.0 is the next version of a revolutionary rich client platform for developing immersive desktop applications. One of the new features in JavaFX 2.0 is a set of pure Java APIs that can be used from any JVM language, opening up tremendous possibilities. This presentation demonstrates the benefits of using JavaFX 2.0 together with the Scala programming language to provide a type-safe declarative syntax with support for lazy bindings and collections. Advanced language features, such as DelayedInit and @specialized will be discussed, as will ways of forcing prioritization of implicit conversions for n-level cases. Those who survive the pure technical geekiness of this talk will be rewarded with plenty of JavaFX UI eye candy.
The 7 Things I Know About Cyber Security After 25 Years | April 2024
JavaFX 2 and Scala - Like Milk and Cookies (33rd Degrees)
1. JavaFX 2.0 and Scala, Like Milk
and Cookies Stephen Chin
Chief Agile
Methodologist, GXS
steveonjava@gmail.com
tweet: @steveonjava
2. Meet the Presenter
Stephen Chin
> Chief Agile
Methodologist, GXS
Family Man > Java Champion
> Open Source Hacker
JFXtras
Motorcyclist ScalaFX
Visage
> User Group Leader
Silicon Valley JavaFX User
Group
Streamed Live!
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.
4. Programming Languages
> JavaFX Script is no longer supported by Oracle
Existing JavaFX Script based applications will
continue to run
Visage is the open-source successor to the JavaFX
Script language
> JavaFX 2.0 APIs are now in Java
Pure Java APIs for all of JavaFX
Binding and Sequences exposed as Java APIs
FXML Markup for tooling
7. 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. Example Application
public class HelloStage extends Application {
@Override public void start(Stage stage) {
stage.setTitle("Hello Stage");
stage.setWidth(600);
stage.setHeight(450);
Group root = new Group();
Scene scene = new Scene(root);
scene.setFill(Color.LIGHTGREEN);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
9. Example Application Using Builders
public class HelloStage extends Application {
@Override public void start(Stage stage) {
stage.setTitle("Hello Stage");
stage.setScene(SceneBuilder.create()
.fill(Color.LIGHTGREEN)
.width(600)
.height(450)
.build());
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
10. Observable Properties
> Supports watching for changes to
properties
> Implemented via anonymous inner
classes
> Will take advantage of closures in the
future
11. Observable Pseudo-Properties
final Rectangle rect = new Rectangle();
rect.setX(40);
rect.setY(40);
rect.setWidth(100);
rect.setHeight(200);
rect.hoverProperty().addListener(new ChangeListener<Boolean>() {
});
12. Observable Pseudo-Properties
final Rectangle rect = new Rectangle();
rect.setX(40); The property we want to watch
rect.setY(40);
rect.setWidth(100);
rect.setHeight(200);
rect.hoverProperty().addListener(new ChangeListener<Boolean>() {
});
13. Observable Pseudo-Properties
final Rectangle rect = new Rectangle();
rect.setX(40); Only one listener used with
rect.setY(40); generics to specify the data type
rect.setWidth(100);
rect.setHeight(200);
rect.hoverProperty().addListener(new ChangeListener<Boolean>() {
});
14. Observable Pseudo-Properties
final Rectangle rect = new Rectangle();
rect.setX(40);
rect.setY(40);
rect.setWidth(100);
rect.setHeight(200);
rect.hoverProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> property,
Boolean oldValue, Boolean value) {
}
});
Refers to the
Rectangle.hoverProperty()
15. Observable Pseudo-Properties
final Rectangle rect = new Rectangle();
rect.setX(40);
rect.setY(40);
rect.setWidth(100);
rect.setHeight(200);
rect.hoverProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> property,
Boolean oldValue, Boolean value) {
rect.setFill(rect.isHover() ? Color.GREEN : Color.RED);
}
});
16. Binding
> Unquestionably the biggest JavaFX
Script innovation
> Supported via a PropertyBinding class
> Lazy invocation for high performance
> Static construction syntax for simple
cases
e.g.: bind(<property>),
bindBiDirectional(<property>)
17. Sequences in Java
> Replaced with an Observable List
> Public API is based on JavaFX sequences
> Internal code can use lighter collections API
> JavaFX 2.0 also has an Observable Map
19. 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…]
}
}
20. 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);
}
20
25. What is Scala
2012
2001 2006 • Scala 2.9.1-1
• Scala Started • Scala v2.0 (latest)
2003/2004 2011
• Scala v1.0 • Scala 2.9.1
> Started in 2001 by Martin Odersky
> Compiles to Java bytecodes
> Pure object-oriented language
> Also a functional programming language
25
26. Why Scala?
> Shares many language features with JavaFX Script
that make GUI programming easier:
Static Type Checking – Catch your errors at compile
time
Closures – Wrap behavior and pass it by reference
Declarative – Express the UI by describing what it
should look like
> Scala also supports Type Safe DSLs!
Implicit Conversions – type safe class extension
Operator Overloading – with standard precedence rules
26
27. 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();
}
}
27
28. 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)
}
}
}
}
28
29. 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)
}
}
}
}
29
30. 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)
}
}
}
}
30
31. 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)
}
}
}
}
31
32. 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)
}
}
}
}
32
33. 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"
33
34. 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();
34
35. 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();
35
36. 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
36
37. 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
37
38. Event Listeners in Scala
> Supported using the built-in Closure syntax
> Optional arguments for event objects
> 100% type-safe
onMouseClicked = {
Timeline(at(3 s){radius->0}).play()
}
38
39. Event Listeners in Scala
> Supported using the built-in Closure syntax
> Optional arguments for event objects
> 100% type-safe
onMouseClicked = {
Timeline(at(3 s){radius->0}).play()
}
Compact syntax
{body}
39
40. Event Listeners in Scala
> Supported using the built-in Closure syntax
> Optional arguments for event objects Optional event
> 100% type-safe parameter
{(event) => body}
onMouseClicked = { (e: MouseEvent) =>
Timeline(at(3 s){radius->0}).play()
}
40
41. 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.
41
42. 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?!?
42
43. 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.
43
44. 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
44
45. 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.
45
46. 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?
46
47. @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.
47
48. 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?!?
48
49. 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 49
50. 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
50
51. 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/
52. Stephen Chin
steveonjava@gmail.com
tweet: @steveonjava
Pro JavaFX 2 Platform Available Now!
52