javafx and scala - like milk and cookies

Post on 15-Jan-2015

5.481 Views

Category:

Technology

4 Downloads

Preview:

Click to see full reader

DESCRIPTION

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.

TRANSCRIPT

JavaFX and Scala – Like Milk and Cookies

Luc DuponcheelIndependent, ImagineJluc.duponcheel@gmail.comtweet: @LucDup

Stephen ChinJavaFX Evangelist, Oraclesteveonjava@gmail.comtweet: @steveonjava

Meet the Presenters

Stephen Chin

Motorcyclist

Family Man

Luc Duponcheel

BeJUG

Researcher

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

JavaFX

Scala

JavaFX With Java

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

7

Vanishing Circles

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…] }}

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

Setup Binding

circle.strokeWidthProperty().bind(Bindings .when(circle.hoverProperty()) .then(4) .otherwise(0));

10

Setup Event Listeners

circle.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { public void handle(MouseEvent t) { KeyValue collapse = new KeyValue(circle.radiusProperty(), 0); new Timeline(new KeyFrame(Duration.seconds(3), collapse)).play(); }});

11

Begin the Animation

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();

12

13

JavaFX With Scala

Java vs. Scala DSLpublic 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); 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.addEventHandler(MouseEvent.MOUSE_CLICKED, new

EventHandler<MouseEvent>() { public void handle(MouseEvent t) { KeyValue collapse = new KeyValue(circle.radiusProperty(), 0); new Timeline(new KeyFrame(Duration.seconds(3), collapse)).play(); } }); circle.setStroke(Color.WHITE);

circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty()) .then(4) .otherwise(0)); circles.add(circle); } root.getChildren().addAll(circles); primaryStage.setScene(scene); primaryStage.show(); 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(); }}

object VanishingCircles extends JFXApp { var circles: Seq[Circle] = null stage = new Stage { title = "Vanishing Circles" width = 800 height = 600 scene = new Scene { fill = BLACK circles = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, .2) effect = new BoxBlur(10, 10, 3) strokeWidth <== when (hover) then 4 otherwise 0 stroke = WHITE onMouseClicked = { Timeline(at (3 s) {radius -> 0}).play() } } content = circles } }

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 ) } }.play();}

14

40 Lines1299 Characters

33 Lines591 Characters

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 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) } } }}

Base class for JavaFX applications

17

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) } } }}

Declarative Stage definition

18

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) } } }}

Inline property definitions

19

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) } } }}

Sequence Creation Via Loop

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

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

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();

Animation in Scala

22

JavaFX Script-like animation 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();

Animation in Scala

23

Operator overloading for animation syntax

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();

Animation in Scala

24

Optional tween syntax

Event Listeners in Scala

25

> Supported using the built-in Closure syntax> Arguments for event objects> 100% type-safe

onMouseClicked = { (e: MouseEvent) =>

Timeline(at(3 s){radius->0}).play()

}

Event Listeners in Scala

> Supported using the built-in Closure syntax> Arguments for event objects> 100% type-safe

26

Optional event parameter

{(event) => body}

onMouseClicked = { (e: MouseEvent) =>

Timeline(at(3 s){radius->0}).play()

}

________ __ ________ __ __ / _____/\ / /\ / _____/\/__/\ / /\ / /\_____\/ ________ _______ / / / ________ / /\_____\/\ \ / / / / /_/___ / _____/\ /_____ /\ / / / /_____ /\ / /_/__ \ / / / /______ /\ / /\_____\/ \____/ / / / / / \____/ / / / ____/\ \/ / / \_____/ / / / / / / ___ / / / / / / ___ / / / /\____\/ / / /\ ______/ / / / /_/___ / /__/ / / / / / / /__/ / / / / / / / /\ \/________/ / /________/\ /________/ / /__/ / /________/ / /__/ / /__/ / \ \\________\/ \________\/ \________\/ \__\/ \________\/ \__\/ \__\/ \__\/

27

Jumping Frogs Puzzle

Image by renwest: http://www.flickr.com/photos/19941963@N00/438340463

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)

Application

object JumpingFrogsPuzzle extends JFXApp { stage = new Stage { title = TITLE scene = new Scene { content = theShapes.canvasShape :: theShapes.stoneShapes ::: theView.frogShapes } }}

29

Frog

trait Frog { def movesToRight: Boolean def movesToLeft: Boolean}class LeftFrog() extends Frog { def movesToRight = true def movesToLeft = false}class RightFrog() extends Frog { def movesToRight = false def movesToLeft = true}

30

Model (frogMap)

var frogMap: Map[Int, Frog] = _

private val frogAtPosition = (i: Int) => frogMap(i)

val positionOf = (frog: Frog) => (for { (i, `frog`) <- frogMap} yield i) head

31

Model (move)

private val move = (next: Int => Int) => (frog: Frog) => frogMap = (for { entry@(_, aFrog) <- frogMap if (aFrog != frog) } yield { entry }) + (next(positionOf(frog)) -> frog)

32

View (jump)

private val jump = (length: Int) => (direction: (Double, Double) => Double) => (frogShape: FrogShape) => { // ... Timeline(Seq( at(midTime) { frogShape.centerX -> midCenterX }, at(midTime) { frogShape.centerY -> midCenterY }, at(endTime) { frogShape.centerX -> endCenterX }, at(endTime) { frogShape.centerY -> endCenterY } )).play()}

33

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

a.k.a. How to Write Your Own Scala DSL

35

ScalaFX Internals

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.

Luc Viatour / www.Lucnix.be

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?!?

37

DelayedInit

> Introduced in Scala 2.9> How to Use It:1. Extend a special trait called DelayedInit2. Implement a method of type:

def delayedInit(x: => Unit): Unit3. Store off the init closure and call it on the

Application Thread

For me, Scala didn't throw enough away and added too much - a lethal combination.

Joda says…

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!

38

JFXNode

JFXShape

JFXCircle

SFXNode

SFXShape

SFXCircle

?

!

N-Level Implicit Precedence

> Scala throws an exception if two implicits have the same precedence

> Classes that are extended have 1 lower precedence:

> You can stack extended traits n-levels deep to reduce precision by n

39

Well, it may be type safe, but its also silent and very deadly.

Joda says…

object HighPriorityIncludes extends LowerPriorityIncludes {…}trait LowerPriorityIncludes {…}

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

@specialized

> Special annotation that generates primitive variants of the class

> Improves performance by avoiding boxing/unboxing

> Cuts down on code duplication (ScalaFX only has 18 property/value classes total)

41

Whatever the problem, the type system is bound to be part of the solution.

Joda says…

trait ObservableValue[@specialized(Int, Long, Float, Double, Boolean) T, J]

Bindings

42

> 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?!?

Operator Precedence Rules

43

> First Character Determines Precedence

Highest Precedence

10. (all letters)9. |8. ^7. &6. < >5. = !4. :3. + *2. / %1. (all other special characters)

Lowest Precedence

11. Assignment Operators end with equal> But don't start with equal> And cannot be one of:

<= >= !=

Exception Assignment Operators, which are even lower…

Operator Precedence

44

Personally, I find the goal of the open and flexible syntax (arbitrary DSLs) to be not worth the pain

Joda says…

text <== when (rect.hover || circle.hover

&& !disabled) then textField.text + " is

enabled" otherwise "disabled"

911 10

7 105 3

10

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 Chinsteveonjava@gmail.comtweet: @steveonjava

Pro JavaFX 2 Platform Available Now!

top related