• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 import kotlinx.coroutines.*
6 import kotlinx.html.*
7 import kotlinx.html.div
8 import kotlinx.html.dom.*
9 import kotlinx.html.js.onClickFunction
10 import org.w3c.dom.*
11 import kotlinx.browser.*
12 import kotlin.coroutines.*
13 import kotlin.math.*
14 import kotlin.random.Random
15 
16 external fun require(resource: String)
17 
18 fun main() {
19     require("style.css")
20     println("Starting example application...")
21     document.addEventListener("DOMContentLoaded", {
22         Application().start()
23     })
24 }
25 
26 val Double.px get() = "${this}px"
27 
setSizenull28 private fun HTMLElement.setSize(w: Double, h: Double) {
29     with(style) {
30         width = w.px
31         height = h.px
32     }
33 }
34 
HTMLElementnull35 private fun HTMLElement.setPosition(x: Double, y: Double) {
36     with(style) {
37         left = x.px
38         top = y.px
39     }
40 }
41 
42 class Application : CoroutineScope {
43     private val body get() = document.body!!
44     private val scene get() = document.getElementById("scene") as HTMLElement
45     private val sw = 800.0
46     private val sh = 600.0
47     private var animationIndex = 0
48     private var job = Job()
49     override val coroutineContext: CoroutineContext
50         get() = job
51 
startnull52     fun start() {
53         body.append.div("content") {
54             h1 {
55                 +"Kotlin Coroutines JS Example"
56             }
57             div {
58                 button {
59                     +"Rect"
60                     onClickFunction = { onRect() }
61                 }
62                 button {
63                     +"Circle"
64                     onClickFunction = { onCircle() }
65                 }
66                 button {
67                     +"Clear"
68                     onClickFunction = { onClear() }
69                 }
70             }
71             div {
72                 id = "scene"
73             }
74         }
75         scene.setSize(sw, sh)
76     }
77 
animationnull78     private fun animation(cls: String, size: Double, block: suspend CoroutineScope.(HTMLElement) -> Unit) {
79         val elem = scene.append.div(cls)
80         elem.setSize(size, size)
81         val job = launch {
82             block(elem)
83         }
84 
85         job.invokeOnCompletion { scene.removeChild(elem) }
86     }
87 
onRectnull88     private fun onRect() {
89         val index = ++animationIndex
90         val speed = 0.3
91         val rs = 20.0
92         val turnAfter = 5000.0 // seconds
93         val maxX = sw - rs
94         val maxY = sh - rs
95         animation("rect", rs) { rect ->
96             println("Started new 'rect' coroutine #$index")
97             val timer = AnimationTimer()
98             var turnTime = timer.time + turnAfter
99             val turnTimePhase = turnTime - floor(turnTime / turnAfter) * turnAfter
100             var vx = speed
101             var vy = speed
102             var x = 0.0
103             var y = 0.0
104             while (true) {
105                 val dt = timer.await()
106                 x += vx * dt
107                 y += vy * dt
108                 if (x > maxX) {
109                     x = 2 * maxX - x
110                     vx = -vx
111                 }
112                 if (x < 0) {
113                     x = -x
114                     vx = -vx
115                 }
116                 if (y > maxY) {
117                     y = 2 * maxY - y
118                     vy = -vy
119                 }
120                 if (y < 0) {
121                     y = -y
122                     vy = -vy
123                 }
124                 rect.setPosition(x, y)
125                 if (timer.time >= turnTime) {
126                     timer.delay(1000) // pause a bit
127                     // flip direction
128                     val t = vx
129                     if (Random.nextDouble() > 0.5) {
130                         vx = vy
131                         vy = -t
132                     } else {
133                         vx = -vy
134                         vy = t
135                     }
136                     // reset time, but keep turning time phase
137                     turnTime = ceil(timer.reset() / turnAfter) * turnAfter + turnTimePhase
138                     println("Delayed #$index for a while at ${timer.time}, resumed and turned")
139                 }
140             }
141         }
142     }
143 
onCirclenull144     private fun onCircle() {
145         val index = ++animationIndex
146         val acceleration = 5e-4
147         val initialRange = 0.7
148         val maxSpeed = 0.4
149         val initialSpeed = 0.1
150         val radius = 20.0
151         animation("circle", radius) { circle ->
152             println("Started new 'circle' coroutine #$index")
153             val timer = AnimationTimer()
154             val initialAngle = Random.nextDouble() * 2 * PI
155             var vx = sin(initialAngle) * initialSpeed
156             var vy = cos(initialAngle) * initialSpeed
157             var x = (Random.nextDouble() * initialRange + (1 - initialRange) / 2) * sw
158             var y = (Random.nextDouble() * initialRange + (1 - initialRange) / 2) * sh
159             while (true) {
160                 val dt = timer.await()
161                 val dx = sw / 2 - x
162                 val dy = sh / 2 - y
163                 val dn = sqrt(dx * dx + dy * dy)
164                 vx += dx / dn * acceleration * dt
165                 vy += dy / dn * acceleration * dt
166                 val vn = sqrt(vx * vx + vy * vy)
167                 val trim = vn.coerceAtMost(maxSpeed)
168                 vx = vx / vn * trim
169                 vy = vy / vn * trim
170                 x += vx * dt
171                 y += vy * dt
172                 circle.setPosition(x, y)
173             }
174         }
175 
176     }
177 
onClearnull178     private fun onClear() {
179         job.cancel()
180         job = Job()
181     }
182 }
183 
184 class AnimationTimer {
185     var time = window.performance.now()
186 
awaitnull187     suspend fun await(): Double {
188         val newTime = window.awaitAnimationFrame()
189         val dt = newTime - time
190         time = newTime
191         return dt.coerceAtMost(200.0) // at most 200ms
192     }
193 
resetnull194     fun reset(): Double {
195         time = window.performance.now()
196         return time
197     }
198 
delaynull199     suspend fun delay(i: Int) {
200         var dt = 0.0
201         while (dt < i) {
202             dt += await()
203         }
204     }
205 }
206