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 Gr8Conf EU 2017

430 views

Published on

Slides from my GroovyFX - groove JavaFX talk at Gr8Conf EU 2017

Published in: Software
  • Be the first to comment

  • Be the first to like this

GroovyFX - groove JavaFX Gr8Conf EU 2017

  1. 1. GroovyFX - Groove JavaFX_ Alexander (Sascha) Klein <alexander.klein@codecentric.de>   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 myField = textField() label text: bind(myField.textProperty()) label text: bind(myField.text()) label text: bind{myField.text} label text: bind(myField, 'text') label text: bind(myField, 'text').using{ "Text: $it" } label id: 'lastLabel' bind lastLabel.text() to myField.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 -> List<Color> 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) return 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

×