JavaFX e Scala – Como Leite com Bolacha Stephen Chin Rafael Afonso Independente, Magna JavaFX Evangelist, Oracle Sistemas steveonjava@gmail.com rafael.afonso@gmail.com tweet: @rucafonso tweet: @steveonjava
Conheça os Apresentadores Stephen Chin Rafael Afonso Programador Homem de Java desde 2001 Família Motorciclista Interessado em Scala desde 2008
Plataforma JavaFX 2.0 Experiências de Aplicações imersivas > Animações, Videos e Gráficos Cross-platform > Integra Java, JavaScript e HTML5 na mesma aplicação > Nova pilha gráfica toma vantagem da aceleração de hardware para aplicações 2D e 3D > Use sua IDE favorita: NetBeans, Eclipse, IntelliJ, etc.
JavaFX Scala 4
JavaFX Com Java
JavaFX em Java > A API do JavaFX usa uma melhora do padrão JavaBeans > Similar a outros UI toolkits (Swing, Pivot, etc.) > Usa o Design Pattern Builder para minimizar a parte monótona.
Vanishing Circles 7
Esqueleto da Aplicação 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); [cria os círculos…] root.getChildren().addAll(circles); primaryStage.setScene(scene); primaryStage.show(); [inicia a animação…] } }
Criação dos Círculos 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); [configura os bindings…] [configura os event listeners…] circles.add(circle); } 9
Configuração do Binding circle.strokeWidthProperty().bind(Bindings .when(circle.hoverProperty()) .then(4) .otherwise(0) ); 10
Configuração de Event Listeners circle.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { public void handle(MouseEvent t) { KeyValue collapse = new KeyValue(circle.radiusProperty(), 0); new Timeline(new KeyFrame(Duration.seconds(3), collapse)).play(); } }); 11
Iniciando a Animação Timeline moveCircles = new Timeline(); for (Circle circle : circles) { KeyValue moveX = new KeyValue(circle.centerXProperty(), Math.random() * 800); KeyValue moveY = new KeyValue(circle.centerYProperty(), Math.random() * 600); moveCircles.getKeyFrames().add(new KeyFrame(Duration.seconds(40), moveX, moveY)); } moveCircles.play(); 12
JavaFX Com Scala 13
Java vs. Scala DSL public class VanishingCircles extends Application { object VanishingCircles extends JFXApp { var circles: Seq[Circle] = null public static void main(String[] args) { stage = new Stage { Application.launch(args); title = "Vanishing Circles" } width = 800 height = 600 @Override scene = new Scene { public void start(Stage primaryStage) { fill = BLACK primaryStage.setTitle("Vanishing Circles"); circles = for (i <- 0 until 50) yield new Circle { Group root = new Group(); centerX = random * 800 Scene scene = new Scene(root, 800, 600, Color.BLACK); centerY = random * 600 List<Circle> circles = new ArrayList<Circle>(); radius = 150 for (int i = 0; i < 50; i++) { fill = color(random, random, random, .2) final Circle circle = new Circle(150); effect = new BoxBlur(10, 10, 3) 40 Linhas circle.setCenterX(Math.random() * 800); circle.setCenterY(Math.random() * 600); circle.setFill(new Color(Math.random(), Math.random(), Math.random(), .2)); circle.setEffect(new BoxBlur(10, 10, 3)); 33 Linhas strokeWidth <== when (hover) then 4 otherwise 0 stroke = WHITE onMouseClicked = { Timeline(at (3 s) {radius -> 0}).play() 1299 Caracteres circle.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { public void handle(MouseEvent t) { KeyValue collapse = new KeyValue(circle.radiusProperty(), 0); } } } 591 Caracteres content = circles new Timeline(new KeyFrame(Duration.seconds(3), collapse)).play(); } } }); new Timeline { circle.setStroke(Color.WHITE); cycleCount = INDEFINITE circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty()) autoReverse = true .then(4) keyFrames = for (circle <- circles) yield at (40 s) { .otherwise(0)); Set( circles.add(circle); circle.centerX -> random * stage.width, } circle.centerY -> random * stage.height root.getChildren().addAll(circles); ) primaryStage.setScene(scene); } primaryStage.show(); }.play(); } Timeline moveCircles = new Timeline(); for (Circle circle : circles) { KeyValue moveX = new KeyValue(circle.centerXProperty(), Math.random() * 800); KeyValue moveY = new KeyValue(circle.centerYProperty(), Math.random() * 600); moveCircles.getKeyFrames().add(new KeyFrame(Duration.seconds(40), moveX, moveY)); } moveCircles.play(); } } 14
object VanishingCircles extends JFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 height = 600 scene = new Scene { fill = BLACK content = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } } } 15
object VanishingCircles extends JFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 Classe base para height = 600 scene = new Scene { ScalaFX aplicações fill = BLACK content = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } } } 16
object VanishingCircles extends JFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 height = 600 Definição Declarativa scene = new Scene { fill = BLACK do Stage content = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } } } 17
object VanishingCircles extends JFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 Definições de height = 600 propriedades inline scene = new Scene { fill = BLACK content = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } } } 18
object VanishingCircles extends JFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 Criação de Sequência height = 600 Via Loop scene = new Scene { fill = BLACK content = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } } } 19
Binding em Scala Adição/Subtração/Multiplicação/Divisão Infixas: height <== rect1.height + rect2.height Operadores de Agregação: width <== max(rect1.width, rect2.width, rect3.width) Expressões Condicionais: strokeWidth <== when (hover) then 4 otherwise 0 Expressões Compostas: text <== when (rect.hover || circle.hover && !disabled) then textField.text + " is enabled" otherwise "disabled" 20
Animação em Scala val timeline = new Timeline { cycleCount = INDEFINITE autoReverse = true keyFrames = for (circle <- circles) yield at (40 s) { Set( circle.centerX -> random * stage.width.get, circle.centerY -> random * stage.height.get ) } } timeline.play(); 21
Sintaxe de animação como no Animação em Scala JavaFX Script: at (duração) {keyframes} val timeline = new Timeline { cycleCount = INDEFINITE autoReverse = true keyFrames = for (circle <- circles) yield at (40 s) { Set( circle.centerX -> random * stage.width.get, circle.centerY -> random * stage.height.get ) } } timeline.play(); 22
Animação em Scala val timeline = new Timeline { cycleCount = INDEFINITE autoReverse = true keyFrames = for (circle <- circles) yield at (40 s) { Set( circle.centerX -> random * stage.width.get, circle.centerY -> random * stage.height.get ) } } timeline.play(); Sobrecarga de Operador para sintaxe de animação 23
Animação em 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(); Sintaxe tween opcional 24
Event Listeners em Scala > Suporte a sintaxe de Closure > Argumentos para objetos eventos > 100% type-safe onMouseClicked = { (e: MouseEvent) => Timeline(at(3 s){radius->0}).play() } 25
Event Listeners em Scala Parâmetro evento > Suporte a sintaxe de Closure opcional: {(event) => body} > Argumentos para objetos eventos > 100% type-safe onMouseClicked = { (e: MouseEvent) => Timeline(at(3 s){radius->0}).play() } 26
Color Selector Red Control Green Control Pre-defined colors (from Color JavaFX class) Blue Control Opacity (Alpha) Control Syncronizer Values goes from 0 to 255 Enable/Disable Opacity Colors Formatation: • Hexadecimal: #ADFF2F • RGB: rgba(173, 255, 47, 0,61) • Percent: rgba( 67%, 100%, 18%, 0,61) • HSB: hsba( 84, 82%, 100%, 0,61) 27
Color Selector > Versões anteriores feitas em Java Swing, JavaFX 1.3, HTML4 e HTML5. > Objetivo é alterar os parâmetros da cor do retângulo pelos componentes Vermelho (R), Verde (G), Azul (B) e Opacidade (A – Alpha), com valores variando de 0 a 255. > É possível escolher uma cor pré-definida no objeto scalafx.scene.paint.Color (que é um wrapper da classe javafx.scene.paint.Color). > Também é possível exibir o valor da cor tal como usado no HTML e ainda escolher a formatação (Hexedecimal, RGB, HSL). 28
SliderControl Propriedade usada Wrapper de Internamente class SliderControl extends HBox { javafx.beans.property.DoubleProperty val realValue = new DoubleProperty(new SimpleDoubleProperty) def value = this.realValue def value_=(d: Double) { if (d < Min) { value() = Min } else if (d > Max) { getter e setter do valor (equivalente às properties do C#). value() = Max } else { value() = d Equivalente a fazer realValue.set(d) } } val sldValue = new Slider { // ... value <==> realValue } Operador de } Duplo Binding 29
Cor do Retângulo val currentColor = new ObjectProperty[Color](Color.WHITE, "Color") currentColor.onChange(rectangleRegion.setStyle("-fx-background-color: " + RgbFormatter.format(currentColor(), !this.chbDisableAlpha.selected.get))) private def changeColor { val newAlphaValue = if (controlAlpha.disabled.get()) 1.0 else (controlAlpha.value.toDouble / 255) this.currentColor() = Color.rgb(controlRed.value.toInt, controlGreen.value.toInt, controlBlue.value.toInt, Se fosse em Java, teríamos que escrever: newAlphaValue) controlRed.valueProperty.addListener( } new ChangeListener<DoubleProperty>() { @Override public void changed(ObservableDouble d, Double old, val controlRed = new SliderControl("R") { Double new) { value = 255 changeColor(); } } } controlRed.value.onChange(changeColor) }); O ScalaFX já faz isso por nós usando closures. 30
Sincronização de Valores Buffer que reúne os val synchronizedValue = new DoubleProperty(new SimpleDoubleProperty) controle sincronizados val synchronizedControls = new ObservableBuffer[SliderControl] synchronizedControls.onChange((buffer, changes) => synchronizedValues(buffer, changes)) private def controlSelected(control: SliderControl) { // Método chamado ao clicar no Checkbox de sincronização if (control.selectedControl.get) synchronizedControls.add(control) Versão ScalaFX do Super trait das else synchronizedControls.remove(control) ObservableList do JavaFX listas em Scala } // Método chamado ao adicionar/remover um elemento private def synchronizeValues(buffer: ObservableBuffer[SliderControl], changes: Seq[Change]) { changes(0) match { case Add(pos, added) => { val media = buffer.map(_.value.get).sum / buffer.size added.last.asInstanceOf[SliderControl].value <==> synchronizedValue Adiciona duplo binding buffer.foreach(_.value = media) } case Remove(pos, removed) => { removed.last.asInstanceOf[SliderControl].value unbind synchronizedValue Remove duplo binding } } } controlRed.selectedControl.onChange(controlSelected(controlRed)) 31
Cores pré-definidas import scalafx.scene.paint.Color import scalafx.scene.paint.Color._ object WebColor { val colors = List( WebColor("ALICEBLUE", ALICEBLUE), WebColor("ANTIQUEWHITE", ANTIQUEWHITE), WebColor("AQUA", AQUA), ... WebColor("WHITE", WHITE), WebColor("WHITESMOKE", WHITESMOKE), WebColor("YELLOW", YELLOW), WebColor("YELLOWGREEN", YELLOWGREEN)) } sealed case class WebColor(name: String, color: Color) 32
Exibição das cores pré-definidas object ColorSelector extends JFXApp { private def verifyWebColor { cmbWebColor.value() = WebColor.colors.find(_.sameColor(currentColor.get)).orNull } private def webColorSelected { if (this.cmbWebColor.value.get != null) { val color = this.cmbWebColor.value.get.color controlRed.value() = doubleToInt(color.red) controlGreen.value() = doubleToInt(color.green) controlBlue.value() = doubleToInt(color.blue) } } val cmbWebColor = new ComboBox[WebColor](WebColor.colors) { onAction = webColorSelected converter = StringConverter.toStringConverter((wc: WebColor) => wc.name) } } 33
Formatação das cores object Formatter { val formatters = List(HexFormatter, RgbFormatter, PercentFormatter, HsbFormatter) } abstract sealed case class Formatter(val description: String) { protected def colorToRgbInt(c: Color): (Int, Int, Int) = (doubleToInt(c.red), doubleToInt(c.green), doubleToInt(c.blue)) protected def formatWithAlpha(c: Color): String protected def formatWithoutAlpha(c: Color): String def format(c: Color, hasAlpha: Boolean): String = if (hasAlpha) formatWithAlpha(c) else formatWithoutAlpha(c) } object HexFormatter extends Formatter("Hexadecimal") { val HEXADECIMAL_FORMAT = "#%02x%02x%02x"; def formatWithAlpha(c: Color): String = { val (r, g, b) = super.colorToRgbInt(c) HEXADECIMAL_FORMAT.format(r, g, b).toUpperCase } def formatWithoutAlpha(c: Color): String = formatWithAlpha(c) } 34
Exibição da formatação das cores private def formatColor { this.txfColorValue.text() = this.cmbColorFormat.value.get.format(this.currentColo r.get, !this.chbDisableAlpha.selected.get) } val cmbColorFormat = new ComboBox[Formatter](Formatter.formatters) { promptText = "Color Format" converter = StringConverter.toStringConverter((f: Formatter) => f.description) value = RgbFormatter onAction = formatColor } 35
Funcionamento do ScalaFX Ou: Como escrever sua própria DSL em Scala. Com citações de Stephen Colebourne (@jodastephen) para o bem de nossa sanidade! Aviso: Declarações extraídas de http://blog.joda.org e podem não refletir exatamente sua opnião ou ponto de vista. 36
Inicialização da Aplicação > JavaFX requer que todo código de UI seja executado na Thread da aplicação. > Mas nossa Aplicação ScalaFX não possui método start: object VanishingCircles extends JFXApp { stage = new Stage { … } } Como esse código funciona?!? 37
DelayedInit > Introduzido no Scala 2.9 > Como usar: 1. Estender um trait especial chamado DelayedInit 2. Implementar um método do tipo:  def delayedInit(x: => Unit): Unit 3. Guardar a closure init e chamá-la no Thread da Aplicação Joda diz… Para mim, Scala não inova o suficiente e adiciona demais – uma combinação letal. 38
Hierarquia de Conversões implicitas > ScalaFX define um conjunto de proxies que espelham a hierarquia de JavaFX > As classes JavaFX são “implicitamente” acondicionadas (wrapped) quando se chama a API do ScalaFX > Mas a prioridade de implicit de Scala ignora a hierarquia de tipos! JFXNode SFXNode JFXShape ? SFXShape JFXCircle ! SFXCircle 39
Precedência de Implicits de N Níveis > Scala dispara uma exceção se dois implicits têm a mesma precedência. > Classes que são estendidas têm uma precedência menor object HighPriorityIncludes extends LowerPriorityIncludes {…} trait LowerPriorityIncludes {…} > Você pode empilhar os traits com profundidade de n níveis para reduzir a precisão para n. Joda diz… Bem, pode ser type safe, mas é também silencioso e bem mortal. 40
Propriedades > JavaFX suporta propriedades do tipo Boolean, Integer, Long, Float, Double, String, e Object > Propriedades usam Genéricos para segurança de tipos. > Mas genéricos não suportam primitivos… > JavaFX soluciona isso com 20 interfaces e 44 classes para todos os tipos de combinações de tipos somente-leitura ou graváveis. > Podemos melhorar? 41
@specialized > Anotação especial que gera variantes primitivas da classe. > Melhora a performance ao evitar boxing/unboxing trait ObservableValue[@specialized(Int, Long, Float, Double, Boolean) T, J] > Diminui a duplicação de código (ScalaFX tem apenas 18 classes de Propriedades/Valores) Joda diz… Qualquer que seja o problema o sistema de tipos deve ser parte da solução. 42
Bindings > Como Scala sabe a ordem de avaliação? text <== when (rect.hover || circle.hover && !disabled) then textField.text + " is enabled" otherwise " disabled" E por que esse operador esquisito de binding?!? 43
Regra de Precedência de Operadores > Primeiro caractere determina a precedência Menor Precedência Exceção são os 10. (all letters) operadores de atribuição 9. | cuja prioridade é menor 8. ^ ainda… 7. & 6. < > 11. Operadoresde atribuição 5. = ! que terminam com igual 4. : > Mas não começam com igual 3. + * > E não podem ser: 2. / %  <= 1. (todos os outros  >= caracteres especiais)  != Maior Precedência 44
Precedência de Operadores text <== when (rect.hover || circle.hover 11 10 9 && !disabled) then textField.text + " is 7 5 10 3 enabled" otherwise "disabled" 10 Joda diz… Pessoalmente, acho que o objetivo de sintaxe aberta e flexível (DSLs arbitrárias) não compensam o esforço 45
Conclusão > Você pode usar Scala e JavaFX juntos. > ScalaFX fornece APIs mais simples, feita sob medida para Scala. > Experimente ScalaFX hoje e ajude a contribuir com APIs para a futura versão 1.0! http://code.google.com/p/scalafx/
Obrigado Stephen Chin steveonjava@gmail.com tweet: @steveonjava Desconto especial de 40% para o JustJava. Entre em apress.com e digite o código PJVF73 47

JavaFX e Scala - Como Leite com Bolacha

  • 1.
    JavaFX e Scala– Como Leite com Bolacha Stephen Chin Rafael Afonso Independente, Magna JavaFX Evangelist, Oracle Sistemas steveonjava@gmail.com rafael.afonso@gmail.com tweet: @rucafonso tweet: @steveonjava
  • 2.
    Conheça os Apresentadores Stephen Chin Rafael Afonso Programador Homem de Java desde 2001 Família Motorciclista Interessado em Scala desde 2008
  • 3.
    Plataforma JavaFX 2.0 Experiênciasde Aplicações imersivas > Animações, Videos e Gráficos Cross-platform > Integra Java, JavaScript e HTML5 na mesma aplicação > Nova pilha gráfica toma vantagem da aceleração de hardware para aplicações 2D e 3D > Use sua IDE favorita: NetBeans, Eclipse, IntelliJ, etc.
  • 4.
  • 5.
  • 6.
    JavaFX em Java > A API do JavaFX usa uma melhora do padrão JavaBeans > Similar a outros UI toolkits (Swing, Pivot, etc.) > Usa o Design Pattern Builder para minimizar a parte monótona.
  • 7.
  • 8.
    Esqueleto da Aplicação publicclass 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); [cria os círculos…] root.getChildren().addAll(circles); primaryStage.setScene(scene); primaryStage.show(); [inicia a animação…] } }
  • 9.
    Criação dos Círculos 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); [configura os bindings…] [configura os event listeners…] circles.add(circle); } 9
  • 10.
    Configuração do Binding circle.strokeWidthProperty().bind(Bindings .when(circle.hoverProperty()) .then(4) .otherwise(0) ); 10
  • 11.
    Configuração de EventListeners circle.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { public void handle(MouseEvent t) { KeyValue collapse = new KeyValue(circle.radiusProperty(), 0); new Timeline(new KeyFrame(Duration.seconds(3), collapse)).play(); } }); 11
  • 12.
    Iniciando a Animação TimelinemoveCircles = new Timeline(); for (Circle circle : circles) { KeyValue moveX = new KeyValue(circle.centerXProperty(), Math.random() * 800); KeyValue moveY = new KeyValue(circle.centerYProperty(), Math.random() * 600); moveCircles.getKeyFrames().add(new KeyFrame(Duration.seconds(40), moveX, moveY)); } moveCircles.play(); 12
  • 13.
  • 14.
    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) final Circle circle = new Circle(150); effect = new BoxBlur(10, 10, 3) 40 Linhas circle.setCenterX(Math.random() * 800); circle.setCenterY(Math.random() * 600); circle.setFill(new Color(Math.random(), Math.random(), Math.random(), .2)); circle.setEffect(new BoxBlur(10, 10, 3)); 33 Linhas strokeWidth <== when (hover) then 4 otherwise 0 stroke = WHITE onMouseClicked = { Timeline(at (3 s) {radius -> 0}).play() 1299 Caracteres circle.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { public void handle(MouseEvent t) { KeyValue collapse = new KeyValue(circle.radiusProperty(), 0); } } } 591 Caracteres content = circles new Timeline(new KeyFrame(Duration.seconds(3), collapse)).play(); } } }); new Timeline { circle.setStroke(Color.WHITE); cycleCount = INDEFINITE circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty()) autoReverse = true .then(4) keyFrames = for (circle <- circles) yield at (40 s) { .otherwise(0)); Set( circles.add(circle); circle.centerX -> random * stage.width, } circle.centerY -> random * stage.height root.getChildren().addAll(circles); ) primaryStage.setScene(scene); } primaryStage.show(); }.play(); } Timeline moveCircles = new Timeline(); for (Circle circle : circles) { KeyValue moveX = new KeyValue(circle.centerXProperty(), Math.random() * 800); KeyValue moveY = new KeyValue(circle.centerYProperty(), Math.random() * 600); moveCircles.getKeyFrames().add(new KeyFrame(Duration.seconds(40), moveX, moveY)); } moveCircles.play(); } } 14
  • 15.
    object VanishingCircles extendsJFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 height = 600 scene = new Scene { fill = BLACK content = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } } } 15
  • 16.
    object VanishingCircles extendsJFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 Classe base para height = 600 scene = new Scene { ScalaFX aplicações fill = BLACK content = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } } } 16
  • 17.
    object VanishingCircles extendsJFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 height = 600 Definição Declarativa scene = new Scene { fill = BLACK do Stage content = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } } } 17
  • 18.
    object VanishingCircles extendsJFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 Definições de height = 600 propriedades inline scene = new Scene { fill = BLACK content = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } } } 18
  • 19.
    object VanishingCircles extendsJFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 Criação de Sequência height = 600 Via Loop scene = new Scene { fill = BLACK content = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } } } 19
  • 20.
    Binding em Scala Adição/Subtração/Multiplicação/DivisãoInfixas: height <== rect1.height + rect2.height Operadores de Agregação: width <== max(rect1.width, rect2.width, rect3.width) Expressões Condicionais: strokeWidth <== when (hover) then 4 otherwise 0 Expressões Compostas: text <== when (rect.hover || circle.hover && !disabled) then textField.text + " is enabled" otherwise "disabled" 20
  • 21.
    Animação em Scala valtimeline = new Timeline { cycleCount = INDEFINITE autoReverse = true keyFrames = for (circle <- circles) yield at (40 s) { Set( circle.centerX -> random * stage.width.get, circle.centerY -> random * stage.height.get ) } } timeline.play(); 21
  • 22.
    Sintaxe de animaçãocomo no Animação em Scala JavaFX Script: at (duração) {keyframes} val timeline = new Timeline { cycleCount = INDEFINITE autoReverse = true keyFrames = for (circle <- circles) yield at (40 s) { Set( circle.centerX -> random * stage.width.get, circle.centerY -> random * stage.height.get ) } } timeline.play(); 22
  • 23.
    Animação em Scala valtimeline = new Timeline { cycleCount = INDEFINITE autoReverse = true keyFrames = for (circle <- circles) yield at (40 s) { Set( circle.centerX -> random * stage.width.get, circle.centerY -> random * stage.height.get ) } } timeline.play(); Sobrecarga de Operador para sintaxe de animação 23
  • 24.
    Animação em Scala valtimeline = 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(); Sintaxe tween opcional 24
  • 25.
    Event Listeners emScala > Suporte a sintaxe de Closure > Argumentos para objetos eventos > 100% type-safe onMouseClicked = { (e: MouseEvent) => Timeline(at(3 s){radius->0}).play() } 25
  • 26.
    Event Listeners emScala Parâmetro evento > Suporte a sintaxe de Closure opcional: {(event) => body} > Argumentos para objetos eventos > 100% type-safe onMouseClicked = { (e: MouseEvent) => Timeline(at(3 s){radius->0}).play() } 26
  • 27.
    Color Selector RedControl Green Control Pre-defined colors (from Color JavaFX class) Blue Control Opacity (Alpha) Control Syncronizer Values goes from 0 to 255 Enable/Disable Opacity Colors Formatation: • Hexadecimal: #ADFF2F • RGB: rgba(173, 255, 47, 0,61) • Percent: rgba( 67%, 100%, 18%, 0,61) • HSB: hsba( 84, 82%, 100%, 0,61) 27
  • 28.
    Color Selector > Versões anteriores feitas em Java Swing, JavaFX 1.3, HTML4 e HTML5. > Objetivo é alterar os parâmetros da cor do retângulo pelos componentes Vermelho (R), Verde (G), Azul (B) e Opacidade (A – Alpha), com valores variando de 0 a 255. > É possível escolher uma cor pré-definida no objeto scalafx.scene.paint.Color (que é um wrapper da classe javafx.scene.paint.Color). > Também é possível exibir o valor da cor tal como usado no HTML e ainda escolher a formatação (Hexedecimal, RGB, HSL). 28
  • 29.
    SliderControl Propriedade usada Wrapper de Internamente class SliderControl extends HBox { javafx.beans.property.DoubleProperty val realValue = new DoubleProperty(new SimpleDoubleProperty) def value = this.realValue def value_=(d: Double) { if (d < Min) { value() = Min } else if (d > Max) { getter e setter do valor (equivalente às properties do C#). value() = Max } else { value() = d Equivalente a fazer realValue.set(d) } } val sldValue = new Slider { // ... value <==> realValue } Operador de } Duplo Binding 29
  • 30.
    Cor do Retângulo valcurrentColor = new ObjectProperty[Color](Color.WHITE, "Color") currentColor.onChange(rectangleRegion.setStyle("-fx-background-color: " + RgbFormatter.format(currentColor(), !this.chbDisableAlpha.selected.get))) private def changeColor { val newAlphaValue = if (controlAlpha.disabled.get()) 1.0 else (controlAlpha.value.toDouble / 255) this.currentColor() = Color.rgb(controlRed.value.toInt, controlGreen.value.toInt, controlBlue.value.toInt, Se fosse em Java, teríamos que escrever: newAlphaValue) controlRed.valueProperty.addListener( } new ChangeListener<DoubleProperty>() { @Override public void changed(ObservableDouble d, Double old, val controlRed = new SliderControl("R") { Double new) { value = 255 changeColor(); } } } controlRed.value.onChange(changeColor) }); O ScalaFX já faz isso por nós usando closures. 30
  • 31.
    Sincronização de Valores Buffer que reúne os val synchronizedValue = new DoubleProperty(new SimpleDoubleProperty) controle sincronizados val synchronizedControls = new ObservableBuffer[SliderControl] synchronizedControls.onChange((buffer, changes) => synchronizedValues(buffer, changes)) private def controlSelected(control: SliderControl) { // Método chamado ao clicar no Checkbox de sincronização if (control.selectedControl.get) synchronizedControls.add(control) Versão ScalaFX do Super trait das else synchronizedControls.remove(control) ObservableList do JavaFX listas em Scala } // Método chamado ao adicionar/remover um elemento private def synchronizeValues(buffer: ObservableBuffer[SliderControl], changes: Seq[Change]) { changes(0) match { case Add(pos, added) => { val media = buffer.map(_.value.get).sum / buffer.size added.last.asInstanceOf[SliderControl].value <==> synchronizedValue Adiciona duplo binding buffer.foreach(_.value = media) } case Remove(pos, removed) => { removed.last.asInstanceOf[SliderControl].value unbind synchronizedValue Remove duplo binding } } } controlRed.selectedControl.onChange(controlSelected(controlRed)) 31
  • 32.
    Cores pré-definidas import scalafx.scene.paint.Color importscalafx.scene.paint.Color._ object WebColor { val colors = List( WebColor("ALICEBLUE", ALICEBLUE), WebColor("ANTIQUEWHITE", ANTIQUEWHITE), WebColor("AQUA", AQUA), ... WebColor("WHITE", WHITE), WebColor("WHITESMOKE", WHITESMOKE), WebColor("YELLOW", YELLOW), WebColor("YELLOWGREEN", YELLOWGREEN)) } sealed case class WebColor(name: String, color: Color) 32
  • 33.
    Exibição das corespré-definidas object ColorSelector extends JFXApp { private def verifyWebColor { cmbWebColor.value() = WebColor.colors.find(_.sameColor(currentColor.get)).orNull } private def webColorSelected { if (this.cmbWebColor.value.get != null) { val color = this.cmbWebColor.value.get.color controlRed.value() = doubleToInt(color.red) controlGreen.value() = doubleToInt(color.green) controlBlue.value() = doubleToInt(color.blue) } } val cmbWebColor = new ComboBox[WebColor](WebColor.colors) { onAction = webColorSelected converter = StringConverter.toStringConverter((wc: WebColor) => wc.name) } } 33
  • 34.
    Formatação das cores objectFormatter { val formatters = List(HexFormatter, RgbFormatter, PercentFormatter, HsbFormatter) } abstract sealed case class Formatter(val description: String) { protected def colorToRgbInt(c: Color): (Int, Int, Int) = (doubleToInt(c.red), doubleToInt(c.green), doubleToInt(c.blue)) protected def formatWithAlpha(c: Color): String protected def formatWithoutAlpha(c: Color): String def format(c: Color, hasAlpha: Boolean): String = if (hasAlpha) formatWithAlpha(c) else formatWithoutAlpha(c) } object HexFormatter extends Formatter("Hexadecimal") { val HEXADECIMAL_FORMAT = "#%02x%02x%02x"; def formatWithAlpha(c: Color): String = { val (r, g, b) = super.colorToRgbInt(c) HEXADECIMAL_FORMAT.format(r, g, b).toUpperCase } def formatWithoutAlpha(c: Color): String = formatWithAlpha(c) } 34
  • 35.
    Exibição da formataçãodas cores private def formatColor { this.txfColorValue.text() = this.cmbColorFormat.value.get.format(this.currentColo r.get, !this.chbDisableAlpha.selected.get) } val cmbColorFormat = new ComboBox[Formatter](Formatter.formatters) { promptText = "Color Format" converter = StringConverter.toStringConverter((f: Formatter) => f.description) value = RgbFormatter onAction = formatColor } 35
  • 36.
    Funcionamento do ScalaFX Ou: Como escrever sua própria DSL em Scala. Com citações de Stephen Colebourne (@jodastephen) para o bem de nossa sanidade! Aviso: Declarações extraídas de http://blog.joda.org e podem não refletir exatamente sua opnião ou ponto de vista. 36
  • 37.
    Inicialização da Aplicação > JavaFX requer que todo código de UI seja executado na Thread da aplicação. > Mas nossa Aplicação ScalaFX não possui método start: object VanishingCircles extends JFXApp { stage = new Stage { … } } Como esse código funciona?!? 37
  • 38.
    DelayedInit > Introduzido no Scala 2.9 > Como usar: 1. Estender um trait especial chamado DelayedInit 2. Implementar um método do tipo:  def delayedInit(x: => Unit): Unit 3. Guardar a closure init e chamá-la no Thread da Aplicação Joda diz… Para mim, Scala não inova o suficiente e adiciona demais – uma combinação letal. 38
  • 39.
    Hierarquia de Conversõesimplicitas > ScalaFX define um conjunto de proxies que espelham a hierarquia de JavaFX > As classes JavaFX são “implicitamente” acondicionadas (wrapped) quando se chama a API do ScalaFX > Mas a prioridade de implicit de Scala ignora a hierarquia de tipos! JFXNode SFXNode JFXShape ? SFXShape JFXCircle ! SFXCircle 39
  • 40.
    Precedência de Implicitsde N Níveis > Scala dispara uma exceção se dois implicits têm a mesma precedência. > Classes que são estendidas têm uma precedência menor object HighPriorityIncludes extends LowerPriorityIncludes {…} trait LowerPriorityIncludes {…} > Você pode empilhar os traits com profundidade de n níveis para reduzir a precisão para n. Joda diz… Bem, pode ser type safe, mas é também silencioso e bem mortal. 40
  • 41.
    Propriedades > JavaFX suporta propriedades do tipo Boolean, Integer, Long, Float, Double, String, e Object > Propriedades usam Genéricos para segurança de tipos. > Mas genéricos não suportam primitivos… > JavaFX soluciona isso com 20 interfaces e 44 classes para todos os tipos de combinações de tipos somente-leitura ou graváveis. > Podemos melhorar? 41
  • 42.
    @specialized > Anotação especial que gera variantes primitivas da classe. > Melhora a performance ao evitar boxing/unboxing trait ObservableValue[@specialized(Int, Long, Float, Double, Boolean) T, J] > Diminui a duplicação de código (ScalaFX tem apenas 18 classes de Propriedades/Valores) Joda diz… Qualquer que seja o problema o sistema de tipos deve ser parte da solução. 42
  • 43.
    Bindings > Como Scala sabe a ordem de avaliação? text <== when (rect.hover || circle.hover && !disabled) then textField.text + " is enabled" otherwise " disabled" E por que esse operador esquisito de binding?!? 43
  • 44.
    Regra de Precedênciade Operadores > Primeiro caractere determina a precedência Menor Precedência Exceção são os 10. (all letters) operadores de atribuição 9. | cuja prioridade é menor 8. ^ ainda… 7. & 6. < > 11. Operadoresde atribuição 5. = ! que terminam com igual 4. : > Mas não começam com igual 3. + * > E não podem ser: 2. / %  <= 1. (todos os outros  >= caracteres especiais)  != Maior Precedência 44
  • 45.
    Precedência de Operadores text<== when (rect.hover || circle.hover 11 10 9 && !disabled) then textField.text + " is 7 5 10 3 enabled" otherwise "disabled" 10 Joda diz… Pessoalmente, acho que o objetivo de sintaxe aberta e flexível (DSLs arbitrárias) não compensam o esforço 45
  • 46.
    Conclusão > Você pode usar Scala e JavaFX juntos. > ScalaFX fornece APIs mais simples, feita sob medida para Scala. > Experimente ScalaFX hoje e ajude a contribuir com APIs para a futura versão 1.0! http://code.google.com/p/scalafx/
  • 47.
    Obrigado Stephen Chin steveonjava@gmail.com tweet: @steveonjava Desconto especial de 40% para o JustJava. Entre em apress.com e digite o código PJVF73 47

Notas do Editor