Use modern API of Kotlin 1.3 in JS canvas examples

This commit is contained in:
Ilya Gorbunov
2018-09-18 02:26:36 +03:00
parent 24e7bafa7d
commit 0ca59b3a40
4 changed files with 112 additions and 137 deletions
+30 -40
View File
@@ -1,13 +1,15 @@
/*
In this example strange creatures are watching the kotlin logo. You can drag'n'drop them as well as the logo.
Doubleclick to add more creatures but be careful. They may be watching you!
*/
* In this example strange creatures are watching the kotlin logo.
* You can drag'n'drop them as well as the logo. Doubleclick to add
* more creatures but be careful. They may be watching you!
*/
package creatures
import jquery.*
import kotlin.js.*
import kotlin.browser.window
import org.w3c.dom.*
import kotlin.browser.document
import kotlin.browser.window
import kotlin.math.*
fun getImage(path: String): HTMLImageElement {
@@ -30,7 +32,8 @@ abstract class Shape() {
abstract fun draw(state: CanvasState)
// these two abstract methods defines that our shapes can be dragged
abstract operator fun contains(mousePos: Vector): Boolean
operator abstract fun contains(mousePos: Vector): Boolean
abstract var pos: Vector
var selected: Boolean = false
@@ -57,9 +60,9 @@ abstract class Shape() {
val Kotlin = Logo(v(250.0, 75.0))
class Logo(override var pos: Vector) : Shape() {
val relSize: Double = 0.25
val relSize: Double = 0.15
val shadowOffset = v(-3.0, 3.0)
val imageSize = v(377.0, 393.0)
val imageSize = v(200.0, 200.0)
var size: Vector = imageSize * relSize
// get-only properties like this saves you lots of typing and are very expressive
val position: Vector
@@ -69,7 +72,7 @@ class Logo(override var pos: Vector) : Shape() {
fun drawLogo(state: CanvasState) {
size = imageSize * (state.size.x / imageSize.x) * relSize
// getKotlinLogo() is a 'magic' function here defined only for purposes of demonstration but in fact it just find an element containing the logo
state.context.drawImage(getImage("http://kotlin-demo.jetbrains.com/static/images/kotlinlogowobackground.png"), 0.0, 0.0,
state.context.drawImage(getImage("http://try.kotlinlang.org/static/images/canvas/Kotlin-logo.png"), 0.0, 0.0,
imageSize.x, imageSize.y,
position.x, position.y,
size.x, size.y)
@@ -82,8 +85,7 @@ class Logo(override var pos: Vector) : Shape() {
context.shadowed(shadowOffset, 0.2) {
drawLogo(state)
}
}
else {
} else {
drawLogo(state)
}
}
@@ -114,7 +116,7 @@ class Creature(override var pos: Vector, val state: CanvasState) : Shape() {
// defining more nice extension functions
fun CanvasRenderingContext2D.circlePath(position: Vector, rad: Double) {
arc(position.x, position.y, rad, 0.0, 2 * Math.PI, false)
arc(position.x, position.y, rad, 0.0, 2 * PI, false)
}
//notice we can use an extension function we just defined inside another extension function
@@ -128,8 +130,7 @@ class Creature(override var pos: Vector, val state: CanvasState) : Shape() {
val context = state.context
if (!selected) {
drawCreature(context)
}
else {
} else {
drawCreatureWithShadow(context)
}
}
@@ -156,7 +157,7 @@ class Creature(override var pos: Vector, val state: CanvasState) : Shape() {
val tailDirection = -directionToLogo
val tailPos = position + tailDirection * radius * 1.0
val tailSize = radius * 1.6
val angle = Math.PI / 6.0
val angle = PI / 6.0
val p1 = tailPos + tailDirection.rotatedBy(angle) * tailSize
val p2 = tailPos + tailDirection.rotatedBy(-angle) * tailSize
val middlePoint = position + tailDirection * radius * 1.0
@@ -189,13 +190,13 @@ class Creature(override var pos: Vector, val state: CanvasState) : Shape() {
}
class CanvasState(val canvas: HTMLCanvasElement) {
var width = canvas.width.toDouble()
var height = canvas.height.toDouble()
var width = canvas.width
var height = canvas.height
val size: Vector
get() = v(width, height)
get() = v(width.toDouble(), height.toDouble())
val context = creatures.context
var valid = false
var shapes = ArrayList<Shape>()
var shapes = mutableListOf<Shape>()
var selection: Shape? = null
var dragOff = Vector()
val interval = 1000 / 30
@@ -246,7 +247,7 @@ class CanvasState(val canvas: HTMLCanvasElement) {
var element: HTMLElement? = canvas
while (element != null) {
val el: HTMLElement = element
offset += Vector(el.offsetLeft, el.offsetTop)
offset += Vector(el.offsetLeft.toDouble(), el.offsetTop.toDouble())
element = el.offsetParent as HTMLElement?
}
return Vector(e.pageX, e.pageY) - offset
@@ -259,17 +260,17 @@ class CanvasState(val canvas: HTMLCanvasElement) {
fun clear() {
context.fillStyle = "#FFFFFF"
context.fillRect(0.0, 0.0, width, height)
context.fillRect(0.0, 0.0, width.toDouble(), height.toDouble())
context.strokeStyle = "#000000"
context.lineWidth = 4.0
context.strokeRect(0.0, 0.0, width, height)
context.strokeRect(0.0, 0.0, width.toDouble(), height.toDouble())
}
fun draw() {
if (valid) return
clear()
for (shape in shapes.reversed()) {
for (shape in shapes.asReversed()) {
shape.draw(this)
}
Kotlin.draw(this)
@@ -278,7 +279,7 @@ class CanvasState(val canvas: HTMLCanvasElement) {
}
class RadialGradientGenerator(val context: CanvasRenderingContext2D) {
val gradients = ArrayList<Array<out Pair<Double, String>>>()
val gradients = mutableListOf<Array<out Pair<Double, String>>>()
var current = 0
fun newColorStops(vararg colorStops: Pair<Double, String>) {
@@ -302,29 +303,26 @@ class RadialGradientGenerator(val context: CanvasRenderingContext2D) {
}
fun v(x: Double, y: Double) = Vector(x, y)
fun v(x: Int, y: Int) = Vector(x.toDouble(), y.toDouble())
class Vector(val x: Double = 0.0, val y: Double = 0.0) {
constructor(x: Int, y: Int) : this(x.toDouble(), y.toDouble())
operator fun plus(v: Vector) = v(x + v.x, y + v.y)
operator fun unaryMinus() = v(-x, -y)
operator fun minus(v: Vector) = v(x - v.x, y - v.y)
operator fun times(koef: Double) = v(x * koef, y * koef)
infix fun distanceTo(v: Vector) = Math.sqrt((this - v).sqr)
infix fun distanceTo(v: Vector) = sqrt((this - v).sqr)
fun rotatedBy(theta: Double): Vector {
val sin = Math.sin(theta)
val cos = Math.cos(theta)
val sin = sin(theta)
val cos = cos(theta)
return v(x * cos - y * sin, x * sin + y * cos)
}
fun isInRect(topLeft: Vector, size: Vector) = (x >= topLeft.x) && (x <= topLeft.x + size.x) &&
(y >= topLeft.y) && (y <= topLeft.y + size.y)
(y >= topLeft.y) && (y <= topLeft.y + size.y)
val sqr: Double
get() = x * x + y * y
val normalized: Vector
get() = this * (1.0 / Math.sqrt(sqr))
get() = this * (1.0 / sqrt(sqr))
}
fun main(args: Array<String>) {
@@ -340,11 +338,3 @@ fun main(args: Array<String>) {
}
}
fun <T> List<T>.reversed(): List<T> {
val result = ArrayList<T>()
var i = size
while (i > 0) {
result.add(get(--i))
}
return result
}
@@ -6,10 +6,12 @@ Note that only a subset of the api is supported for now.
package fancylines
import kotlin.js.*
import kotlin.browser.window
import org.w3c.dom.*
import jquery.*
import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.HTMLCanvasElement
import kotlin.browser.document
import kotlin.browser.window
import kotlin.random.*
fun main(args: Array<String>) {
jq {
@@ -26,25 +28,27 @@ class FancyLines() {
val context = canvas.getContext("2d") as CanvasRenderingContext2D
val height = canvas.height.toDouble()
val width = canvas.width.toDouble()
var x = width * Math.random()
var y = height * Math.random()
var hue = 0;
fun nextX() = Random.nextDouble(width)
fun nextY() = Random.nextDouble(height)
var x = nextX()
var y = nextY()
var hue = 0
fun line() {
context.save();
context.beginPath();
context.lineWidth = 20.0 * Math.random();
context.lineWidth = Random.nextDouble(20.0)
context.moveTo(x, y);
x = width * Math.random();
y = height * Math.random();
x = nextX()
y = nextY()
context.bezierCurveTo(width * Math.random(), height * Math.random(),
width * Math.random(), height * Math.random(), x, y);
context.bezierCurveTo(nextX(), nextY(),
nextX(), nextY(), x, y)
hue += (Math.random() * 10).toInt();
hue += Random.nextInt(10)
context.strokeStyle = "hsl($hue, 50%, 50%)";
@@ -3,10 +3,13 @@
*/
package hello
import kotlin.js.*
import kotlin.browser.window
import org.w3c.dom.*
import jquery.*
import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.HTMLCanvasElement
import kotlin.browser.document
import kotlin.browser.window
import kotlin.random.*
import kotlin.math.*
val canvas: HTMLCanvasElement
get() {
@@ -19,21 +22,21 @@ val context: CanvasRenderingContext2D
}
val width: Double
val width: Int
get() {
return canvas.width.toDouble()
return canvas.width
}
val height: Double
val height: Int
get() {
return canvas.height.toDouble()
return canvas.height
}
// class representing a floating text
class HelloKotlin() {
var relX = 0.2 + 0.2 * Math.random()
var relY = 0.4 + 0.2 * Math.random()
var relX = Random.nextDouble(0.2, 0.4)
var relY = Random.nextDouble(0.4, 0.6)
val absX: Double
get() = (relX * width)
@@ -66,28 +69,25 @@ class HelloKotlin() {
fun move() {
val relTextWidth = textWidthInPixels / width
if (relX > (1.0 - relTextWidth - relXVelocity.abs) || relX < relXVelocity.abs) {
if (relX > (1.0 - relTextWidth - relXVelocity.absoluteValue) || relX < relXVelocity.absoluteValue) {
relXVelocity *= -1
}
val relTextHeight = textHeightInPixels / height
if (relY > (1.0 - relYVelocity.abs) || relY < relYVelocity.abs + relTextHeight) {
if (relY > (1.0 - relYVelocity.absoluteValue) || relY < relYVelocity.absoluteValue + relTextHeight) {
relYVelocity *= -1
}
relX += relXVelocity
relY += relYVelocity
}
fun randomVelocity() = 0.03 * Math.random() * (if (Math.random() < 0.5) 1 else -1)
fun randomVelocity() = Random.nextDouble(-0.03, 0.03) // same as 0.03 * Math.random() * (if (Math.random() < 0.5) 1 else -1)
val Double.abs: Double
get() = if (this > 0) this else -this
}
fun renderBackground() {
context.save()
context.fillStyle = "#5C7EED"
context.fillRect(0.0, 0.0, width, height)
context.fillRect(0.0, 0.0, width.toDouble(), height.toDouble())
context.restore()
}
@@ -107,3 +107,5 @@ fun main(args: Array<String>) {
}, interval)
}
}
@@ -1,9 +1,16 @@
/*
* In this example you can see a crossroads. Traffic light change color by timer,
* but you can change it manually using controls at the right part of screen.
*/
package traffic
import kotlin.js.*
import kotlin.browser.window
import org.w3c.dom.*
import jquery.*
import org.w3c.dom.*
import kotlin.browser.document
import kotlin.browser.window
import kotlin.math.*
import kotlin.random.*
import kotlin.js.Date
fun getImage(path: String): HTMLImageElement {
val image = window.document.createElement("img") as HTMLImageElement
@@ -22,22 +29,10 @@ val context: CanvasRenderingContext2D
}
val PATH_TO_IMAGES = "http://kotlin-demo.jetbrains.com/static/images/canvas/"
val PATH_TO_IMAGES = "http://try.kotlinlang.org/static/images/canvas/"
var _state: CanvasState? = null
val state: CanvasState
get() {
if (_state == null) {
_state = CanvasState(canvas)
}
return _state!!
}
val colors: Colors
get() {
return Colors()
}
val state: CanvasState by lazy { CanvasState(canvas) }
var trafficLightUp = TrafficLight(v(180.0, 181.0), "up", "red")
var trafficLightDown = TrafficLight(v(100.0, 77.0), "down", "red")
@@ -63,7 +58,6 @@ fun main(args: Array<String>) {
}
fun v(x: Double, y: Double) = Vector(x, y)
fun v(x: Int, y: Int) = Vector(x, y)
class Image(val src: String, override var pos: Vector, var imageSize: Vector) : Shape() {
override fun draw() {
@@ -73,7 +67,7 @@ class Image(val src: String, override var pos: Vector, var imageSize: Vector) :
imageSize.x, imageSize.y)
}
operator fun contains(mousePos: Vector): Boolean = mousePos.isInRect(pos, imageSize)
fun contains(mousePos: Vector): Boolean = mousePos.isInRect(pos, imageSize)
}
class Button(val src: String, override var pos: Vector, var imageSize: Vector) : Shape() {
@@ -88,16 +82,14 @@ class Button(val src: String, override var pos: Vector, var imageSize: Vector) :
pos.x, pos.y,
imageSize.x, imageSize.y)
}
}
else if (isMouseDown) {
} else if (isMouseDown) {
state.context.shadowed(v(-3.0, 3.0), 0.8) {
state.context.drawImage(getImage(src), 0.0, 0.0,
imageSize.x, imageSize.y,
pos.x, pos.y,
imageSize.x, imageSize.y)
}
}
else {
} else {
state.context.drawImage(getImage(src), 0.0, 0.0,
imageSize.x, imageSize.y,
pos.x, pos.y,
@@ -129,14 +121,14 @@ class Border() : Shape() {
override var pos = Vector(4.0, 4.0);
override fun draw() {
state.context.fillStyle = colors.white
state.context.fillStyle = Colors.white
state.context.fillRect(2.0, 4.0, 10.0, 292.0)
state.context.fillRect(330.0, 4.0, 370.0, 292.0)
state.context.fillRect(2.0, 2.0, 330.0, 10.0)
state.context.fillRect(4.0, 265.0, 340.0, 380.0)
state.context.strokeStyle = colors.black
state.context.strokeStyle = Colors.black
state.context.lineWidth = 4.0
state.context.strokeRect(0.0, 0.0, state.width, state.height)
state.context.strokeRect(0.0, 0.0, state.width.toDouble(), state.height.toDouble())
}
}
@@ -149,7 +141,7 @@ class Timer(override var pos: Vector) : Shape() {
override fun draw() {
timeLeftForChangeColor = ("" + (timerLength - (Date().getTime() - timeStartLastChangeColor) / 1000)).get(0)
state.context.font = "bold 9px Arial, serif"
state.context.fillStyle = colors.black
state.context.fillStyle = Colors.black
state.context.fillText("" + timeLeftForChangeColor, pos.x, pos.y)
}
@@ -160,7 +152,7 @@ class Timer(override var pos: Vector) : Shape() {
}
//Colors constants
class Colors() {
object Colors {
val black: String = "#000000"
val white = "#FFFFFF"
val grey = "#C0C0C0"
@@ -170,12 +162,12 @@ class Colors() {
}
class TrafficLight(override var pos: Vector, val direction: String, val startColor: String) : Shape() {
val list = ArrayList<TrafficLightItem>()
val list = mutableListOf<TrafficLightItem>()
var size = Vector(27.0, 34.0);
var timer = Timer(Vector(pos.x + 6, pos.y + 12))
var currentColor = startColor;
var isForceColorChange = false
private var changeColorForward = (startColor == "red")
var shouldChangeColorForward = (startColor == "red")
init {
list.add(TrafficLightItem(v(pos.x, pos.y), PATH_TO_IMAGES + "red_color.png"))
@@ -211,11 +203,11 @@ class TrafficLight(override var pos: Vector, val direction: String, val startCol
}
fun changeColor() {
if (changeColorForward) changeColorForward() else changeColorBackward()
if (shouldChangeColorForward) changeColorForward() else changeColorBackward()
}
fun changeColorForward() {
changeColorForward = false
shouldChangeColorForward = false
currentColor = "yellow"
window.setTimeout({
if (!isForceColorChange) timer.resetTimer() else isForceColorChange = false
@@ -226,7 +218,7 @@ class TrafficLight(override var pos: Vector, val direction: String, val startCol
fun changeColorBackward() {
changeColorForward = true
shouldChangeColorForward = true
currentColor = "green_flash"
window.setTimeout({
currentColor = "yellow"
@@ -261,12 +253,10 @@ class TrafficLightItem(override var pos: Vector, val imageSrc: String) : Shape()
if (countOfFlash > 6) {
isFlashNow = false
countOfFlash = 0
}
else {
} else {
countOfFlash++
}
}
else {
} else {
state.context.drawImage(getImage(PATH_TO_IMAGES + "green_color.png"), 0.0, 0.0,
imageSize.x, imageSize.y,
pos.x, pos.y,
@@ -274,13 +264,11 @@ class TrafficLightItem(override var pos: Vector, val imageSrc: String) : Shape()
if (countOfFlash > 6) {
isFlashNow = true
countOfFlash = 0
}
else {
} else {
countOfFlash++
}
}
}
else {
} else {
state.context.drawImage(getImage(imageSrc), 0.0, 0.0,
imageSize.x, imageSize.y,
pos.x, pos.y,
@@ -292,7 +280,8 @@ class TrafficLightItem(override var pos: Vector, val imageSrc: String) : Shape()
class Car(override var pos: Vector, val direction: String, val color: String) : Shape() {
val imageSize = v(25.0, 59.0)
var speed = getRandomArbitary(2, 10);
fun randomSpeed() = Random.nextDouble(2.0, 10.0)
var speed = randomSpeed()
override fun draw() {
if (direction == "up" || direction == "down") {
@@ -302,21 +291,18 @@ class Car(override var pos: Vector, val direction: String, val color: String) :
imageSize.x, imageSize.y)
if ((!isNearStopLine()) || (trafficLightUp.canMove() && isNearStopLine()) ) {
move()
} else {
speed = randomSpeed()
}
else {
speed = getRandomArbitary(2, 10)
}
}
else {
} else {
state.context.drawImage(getImage(PATH_TO_IMAGES + color + "_car.png"), 0.0, 0.0,
imageSize.y, imageSize.x,
pos.x, pos.y,
imageSize.y, imageSize.x)
if ((!isNearStopLine()) || (trafficLightLeft.canMove() && isNearStopLine()) ) {
move()
}
else {
speed = getRandomArbitary(2, 10)
} else {
speed = randomSpeed()
}
}
@@ -368,13 +354,13 @@ class Map(override var pos: Vector) : Shape() {
class CanvasState(val canvas: HTMLCanvasElement) {
val context = traffic.context
var shapes = ArrayList<Shape>()
var shapes = mutableListOf<Shape>()
var width = canvas.width.toDouble()
var height = canvas.height.toDouble()
var width = canvas.width
var height = canvas.height
val size: Vector
get() = v(width, height)
get() = v(width.toDouble(), height.toDouble())
fun addShape(shape: Shape) {
shapes.add(shape)
@@ -414,7 +400,6 @@ class CanvasState(val canvas: HTMLCanvasElement) {
val mousePos = mousePos(it)
for (shape in shapes) {
if (shape is Button && mousePos in shape) {
val name = shape.src
shape.mouseOver()
}
}
@@ -440,7 +425,7 @@ class CanvasState(val canvas: HTMLCanvasElement) {
var element: HTMLElement? = canvas
while (element != null) {
val el: HTMLElement = element
offset += Vector(el.offsetLeft, el.offsetTop)
offset += Vector(el.offsetLeft.toDouble(), el.offsetTop.toDouble())
element = el.offsetParent as HTMLElement?
}
return Vector(e.pageX, e.pageY) - offset
@@ -454,8 +439,8 @@ class CanvasState(val canvas: HTMLCanvasElement) {
}
fun clear() {
context.fillStyle = colors.white
context.fillRect(0.0, 0.0, width, height)
context.fillStyle = Colors.white
context.fillRect(0.0, 0.0, width.toDouble(), height.toDouble())
}
}
@@ -484,28 +469,22 @@ abstract class Shape() {
}
class Vector(val x: Double = 0.0, val y: Double = 0.0) {
constructor(x: Int, y: Int) : this(x.toDouble(), y.toDouble())
operator fun plus(v: Vector) = v(x + v.x, y + v.y)
operator fun unaryMinus() = v(-x, -y)
operator fun minus(v: Vector) = v(x - v.x, y - v.y)
operator fun times(koef: Double) = v(x * koef, y * koef)
fun distanceTo(v: Vector) = Math.sqrt((this - v).sqr)
fun distanceTo(v: Vector) = sqrt((this - v).sqr)
fun rotatedBy(theta: Double): Vector {
val sin = Math.sin(theta)
val cos = Math.cos(theta)
val sin = sin(theta)
val cos = cos(theta)
return v(x * cos - y * sin, x * sin + y * cos)
}
fun isInRect(topLeft: Vector, size: Vector) = (x >= topLeft.x) && (x <= topLeft.x + size.x) &&
(y >= topLeft.y) && (y <= topLeft.y + size.y)
(y >= topLeft.y) && (y <= topLeft.y + size.y)
val sqr: Double
get() = x * x + y * y
val normalized: Vector
get() = this * (1.0 / Math.sqrt(sqr))
get() = this * (1.0 / sqrt(sqr))
}
fun getRandomArbitary(min: Int, max: Int): Double {
return Math.random() * (max - min) + min;
}