Hacking JavaFX with Groovy, Clojure, Scala, and Visage Stephen Chin Java Evangelist, Oracle stephen.chin@oracle.com tweet: @steveonjava
Meet the Presenter Stephen Chin >  Java Evangelist, Oracle >  Author, Pro JavaFX Platform 2 Family Man >  Open Source Hacker l  JFXtras l  ScalaFX Motorcyclist l  Visage >  User Group Co-Leader l  Silicon Valley JavaFX User Group l  Streamed Live!
Disclaimer: This is Code-Heavy
JavaFX 2.0 Platform Immersive Application Experience Leverage your Java skills with modern JavaFX APIs >  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.
JavaFX is Now Open Source! Part of the OpenJDK Project Controls available now, additional code added incrementally Project Page: >  http://openjdk.java.net/projects/openjfx/ 5
And Will Run on Tablets!* >  iPad (iOS) >  Linux (Popular Platform for Tablets That Runs Something Similar to Java) *No Release Timeline Announced Yet 6
JavaFX With Java
Programming Languages >  JavaFX 2.0 APIs are now in Java l  Pure Java APIs for all of JavaFX l  Binding and Sequences exposed as Java APIs l  FXML Markup for tooling >  Embrace all JVM languages l  Groovy, Scala, Clojure, JRuby l  Fantom, Mira, Gosu, Jython, etc. >  JavaFX Script is no longer supported by Oracle l  Existing JavaFX Script based applications will continue to run l  Visage is the open-source successor to the JavaFX Script language
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
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);      }   }  
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);      }   }  
Observable Properties >  Supports watching for changes to properties >  Implemented via anonymous inner classes >  Will take advantage of closures in the future
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>()  {                   });  
Observable Pseudo-Properties   final  Rectangle  rect  =  new  Rectangle();   rect.setX(40);   rect.setY(40);   The property we want to watch rect.setWidth(100);   rect.setHeight(200);       rect.hoverProperty().addListener(new  ChangeListener<Boolean>()  {                 });  
Observable Pseudo-Properties   final  Rectangle  rect  =  new  Rectangle();   rect.setX(40);   Only one listener used with generics to rect.setY(40);   specify the data type rect.setWidth(100);   rect.setHeight(200);       rect.hoverProperty().addListener(new  ChangeListener<Boolean>()  {                     });  
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()
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);      }   });  
Binding >  Unquestionably the biggest JavaFX Script innovation >  Supported via a PropertyBinding class >  Lazy invocation for high performance >  Static construction syntax for simple cases l  e.g.: bind(<property>), bindBiDirectional(<property>)
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
Vanishing Circles ! 20
Vanishing Circles in Java 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);          List<Circle>  circles  =  new  ArrayList<Circle>();          for  (int  i  =  0;  i  <  50;  i++)  {   40 Lines            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));   1299 Characters            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();      }   }   21
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);   }   23
Setup Binding circle.strokeWidthProperty().bind(Bindings      .when(circle.hoverProperty())      .then(4)      .otherwise(0)   );   24
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();      }   });   25
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();   26
JavaFX With Groovy
Features of Groovy >  Modern language l  Closures l  AST Transforms l  Strongly typed dynamic language >  Tight integration with Java l  Very easy to port from Java to Groovy >  Declarative syntax with GroovyFX Builders l  Familiar to Groovy and JavaFX Script developers
Java vs. GroovyFX DSL public  class  VanishingCircles  extends  Application  {   GroovyFX.start  {  primaryStage  -­‐>        def  sg  =  new  SceneGraphBuilder()      public  static  void  main(String[]  args)  {      def  rand  =  new  Random().&nextInt          Application.launch(args);      def  circles  =  []      }              sg.stage(title:  'Vanishing  Circles',  show:  true)  {      @Override          scene(fill:  black,  width:  800,  height:  600)  {      public  void  start(Stage  primaryStage)  {              50.times  {          primaryStage.setTitle("Vanishing  Circles");                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),  radius:  150,  stroke:  white,          Group  root  =  new  Group();                                  strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {          Scene  scene  =  new  Scene(root,  800,  600,  Color.BLACK);                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)          List<Circle>  circles  =  new  ArrayList<Circle>();                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)          for  (int  i  =  0;  i  <  50;  i++)  {                      onMouseClicked  {  e  -­‐>   40 Lines 29 Lines            final  Circle  circle  =  new  Circle(150);                          timeline  {              circle.setCenterX(Math.random()  *  800);                              at(3.s)  {  change  e.source.radiusProperty()  to  0  }              circle.setCenterY(Math.random()  *  600);                          }.play()              circle.setFill(new  Color(Math.random(),  Math.random(),  Math.random(),  .2));                      }   671 Characters            circle.setEffect(new  BoxBlur(10,  10,  3));                  }   1299 Characters            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();              }          }            timeline(cycleCount:  Timeline.INDEFINITE,  autoReverse:  true)  {                  }              circles.each  {  circle  -­‐>              });                  at  (40.s)  {              circle.setStroke(Color.WHITE);                      change  circle.centerXProperty()  to  rand(800)              circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty())                      change  circle.centerYProperty()  to  rand(600)                  .then(4)                  }                  .otherwise(0));              }              circles.add(circle);          }.play()          }      }          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();      }   }   29
GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      def  rand  =  new  Random().&nextInt      def  circles  =  []        sg.stage(title:  'Vanishing  Circles',  show:  true)  {          scene(fill:  black,  width:  800,  height:  600)  {              50.times  {                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),                          radius:  150,  stroke:  white,                          strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)                  }              }          }      }   }   30
GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      def  rand  =  new  Random().&nextInt      def  circles  =  []        sg.stage(title:  'Vanishing  Circles',  show:  true)  {   Builder for GroovyFX scene graphs        scene(fill:  black,  width:  800,  height:  600)  {              50.times  {                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),                          radius:  150,  stroke:  white,                          strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)                  }              }          }      }   }   31
GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      def  rand  =  new  Random().&nextInt      def  circles  =  []        sg.stage(title:  'Vanishing  Circles',  show:  true)  {          scene(fill:  black,  width:  800,  height:  600)  {              50.times  {                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),                          radius:  150,  stroke:  white,   Declarative Stage definition                        strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)                  }              }          }      }   }   32
GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      def  rand  =  new  Random().&nextInt      def  circles  =  []     Inline property definitions    sg.stage(title:  'Vanishing  Circles',  show:  true)  {          scene(fill:  black,  width:  800,  height:  600)  {              50.times  {                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),                          radius:  150,  stroke:  white,                          strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)                  }              }          }      }   }   33
GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      def  rand  =  new  Random().&nextInt      def  circles  =  []        sg.stage(title:  'Vanishing  Circles',  show:  true)  {          scene(fill:  black,  width:  800,  height:  600)  {   Bind to properties            50.times  {                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),                          radius:  150,  stroke:  white,                          strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)                  }              }          }      }   }   34
GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      def  rand  =  new  Random().&nextInt      def  circles  =  []        sg.stage(title:  'Vanishing  Circles',  show:  true)  {   Creation Via Loop Sequence        scene(fill:  black,  width:  800,  height:  600)  {              50.times  {                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),                          radius:  150,  stroke:  white,                          strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)                  }              }          }      }   }   35
Animation in GroovyFX timeline(cycleCount:  Timeline.INDEFINITE,  autoReverse:  true)  {      circles.each  {  circle  -­‐>          at  (40.s)  {              change  circle.centerXProperty()  to  rand(800)              change  circle.centerYProperty()  to  rand(600)          }      }   }.play()   36
Animation in GroovyFX timeline(cycleCount:  Timeline.INDEFINITE,  autoReverse:  true)  {      circles.each  {  circle  -­‐>          at  (40.s)  {              change  circle.centerXProperty()  to  rand(800)              change  circle.centerYProperty()  to  rand(600)          }      }   Easy animation syntax: }.play()   at (duration) {keyframes} 37
Animation in GroovyFX timeline(cycleCount:  Timeline.INDEFINITE,  autoReverse:  true)  {      circles.each  {  circle  -­‐>          at  (40.s)  {              change  circle.centerXProperty()  to  rand(800)              change  circle.centerYProperty()  to  rand(600)          }      }   }.play()   Key frame DSL 38
Animation in GroovyFX timeline(cycleCount:  Timeline.INDEFINITE,  autoReverse:  true)  {      circles.each  {  circle  -­‐>          at  (40.s)  {              change  circle.centerXProperty()  to  rand(800)  tween  ease_both              change  circle.centerYProperty()  to  rand(600)  tween  linear          }      }   }.play()   Optional easing 39
Event Listeners in GroovyFX >  Supported using the built-in Closure syntax >  Optional arguments for event objects onMouseClicked  {  e  -­‐>      timeline  {          at(3.s)  {  change  e.source.radiusProperty()  to  0  }      }.play()   }   40
Event Listeners in GroovyFX >  Supported using the built-in Closure syntax >  Optional arguments for event objects onMouseClicked  {  MouseEvent  e  -­‐>      timeline  {          at(3.s)  {  change  e.source.radiusProperty()  to  0  }      }.play()   }   Compact syntax {body} 41
Event Listeners in GroovyFX >  Supported using the built-in Closure syntax >  Optional arguments for event objects Optional event parameter {event -> body} onMouseClicked  {  MouseEvent  e  -­‐>      timeline  {          at(3.s)  {  change  e.source.radiusProperty()  to  0  }      }.play()   }   42
But wait, there is more Grooviness… 43
Properties in Java public class Person {! private StringProperty firstName;! public void setFirstName(String val) { firstNameProperty().set(val); }! public String getFirstName() { return firstNameProperty().get(); }! public StringProperty firstNameProperty() { ! if (firstName == null) ! firstName = new SimpleStringProperty(this, "firstName");! return firstName; ! }! ! private StringProperty lastName;! public void setLastName(String value) { lastNameProperty().set(value); }! public String getLastName() { return lastNameProperty().get(); }! public StringProperty lastNameProperty() { ! if (lastName == null) // etc.! } ! }!   44
Properties in GroovyFX public class Person {! @FXBindable String firstName; ! @FXBindable String lastName;! }!   45
Properties in GroovyFX public class Person {! @FXBindable String firstName; ! @FXBindable String lastName = “Smith”;! }!   Optional initializers 46
Properties in GroovyFX public class Person {! @FXBindable String firstName; ! @FXBindable String lastName = “Smith”;! }! ! Get and set values def p = new Person()! def last = p.lastName! p.firstName = “Agent”! ! 47
Properties in GroovyFX public class Person {! @FXBindable String firstName; ! @FXBindable String lastName = “Smith”;! }! ! def p = new Person()! def last = p.lastName! Access underlying property for p.firstName = “Agent”! binding ! textField(text: bind(p.lastNameProperty()))! ! 48
Binding in GroovyFX @FXBindable   class  Time  {      Integer  hours      Integer  minutes      Integer  seconds        Double  hourAngle      Double  minuteAngle      Double  secondAngle        public  Time()  {          //  bind  the  angle  properties  to  the  clock  time          hourAngleProperty().bind((hoursProperty()  *  30.0)  +  (minutesProperty()  *  0.5))          minuteAngleProperty().bind(minutesProperty()  *  6.0)          secondAngleProperty().bind(secondsProperty()  *  6.0)      }   }   49
TableView in Java ObservableList<Person> items = ...! TableView<Person> tableView = new TableView<Person>(items);! ! TableColumn<Person,String> firstNameCol = ! new TableColumn<Person,String>("First Name");! ! firstNameCol.setCellValueFactory(! new Callback<CellDataFeatures<Person, String>, ! ObservableValue<String>>() {! public ObservableValue<String> call(CellDataFeatures<Person, String> p) ! {! return p.getValue().firstNameProperty();! }! });! ! tableView.getColumns().add(firstNameCol);! 50
TableView in GroovyFX def dateFormat = new SimpleDateFormat("yyyy-MM-dd");! ! tableView(items: persons) {! tableColumn(property: "name", text: "Name", prefWidth: 150)! tableColumn(property: "age", text: "Age", prefWidth: 50)! tableColumn(property: "gender", text: "Gender", prefWidth: 150)! tableColumn(property: "dob", text: "Birth", prefWidth: 150, ! type: Date,! converter: { from -> return dateFormat.format(from) })! }! 51
Layout in Java TextField urlField = new TextField(“http://www.google.com”);! HBox.setHgrow(urlField, Priority.ALWAYS);! ! HBox hbox = new HBox();! hbox.getChildren().add(urlField);! ! WebView webView = new WebView();! VBox.setVgrow(webView, Priority.ALWAYS);! ! VBox vbox = new VBox();! vbox.getChildren().addAll(hbox, webView);! 52
Layout in GroovyFX sg.stage(title: "GroovyFX WebView Demo", show: true) { scene(fill: groovyblue, width: 1024, height: 800) { vbox { hbox(padding: 10, spacing: 5) { textField(“http://www.yahoo.com”, hgrow: "always") button("Go”) } webView(vgrow: "always") } } } 53
Layout in GroovyFX 54
Layout in GroovyFX gridPane(hgap: 5, vgap: 10, padding: 25) {! columnConstraints(minWidth: 50, halignment: "right")! columnConstraints(prefWidth: 250)! label("Send Us Your Feedback", font: "24pt sanserif", ! row: 0, columnSpan: GridPane.REMAINING, halignment: "center",! margin: [0, 0, 10])! ! label("Name: ", row: 1, column: 0)! textField(promptText: "Your name", row: 1, column: 1, hgrow: 'always')! ! label("Email:", row: 2, column: 0)! textField(promptText: "Your email", row: 2, column: 1, hgrow: 'always')! ! label("Message:", row: 3, column: 0, valignment: "baseline")! textArea(row: 3, column: 1, hgrow: "always", vgrow: "always")! ! button("Send Message", row: 4, column: 1, halignment: "right")! }! 55
Layout in GroovyFX 56
GroovyFX Supports… 57
GroovyFX Supports… 58
JavaFX With Clojure Artwork by Augusto Sellhorn http://sellmic.com/ 59
A Little About Clojure >  Started in 2007 by Rich Hickey >  Functional Programming Language >  Derived from LISP >  Optimized for High Concurrency (def hello (fn [] "Hello world")) (hello) >  … and looks nothing like Java! 60
Clojure Syntax in One Slide Symbols Collections (commas optional) >  numbers – 2.178 >  Lists >  ratios – 355/113 (1, 2, 3, 4, 5) >  strings – “clojure”, “rocks” >  Vectors >  characters – a b c d [1, 2, 3, 4, 5] >  symbols – a b c d >  Maps >  keywords – :alpha :beta {:a 1, :b 2, :c 3, :d 4} >  boolean – true, false >  Sets >  null - nil #{:a :b :c :d :e} (plus macros that are syntactic sugar wrapping the above) 61
Clojure GUI Example (defn  javafxapp  []      (let  [stage  (Stage.  "JavaFX  Stage")                  scene  (Scene.)]          (.setFill  scene  Color/LIGHTGREEN)          (.setWidth  stage  600)          (.setHeight  stage  450)          (.setScene  stage  scene)          (.setVisible  stage  true)))   (javafxapp)   62
Refined Clojure GUI Example (defn  javafxapp  []      (doto  (Stage.  "JavaFX  Stage")          (.setWidth  600)          (.setHeight  450)          (.setScene  (doto  (Scene.)              (.setFill  Color/LIGHTGREEN)              (.setContent  (list  (doto  (Rectangle.)                  (.setX  25)                  (.setY  40)                  (.setWidth  100)                  (.setHeight  50)                  (.setFill  Color/RED))))))          (.setVisible  true)))   (javafxapp)   63
Refined Clojure GUI Example (defn  javafxapp  []      (doto  (Stage.  "JavaFX  Stage")          (.setWidth  600)   Doto allows nested data        (.setHeight  450)          (.setScene  (doto  (Scene.)   structures            (.setFill  Color/LIGHTGREEN)              (.setContent  (list  (doto  (Rectangle.)                  (.setX  25)                  (.setY  40)                  (.setWidth  100)                  (.setHeight  50)                  (.setFill  Color/RED))))))          (.setVisible  true)))   (javafxapp)   64
Closures in Clojure >  Inner classes can be created using proxy   (.addListener  hoverProperty      (proxy  [ChangeListener]  []          (handle  [p,  o,  v]              (.setFill  rect                  (if  (.isHover  rect)  Color/GREEN  Color/RED)))))   65
Closures in Clojure >  Inner classes can be created using proxy   Proxy form: (proxy  [class]  [args]  fs+)   f => (name  [params*]  body)   (.addListener  hoverProperty      (proxy  [ChangeListener]  []          (handle  [p,  o,  v]              (.setFill  rect                  (if  (.isHover  rect)  Color/GREEN  Color/RED)))))   66
JavaFX With Scala 67
What is Scala 2001 2006 •  Scala Started •  Scala v2.0 2003/2004 2011 •  Scala v1.0 •  Scala 2.9.2 (latest) >  Started in 2001 by Martin Odersky >  Compiles to Java bytecodes >  Pure object-oriented language >  Also a functional programming language 68
Why Scala? >  Shares many language features with JavaFX Script that make GUI programming easier: l  Static Type Checking – Catch your errors at compile time l  Closures – Wrap behavior and pass it by reference l  Declarative – Express the UI by describing what it should look like >  Scala also supports Type Safe DSLs! l  Implicit Conversions – type safe class extension l  Operator Overloading – with standard precedence rules l  DelayedInit / @specialized – advanced language features 69
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)   40 Lines 33 Lines            final  Circle  circle  =  new  Circle(150);                  effect  =  new  BoxBlur(10,  10,  3)              circle.setCenterX(Math.random()  *  800);                  strokeWidth  <==  when  (hover)  then  4  otherwise  0              circle.setCenterY(Math.random()  *  600);                  stroke  =  WHITE              circle.setFill(new  Color(Math.random(),  Math.random(),  Math.random(),  .2));                  onMouseClicked  =  {              circle.setEffect(new  BoxBlur(10,  10,  3));                      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);                      new  Timeline(new  KeyFrame(Duration.seconds(3),  collapse)).play();              }   591 Characters                }              content  =  circles          }                  }      }              });                circle.setStroke(Color.WHITE);      new  Timeline  {              circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty())          cycleCount  =  INDEFINITE                  .then(4)          autoReverse  =  true                  .otherwise(0));          keyFrames  =  for  (circle  <-­‐  circles)  yield  at  (40  s)  {              circles.add(circle);              Set(          }                  circle.centerX  -­‐>  random  *  stage.width,          root.getChildren().addAll(circles);                  circle.centerY  -­‐>  random  *  stage.height          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();      }   }   70
object  VanishingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800          height  =  600          scene  =  new  Scene  {              fill  =  BLACK              children  =  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)              }          }      }   }   71
object  VanishingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800          height  =  600   for JavaFX applications Base class        scene  =  new  Scene  {              fill  =  BLACK              children  =  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)              }          }      }   }   72
object  VanishingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800          height  =  600          scene  =  new  Scene  {   Declarative Stage definition            fill  =  BLACK              children  =  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)              }          }      }   }   73
object  VanishingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800   Inline property definitions        height  =  600          scene  =  new  Scene  {              fill  =  BLACK              children  =  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)              }          }      }   }   74
object  VanishingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800          height  =  600   Sequence Creation Via Loop        scene  =  new  Scene  {              fill  =  BLACK              children  =  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)              }          }      }   }   75
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"   76
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();   77
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();   78
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          )      }   }   Operator overloading for animation timeline.play();   syntax 79
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 80
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()   }   81
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()   }   Compact syntax {body} 82
Event Listeners in Scala >  Supported using the built-in Closure syntax >  Arguments for event objects Event parameter >  100% type-safe {(event) => body} onMouseClicked  =  {  (e:  MouseEvent)  =>      Timeline(at(3  s){radius-­‐>0}).play()   }   83
TableView in ScalaFX def dateFormat = new SimpleDateFormat("yyyy-MM-dd")! new TableView[Speaker](persons) {! columns = Seq(! new TableColumn[Speaker, String] {! text: "Name"! converter = {_.firstName}! } new TableColumn[Speaker, String] {! text: "Age"! converter = {_.age}! }! new TableColumn[Speaker, String] {! text: "Gender"! converter = {_.gender}! }! new TableColumn[Speaker, String] {! text: "Birth"! converter = {dateFormat.format(_.dob)}, ! }! )}! 84
JavaFX With Visage 85
About Project Visage >  “Visage is a domain specific language (DSL) designed for the express purpose of writing user interfaces.” >  Visage project goals: l  Compile to JavaFX Java APIs l  Evolve the Language (Annotations, Maps, etc.) l  Support Other Toolkits >  Come join the team! >  For more info: http://visage-lang.org/ 86
Java vs. Visage DSL public  class  VanishingCircles  extends  Application  {   var  circles:Circle[];     Stage  {      public  static  void  main(String[]  args)  {      title:  "Vanishing  Circles"          Application.launch(args);      Scene  {      }          width:  800                height:  600      @Override          fill:  BLACK      public  void  start(Stage  primaryStage)  {          Group  {          primaryStage.setTitle("Vanishing  Circles");              circles  =  for  (i  in  [1..50])  {          Group  root  =  new  Group();                  def  c:Circle  =  Circle  {          Scene  scene  =  new  Scene(root,  800,  600,  Color.BLACK);                      centerX:  random()  *  800          List<Circle>  circles  =  new  ArrayList<Circle>();                      centerY:  random()  *  600          for  (int  i  =  0;  i  <  50;  i++)  {                      radius:  150   40 Lines 35 Lines            final  Circle  circle  =  new  Circle(150);                      fill:  color(random(),  random(),  random(),  .2)              circle.setCenterX(Math.random()  *  800);                      effect:  BoxBlur  {              circle.setCenterY(Math.random()  *  600);                          height:  10              circle.setFill(new  Color(Math.random(),  Math.random(),  Math.random(),  .2));                          width:  10              circle.setEffect(new  BoxBlur(10,  10,  3));                          iterations:  3   1299 Characters            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();   487 Characters                    }                      stroke:  WHITE                      strokeWidth:  bind  if  (c.hover)  5  else  0                      onMouseClicked:  function(e)  {                  }                          Timeline  {at  (3s)  {c.radius  =>  0}}.play()              });                      }              circle.setStroke(Color.WHITE);                  }              circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty())              }                  .then(4)          }                  .otherwise(0));      }              circles.add(circle);   }          }            root.getChildren().addAll(circles);   Timeline  {          primaryStage.setScene(scene);      for  (circle  in  circles)  at  (40s)  {          primaryStage.show();          circle.centerX  =>  random()  *  800;                    circle.centerY  =>  random()  *  600          Timeline  moveCircles  =  new  Timeline();      }          for  (Circle  circle  :  circles)  {   }.play()              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();      }   }   87
How about JavaFX on… Visage Stage  {      title:  "Vanishing  Circles"      scene:  Scene  {          width:  800          height:  600          fill:  BLACK          content:  Group  {              circles  =  for  (i  in  [1..50])  {                  Circle  {                      centerX:  random()  *  800                      centerY:  random()  *  600                  }              }          }      }   }   88
How about JavaFX on… Visage Stage  {      title:  "Vanishing  Circles"      scene:  Scene  {          width:  800          height:  600          fill:  BLACK          content:  Group  {              circles  =  for  (i  in  [1..50])  {                  Circle  {                      centerX:  random()  *  800                      centerY:  random()  *  600                  }              }          }      }   }   89
How about JavaFX on… Visage Stage  {      title:  "Vanishing  Circles"      Scene  {          width:  800          height:  600          fill:  BLACK          Group  {              circles  =  for  (i  in  [1..50])  {                  Circle  {                      centerX:  random()  *  800                      centerY:  random()  *  600                  }              }          }      }   }   90
Visage is JavaFX Script++ >  Default Parameters >  New Literal Syntax For: l  Angles – 35deg,  4rad,  1turn   l  Colors – #DDCCBB,  #AA33AA|CC   l  Lengths – 5px,  2pt,  3in,  4sp   >  Null-check Dereference l  var width = rect.!width >  Built-in Bindable Maps (coming soon!) l  var fruitMap = ["red" : apple, "yellow" : banana] l  var fruit = bind fruitMap["red"] 91
Visage and JavaFX 2.0 are made for each other… >  Enhanced Binding l  Retains lazy evaluation properties with additional expressive power >  Integrated Collections l  Sequences and Maps automatically convert between JavaFX Observable Lists/Maps >  Built-in Animation Syntax l  Ties into JavaFX animation subsystem l  Provides consistent, clean APIs 92
Other JVM Languages to Try >  JRuby l  Faithful to Ruby language with the power of the JVM >  Gosu l  Up and coming language created at GuideWire l  Easy to enhance libraries and create DSLs >  Mirah l  Invented by Charles Nutter l  Local Type Inference, Static and Dynamic Typing >  Fantom l  Created by Brian and Andy Frank l  Portable to Java and .NET l  Local Type Inference, Static and Dynamic Typing 93
Conclusion >  You can write JavaFX applications in pure Java >  JavaFX is also usable in alternate languages >  You can get improved support using DSL libraries l  GroovyFX l  ScalaFX >  Or a dedicated UI JVM Language l  Visage
Stephen Chin stephen.chin@oracle.com tweet: @steveonjava Thanks to Dean Iverson and Jonathan Giles for help preparing this talk 95

