Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

GroovyFX - Groove JavaFX

443 views

Published on

Oracle decided to give JavaFX a pure Java-API - a good one.
But we have to pay the price with longer code and worse readability.
GroovyFX simplifies JavaFX development, makes it nicer and more groovy.
This session compares JavaFX and GroovyFX and shows how easy JavaFX development can be.

Published in: Software
  • Be the first to comment

  • Be the first to like this

GroovyFX - Groove JavaFX

  1. 1. GroovyFX - Groove JavaFX_ Alexander Klein   1
  2. 2. About me Alexander Klein Branchmanager codecentric AG Curiestr. 2 70563 Stuttgart, Germany +49 (0) 172 529 40 20 alexander.klein@codecentric.de www.codecentric.de blog.codecentric.de @saschaklein   2
  3. 3. What is GroovyFX   3
  4. 4. GroovyFX library for JavaFX using Groovy Groovy Builder Pattern DSL on top of JavaFX declarative simpler to write easier to read more natural http://groovyfx.org https://github.com/groovyfx-project/groovyfx http://groovy.jmiguel.eu/groovy.codehaus.org/GroovyFX.html   4
  5. 5. Example groovyx.javafx.GroovyFX.start { stage(title: 'Hello GroovyFX', visible: true) { scene(fill: DARKSLATEGREY, width: 860, height: 430) { borderPane { top { hbox(padding: [20, 60, 20, 60]) { text(text: 'Hello ', font: '80pt sanserif') { fill linearGradient(endX: 0, stops: [PALEGREEN, SEAGREEN]) } text(text: 'GroovyFX', font: '80pt sanserif') { fill linearGradient(endX: 0, stops: [CYAN, DODGERBLUE]) effect dropShadow(color: DODGERBLUE, radius: 25, spread: 0.25) } } } group(id: 'logo', scaleX: 2, scaleY: 2) { transitions = parallelTransition() star delegate, 12, [LIGHTGREEN, GREEN]*.brighter() star delegate, 6, [LIGHTBLUE, BLUE]*.brighter() star delegate, 0, [YELLOW, ORANGE] fxLabel delegate onMouseClicked { transitions.playFromStart() } } } } } transitions.delay = Duration.seconds(1) transitions.playFromStart() transitions.delay = Duration.seconds(0) }   5
  6. 6. Example   6
  7. 7. Simple Scene General contract import static groovyx.javafx.GroovyFX.start start { stage(title: 'Hello GroovyFX', visible: true) { scene { stylesheets('groovyfx.css') label('Hello GroovyFX', styleClass: 'big') } } } [container name](value?, attributes*) { [subcontainer name](value?, attributes*) { [node name](value?, attributes*) } }   7
  8. 8. Simple Scene   8
  9. 9. Forms and components   9
  10. 10. Simple Form import static groovyx.javafx.GroovyFX.start start { stage(title: 'Simple form', visible: true) { scene { stylesheets('groovyfx.css') vbox { label('Name') textField(onAction: { evt -> result.text = evt.source.text }) label(id: 'result') } } } }   10
  11. 11. Simple Form   11
  12. 12. Binding import static groovyx.javafx.GroovyFX.start start { stage(title: 'Simple form binding', visible: true) { scene { stylesheets('groovyfx.css') vbox { label('Name') def textField = textField() label text: bind(textField.textProperty()) label text: bind(textField.text()) label text: bind{textField.text} label text: bind(textField, 'text') label text: bind(textField, 'text').using{ "Text: $it" } label id: 'lastLabel' bind lastLabel.text() to textField.text() } } } }   12
  13. 13. Binding   13
  14. 14. Bean Binding import java.time.LocalDate import groovyx.javafx.beans.FXBindable @FXBindable class Person { String name LocalDate birth } def person = new Person(name: 'Sascha', birth: new LocalDate(1975, 4, 1)) groovyx.javafx.GroovyFX.start { stage(title: 'Bean binding', visible: true) { scene { stylesheets('groovyfx.css') vbox { label 'Name' textField(text: bind(person, 'name')) label 'Birthday' datePicker(value: bind(person.birth())) label text: bind { person.birth && person.name }.using { "$person.name is ${LocalDate.now().year - person.birth.year} years old" } } } } }   14
  15. 15. Bean Binding   15
  16. 16. Layouting import static groovyx.javafx.GroovyFX.start start { stage(title: 'Border Pane Demo', visible: true) { scene { stylesheets('groovyfx.css') borderPane { top { label('Header') } // center { textField(id: 'tf') // } bottom { label text: bind(tf.textProperty()) } right { imageView('GroovyFX_logo.png') } } } } }   16
  17. 17. Layouting   17
  18. 18. GridLayout groovyx.javafx.GroovyFX.start { stage(title: 'GridLayout form', visible: true) { scene { stylesheets('groovyfx.css') gridPane(hgap: 5, vgap: 10, padding: 25, alignment: TOP_CENTER) { columnConstraints(minWidth: 50, halignment: RIGHT) columnConstraints(prefWidth: 250, hgrow: 'always') label("Please send us your feedback", style: "-fx-font-size: 18px;", row: 0, columnSpan: 2, halignment: "center", margin: [0, 0, 10]) { onMouseEntered { e -> e.source.parent.gridLinesVisible = true } onMouseExited { e -> e.source.parent.gridLinesVisible = false } } label("Name", hgrow: "never", row: 1, column: 0) textField(promptText: "Your name", row: 1, column: 1 ) label("Email", row: 2, column: 0) textField(promptText: "Your email address", row: 2, column: 1) label("Message", row: 3, column: 0, valignment: "baseline") textArea(prefRowCount: 8, row: 3, column: 1, vgrow: 'always') button("Send Message", row: 4, column: 1, halignment: "right") } } } }   18
  19. 19. GridLayout   19
  20. 20. Lists @FXBindable class SelectionHolder { def selected } def selectionHolder = new SelectionHolder() def colors = ['blue', 'green', 'red'] start { primaryStage -> stage(title: "GroovyFX List Demo", width: 400, height: 200, visible: true) { scene(fill: WHITE) { vbox(padding: 10, spacing: 5) { choiceBox(value: bind(selectionHolder.selected()), items: colors) listView(id: 'myList', items: colors) { onSelect { control, item -> selectionHolder.selected = item } } selectionHolder.selected().onChange { source, oldValue, newValue -> myList.selectionModel.select(newValue) } label(text: bind(selectionHolder.selected())) } } } }   20
  21. 21. Lists   21
  22. 22. Colors start { primaryStage -> def colors = [ BLUE, GREEN, RED, hsb(67, 0.8, 0.91), rgb(39, 209, 100), rgba(20, 100, 150, 0.60), delegate."#AA4411" ] stage(title: "GroovyFX Table Demo", visible: true) { scene(fill: WHITE) { vbox(padding: 9, spacing: 5) { //...   22
  23. 23. Tables tableView(selectionMode: "single", cellSelectionEnabled: true, items: colors) { tableColumn text: "Color", prefWidth: 50, cellValueFactory: { new ReadOnlyObjectWrapper(it.value) }, cellFactory: { column -> Rectangle rect = rectangle(width: 40, height: 20) new TableCell<Color, Color>() { void updateItem(Color color, boolean empty) { rect.fill = empty ? Color.TRANSPARENT : color setGraphic(rect) } } } tableColumn text: "Web", prefWidth: 80, cellValueFactory: { Color color = it.value int r = Math.round(color.red * 255.0) int g = Math.round(color.green * 255.0) int b = Math.round(color.blue * 255.0) new ReadOnlyObjectWrapper(String.format("#%02X%02X%02X", r, g, b)) } tableColumn(property: "opacity", text: "Opacity", prefWidth: 70, converter: { from -> "${Math.round(from * 100)}%" }) tableColumn(property: "hue", text: "Hue", prefWidth: 120) tableColumn(property: "brightness", text: "Brightness", prefWidth: 120) tableColumn(property: "saturation", text: "Saturation", prefWidth: 120) } } } } }   23
  24. 24. Tables   24
  25. 25. Actions start { actions { fxaction(id: 'saveAction', name: 'Save', description: 'This saves something', accelerator: 'Ctrl+S', enabled: false, onAction: { println "Save" }) fxaction(id: 'exitAction', name: 'Exit', onAction: { primaryStage.close() }) fxaction(id: 'copyAction', name: 'Copy', icon: 'icons/copy.png', onAction: { println "Copy" } ) fxaction(id: 'pasteAction', name: 'Paste', icon: 'icons/paste.png', onAction: { println "Paste" } ) fxaction(id: 'checkAction', name: 'Check', selected: true, onAction: { println "Check" } ) } // ...   25
  26. 26. Actions stage(title: "GroovyFX Menu Demo", width: 650, height: 450, visible: true) { scene(fill: WHITE) { borderPane { top { menuBar { menu("File") { menuItem("Open", onAction: { println "Open" }) { rectangle(width: 16, height: 16, fill: RED) } menuItem(saveAction) { graphic(circle(radius: 8, fill: BLUE)) } separatorMenuItem() menuItem(exitAction) } menu(text: "Edit") { menuItem(text: "Cut", onAction: { println "Cut" }) { imageView('/icons/cut.png') } menuItem(copyAction) menuItem(pasteAction) separatorMenuItem() checkMenuItem(checkAction) def toggleGroup = new ToggleGroup() radioMenuItem("Radio1", toggleGroup: toggleGroup, selected: true) radioMenuItem("Radio2", toggleGroup: toggleGroup) menu("Foo") { menuItem("Bar") menuItem("FooBar") } } } } // ...   26
  27. 27. Actions center { vbox(spacing: 20, padding: 10) { checkBox("Enable 'Save' menu", id: 'cb') bean(saveAction, enabled: bind(cb.selectedProperty())) } } bottom { toolBar { button(onAction: { println "Cut" }) { graphic imageView('/icons/cut.png') } button(copyAction, skipName: true) button(pasteAction, skipName: true) } } } } } }   27
  28. 28. Actions   28
  29. 29. Charts def pieData = FXCollections.observableArrayList([ new PieChart.Data("Yours", 42), new PieChart.Data("Mine", 58) ]) start { Map data = [first: 0.25f, second: 0.25f, third: 0.25f] stage(title: 'Chart Demo', visible: true, width: 1024, height: 960) { scene { stylesheets('groovyfx.css') stackPane { scrollPane { tilePane(padding: 10, prefTileWidth: 480, prefColumns: 2) { pieChart(data: [first: 0.25f, second: 0.25f, third: 0.25f]) stackPane(alignment: TOP_RIGHT) { pieChart(data: pieData, animated: true) button('Add Slice') { onAction { pieData.add(new PieChart.Data('Other', 25)) } } } // ...   29
  30. 30. Charts lineChart(data: [First: [0,0.25,0.5,1.5,2,1], Second: [0.25,0,0.5,0.5,1.5,0.75]]) final yAxis = numberAxis(label: "Y Axis", lowerBound: -1.2, upperBound: 1.2, tickUnit: 0.2, autoRanging: false) lineChart(xAxis: categoryAxis(label: "X Axis"), yAxis: yAxis) { series(name: 'First Series', data: ["A", 0, "B", 1, "C", -1]) series(name: 'Second Series', data: [["A", 0], ["B", -1], ["C", 1], ["D", 0]]) } areaChart { series(name: 'First Series', data: [0, 0, 0.5, 0.2, 1.5, 0.6, 2, 0.8]) series(name: 'Second Series', data: [0, 0, 0.25, 0.2, 0.5, 0.6, 2.25, 0.4]) } bubbleChart { series(name: 'First Series', data: [[0,0.2,0.1], [0.5,0.2,0.25], [1.5,0.1,0.5]]) series(name: 'Second Series', data: [[0, 0.1, 0.25], [0.2, 0.5, 0.2]]) } scatterChart { series(name: 'First Series', data: [0, 0, 0.5, 0.2, 1.5, 0.6, 2, 0.8]) series(name: 'Second Series', data: [0, 0, 0.25, 0.2, 0.5, 0.6, 2.25, 0.4]) } barChart(barGap: 0, categoryGap: 0) { series(name: '2010', data: ['Q1', 1534, 'Q2', 2698, 'Q3', 1945, 'Q4', 3156]) series(name: '2012', data: ['Q1', 2200, 'Q2', 2750, 'Q3', 2125, 'Q4', 3100]) } } } } } } }   30
  31. 31. Charts DEMO   31
  32. 32. Graphics and animations   32
  33. 33. Paths start { stage(title: "GroovyFX Path Demo", visible: true) { scene(fill: BLACK) { path(translateX: 0, translateY: 493.672 + 10, fill: WHITE, stroke: GREY, strokeWidth: 1, strokeLineCap: StrokeLineCap.BUTT, strokeLineJoin: StrokeLineJoin.MITER, strokeMiterLimit: 4.00000000) { moveTo(x: 105.367, y: -493.672) cubicCurveTo( controlX1: 128.507, controlY1: -478.22, controlX2: 151.465, controlY2: -462.40, x: 173.917, y: -446.100 ) cubicCurveTo( controlX1: 128.862, controlY1: -466.995, controlX2: 79.407, controlY2: -482.018, x: 24.547, y: -490.346 ) // ...   33
  34. 34. Paths cubicCurveTo(controlX1: 71.244, controlY1: -463.626, controlX2: 116.143, controlY2: -434.766, x: 160.252, y: -404.822) cubicCurveTo(controlX1: 123.049, controlY1: -422.855, controlX2: 82.772, controlY2: -437.042, x: 38.650, y: -446.192) cubicCurveTo(controlX1: 96.868, controlY1: -411.870, controlX2: 148.018, controlY2: -373.727, x: 193.360, y: -331.986) cubicCurveTo(controlX1: 136.020, controlY1: -284.773, controlX2: 86.295, controlY2: -227.283, x: 45.790, y: -157.820) cubicCurveTo(controlX1: 72.900, controlY1: -182.110, controlX2: 100.700, controlY2: -205.365, x: 128.658, y: -228.500) cubicCurveTo(controlX1: 81.942, controlY1: -172.640, controlX2: 45.050, controlY2: -106.990, x: 20.200, y: -29.865) cubicCurveTo(controlX1: 40.560, controlY1: -54.485, controlX2: 61.188, controlY2: -78.068, x: 82.105, y: -100.682) cubicCurveTo(controlX1: 126.805, controlY1: -168.167, controlX2: 171.672, controlY2: -247.792, x: 230.961, y: -271.100) cubicCurveTo(controlX1: 201.351, controlY1: -240.392, controlX2: 167.601, controlY2: -195.936, x: 132.711, y: -152.955) cubicCurveTo(controlX1: 173.701, controlY1: -193.392, controlX2: 215.801, controlY2: -230.415, x: 259.126, y: -264.467) cubicCurveTo(controlX1: 320.724, controlY1: -193.977, controlX2: 369.883, controlY2: -115.087, x: 411.271, y: -28.594) cubicCurveTo(controlX1: 404.533, controlY1: -73.388, controlX2: 394.475, controlY2: -115.978, x: 381.241, y: -156.260) lineTo(x: 427.685, y: -90.730) // ...   34
  35. 35. Paths cubicCurveTo(controlX1: 427.685, controlY1: -90.730, controlX2: 401.648, controlY2: -163.420, x: 384.025, y: -192.717) cubicCurveTo(controlX1: 424.785, controlY1: -136.807, controlX2: 462.233, controlY2: -78.289, x: 496.353, y: -17.512) cubicCurveTo(controlX1: 477.679, controlY1: -106.966, controlX2: 445.841, controlY2: -187.284, x: 397.460, y: -255.736) cubicCurveTo(controlX1: 432.366, controlY1: -221.046, controlX2: 466.097, controlY2: -184.636, x: 498.390, y: -146.691) cubicCurveTo(controlX1: 465.048, controlY1: -223.173, controlX2: 423.580, controlY2: -290.180, x: 372.214, y: -345.000) cubicCurveTo(controlX1: 412.438, controlY1: -370.887, controlX2: 453.694, controlY2: -394.730, x: 496.077, y: -416.783) cubicCurveTo(controlX1: 464.052, controlY1: -411.223, controlX2: 433.587, controlY2: -403.863, x: 404.071, y: -394.849) cubicCurveTo(controlX1: 425.907, controlY1: -411.022, controlX2: 448.481, controlY2: -426.973, x: 471.095, y: -442.372) cubicCurveTo(controlX1: 433.108, controlY1: -430.462, controlX2: 396.462, controlY2: -416.597, x: 362.028, y: -400.939) cubicCurveTo(controlX1: 404.696, controlY1: -428.612, controlX2: 448.348, controlY2: -454.607, x: 493.032, y: -479.541) lineTo(x: 493.029, y: -479.541) cubicCurveTo(controlX1: 425.559, controlY1: -461.486, controlX2: 362.199, controlY2: -437.351, x: 304.031, y: -405.993) cubicCurveTo(controlX1: 247.737, controlY1: -447.783, controlX2: 182.021, controlY2: -477.780, x: 105.368, y: -493.673) lineTo(x: 105.367, y: -493.672) closePath() } } } }   35
  36. 36. Paths   36
  37. 37. Shapes start { stage(title: "GroovyFX Shape Demo", width: 400, height: 400, visible: true) { scene(fill: BLACK) { group(id: 'group') { rectangle x: 25, y: 25, width: 150, height: 75, arcWidth: 20, arcHeight: 20, fill: GROOVYBLUE, stroke: ORANGE, strokeWidth: 2 circle centerX: 150, centerY: 100, radius: 20, fill: RED svgPath content: """ M248.91 50c11.882-.006 23.875 1.018 35.857 3.13 85.207 15.025 152.077 81.895 167.102 167.102 15.023 85.208-24.944 170.917-99.874 214.178-32.782 18.927-69.254 27.996-105.463 27.553-46.555-.57-92.675-16.865-129.957-48.15l30.855-36.768c50.95 42.75 122.968 49.05 180.566 15.797 57.597-33.254 88.152-98.777 76.603-164.274-11.55-65.497-62.672-116.62-128.17-128.168-51.656-9.108-103.323 7.98-139.17 43.862L185 192H57V64l46.34 46.342C141.758 71.962 194.17 50.03 248.91 50z""", translateX: -130, translateY: -200, scaleX: 0.1, scaleY: 0.1, fill: WHITE, stroke: WHITE, strokeWidth: 2 // ...   37
  38. 38. Shapes and Animations polygon(id: 'triangle', points: [0, -10, 10, 10, -10, 10, 0, -10], translateX: 70, translateY: 60, scaleX: 2.0, scaleY: 2.0, fill: BLUE, onMousePressed: { if (rotation.status == Animation.Status.RUNNING) rotation.pause() else rotation.play() }) { rotation = rotateTransition 2.s, tween: LINEAR, to: -360, cycleCount: INDEFINITE } parallelTransition(onFinished: { println "parallel done" }) { translateTransition 3.s, tween: EASE_OUT, to: 100, onFinished: { println "translate done" } scaleTransition 3.s, interpolator: EASE_IN, to: 2.0, onFinished: { println "scale done" } }.playFromStart() } } } }   38
  39. 39. Shapes   39
  40. 40. Effects start { stage(title: 'Animation Demo', visible: true) { scene { rectangle(width: 800, height: 600, effect: blend(mode: "screen") { topInput { imageInput(source: "background-ripples.png", x: 0, y: 0) } bottomInput { color = colorInput( paint: radialGradient( center: [0.7, 0.05], radius: 0.6, stops: ["#767A7B", "#222222"] ).build(), x: 0, y: 0, width: 800, height: 600 ) } } ) // ...   40
  41. 41. Animated Objects circle id: 'circle', radius: 60, fill: radialGradient( center: [0.5, 0.5], radius: 0.7, stops: [ORANGE, DARKORANGE] ), effect: glow(level: 0.5) noparent { path(id: 'thePath', translateX: 50, translateY: 5) { moveTo(x: 100, y: 100) arcTo(x: 300, y: 100, radiusX: 5, radiusY: 10) lineTo(x: 600, y: 200) lineTo(x: 300, y: 500) arcTo(x: 150, y: 200, radiusX: 50, radiusY: 100) closePath() } } } } pathTransition(10.s, delay: 100.ms, node: circle, path: thePath, orientation: PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT ).play() // ...   41
  42. 42. Animated Background timeline(cycleCount: -1, autoReverse: true) { at(4.s) { change(color, "paint") to(x: 0.3, y: 0.95) tween new CenterInterpolator() onFinished { println "4 seconds elapsed" } } }.play() } class CenterInterpolator extends Interpolator { @Override Object interpolate(Object startValue, Object endValue, double fraction) { RadialGradient s = startValue return new RadialGradient( s.focusAngle, s.focusDistance, EASE_BOTH.interpolate(s.centerX, endValue.x, fraction), EASE_BOTH.interpolate(s.centerY, endValue.y, fraction), s.radius, s.proportional, s.cycleMethod, s.stops) } @Override protected double curve(double t) { return 0 } }   42
  43. 43. Animations   43
  44. 44. Animations DEMO   44
  45. 45. Enhancing GroovyFX   45
  46. 46. Using custom components start { def colors = [BLUE, GREEN, RED, hsb(67, 0.8, 0.91), rgb(39, 209, 100), rgba(20, 100, 150, 0.60), delegate."#AA4411"] as ObservableList stage(title: 'Custom Components Demo', visible: true) { scene() { borderPane { treeView(id: 'tree', showRoot: false, prefHeight: 300) { treeItem(expanded: true, value: "Root") { treeItem(value: "one") { treeItem(value: "one.one") treeItem(value: "one.two") treeItem(value: "one.three") graphic { rectangle(width: 20, height: 20, fill: RED) } } treeItem(value: "two") { treeItem(value: "two.one") treeItem(value: "two.two") treeItem(value: "two.three") graphic { rectangle(width: 20, height: 20, fill: GREEN) } } } } // ...   46
  47. 47. Using custom components tree.selectionModel.selectionMode = SelectionMode.SINGLE top { node new BreadCrumbBar(tree.root), selectedCrumb: bind(tree.selectionModel, 'selectedItem'), prefHeight: 30 } bottom { container new MigPane("wrap 2", "20[]5[fill, grow]20", ""), { 4.times { button("Button $it") } } } } } } }   47
  48. 48. Using custom components   48
  49. 49. Own Factories import groovyfx.javafx.factory.MessageFactory import org.controlsfx.control.GridView import org.controlsfx.control.cell.ColorGridCell groovyx.javafx.GroovyFX.start { delegate.registerBeanFactory('gridView', GridView) delegate.registerFactory('message', new MessageFactory()) stage(title: 'Custom Factory Demo', visible: true) { scene { stylesheets('groovyfx.css') vbox { label('Hello GroovyFX', styleClass: 'big') gridView(items: [RED, GREEN, YELLOW, SLATEGREY], prefHeight: 200, cellFactory: { new ColorGridCell() } ) message(message: 'This is a message') } } } }   49
  50. 50. Own Factories   50
  51. 51. Think Big Application Framework Java, Groovy and Kotlin JavaFX, Swing, Pivot, Lanterna MVC, MVVC, PresentationModel a lot of usefull stuff http://griffon-framework.org   51
  52. 52. Questions? Alexander Klein Branchmanager codecentric AG Curiestr. 2 70563 Stuttgart, Germany +49 (0) 172 529 40 20 alexander.klein@codecentric.de www.codecentric.de blog.codecentric.de @saschaklein   52

×