1 /*
2  * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package examples
6 
7 import javafx.application.*
8 import javafx.scene.*
9 import javafx.scene.control.*
10 import javafx.scene.layout.*
11 import javafx.scene.paint.*
12 import javafx.scene.shape.*
13 import javafx.stage.*
14 import kotlinx.coroutines.*
15 import kotlinx.coroutines.javafx.*
16 import java.text.*
17 import java.util.*
18 import kotlin.coroutines.*
19 
mainnull20 fun main(args: Array<String>) {
21     Application.launch(FxTestApp::class.java, *args)
22 }
23 
lognull24 fun log(msg: String) = println("${SimpleDateFormat("yyyyMMdd-HHmmss.sss").format(Date())} [${Thread.currentThread().name}] $msg")
25 
26 class FxTestApp : Application(), CoroutineScope {
27     val buttons = FlowPane().apply {
28         children += Button("Rect").apply {
29             setOnAction { doRect() }
30         }
31         children += Button("Circle").apply {
32             setOnAction { doCircle() }
33         }
34         children += Button("Clear").apply {
35             setOnAction { doClear() }
36         }
37     }
38 
39     val root = Pane().apply {
40         children += buttons
41     }
42 
43     val scene = Scene(root, 600.0, 400.0)
44 
45     override fun start(stage: Stage) {
46         stage.title = "Hello world!"
47         stage.scene = scene
48         stage.show()
49     }
50 
51     val random = Random()
52     var animationIndex = 0
53     var job = Job()
54     override val coroutineContext: CoroutineContext
55         get() = Dispatchers.JavaFx + job
56 
57     private fun animation(node: Node, block: suspend CoroutineScope.() -> Unit) {
58         root.children += node
59         launch(block = block).also {
60             it.invokeOnCompletion { root.children -= node }
61         }
62     }
63 
64     fun doRect() {
65         val node = Rectangle(20.0, 20.0).apply {
66             fill = Color.RED
67         }
68         val index = ++animationIndex
69         val speed = 5.0
70         animation(node) {
71             log("Started new 'rect' coroutine #$index")
72             var vx = speed
73             var vy = speed
74             var counter = 0
75             while (true) {
76                 awaitPulse()
77                 node.x += vx
78                 node.y += vy
79                 val xRange = 0.0 .. scene.width - node.width
80                 val yRange = 0.0 .. scene.height - node.height
81                 if (node.x !in xRange ) {
82                     node.x = node.x.coerceIn(xRange)
83                     vx = -vx
84                 }
85                 if (node.y !in yRange) {
86                     node.y = node.y.coerceIn(yRange)
87                     vy = -vy
88                 }
89                 if (counter++ > 100) {
90                     counter = 0
91                     delay(1000) // pause a bit
92                     log("Delayed #$index for a while, resume and turn")
93                     val t = vx
94                     vx = vy
95                     vy = -t
96                 }
97             }
98         }
99     }
100 
101     fun doCircle() {
102         val node = Circle(20.0).apply {
103             fill = Color.BLUE
104         }
105         val index = ++animationIndex
106         val acceleration = 0.1
107         val maxSpeed = 5.0
108         animation(node) {
109             log("Started new 'circle' coroutine #$index")
110             var sx = random.nextDouble() * maxSpeed
111             var sy = random.nextDouble() * maxSpeed
112             while (true) {
113                 awaitPulse()
114                 val dx = root.width / 2 - node.translateX
115                 val dy = root.height / 2 - node.translateY
116                 val dn = Math.sqrt(dx * dx + dy * dy)
117                 sx += dx / dn * acceleration
118                 sy += dy / dn * acceleration
119                 val sn = Math.sqrt(sx * sx + sy * sy)
120                 val trim = sn.coerceAtMost(maxSpeed)
121                 sx = sx / sn * trim
122                 sy = sy / sn * trim
123                 node.translateX += sx
124                 node.translateY += sy
125             }
126         }
127     }
128 
129     fun doClear() {
130         job.cancel()
131         job = Job()
132     }
133 }