Hacking JavaFX with Groovy, Clojure, Scala, and Visage: Stephen Chin

  • 1.
    Hacking JavaFX withGroovy, Clojure, Scala, and Visage Stephen Chin Java Evangelist, Oracle stephen.chin@oracle.com tweet: @steveonjava
  • 2.
    Meet the Presenter Stephen Chin >  Java Evangelist, Oracle >  Author, Pro JavaFX Platform 2 Family Man >  Open Source Hacker l  JFXtras l  ScalaFX Motorcyclist l  Visage >  User Group Co-Leader l  Silicon Valley JavaFX User Group l  Streamed Live!
  • 3.
  • 4.
    JavaFX 2.0 Platform ImmersiveApplication Experience Leverage your Java skills with modern JavaFX APIs >  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.
  • 5.
    JavaFX is NowOpen Source! Part of the OpenJDK Project Controls available now, additional code added incrementally Project Page: >  http://openjdk.java.net/projects/openjfx/ 5
  • 6.
    And Will Runon Tablets!* >  iPad (iOS) >  Linux (Popular Platform for Tablets That Runs Something Similar to Java) *No Release Timeline Announced Yet 6
  • 7.
  • 8.
    Programming Languages >  JavaFX 2.0 APIs are now in Java l  Pure Java APIs for all of JavaFX l  Binding and Sequences exposed as Java APIs l  FXML Markup for tooling >  Embrace all JVM languages l  Groovy, Scala, Clojure, JRuby l  Fantom, Mira, Gosu, Jython, etc. >  JavaFX Script is no longer supported by Oracle l  Existing JavaFX Script based applications will continue to run l  Visage is the open-source successor to the JavaFX Script language
  • 9.
    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
  • 10.
    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);      }   }  
  • 11.
    Example Application UsingBuilders 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);      }   }  
  • 12.
    Observable Properties >  Supportswatching for changes to properties >  Implemented via anonymous inner classes >  Will take advantage of closures in the future
  • 13.
    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>()  {                   });  
  • 14.
    Observable Pseudo-Properties   final  Rectangle  rect  =  new  Rectangle();   rect.setX(40);   rect.setY(40);   The property we want to watch rect.setWidth(100);   rect.setHeight(200);       rect.hoverProperty().addListener(new  ChangeListener<Boolean>()  {                 });  
  • 15.
    Observable Pseudo-Properties   final  Rectangle  rect  =  new  Rectangle();   rect.setX(40);   Only one listener used with generics to rect.setY(40);   specify the data type rect.setWidth(100);   rect.setHeight(200);       rect.hoverProperty().addListener(new  ChangeListener<Boolean>()  {                     });  
  • 16.
    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()
  • 17.
    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);      }   });  
  • 18.
    Binding >  Unquestionably thebiggest JavaFX Script innovation >  Supported via a PropertyBinding class >  Lazy invocation for high performance >  Static construction syntax for simple cases l  e.g.: bind(<property>), bindBiDirectional(<property>)
  • 19.
    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
  • 20.
  • 21.
    Vanishing Circles inJava 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);          List<Circle>  circles  =  new  ArrayList<Circle>();          for  (int  i  =  0;  i  <  50;  i++)  {   40 Lines            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));   1299 Characters            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();      }   }   21
  • 22.
    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…]      }   }  
  • 23.
    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);   }   23
  • 24.
    Setup Binding circle.strokeWidthProperty().bind(Bindings      .when(circle.hoverProperty())      .then(4)      .otherwise(0)   );   24
  • 25.
    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();      }   });   25
  • 26.
    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();   26
  • 27.
  • 28.
    Features of Groovy >  Modern language l  Closures l  AST Transforms l  Strongly typed dynamic language >  Tight integration with Java l  Very easy to port from Java to Groovy >  Declarative syntax with GroovyFX Builders l  Familiar to Groovy and JavaFX Script developers
  • 29.
    Java vs. GroovyFXDSL public  class  VanishingCircles  extends  Application  {   GroovyFX.start  {  primaryStage  -­‐>        def  sg  =  new  SceneGraphBuilder()      public  static  void  main(String[]  args)  {      def  rand  =  new  Random().&nextInt          Application.launch(args);      def  circles  =  []      }              sg.stage(title:  'Vanishing  Circles',  show:  true)  {      @Override          scene(fill:  black,  width:  800,  height:  600)  {      public  void  start(Stage  primaryStage)  {              50.times  {          primaryStage.setTitle("Vanishing  Circles");                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),  radius:  150,  stroke:  white,          Group  root  =  new  Group();                                  strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {          Scene  scene  =  new  Scene(root,  800,  600,  Color.BLACK);                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)          List<Circle>  circles  =  new  ArrayList<Circle>();                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)          for  (int  i  =  0;  i  <  50;  i++)  {                      onMouseClicked  {  e  -­‐>   40 Lines 29 Lines            final  Circle  circle  =  new  Circle(150);                          timeline  {              circle.setCenterX(Math.random()  *  800);                              at(3.s)  {  change  e.source.radiusProperty()  to  0  }              circle.setCenterY(Math.random()  *  600);                          }.play()              circle.setFill(new  Color(Math.random(),  Math.random(),  Math.random(),  .2));                      }   671 Characters            circle.setEffect(new  BoxBlur(10,  10,  3));                  }   1299 Characters            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();              }          }            timeline(cycleCount:  Timeline.INDEFINITE,  autoReverse:  true)  {                  }              circles.each  {  circle  -­‐>              });                  at  (40.s)  {              circle.setStroke(Color.WHITE);                      change  circle.centerXProperty()  to  rand(800)              circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty())                      change  circle.centerYProperty()  to  rand(600)                  .then(4)                  }                  .otherwise(0));              }              circles.add(circle);          }.play()          }      }          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();      }   }   29
  • 30.
    GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      def  rand  =  new  Random().&nextInt      def  circles  =  []        sg.stage(title:  'Vanishing  Circles',  show:  true)  {          scene(fill:  black,  width:  800,  height:  600)  {              50.times  {                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),                          radius:  150,  stroke:  white,                          strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)                  }              }          }      }   }   30
  • 31.
    GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      def  rand  =  new  Random().&nextInt      def  circles  =  []        sg.stage(title:  'Vanishing  Circles',  show:  true)  {   Builder for GroovyFX scene graphs        scene(fill:  black,  width:  800,  height:  600)  {              50.times  {                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),                          radius:  150,  stroke:  white,                          strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)                  }              }          }      }   }   31
  • 32.
    GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      def  rand  =  new  Random().&nextInt      def  circles  =  []        sg.stage(title:  'Vanishing  Circles',  show:  true)  {          scene(fill:  black,  width:  800,  height:  600)  {              50.times  {                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),                          radius:  150,  stroke:  white,   Declarative Stage definition                        strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)                  }              }          }      }   }   32
  • 33.
    GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      def  rand  =  new  Random().&nextInt      def  circles  =  []     Inline property definitions    sg.stage(title:  'Vanishing  Circles',  show:  true)  {          scene(fill:  black,  width:  800,  height:  600)  {              50.times  {                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),                          radius:  150,  stroke:  white,                          strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)                  }              }          }      }   }   33
  • 34.
    GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      def  rand  =  new  Random().&nextInt      def  circles  =  []        sg.stage(title:  'Vanishing  Circles',  show:  true)  {          scene(fill:  black,  width:  800,  height:  600)  {   Bind to properties            50.times  {                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),                          radius:  150,  stroke:  white,                          strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)                  }              }          }      }   }   34
  • 35.
    GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      def  rand  =  new  Random().&nextInt      def  circles  =  []        sg.stage(title:  'Vanishing  Circles',  show:  true)  {   Creation Via Loop Sequence        scene(fill:  black,  width:  800,  height:  600)  {              50.times  {                  circles  <<  circle(centerX:  rand(800),  centerY:  rand(600),                          radius:  150,  stroke:  white,                          strokeWidth:  bind('hover',  converter:  {val  -­‐>  val  ?  4  :  0}))  {                      fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                      effect  boxBlur(width:  10,  height:  10,  iterations:  3)                  }              }          }      }   }   35
  • 36.
    Animation in GroovyFX timeline(cycleCount:  Timeline.INDEFINITE,  autoReverse:  true)  {      circles.each  {  circle  -­‐>          at  (40.s)  {              change  circle.centerXProperty()  to  rand(800)              change  circle.centerYProperty()  to  rand(600)          }      }   }.play()   36
  • 37.
    Animation in GroovyFX timeline(cycleCount:  Timeline.INDEFINITE,  autoReverse:  true)  {      circles.each  {  circle  -­‐>          at  (40.s)  {              change  circle.centerXProperty()  to  rand(800)              change  circle.centerYProperty()  to  rand(600)          }      }   Easy animation syntax: }.play()   at (duration) {keyframes} 37
  • 38.
    Animation in GroovyFX timeline(cycleCount:  Timeline.INDEFINITE,  autoReverse:  true)  {      circles.each  {  circle  -­‐>          at  (40.s)  {              change  circle.centerXProperty()  to  rand(800)              change  circle.centerYProperty()  to  rand(600)          }      }   }.play()   Key frame DSL 38
  • 39.
    Animation in GroovyFX timeline(cycleCount:  Timeline.INDEFINITE,  autoReverse:  true)  {      circles.each  {  circle  -­‐>          at  (40.s)  {              change  circle.centerXProperty()  to  rand(800)  tween  ease_both              change  circle.centerYProperty()  to  rand(600)  tween  linear          }      }   }.play()   Optional easing 39
  • 40.
    Event Listeners inGroovyFX >  Supported using the built-in Closure syntax >  Optional arguments for event objects onMouseClicked  {  e  -­‐>      timeline  {          at(3.s)  {  change  e.source.radiusProperty()  to  0  }      }.play()   }   40
  • 41.
    Event Listeners inGroovyFX >  Supported using the built-in Closure syntax >  Optional arguments for event objects onMouseClicked  {  MouseEvent  e  -­‐>      timeline  {          at(3.s)  {  change  e.source.radiusProperty()  to  0  }      }.play()   }   Compact syntax {body} 41
  • 42.
    Event Listeners inGroovyFX >  Supported using the built-in Closure syntax >  Optional arguments for event objects Optional event parameter {event -> body} onMouseClicked  {  MouseEvent  e  -­‐>      timeline  {          at(3.s)  {  change  e.source.radiusProperty()  to  0  }      }.play()   }   42
  • 43.
    But wait, thereis more Grooviness… 43
  • 44.
    Properties in Java publicclass Person {! private StringProperty firstName;! public void setFirstName(String val) { firstNameProperty().set(val); }! public String getFirstName() { return firstNameProperty().get(); }! public StringProperty firstNameProperty() { ! if (firstName == null) ! firstName = new SimpleStringProperty(this, "firstName");! return firstName; ! }! ! private StringProperty lastName;! public void setLastName(String value) { lastNameProperty().set(value); }! public String getLastName() { return lastNameProperty().get(); }! public StringProperty lastNameProperty() { ! if (lastName == null) // etc.! } ! }!   44
  • 45.
    Properties in GroovyFX publicclass Person {! @FXBindable String firstName; ! @FXBindable String lastName;! }!   45
  • 46.
    Properties in GroovyFX publicclass Person {! @FXBindable String firstName; ! @FXBindable String lastName = “Smith”;! }!   Optional initializers 46
  • 47.
    Properties in GroovyFX publicclass Person {! @FXBindable String firstName; ! @FXBindable String lastName = “Smith”;! }! ! Get and set values def p = new Person()! def last = p.lastName! p.firstName = “Agent”! ! 47
  • 48.
    Properties in GroovyFX publicclass Person {! @FXBindable String firstName; ! @FXBindable String lastName = “Smith”;! }! ! def p = new Person()! def last = p.lastName! Access underlying property for p.firstName = “Agent”! binding ! textField(text: bind(p.lastNameProperty()))! ! 48
  • 49.
    Binding in GroovyFX @FXBindable   class  Time  {      Integer  hours      Integer  minutes      Integer  seconds        Double  hourAngle      Double  minuteAngle      Double  secondAngle        public  Time()  {          //  bind  the  angle  properties  to  the  clock  time          hourAngleProperty().bind((hoursProperty()  *  30.0)  +  (minutesProperty()  *  0.5))          minuteAngleProperty().bind(minutesProperty()  *  6.0)          secondAngleProperty().bind(secondsProperty()  *  6.0)      }   }   49
  • 50.
    TableView in Java ObservableList<Person>items = ...! TableView<Person> tableView = new TableView<Person>(items);! ! TableColumn<Person,String> firstNameCol = ! new TableColumn<Person,String>("First Name");! ! firstNameCol.setCellValueFactory(! new Callback<CellDataFeatures<Person, String>, ! ObservableValue<String>>() {! public ObservableValue<String> call(CellDataFeatures<Person, String> p) ! {! return p.getValue().firstNameProperty();! }! });! ! tableView.getColumns().add(firstNameCol);! 50
  • 51.
    TableView in GroovyFX defdateFormat = new SimpleDateFormat("yyyy-MM-dd");! ! tableView(items: persons) {! tableColumn(property: "name", text: "Name", prefWidth: 150)! tableColumn(property: "age", text: "Age", prefWidth: 50)! tableColumn(property: "gender", text: "Gender", prefWidth: 150)! tableColumn(property: "dob", text: "Birth", prefWidth: 150, ! type: Date,! converter: { from -> return dateFormat.format(from) })! }! 51
  • 52.
    Layout in Java TextFieldurlField = new TextField(“http://www.google.com”);! HBox.setHgrow(urlField, Priority.ALWAYS);! ! HBox hbox = new HBox();! hbox.getChildren().add(urlField);! ! WebView webView = new WebView();! VBox.setVgrow(webView, Priority.ALWAYS);! ! VBox vbox = new VBox();! vbox.getChildren().addAll(hbox, webView);! 52
  • 53.
    Layout in GroovyFX sg.stage(title:"GroovyFX WebView Demo", show: true) { scene(fill: groovyblue, width: 1024, height: 800) { vbox { hbox(padding: 10, spacing: 5) { textField(“http://www.yahoo.com”, hgrow: "always") button("Go”) } webView(vgrow: "always") } } } 53
  • 54.
  • 55.
    Layout in GroovyFX gridPane(hgap:5, vgap: 10, padding: 25) {! columnConstraints(minWidth: 50, halignment: "right")! columnConstraints(prefWidth: 250)! label("Send Us Your Feedback", font: "24pt sanserif", ! row: 0, columnSpan: GridPane.REMAINING, halignment: "center",! margin: [0, 0, 10])! ! label("Name: ", row: 1, column: 0)! textField(promptText: "Your name", row: 1, column: 1, hgrow: 'always')! ! label("Email:", row: 2, column: 0)! textField(promptText: "Your email", row: 2, column: 1, hgrow: 'always')! ! label("Message:", row: 3, column: 0, valignment: "baseline")! textArea(row: 3, column: 1, hgrow: "always", vgrow: "always")! ! button("Send Message", row: 4, column: 1, halignment: "right")! }! 55
  • 56.
  • 57.
  • 58.
  • 59.
    JavaFX With Clojure Artwork by Augusto Sellhorn http://sellmic.com/ 59
  • 60.
    A Little About Clojure >  Started in 2007 by Rich Hickey >  Functional Programming Language >  Derived from LISP >  Optimized for High Concurrency (def hello (fn [] "Hello world")) (hello) >  … and looks nothing like Java! 60
  • 61.
    Clojure Syntax inOne Slide Symbols Collections (commas optional) >  numbers – 2.178 >  Lists >  ratios – 355/113 (1, 2, 3, 4, 5) >  strings – “clojure”, “rocks” >  Vectors >  characters – a b c d [1, 2, 3, 4, 5] >  symbols – a b c d >  Maps >  keywords – :alpha :beta {:a 1, :b 2, :c 3, :d 4} >  boolean – true, false >  Sets >  null - nil #{:a :b :c :d :e} (plus macros that are syntactic sugar wrapping the above) 61
  • 62.
    Clojure GUI Example (defn  javafxapp  []      (let  [stage  (Stage.  "JavaFX  Stage")                  scene  (Scene.)]          (.setFill  scene  Color/LIGHTGREEN)          (.setWidth  stage  600)          (.setHeight  stage  450)          (.setScene  stage  scene)          (.setVisible  stage  true)))   (javafxapp)   62
  • 63.
    Refined Clojure GUIExample (defn  javafxapp  []      (doto  (Stage.  "JavaFX  Stage")          (.setWidth  600)          (.setHeight  450)          (.setScene  (doto  (Scene.)              (.setFill  Color/LIGHTGREEN)              (.setContent  (list  (doto  (Rectangle.)                  (.setX  25)                  (.setY  40)                  (.setWidth  100)                  (.setHeight  50)                  (.setFill  Color/RED))))))          (.setVisible  true)))   (javafxapp)   63
  • 64.
    Refined Clojure GUIExample (defn  javafxapp  []      (doto  (Stage.  "JavaFX  Stage")          (.setWidth  600)   Doto allows nested data        (.setHeight  450)          (.setScene  (doto  (Scene.)   structures            (.setFill  Color/LIGHTGREEN)              (.setContent  (list  (doto  (Rectangle.)                  (.setX  25)                  (.setY  40)                  (.setWidth  100)                  (.setHeight  50)                  (.setFill  Color/RED))))))          (.setVisible  true)))   (javafxapp)   64
  • 65.
    Closures in Clojure >  Inner classes can be created using proxy   (.addListener  hoverProperty      (proxy  [ChangeListener]  []          (handle  [p,  o,  v]              (.setFill  rect                  (if  (.isHover  rect)  Color/GREEN  Color/RED)))))   65
  • 66.
    Closures in Clojure >  Inner classes can be created using proxy   Proxy form: (proxy  [class]  [args]  fs+)   f => (name  [params*]  body)   (.addListener  hoverProperty      (proxy  [ChangeListener]  []          (handle  [p,  o,  v]              (.setFill  rect                  (if  (.isHover  rect)  Color/GREEN  Color/RED)))))   66
  • 67.
  • 68.
    What is Scala 2001 2006 •  Scala Started •  Scala v2.0 2003/2004 2011 •  Scala v1.0 •  Scala 2.9.2 (latest) >  Started in 2001 by Martin Odersky >  Compiles to Java bytecodes >  Pure object-oriented language >  Also a functional programming language 68
  • 69.
    Why Scala? >  Shares many language features with JavaFX Script that make GUI programming easier: l  Static Type Checking – Catch your errors at compile time l  Closures – Wrap behavior and pass it by reference l  Declarative – Express the UI by describing what it should look like >  Scala also supports Type Safe DSLs! l  Implicit Conversions – type safe class extension l  Operator Overloading – with standard precedence rules l  DelayedInit / @specialized – advanced language features 69
  • 70.
    Java vs. ScalaDSL 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)   40 Lines 33 Lines            final  Circle  circle  =  new  Circle(150);                  effect  =  new  BoxBlur(10,  10,  3)              circle.setCenterX(Math.random()  *  800);                  strokeWidth  <==  when  (hover)  then  4  otherwise  0              circle.setCenterY(Math.random()  *  600);                  stroke  =  WHITE              circle.setFill(new  Color(Math.random(),  Math.random(),  Math.random(),  .2));                  onMouseClicked  =  {              circle.setEffect(new  BoxBlur(10,  10,  3));                      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);                      new  Timeline(new  KeyFrame(Duration.seconds(3),  collapse)).play();              }   591 Characters                }              content  =  circles          }                  }      }              });                circle.setStroke(Color.WHITE);      new  Timeline  {              circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty())          cycleCount  =  INDEFINITE                  .then(4)          autoReverse  =  true                  .otherwise(0));          keyFrames  =  for  (circle  <-­‐  circles)  yield  at  (40  s)  {              circles.add(circle);              Set(          }                  circle.centerX  -­‐>  random  *  stage.width,          root.getChildren().addAll(circles);                  circle.centerY  -­‐>  random  *  stage.height          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();      }   }   70
  • 71.
    object  VanishingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800          height  =  600          scene  =  new  Scene  {              fill  =  BLACK              children  =  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)              }          }      }   }   71
  • 72.
    object  VanishingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800          height  =  600   for JavaFX applications Base class        scene  =  new  Scene  {              fill  =  BLACK              children  =  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)              }          }      }   }   72
  • 73.
    object  VanishingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800          height  =  600          scene  =  new  Scene  {   Declarative Stage definition            fill  =  BLACK              children  =  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)              }          }      }   }   73
  • 74.
    object  VanishingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800   Inline property definitions        height  =  600          scene  =  new  Scene  {              fill  =  BLACK              children  =  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)              }          }      }   }   74
  • 75.
    object  VanishingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800          height  =  600   Sequence Creation Via Loop        scene  =  new  Scene  {              fill  =  BLACK              children  =  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)              }          }      }   }   75
  • 76.
    Binding in Scala InfixAddition/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"   76
  • 77.
    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();   77
  • 78.
    JavaFX Script-like animation Animationin 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();   78
  • 79.
    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          )      }   }   Operator overloading for animation timeline.play();   syntax 79
  • 80.
    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 80
  • 81.
    Event Listeners inScala >  Supported using the built-in Closure syntax >  Arguments for event objects >  100% type-safe onMouseClicked  =  {  (e:  MouseEvent)  =>      Timeline(at(3  s){radius-­‐>0}).play()   }   81
  • 82.
    Event Listeners inScala >  Supported using the built-in Closure syntax >  Arguments for event objects >  100% type-safe onMouseClicked  =  {  (e:  MouseEvent)  =>      Timeline(at(3  s){radius-­‐>0}).play()   }   Compact syntax {body} 82
  • 83.
    Event Listeners inScala >  Supported using the built-in Closure syntax >  Arguments for event objects Event parameter >  100% type-safe {(event) => body} onMouseClicked  =  {  (e:  MouseEvent)  =>      Timeline(at(3  s){radius-­‐>0}).play()   }   83
  • 84.
    TableView in ScalaFX defdateFormat = new SimpleDateFormat("yyyy-MM-dd")! new TableView[Speaker](persons) {! columns = Seq(! new TableColumn[Speaker, String] {! text: "Name"! converter = {_.firstName}! } new TableColumn[Speaker, String] {! text: "Age"! converter = {_.age}! }! new TableColumn[Speaker, String] {! text: "Gender"! converter = {_.gender}! }! new TableColumn[Speaker, String] {! text: "Birth"! converter = {dateFormat.format(_.dob)}, ! }! )}! 84
  • 85.
  • 86.
    About Project Visage >  “Visage is a domain specific language (DSL) designed for the express purpose of writing user interfaces.” >  Visage project goals: l  Compile to JavaFX Java APIs l  Evolve the Language (Annotations, Maps, etc.) l  Support Other Toolkits >  Come join the team! >  For more info: http://visage-lang.org/ 86
  • 87.
    Java vs. VisageDSL public  class  VanishingCircles  extends  Application  {   var  circles:Circle[];     Stage  {      public  static  void  main(String[]  args)  {      title:  "Vanishing  Circles"          Application.launch(args);      Scene  {      }          width:  800                height:  600      @Override          fill:  BLACK      public  void  start(Stage  primaryStage)  {          Group  {          primaryStage.setTitle("Vanishing  Circles");              circles  =  for  (i  in  [1..50])  {          Group  root  =  new  Group();                  def  c:Circle  =  Circle  {          Scene  scene  =  new  Scene(root,  800,  600,  Color.BLACK);                      centerX:  random()  *  800          List<Circle>  circles  =  new  ArrayList<Circle>();                      centerY:  random()  *  600          for  (int  i  =  0;  i  <  50;  i++)  {                      radius:  150   40 Lines 35 Lines            final  Circle  circle  =  new  Circle(150);                      fill:  color(random(),  random(),  random(),  .2)              circle.setCenterX(Math.random()  *  800);                      effect:  BoxBlur  {              circle.setCenterY(Math.random()  *  600);                          height:  10              circle.setFill(new  Color(Math.random(),  Math.random(),  Math.random(),  .2));                          width:  10              circle.setEffect(new  BoxBlur(10,  10,  3));                          iterations:  3   1299 Characters            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();   487 Characters                    }                      stroke:  WHITE                      strokeWidth:  bind  if  (c.hover)  5  else  0                      onMouseClicked:  function(e)  {                  }                          Timeline  {at  (3s)  {c.radius  =>  0}}.play()              });                      }              circle.setStroke(Color.WHITE);                  }              circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty())              }                  .then(4)          }                  .otherwise(0));      }              circles.add(circle);   }          }            root.getChildren().addAll(circles);   Timeline  {          primaryStage.setScene(scene);      for  (circle  in  circles)  at  (40s)  {          primaryStage.show();          circle.centerX  =>  random()  *  800;                    circle.centerY  =>  random()  *  600          Timeline  moveCircles  =  new  Timeline();      }          for  (Circle  circle  :  circles)  {   }.play()              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();      }   }   87
  • 88.
    How about JavaFXon… Visage Stage  {      title:  "Vanishing  Circles"      scene:  Scene  {          width:  800          height:  600          fill:  BLACK          content:  Group  {              circles  =  for  (i  in  [1..50])  {                  Circle  {                      centerX:  random()  *  800                      centerY:  random()  *  600                  }              }          }      }   }   88
  • 89.
    How about JavaFXon… Visage Stage  {      title:  "Vanishing  Circles"      scene:  Scene  {          width:  800          height:  600          fill:  BLACK          content:  Group  {              circles  =  for  (i  in  [1..50])  {                  Circle  {                      centerX:  random()  *  800                      centerY:  random()  *  600                  }              }          }      }   }   89
  • 90.
    How about JavaFXon… Visage Stage  {      title:  "Vanishing  Circles"      Scene  {          width:  800          height:  600          fill:  BLACK          Group  {              circles  =  for  (i  in  [1..50])  {                  Circle  {                      centerX:  random()  *  800                      centerY:  random()  *  600                  }              }          }      }   }   90
  • 91.
    Visage is JavaFXScript++ >  Default Parameters >  New Literal Syntax For: l  Angles – 35deg,  4rad,  1turn   l  Colors – #DDCCBB,  #AA33AA|CC   l  Lengths – 5px,  2pt,  3in,  4sp   >  Null-check Dereference l  var width = rect.!width >  Built-in Bindable Maps (coming soon!) l  var fruitMap = ["red" : apple, "yellow" : banana] l  var fruit = bind fruitMap["red"] 91
  • 92.
    Visage and JavaFX2.0 are made for each other… >  Enhanced Binding l  Retains lazy evaluation properties with additional expressive power >  Integrated Collections l  Sequences and Maps automatically convert between JavaFX Observable Lists/Maps >  Built-in Animation Syntax l  Ties into JavaFX animation subsystem l  Provides consistent, clean APIs 92
  • 93.
    Other JVM Languagesto Try >  JRuby l  Faithful to Ruby language with the power of the JVM >  Gosu l  Up and coming language created at GuideWire l  Easy to enhance libraries and create DSLs >  Mirah l  Invented by Charles Nutter l  Local Type Inference, Static and Dynamic Typing >  Fantom l  Created by Brian and Andy Frank l  Portable to Java and .NET l  Local Type Inference, Static and Dynamic Typing 93
  • 94.
    Conclusion >  You canwrite JavaFX applications in pure Java >  JavaFX is also usable in alternate languages >  You can get improved support using DSL libraries l  GroovyFX l  ScalaFX >  Or a dedicated UI JVM Language l  Visage
  • 95.
    Stephen Chin stephen.chin@oracle.com tweet: @steveonjava Thanks to Dean Iverson and Jonathan Giles for help preparing this talk 95