Java FX 2.0 - A Developer's Guide

18,268 views
18,025 views

Published on

A University talk about JavaFX 2.0 do

Published in: Technology
0 Comments
7 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
18,268
On SlideShare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
609
Comments
0
Likes
7
Embeds 0
No embeds

No notes for slide

Java FX 2.0 - A Developer's Guide

  1. 1. JavaFX 2.0 – A Java Developers GuideStephen ChinChief Agile Methodologist, GXShttp://steveonjava.com @steveonjavaPeter PilgrimOracle Java Championhttp://www.xenonique.co.uk/blog/ @peter_pilgrim
  2. 2. Meet the Presenters Stephen Chin Peter Pilgrim @steveonjava @peter_pilgrim Football, Music & Partner Family Man Motorcyclist
  3. 3. This is a Participatory Session!>  Every now and then we will say something interesting… and it will go out in a tweet.>  Follow @steveonjava to watch the tweets and retweet the ones you like to your followers>  Feel free to create your own tweets using the hash tags "#JavaFX #Devoxx">  We have some goodies for folks who play. J 3
  4. 4. At the end of the day, on the one hand we have computer systems, and on the other, people. Connecting them together, and allowing people to interact with computer systems in a compelling way, requires graphical user interfaces Chris Oliver, Creator of F3http://blogs.oracle.com/chrisoliver/entry/epitaph 4
  5. 5. JavaFX 2.0 PlatformImmersive Desktop ExperienceCombining the Best of JavaFX and WebTechnologies>  Leverage your Java skills with modern JavaFX APIs>  Integrate Java, JavaScript, and HTML5 in the same application>  New graphics stack takes advantage of hardware acceleration for 2D and 3D applications>  Integrate in Swing applications using JFXPanel>  User your favorite IDE: NetBeans, Eclipse, IntelliJ, etc.
  6. 6. DemoVideo Fracture
  7. 7. Headline News >  RT @Stephan007: The #JavaOne11 channel on #Parleys passed 51.800 views! Most viewed: "Introduction to #JavaFX 2.0", close to 11,000 - @JavaFX4You, 9th November 2011 >  The most interesting announcement came when Oracle demoed both Java and JavaFX applications running on a Windows tablet, an iPad, and a "Linux-based tablet". - Eric Bruno, Dr Dobbs Journal, 5th October 2011 7
  8. 8. Platform Support>  Windows fully supported l  For 3D you need one of the supported graphics cards (ATI, NVidia, Intel)>  Mac OS X preview available>  Linux Planned for end of 2012 8
  9. 9. Windows OS RequirementsOperating System (32-Bit and 64-Bit) Browsers (32-Bit and 64-Bit)Windows XP Home and Professional Internet Explorer 7 and 8with Service Pack 3 ChromeWindows Vista Home Basic, Home Internet Explorer 7, 8, and 9Premium, Business, and Ultimate with Firefox 3.5, 3.6, and 4Service Pack 2 ChromeWindows 7 Home Premium, Internet Explorer 8 and 9Professional, and Ultimate Firefox 3.5, 3.6, and 4 Chrome 9
  10. 10. Supported Graphics Cards Supported Graphics ProcessingGraphics Card Units (GPUs)NVIDIA Mobile GPUs: GeForce 8M and 100M series or higher, NVS 2100M series or higher, and Mobility Quadro FX 300M series or higher Desktop GPUs: GeForce 8 and 100 series or higher Workstation GPUs: Quadro FX 300 series or higherATI Mobile GPUs: Mobility Radeon HD 3000, 4000, and 5000 series Desktop GPUs: Radeon HD 2400, 3000, 4000, 5000, and 6000 seriesIntel Mobile GPUs: GMA 4500MHD and GMA HD Desktop GPUs: GMA 4500 and GMA HD 10
  11. 11. Building JavaFX Applications>  Deploys in the browser or on desktop>  Pure Java APIs>  Includes builders for declarative construction>  Alternative languages can also be used for simpler UI creation l  GroovyFX l  ScalaFX l  Visage 11
  12. 12. Hello Devoxx (Java Version)public  class  HelloDevoxx  extends  Application  {      public  static  void  main(String[]  args)  {          launch(args);      }      @Override      public  void  start(Stage  primaryStage)  {          primaryStage.setTitle("Hello  Devoxx");          Group  root  =  new  Group();   19 Lines        Scene  scene  =  new  Scene(root,  400,  250,  Color.ALICEBLUE);          Text  text  =  new  Text();   Characters 425        text.setX(105);          text.setY(120);          text.setFont(new  Font(30));          text.setText("Hello  Devoxx");          root.getChildren().add(text);                          primaryStage.setScene(scene);          primaryStage.show();      }  }   12
  13. 13. Hello Devoxx (Builder Version)public  void  start(Stage  primaryStage)  {      primaryStage.setTitle("Hello  Devoxx");      primaryStage.setScene(SceneBuilder.create()          .width(400)          .height(250)          .fill(Color.ALICEBLUE)          .root(   25 Lines            GroupBuilder.create().children(              TextBuilder.create()                  .x(105)   418 Characters                .y(120)                  .text("Hello  Devoxx")                  .font(new  Font(30))                  .build()              ).build()          )      .build());      primaryStage.show();  }   13
  14. 14. Hello Devoxx (GroovyFX Version)GroovyFX.start  {  primaryStage  -­‐>      def  sg  =  new  SceneGraphBuilder()      sg.stage(          title:  Hello  Devoxx,          show:  true)  {              scene(                  fill:  aliceblue,   17 Lines                width:  400,                  height:  250)  {   Characters 190                    text(                          x:  105,                          y:  120,                          text:  "Hello  Devoxx"                          font:  "30pt")                  }          }  }   14
  15. 15. Hello Devoxx (ScalaFX Version)object  HelloDevoxx  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Hello  Devoxx"          width  =  400          height  =  250          scene  =  new  Scene  {              fill  =  BLUE   Lines 16            Text  {   163 Characters                x  =  105                  y  =  120                  text  =  "Hello  Devoxx"                  font  =  Font(size:  30)              }          }      }  }   15
  16. 16. Hello Devoxx (Visage Version)Stage  {      title:  "Hello  Devoxx"      width:  400      height:  250      scene:  Scene  {          fill:  BLUE   14 Lines        content:  Text  {  Characters 129            x:  105              y:  120              text:  "Hello  Devoxx"              font:  Font  {size:  30pt}          }      }  }   16
  17. 17. Architecture of JavaFX 2.0 JavaFX Public API Quantum Toolkit Prism Glass Media Web WinTk Engine Engine Java2D Open GL D3D Java Virtual Machine
  18. 18. JavaFX 2.0FundamentalsProperties, Lists, Binding 18
  19. 19. JavaFX 2.0 Properties>  Primitive Properties>  Object Properties>  FX List Collection Properties>  Properties are: l  Observable l  Lazy l  Type Safe 19
  20. 20. Properties in Java Property Declarationpublic  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;        }  }     20
  21. 21. Properties in Javapublic  class  Person  {      private  StringProperty  firstName;      public  void  setFirstName(String  val)  {          firstNameProperty().set(val);      }      public  String  getFirstName()  {          return  firstNameProperty().get();   Setter Method    }   (Convenience)    public  StringProperty  firstNameProperty()  {            if  (firstName  ==  null)                firstName  =  new  SimpleStringProperty(this,  "firstName");          return  firstName;        }  }     21
  22. 22. Properties in Javapublic  class  Person  {      private  StringProperty  firstName;   Getter Method    public  void  setFirstName(String  val)  {   (Convenience)        firstNameProperty().set(val);      }      public  String  getFirstName()  {          return  firstNameProperty().get();      }      public  StringProperty  firstNameProperty()  {            if  (firstName  ==  null)                firstName  =  new  SimpleStringProperty(this,  "firstName");          return  firstName;        }  }     22
  23. 23. Properties in Javapublic  class  Person  {      private  StringProperty  firstName;      public  void  setFirstName(String  val)  {   Property Fetcher        firstNameProperty().set(val);   (Lazy Construction)    }      public  String  getFirstName()  {          return  firstNameProperty().get();      }      public  StringProperty  firstNameProperty()  {            if  (firstName  ==  null)                firstName  =  new  SimpleStringProperty(this,  "firstName");          return  firstName;        }  }     23
  24. 24. Properties in Javapublic  class  Person  {      private  StringProperty  firstName;   Even Lazier…    public  void  setFirstName(String  val)  {          firstNameProperty().set(val);      }      public  String  getFirstName()  {          return  firstName  ==  null  ?  null  :  firstNameProperty().get();      }      public  StringProperty  firstNameProperty()  {            if  (firstName  ==  null)                firstName  =  new  SimpleStringProperty(this,  "firstName");          return  firstName;        }  }     24
  25. 25. Properties in Javapublic  class  Person  {      private  StringProperty  firstName;      public  void  setFirstName(String  val)  {   With initial value        firstNameProperty().set(val);      }      public  String  getFirstName()  {          return  firstName  ==  null  ?  "Peter"  :  firstNameProperty().get();      }      public  StringProperty  firstNameProperty()  {            if  (firstName  ==  null)                firstName  =  new  SimpleStringProperty(this,  "firstName",  "Peter");          return  firstName;        }  }     25
  26. 26. Demo PropertiesUndirectional, Bidirectional and Lazy binding in Java
  27. 27. Listeners>  Change Listener l  Observable, old value, new value>  Invalidation Listeners l  Observable 2 7
  28. 28. Property Listener Example IntegerProperty  temperature  =  new   SimpleIntegerProperty(0);     temperature.setOnChangeListener(        new  ChangeListener<Number>()  {            public  void  change(                  ObservableValue<?  Extends  Number>  ov,                Number  newValue,  Number  oldValue)            System.out.printf(“%f  %f  %f”,                ov.doubleValue(),  oldValue.doubleValue(),                newValue.doubleValue()      }   );   temperature.set(2);   temperature.set(4);     28
  29. 29. Hey Ho! This is all just to say:var  temperature:  Integer  =  23  on  replace  oldValue  {      println("temperature={temperature},          oldValue={oldValue}");  }  //  Visage  (previously  JavaFX  Script)     29
  30. 30. JavaFX 2.0 Observable Lists>  Extends java.util.List>  Includes addListener/removeListener>  For bulk operations use: l  addAll l  removeAll l  setAll>  Sort with l  FXCollections.sort() 30
  31. 31. List Change Listenerlist.addListener(new  ListChangeListener<Item>()  {      public  void  onChanged(Change<Item>  c)  {          while  (c.next())  {              if  (c.wasPermutated())  {                  for  (int  i  =  c.getFrom();  i  <  c.getTo();  i++)  {                      int  newLoc  =  getPermutation(i);                      //  handle  move  (a.k.a.  sort)                  }              }  else  {                  for  (Item  removed  :  c.getRemoved())  {                      //  handle  removal                  }  for  (Item  added  :  c.getAddedSubList())  {                      //  handle  addition                  }  }}}});   31
  32. 32. JavaFX 2.0 Observable Maps>  Extends java.util.Map  >  Includes addListener() / removeListener()  >  MapChangeListener   l  Supports add()  / remove()   l  Can get associated: l  Key l  Old Value l  New Value 32
  33. 33. Binding in JavaFX 2.0>  One of the most important features of JavaFX 1.3 and prior>  Now supported via an API (that works with Observable Properties)>  Can handle most cases quite elegantly and with full type safety 33
  34. 34. Binding in JavaFX 2.0Infix  Addition/Subtraction/Multiplication/Division:    heightProperty().bind(rect1.heightProperty().add(rect2.heig htProperty()));    Aggregate  Operators:    widthProperty().bind(Bindings.max(rect1.widthProperty(),   rect2.widthProperty(),  rect3.widthProperty()));    Conditional  Expressions:    strokeWidthProperty().bind(Bindings.when(hover).then(4).oth erwise(0));    Bidirectional  Binding:    heightProperty().bindBidirectional(rect1.heightProperty());   34
  35. 35. Demo Image FractureLife is like a bowl of cherries
  36. 36. “The Scene-graph is King.” 36
  37. 37. Image View Node Image View 1 Image View Image 2 Image View 3 Image View 4 37
  38. 38. Media View 1Media View 2 Media Player MediaMedia View 3 Media View 4 38
  39. 39. Breaking Glass1.  Divide a bounded area into four triangles2.  Are we there yet?3.  For each triangle A.  Choose a random side and split it to two rectangles B.  Recursive descend the two triangles (step 2) 3 9
  40. 40. Controls10 / 10 Break
  41. 41. Button Control>  Simple Button Control>  Add and remove click event handler>  Support Cascading Style Sheets 41
  42. 42. Button Codepublic  class  ButtonDemo  extends  AbstractDevoxxApplication  {      public  void  initialiseScene(Group  root)  {          VBox  vbox  =  VBoxBuilder.create().              spacing(25).layoutX(25).layoutY(25).build();          root.getChildren().add(vbox);          Button  red  =  ButtonBuilder.create()              .text("Red").style("-­‐fx-­‐base:  red;").build();          Button  yellow  =  ButtonBuilder.create()              .text("Yellow").style("-­‐fx-­‐base:  blue;")              .build();          Button  green  =  ButtonBuilder.create()              .text("Green").style("-­‐fx-­‐base:  green;")              .build();          vbox.getChildren().addAll(red,yellow,green);      }  /*  …  */  }   42
  43. 43. TextField Control>  One line entry text field control>  Set OnAction event handler>  Can listen to individual key events>  Support Cascading Style Sheets 43
  44. 44. TextField Demopublic  class  TextDemo  extends  AbstractDevoxxApplication  {      public  void  initialiseScene(Group  root)  {          VBox  vbox  =  VBoxBuilder.create().spacing(25)              .layoutX(25).layoutY(25).build();          root.getChildren().add(vbox);          TextField  red  =  TextFieldBuilder.create()              .text("Red  Devoxx")              .style("-­‐fx-­‐base:  red;  -­‐fx-­‐font-­‐size:  48pt;  ")              .build();          TextField  green  =  TextFieldBuilder.create()              .text("Green  Devoxx")              .style("-­‐fx-­‐base:  green;  -­‐fx-­‐font-­‐size:  24pt;  ")              .build();          vbox.getChildren().addAll(red,green);      }  /*….  */  }   44
  45. 45. ListView Control>  Displays a list of items>  Reuses cells for performance>  Default cell renderer can display Strings>  For other object types l  Create custom CellFactory 45
  46. 46. ListView Codepublic  void  initialiseScene(Group  root)  {                VBox  vbox  =  VBoxBuilder.create()    .spacing(25).layoutX(25).layoutY(25).build();                root.getChildren().add(vbox);                  final  ListView<String>  listView  =        new  ListView<String>();              listView.setItems(FXCollections.observableArrayList(                      "Manchester  City",  "Manchester  United",                        "Newcastle  United",  "Chelsea",                        "Tottenham  Hotspur",  "Liverpool",  /*  …  */  ));                    listView.getSelectionModel()                        .setSelectionMode(SelectionMode.MULTIPLE);                vbox.getChildren().addAll(listView);          }          /*…*/  }   46
  47. 47. TreeView Control>  Displays a tree view of items>  Can dynamically populate as tree is expanded>  Default cell renderer can display Strings>  For other object types l  Create custom CellFactory 47
  48. 48. TreeView Codepublic  void  initialiseScene(Group  root)  {      /*…*/      TreeView  treeView  =  TreeViewBuilder.<String>create().root(          TreeItemBuilder.<String>create().value("JVM  Languages").children(              TreeItemBuilder.<String>create().value("Static").children(                  new  TreeItem<String>("Scala"),                  new  TreeItem<String>("Java"),                  new  TreeItem<String>("Visage"),  /*…*/              ).build(),              TreeItemBuilder.<String>create().value("Dynamic").children(                  new  TreeItem<String>("Clojure"),                  new  TreeItem<String>("Groovy"),  /*…*/              ).build(),  /*…*/          ).build()      ).build();      /*…*/  }   48
  49. 49. ToolBar Demo>  Toolbar displays items horizontally or vertically>  Handles arbitrary number of items>  Can add any node to the toolbar, including custom nodes>  Automatic adds an overflow hint if cannot display all toolbar items 49
  50. 50. Toolbar Codepublic  class  ToolBarDemo  extends  AbstractDevoxxApplication  {      public  void  initialiseScene(Group  root)  {          /*…*/          ToggleGroup  amFm  =  new  ToggleGroup();          ToolBar  toolBar  =  ToolBarBuilder.create().items(              new  Label("Volume"),              new  Slider(),              new  ToggleButton("Loudness"),              RadioButtonBuilder.create()                  .text("AM").toggleGroup(amFm).build(),              RadioButtonBuilder.create()                  .text("FM").toggleGroup(amFm).build(),              new  Button("Tune")          ).build();          vbox.getChildren().addAll(toolBar);  }}   50
  51. 51. Progress Bars and Indicators>  Progress Bars can be laid out vertically and horizontally>  Progress Indicator is a circular widget>  Progress Bar and Indicator can be indefinite>  Double property 0.0 <= x <= 1.0 51
  52. 52. Progress Bar/Indicator CodeVBoxBuilder.create().spacing(10)      .alignment(Pos.CENTER).children(      new  ProgressBar(),      new  ProgressBar(.25),      new  ProgressIndicator(.75)  ).build()   52
  53. 53. Accordion Control>  Accordion is a space saving components>  Accordian accept only TitledPanes>  Each TitledPane managed one scenegraph node>  Can choose the titled pane expanded by default 53
  54. 54. Accordion CodeString  imageUrl  =  new  Image(ResizableImageViewDemo.class.      getResource("images/Squirrel.jpg").toExternalForm();    Accordion  accordion  =  AccordionBuilder.create().panes(      new  TitledPane("Button",  new  Button("Regular")),      new  TitledPane("ToggleButton",  new  ToggleButton("Toggle")),          imagePane  =  new  TitledPane("SquirrelImage",                                      ImageViewBuilder.create().image(imageUrl,                                      500,  500,  true,  true,  false)          ).build()      )  ).build()   54
  55. 55. TabPane Control>  Alternative space saving component>  Choose a side for the Tab>  Each manages one component node>  Tab pane has two modes: l  floating l  recessed 55
  56. 56. TabPane CodeAccordion  accordion;      TitledPane  imagePane;      TabPane  tabPane  =  TabPaneBuilder.create().tabs(          TabBuilder.create().text("Progress").content(              /*  Progress  Tab  Here  */          ).build(),          TabBuilder.create().text("Accordion").content(              /*  Accordion  Tab  Here  */          ).build()      ).build();  accordion.setExpandedPane(imagePane);  vbox.getChildren().addAll(tabPane);   56
  57. 57. JavaFXComponents 57
  58. 58. Pens, papers and inks, first! 58
  59. 59. A component is anencapsulation that bindsa set of nodes with theoperations that act onthose nodes 59
  60. 60. Component Types>  Non-Resizable l  javafx.scene.shape.Shape   l  javafx.scene.Group  >  Resizable l  javafx.scene.layout.Pane   l  javafx.scene.layout.Region   l  javafx.scene.layout.Control   6 0
  61. 61. Group based>  Particularly using the JavaFX primitive nodes under javafx.scene.shape.*;>  Design is not meant to be resized>  Layout is fixed at construction / initialisation time 6 1
  62. 62. Region based>  Region is the sub class of javafx.scene.Parent  >  Adds the ability to resize a node, layout a particular set of children 6 2
  63. 63. Control based>  Allows particular subclass of Region that permits skinning of node with a stylesheet>  You need 3 classes l  The Control java.scene.control.Control l  The Skinnable java.scene.control.Skinnable l  The Behaviour com.sun.javafx.scene.control.BehaviorBase 6 3
  64. 64. Control based contd>  See JFXtras.org for more examples of skinnable components written for JavaFX 2.0>  Define CSS file referencing a Skin class>  A Skin is a Region (JavaFX 2.0)com.sun.javafx.scene.control.BehaviorBase  com.sun.javafx.scene.control.SkinBase   6 4
  65. 65. Skins are StackPanes>  SkinBase is a type of StackPane  >  A StackPane is a Region>  Idea of shuffling a deck of cards and only showing one 6 5
  66. 66. Layout Children Methodvoid  layoutChildren()  >  Layout all of the children of a Region based component>  Compute the preferred width and height of the children (first)>  May cache those values internally as part of the component or use Constraints 6 6
  67. 67. Layout Boundaries>  SDK will make a request to find the best width and height for your component.>  Your component must do the same for its children>  Implement at least computePrefWidth and computePrefHeight methods! 6 7
  68. 68. Layout Methodsprotected  double  computePrefHeight(double  w)  {            return  child.prefHeight(w)  +  2  *ht;  }    protected  double  computePrefWidth(double  h)  {          return  child.prefWidth(h)  +  2  *  wt;  }   6 8
  69. 69. Pedantic CompleteImplement the other four methods too>  computeMinWidth(v)>  computeMinHeight(v)>  computeMaxWidth(v)>  computeMaxHeight(v) 6 9
  70. 70. DemoCustom Border Layout
  71. 71. Writing JavaFX Containers
  72. 72. Event Input on Node>  Mouse Events fall under the pointer iteraction (single) l  Clicked, Entered, Exited, Pressed, Dragged, Released, Wheel l  Target Entered, Target Exited>  Key Events l  Clicked, Pressed, Released
  73. 73. Event Input on Node>  It is possible to consume an event and stop it bubbling further down the scene-graph hierarchy>  You can block a component from event interaction>  Using clever tricks (opaque shape) you can implement dialog / focus / modal behaviour
  74. 74. LayoutSecrets
  75. 75. Reuse SDK Methods 75
  76. 76. Some SDK Methods>  Region#layoutInArea()  >  Region#positionInArea()  >  Node#resize()  >  Node#resizeRelocate()  
  77. 77. Built-in Layouts>  AnchorPane>  BorderPane>  VBox/HBox>  FlowPane>  StackPane>  TilePane>  GridPane
  78. 78. Built-in Layouts>  AnchorPane>  BorderPane>  VBox/HBox>  FlowPane>  StackPane>  TilePane>  GridPane
  79. 79. Built-in Layouts>  AnchorPane>  BorderPane>  VBox/HBox>  FlowPane>  StackPane>  TilePane>  GridPane
  80. 80. Built-in Layouts>  AnchorPane>  BorderPane>  VBox/HBox>  FlowPane>  StackPane>  TilePane>  GridPane
  81. 81. Built-in Layouts>  AnchorPane>  BorderPane>  VBox/HBox>  FlowPane A B C>  StackPane>  TilePane D E>  GridPane
  82. 82. Built-in Layouts>  AnchorPane>  BorderPane>  VBox/HBox>  FlowPane>  StackPane>  TilePane>  GridPane
  83. 83. Built-in Layouts>  AnchorPane>  BorderPane>  VBox/HBox>  FlowPane>  StackPane>  TilePane>  GridPane
  84. 84. Built-in Layouts>  AnchorPane>  BorderPane>  VBox/HBox>  FlowPane>  StackPane>  TilePane>  GridPane
  85. 85. DemoBox Container Pane
  86. 86. Tips>  When Dragging a Node prefer to translate (X,Y)>  When Laying Out a Node prefer to set the position with layoutX, layoutY>  Given steady state conditions you can swap layout (X,Y) for translation (X,Y)
  87. 87. Tips 2>  Use blocking boolean flags to prevent conflict when you animating children in a container>  If you are unsure about MT concurrency you can always use AtomicBoolean>  Prefer JavaFX Properties
  88. 88. Displaying HTML in JavaFX 88
  89. 89. Displaying HTML in JavaFXpublic  class  WebViewDemo  extends  Application  {          public  static  void  main(String[]  args)  {                  launch(args);          }          @Override  public  void  start(Stage  stage)  {                  WebView  webView  =  new  WebView();                  webView.getEngine().load("http://devoxx.com");                  Scene  scene  =  new  Scene(webView);                  stage.setScene(scene);                  stage.setTitle("Web  View  Demo");                  stage.show();  }}   89
  90. 90. For more info on Web/HTML5… Moving to the Client – JavaFX and HTML5 Stephen Chin Kevin Nilson Thursday 10:50 AM, Room 7 10 / 10 Break 90
  91. 91. JavaFX With Groovy
  92. 92. 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
  93. 93. Java vs. GroovyFX DSLpublic  class  HelloStage  extends  Application  {   GroovyFX.start  {  stage  -­‐>        def  sg  =  new  SceneGraphBuilder(stage)      public  void  start(Stage  stage)  {            stage.setTitle("Hello  Stage");      sg.stage(title:  “Hello  Stage”,  width:  600,          stage.setWidth(600);   height:  450)  {          stage.setHeight(450);          scene(fill:  groovyblue)  {          Scene  scene  =  new  Scene();              rectangle(x:  25,  y:  40,  width:  100,          scene.setFill(Color.LIGHTGREEN);   height:  50,  fill:  red)   21 Lines        Rectangle  rect  =  new  Rectangle();          rect.setX(25);   8 Lines        }      }   430 Characters        rect.setY(40);          rect.setWidth(100);          rect.setHeight(50);   }     180 Characters        rect.setFill(Color.RED);          scene.setRoot(new  Group(rect));          stage.setScene(scene);          stage.show();      }        public  static  void  main(String[]  args)  {          launch(HelloStage.class,  args);      }  }   93
  94. 94. Disappearing Circles in Groovydef  sg  =  new  SceneGraphBuilder()  def  hc  =  {  hover  -­‐>  hover  ?  4  :  0  }    sg.stage(title:  Vanishing  Circles,  show:  true)  {      scene(fill:  black,  width:  800,  height:  600)  {          50.times  {              circle(centerX:  rand(800),  centerY:  rand(600),                              radius:  150,  stroke:  white,                              strokeWidth:  bind(hover,  converter:  hc))  {                  fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                  effect  boxBlur(width:  10,  height:  10,  iterations:  3)              }          }      }  }   94
  95. 95. Disappearing Circles in Groovydef  sg  =  new  SceneGraphBuilder()  def  hc  =  {  hover  -­‐>  hover  ?  4  :  0  }    sg.stage(title:  Vanishing  Circles,  show:  true)  {      scene(fill:  black,  width:  800,  height:  600)  {   Builder for GroovyFX scene        50.times  {   graphs            circle(centerX:  rand(800),  centerY:  rand(600),                              radius:  150,  stroke:  white,                              strokeWidth:  bind(hover,  converter:  hc))  {                  fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                  effect  boxBlur(width:  10,  height:  10,  iterations:  3)              }          }      }  }     95
  96. 96. Disappearing Circles in Groovydef  sg  =  new  SceneGraphBuilder()  def  hc  =  {  hover  -­‐>  hover  ?  4  :  0  }    sg.stage(title:  Vanishing  Circles,  show:  true)  {      scene(fill:  black,  width:  800,  height:  600)  {          50.times  {              circle(centerX:  rand(800),  centerY:  rand(600),     Declarative Stage                          radius:  150,  stroke:  white,                              strokeWidth:  bind(hover,  converter:  hc))  {   definition                fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                  effect  boxBlur(width:  10,  height:  10,  iterations:  3)              }          }      }  }   96
  97. 97. Disappearing Circles in Groovydef  sg  =  new  SceneGraphBuilder()  def  hc  =  {  hover  -­‐>  hover  ?  4  :  0  }     Inline propertysg.stage(title:  Vanishing  Circles,  show:  true)  {   definitions    scene(fill:  black,  width:  800,  height:  600)  {          50.times  {              circle(centerX:  rand(800),  centerY:  rand(600),                              radius:  150,  stroke:  white,                              strokeWidth:  bind(hover,  converter:  hc))  {                  fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                  effect  boxBlur(width:  10,  height:  10,  iterations:  3)              }          }      }  }   97
  98. 98. Disappearing Circles in Groovydef  sg  =  new  SceneGraphBuilder()  def  hc  =  {  hover  -­‐>  hover  ?  4  :  0  }   Bind to properties  sg.stage(title:  Vanishing  Circles,  show:  true)  {      scene(fill:  black,  width:  800,  height:  600)  {          50.times  {              circle(centerX:  rand(800),  centerY:  rand(600),                              radius:  150,  stroke:  white,                              strokeWidth:  bind(hover,  converter:  hc))  {                  fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                  effect  boxBlur(width:  10,  height:  10,  iterations:  3)              }          }      }  }   98
  99. 99. Disappearing Circles in Groovydef  sg  =  new  SceneGraphBuilder()  def  hc  =  {  hover  -­‐>  hover  ?  4  :  0  }     Sequence Creation Viasg.stage(title:  Vanishing  Circles,  show:  true)  {   Loop    scene(fill:  black,  width:  800,  height:  600)  {          50.times  {              circle(centerX:  rand(800),  centerY:  rand(600),                              radius:  150,  stroke:  white,                              strokeWidth:  bind(hover,  converter:  hc))  {                  fill  rgb(rand(255),  rand(255),  rand(255),  0.2)                  effect  boxBlur(width:  10,  height:  10,  iterations:  3)              }          }      }  }   99
  100. 100. Properties in Javapublic 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.! } !}!   100
  101. 101. Properties in GroovyFXpublic class Person {! @FXBindable String firstName; ! @FXBindable String lastName;!}!   101
  102. 102. Properties in GroovyFXpublic class Person {! @FXBindable String firstName; ! @FXBindable String lastName = “Smith”;!}!   Optional initializers 102
  103. 103. Properties in GroovyFXpublic class Person {! @FXBindable String firstName; ! @FXBindable String lastName = “Smith”;!}!!def p = new Person()!def last = p.lastName! Get and set valuesp.firstName = “Agent”!! 103
  104. 104. Properties in GroovyFXpublic class Person {! @FXBindable String firstName; ! @FXBindable String lastName = “Smith”;!}!!def p = new Person()!def last = p.lastName! Access underlyingp.firstName = “Agent”! property for binding!textField(text: bind(p.lastNameProperty()))!! 104
  105. 105. 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)      }  }   105
  106. 106. Animation in GroovyFXtimeline(cycleCount:  Timeline.INDEFINITE,   autoReverse:  true)  {      at  (1000.ms)  {          change(rect1,  x)  to  200  tween  ease_both          change  rect2.yProperty()  to  200  tween  linear      }  }.play()   106
  107. 107. Animation in GroovyFXtimeline(cycleCount:  Timeline.INDEFINITE,   autoReverse:  true)  {      at  (1000.ms)  {          change(rect1,  x)  to  200  tween  ease_both          change  rect2.yProperty()  to  200  tween  linear      }  }.play()   Easy animation syntax: at (duration) {keyframes} 107
  108. 108. Animation in GroovyFXtimeline(cycleCount:  Timeline.INDEFINITE,   autoReverse:  true)  {      at  (1000.ms)  {          change(rect1,  x)  to  200          change  rect2.yProperty()  to  200      }  }.play()   Key frame DSL 108
  109. 109. Animation in GroovyFXtimeline(cycleCount:  Timeline.INDEFINITE,   autoReverse:  true)  {      at  (1000.ms)  {          change(rect1,  x)  to  200  tween  ease_both          change  rect2.yProperty()  to  200  tween  linear      }  }.play()   Optional easing 109
  110. 110. Event Listeners in GroovyFX>  Supported using the built-in Closure syntax>  Optional arguments for event objectsonMouseClicked  {  e  -­‐>      timeline  {          at(3.s)  {  change   e.source.radiusProperty()  to  0  }      }.play()  }   110
  111. 111. Event Listeners in GroovyFXSupported  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} 111
  112. 112. Event Listeners in GroovyFXSupported  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()  }   112
  113. 113. TableView in JavaObservableList<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);   113
  114. 114. TableView in GroovyFXdef  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)  })  }   114
  115. 115. Layout in JavaTextField  urlField  =  new  TextField(“h_p://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);   115
  116. 116. Layout in GroovyFXsg.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") } }} 116
  117. 117. Layout in GroovyFX 117
  118. 118. Layout in GroovyFXgridPane(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")!}! 118
  119. 119. Layout in GroovyFX 119
  120. 120. GroovyFX Supports… 120
  121. 121. GroovyFX Supports… 121
  122. 122. JavaFX With Scala 122
  123. 123. What is Scala 2001 2006 •  Scala Started •  Scala v2.0 2003/2004 2011 •  Scala v1.0 •  Scala 2.9.0 (latest)>  Started in 2001 by Martin Odersky>  Compiles to Java bytecodes>  Pure object-oriented language>  Also a functional programming language 123
  124. 124. 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 124
  125. 125. Java vs. Scala DSLpublic  class  HelloStage  extends  Application  {   object  HelloJavaFX  extends  JFXApp  {        stage  =  new  Stage  {      public  void  start(Stage  stage)  {          title  =  "Hello  Stage"          stage.setTitle("Hello  Stage");          width  =  600          stage.setWidth(600);          height  =  450          stage.setHeight(450);          scene  =  new  Scene  {          Scene  scene  =  new  Scene();              fill  =  LIGHTGREEN          scene.setFill(Color.LIGHTGREEN);              content  =  Seq(new  Rectangle  {   21 Lines        Rectangle  rect  =  new  Rectangle();          rect.setX(25);          rect.setY(40);   17 Lines                x  =  25                  y  =  40                  width  =  100   430 Characters        rect.setWidth(100);          rect.setHeight(50);   177 Characters                height  =  50                  fill  =  RED          rect.setFill(Color.RED);              })          scene.setRoot(new  Group(rect));          }          stage.setScene(scene);      }          stage.show();   }      }        public  static  void  main(String[]  args)  {          launch(HelloStage.class,  args);      }  }   125
  126. 126. Disappearing Circles in ScalaFXobject  DisappearingCircles  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)              }          }      }  }  
  127. 127. Disappearing Circles in ScalaFXobject  DisappearingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800          height  =  600   Base class for JavaFX        scene  =  new  Scene  {   applications            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)              }          }      }  }     127
  128. 128. Disappearing Circles in ScalaFXobject  DisappearingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800   Declarative Stage        height  =  600          scene  =  new  Scene  {   definition            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)              }          }      }  }     128
  129. 129. Disappearing Circles in ScalaFXobject  DisappearingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"   Inline property        width  =  800          height  =  600   definitions        scene  =  new  Scene  {              fill  =  BLACK              content  =  for  (i  <-­‐  0  until  50)  yield  new  Circle  {                  centerX  =  random  *  800                  centerY  =  random  *  600                  radius  =  150                  fill  =  color(random,  random,  random,  0.2)                  effect  =  new  BoxBlur(10,  10,  3)              }          }      }  }   129
  130. 130. Disappearing Circles in ScalaFXobject  DisappearingCircles  extends  JFXApp  {      stage  =  new  Stage  {          title  =  "Disappearing  Circles"          width  =  800   Sequence Creation Via        height  =  600   Loop        scene  =  new  Scene  {              fill  =  BLACK              content  =  for  (i  <-­‐  0  until  50)  yield  new  Circle  {                  centerX  =  random  *  800                  centerY  =  random  *  600                  radius  =  150                  fill  =  color(random,  random,  random,  0.2)                  effect  =  new  BoxBlur(10,  10,  3)              }          }      }  }   130
  131. 131. Binding in ScalaInfix 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"   131
  132. 132. Animation in Scalaval  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();   132
  133. 133. Animation in Scalaval  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();   JavaFX Script-like animation syntax: at (duration) {keyframes} 133
  134. 134. Animation in Scalaval  timeline  =  new  Timeline  {      cycleCount  =  INDEFINITE      autoReverse  =  true      keyFrames  =  for  (circle  <-­‐  circles)  yield  at  (40  s)  {          Set(              circle.centerX  -­‐>  random  *  stage.width,              circle.centerY  -­‐>  random  *  stage.height          )      }  }  timeline.play();   Operator overloading for animation syntax 134
  135. 135. Animation in Scalaval  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 135
  136. 136. Event Listeners in Scala>  Supported using the built-in Closure syntax>  Optional arguments for event objects>  100% type-safe onMouseClicked  =  {      Timeline(at(3  s){radius-­‐>0}).play()   }   136
  137. 137. Event Listeners in ScalaSupported  using  the  built-­‐in  Closure  syntax  Optional  arguments  for  event  objects  100%  type-­‐safe   onMouseClicked  =  {      Timeline(at(3  s){radius-­‐>0}).play()   }   Compact syntax {body} 137
  138. 138. Event Listeners in ScalaSupported  using  the  built-­‐in  Closure  syntax  Optional  arguments  for  event  objects  100%  type-­‐safe   Optional event parameter {(event) => body} onMouseClicked  =  {  (e:  MouseEvent)  =>      Timeline(at(3  s){radius-­‐>0}).play()   }   138
  139. 139. 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/ 139
  140. 140. How about JavaFX on… VisageStage  {      title:  "Hello  Stage"      width:  600      height:  450      scene:  Scene  {          fill:  Color.LIGHTGREEN          content:  Rectangle  {              x:  25              y:  40              width:  100              height:  50              fill:  Color.RED          }      }  }   140
  141. 141. How about JavaFX on… VisageStage  {      title:  "Hello  Stage"      width:  600      height:  450      scene:  Scene  {          fill:  Color.LIGHTGREEN          content:  Rectangle  {              x:  25              y:  40              width:  100              height:  50              fill:  Color.RED          }      }  }   141
  142. 142. How about JavaFX on… VisageStage  {      title:  "Hello  Stage"      width:  600      height:  450      Scene  {          fill:  Color.LIGHTGREEN          Rectangle  {              x:  25              y:  40              width:  100              height:  50              fill:  Color.RED          }      }  }   142
  143. 143. 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"] 143
  144. 144. Visage and JavaFX 2.0 are made foreach 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 144
  145. 145. 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
  146. 146. Pro JavaFX 2 Platform Coming Soon! >  Coming 1st Quarter 2012 >  All examples rewritten in Java >  Covers the new JavaFX 2.0 APIs >  Will includes ScalaFX, GroovyFX, and Visage 146
  147. 147. Professional Services peter.pilgrim@gmail.com JavaFX 2.0 in Enterprises Financial Services in London Training with Consultancy Technical Leadership 147
  148. 148. JavaFX 2.0 – A Java Developers Guide Thank YouStephen Chin Peter PilgrimChief Agile Methodologist, GXS Oracle Java Championhttp://steveonjava.com @steveonjava http://www.xenonique.co.uk/blog/ @peter_pilgrim
  149. 149. AttributionsAll images are Creative Commons (CC) License from Flickr.com– “You must attribute the source and you cannot change the content”Tim Ellis http://www.flickr.com/photos/tim_ellis/Lego Basics http://www.flickr.com/photos/tim_ellis/338755101/sizes/l/*PaysImaginaire* http://www.flickr.com/photos/nevrlndtink/Variable Plastic Bag http://www.flickr.com/photos/nevrlndtink/232906118/sizes/m/~Deiby http://www.flickr.com/photos/deiby/Expression http://www.flickr.com/photos/deiby/5489382677/sizes/l/Lisa Sinclair http://www.flickr.com/photos/plumandjello/fruit http://www.flickr.com/photos/plumandjello/2333263539/sizes/l/Nerovivo http://www.flickr.com/photos/dominik99/http://www.flickr.com/photos/dominik99/407716865/sizes/z/in/photostream/
  150. 150. AttributionsAll images are Creative Commons (CC) License from Flickr.com– “You must attribute the source and you cannot change the content”.Guilty http://www.flickr.com/photos/roll_initiative/Arbitrary Function Generator http://www.flickr.com/photos/roll_initiative/3278642272/Loop Oh Rupert Grazer http://www.flickr.com/photos/loop_oh/Pattern at the Senckenberg Museum in Frankfurt am Main / Germany. http://www.flickr.com/photos/loop_oh/4571485915/Lili Vieira de Carvalho, Vancouver, Canada http://www.flickr.com/people/lilivc/Composition of Bowls http://www.flickr.com/photos/lilivc/367582911/sizes/l/Mykl Roventine http://www.flickr.com/people/myklroventine/19/365 Game Over http://www.flickr.com/photos/myklroventine/3210068573/sizes/l/superciliousness / Bentley Smith http://www.flickr.com/people/superciliousness/200510 carpenters tools - inside the workmans shed - all his old tools http://www.flickr.com/photos/superciliousness/57486288/
  151. 151. AttributionsAll images are Creative Commons (CC) License from Flickr.com– “You must attribute the source and you cannot change the content”You’ve got a fast car http://www.flickr.com/photos/coreforce/5910961411/Core Force http://www.flickr.com/photos/coreforce/GAME AT MARBLES http://www.flickr.com/photos/9619972@N08/928099769/sizes/l/in/photostream/(Author unknown, from an antique childrens booLucs Game http://www.flickr.com/photos/9619972@N08/928099769/just.Luc http://www.flickr.com/people/9619972@N08/DIY Easel http://www.flickr.com/photos/68888883@N00/2744696593/in/photostream/Judy of the Woods http://www.flickr.com/people/68888883@N00/

×