Upload
stephen-chin
View
6.702
Download
3
Embed Size (px)
DESCRIPTION
Citation preview
JavaFX e Scala – Como Leite com Bolacha
Rafael AfonsoIndependente, Magna [email protected]: @rucafonso
Stephen ChinJavaFX Evangelist, [email protected]: @steveonjava
Conheça os Apresentadores
Stephen Chin
Motorciclista
Homem de Família
Rafael Afonso
Programador Java desde 2001
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.
4
JavaFX
Scala
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.
7
Vanishing Circles
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
13
JavaFX Com Scala
Java vs. Scala DSLpublic class VanishingCircles extends Application {
public static void main(String[] args) { Application.launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("Vanishing Circles"); Group root = new Group(); Scene scene = new Scene(root, 800, 600, Color.BLACK); List<Circle> circles = new ArrayList<Circle>(); for (int i = 0; i < 50; i++) { final Circle circle = new Circle(150); circle.setCenterX(Math.random() * 800); circle.setCenterY(Math.random() * 600); circle.setFill(new Color(Math.random(), Math.random(),
Math.random(), .2)); circle.setEffect(new BoxBlur(10, 10, 3)); circle.addEventHandler(MouseEvent.MOUSE_CLICKED, new
EventHandler<MouseEvent>() { public void handle(MouseEvent t) { KeyValue collapse = new KeyValue(circle.radiusProperty(), 0); new Timeline(new KeyFrame(Duration.seconds(3), collapse)).play(); } }); circle.setStroke(Color.WHITE);
circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty()) .then(4) .otherwise(0)); circles.add(circle); } root.getChildren().addAll(circles); primaryStage.setScene(scene); primaryStage.show(); Timeline moveCircles = new Timeline(); for (Circle circle : circles) { KeyValue moveX = new KeyValue(circle.centerXProperty(), Math.random()
* 800); KeyValue moveY = new KeyValue(circle.centerYProperty(), Math.random()
* 600); moveCircles.getKeyFrames().add(new KeyFrame(Duration.seconds(40),
moveX, moveY)); } moveCircles.play(); }}
object VanishingCircles extends JFXApp { var circles: Seq[Circle] = null stage = new Stage { title = "Vanishing Circles" width = 800 height = 600 scene = new Scene { fill = BLACK circles = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, .2) effect = new BoxBlur(10, 10, 3) strokeWidth <== when (hover) then 4 otherwise 0 stroke = WHITE onMouseClicked = { Timeline(at (3 s) {radius -> 0}).play() } } content = circles } }
new Timeline { cycleCount = INDEFINITE autoReverse = true keyFrames = for (circle <- circles) yield at (40 s) { Set( circle.centerX -> random * stage.width, circle.centerY -> random * stage.height ) } }.play();}
14
40 Linhas1299 Caracteres
33 Linhas591 Caracteres
object VanishingCircles extends JFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 height = 600 scene = new Scene { fill = BLACK content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } }}
15
16
object VanishingCircles extends JFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 height = 600 scene = new Scene { fill = BLACK content = for (i <- 0 until 50) yield new Circle { centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } }}
Classe base para aplicações ScalaFX
17
object VanishingCircles extends JFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 height = 600 scene = new Scene { fill = BLACK content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } }}
Definição Declarativa do Stage
18
object VanishingCircles extends JFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 height = 600 scene = new Scene { fill = BLACK content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } }}
Definições de propriedades inline
19
object VanishingCircles extends JFXApp { stage = new Stage { title = "Disappearing Circles" width = 800 height = 600 scene = new Scene { fill = BLACK content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800 centerY = random * 600 radius = 150 fill = color(random, random, random, 0.2) effect = new BoxBlur(10, 10, 3) } } }}
Criação de Sequência Via Loop
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
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();
Animação em Scala
22
Sintaxe de animação como no 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();
Animação em Scala
23
Sobrecarga de Operador para sintaxe de animação
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();
Animação em Scala
24
Sintaxe tween opcional
Event Listeners em Scala
25
> Suporte a sintaxe de Closure> Argumentos para objetos eventos> 100% type-safe
onMouseClicked = { (e: MouseEvent) =>
Timeline(at(3 s){radius->0}).play()
}
Event Listeners em Scala
> Suporte a sintaxe de Closure> Argumentos para objetos eventos> 100% type-safe
26
Parâmetro evento opcional:
{(event) => body}
onMouseClicked = { (e: MouseEvent) =>
Timeline(at(3 s){radius->0}).play()
}
27
Red Control
Green Control
Blue Control
Opacity (Alpha)Control
Syncronizer
Values goes from 0 to 255 Enable/Disable Opacity
Pre-defined colors (from Color JavaFX class)
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)
Color Selector
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).
SliderControlclass SliderControl extends HBox {
val realValue = new DoubleProperty(new SimpleDoubleProperty) def value = this.realValue def value_=(d: Double) { if (d < Min) { value() = Min } else if (d > Max) { value() = Max } else { value() = d } }
val sldValue = new Slider { // ... value <==> realValue }}
29
Propriedade usada Internamente
Operador de Duplo Binding
Wrapper de javafx.beans.property.DoubleProperty
getter e setter do valor (equivalente às properties do C#).
Equivalente a fazer realValue.set(d)
Se fosse em Java, teríamos que escrever:controlRed.valueProperty.addListener( new ChangeListener<DoubleProperty>() { @Override public void changed(ObservableDouble d, Double old, Double new) { changeColor(); } }});O ScalaFX já faz isso por nós usando closures.
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, newAlphaValue)}
val controlRed = new SliderControl("R") { value = 255}controlRed.value.onChange(changeColor)
Cor do Retângulo
30
Sincronização de Valores
31
val synchronizedValue = new DoubleProperty(new SimpleDoubleProperty)
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) else synchronizedControls.remove(control)}
// 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 buffer.foreach(_.value = media) } case Remove(pos, removed) => { removed.last.asInstanceOf[SliderControl].value unbind synchronizedValue } }}
controlRed.selectedControl.onChange(controlSelected(controlRed))
Adiciona duplo binding
Remove duplo binding
Buffer que reúne os controle sincronizados
Versão ScalaFX do ObservableList do JavaFX
Super trait das listas em Scala
Cores pré-definidas
32
import scalafx.scene.paint.Colorimport 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)
Exibição das cores pré-definidas
33
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) }}
Formatação das cores
34
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)
}
Exibição da formatação das cores
35
private def formatColor { this.txfColorValue.text() = this.cmbColorFormat.value.get.format(this.currentColor.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 }
Ou: Como escrever sua própria DSL em Scala.
36
Funcionamento do ScalaFX
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.
Luc Viatour / www.Lucnix.be
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?!?
38
DelayedInit
> Introduzido no Scala 2.9> Como usar:1. Estender um trait especial chamado DelayedInit2. Implementar um método do tipo:
def delayedInit(x: => Unit): Unit3. Guardar a closure init e chamá-la no Thread da
Aplicação
Para mim, Scala não inova o suficiente e adiciona demais – uma combinação letal.
Joda diz…
39
> 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
JFXShape
JFXCircle
?!
Hierarquia de Conversões implicitas
SFXNode
SFXShape
SFXCircle
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
> Você pode empilhar os traits com profundidade de n níveis para reduzir a precisão para n.
40
Bem, pode ser type safe, mas é também silencioso e bem mortal.
Joda diz…
object HighPriorityIncludes extends LowerPriorityIncludes {…}trait LowerPriorityIncludes {…}
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
> Diminui a duplicação de código (ScalaFX tem apenas 18 classes de Propriedades/Valores)
42
Qualquer que seja o problema o sistema de tipos deve ser parte da solução.
Joda diz…
trait ObservableValue[@specialized(Int, Long, Float, Double, Boolean) T, J]
Bindings
43
> 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?!?
Regra de Precedência de Operadores
44
> Primeiro caractere determina a precedência
Maior Precedência
10. (all letters)9. |8. ^7. &6. < >5. = !4. :3. + *2. / %1. (todos os outros caracteres especiais)
Menor Precedência
11. Operadores de atribuição que terminam com igual> Mas não começam com
igual> E não podem ser:
<= >= !=
Exceção são os operadores de atribuição cuja prioridade é menor ainda…
Precedência de Operadores
45
Pessoalmente, acho que o objetivo de sintaxe aberta e flexível (DSLs arbitrárias) não compensam o esforço
Joda diz…
text <== when (rect.hover || circle.hover
&& !disabled) then textField.text + " is
enabled" otherwise "disabled"
911 10
7 105 3
10
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
Stephen [email protected]: @steveonjava
Desconto especial de 40% para o JustJava. Entre em apress.com e digite o código PJVF73
Obrigado!