Creatures example working with the new library

This commit is contained in:
pTalanov
2012-05-31 19:22:35 +04:00
parent 0fb7032f4c
commit 44c0baa9e1
@@ -1,33 +1,47 @@
/* /*
In this example strange creatures are watching the kotlin logo. You can drag'n'drop them as well as the logo. 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! Doubleclick to add more creatures but be careful. They may be watching you!
*/ */
package creatures package creatures
// importing some of the API defined
import jquery.*
import html5.*
import java.util.ArrayList
import js.*
import js.Math
import js.setInterval
import html5.getCanvas
import html5.getKotlinLogo
import jquery.jq
import js.setTimeout
import java.util.List
import js.dom.html5.CanvasContext
import js.jquery.*
import js.dom.html5.CanvasGradient
import js.dom.html5.HTMLCanvasElement
import java.util.ArrayList
import java.util.List
import js.dom.html.window
import js.dom.html.HTMLElement
import js.dom.html.HTMLImageElement
fun getImage(path: String): HTMLImageElement {
val image = window.document.createElement("img") as HTMLImageElement
image.src = path
return image
}
val canvas: HTMLCanvasElement
get() {
return window.document.getElementsByTagName("canvas").item(0)!! as HTMLCanvasElement
}
val context: CanvasContext
get() {
return canvas.getContext("2d")!!
}
abstract class Shape() { abstract class Shape() {
abstract fun draw(state : CanvasState) abstract fun draw(state: CanvasState)
// these two abstract methods defines that our shapes can be dragged // these two abstract methods defines that our shapes can be dragged
abstract fun contains(mousePos : Vector) : Boolean abstract fun contains(mousePos: Vector): Boolean
abstract var pos : Vector abstract var pos: Vector
var selected : Boolean = false var selected: Boolean = false
// a couple of helper extension methods we'll be using in the derived classes // a couple of helper extension methods we'll be using in the derived classes
fun Context.shadowed(shadowOffset : Vector, alpha : Double, render : Context.() -> Unit) { fun CanvasContext.shadowed(shadowOffset: Vector, alpha: Double, render: CanvasContext.() -> Unit) {
save() save()
shadowColor = "rgba(100, 100, 100, $alpha)" shadowColor = "rgba(100, 100, 100, $alpha)"
shadowBlur = 5.0 shadowBlur = 5.0
@@ -37,7 +51,7 @@ abstract class Shape() {
restore() restore()
} }
fun Context.fillPath(constructPath : Context.() -> Unit) { fun CanvasContext.fillPath(constructPath: CanvasContext.() -> Unit) {
beginPath() beginPath()
constructPath() constructPath()
closePath() closePath()
@@ -47,27 +61,26 @@ abstract class Shape() {
val Kotlin = Logo(v(250.0, 75.0)) val Kotlin = Logo(v(250.0, 75.0))
class Logo(override var pos : Vector) : Shape() class Logo(override var pos: Vector): Shape()
{ {
val relSize : Double = 0.25 val relSize: Double = 0.25
val shadowOffset = v(-3.0, 3.0) val shadowOffset = v(- 3.0, 3.0)
val imageSize = v(377.0, 393.0) val imageSize = v(377.0, 393.0)
var size : Vector = imageSize * relSize var size: Vector = imageSize * relSize
// get-only properties like this saves you lots of typing and are very expressive // get-only properties like this saves you lots of typing and are very expressive
val position : Vector val position: Vector
get() = if (selected) pos - shadowOffset else pos get() = if (selected) pos - shadowOffset else pos
fun drawLogo(state : CanvasState) { fun drawLogo(state: CanvasState) {
size = imageSize * (state.size.x / imageSize.x) * relSize 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,
state.context.drawImage(getImage("/static/images/kotlinlogowobackground.png"), 0, 0, imageSize.x.toInt(), imageSize.y.toInt(),
imageSize.x.toInt(), imageSize.y.toInt(), position.x.toInt(), position.y.toInt(),
position.x.toInt(), position.y.toInt(), size.x.toInt(), size.y.toInt())
size.x.toInt(), size.y.toInt())
} }
override fun draw(state : CanvasState) { override fun draw(state: CanvasState) {
val context = state.context val context = state.context
if (selected) { if (selected) {
// using helper we defined in Shape class // using helper we defined in Shape class
@@ -81,41 +94,47 @@ class Logo(override var pos : Vector) : Shape()
override fun contains(mousePos: Vector): Boolean = mousePos.isInRect(pos, size) override fun contains(mousePos: Vector): Boolean = mousePos.isInRect(pos, size)
val centre : Vector val centre: Vector
get() = pos + size * 0.5 get() = pos + size * 0.5
} }
val gradientGenerator = RadialGradientGenerator(getContext()) val gradientGenerator: RadialGradientGenerator? = null
get() {
if ($gradientGenerator == null) {
$gradientGenerator = RadialGradientGenerator(context)
}
return $gradientGenerator
}
class Creature(override var pos : Vector, val state : CanvasState) : Shape() { class Creature(override var pos: Vector, val state: CanvasState): Shape() {
val shadowOffset = v(-5.0, 5.0) val shadowOffset = v(- 5.0, 5.0)
val colorStops = gradientGenerator.getNext() val colorStops = gradientGenerator!!.getNext()
val relSize = 0.05 val relSize = 0.05
// these properties have no backing fields and in java/javascript they could be represented as little helper functions // these properties have no backing fields and in java/javascript they could be represented as little helper functions
val radius : Double val radius: Double
get() = state.width * relSize get() = state.width * relSize
val position : Vector val position: Vector
get() = if (selected) pos - shadowOffset else pos get() = if (selected) pos - shadowOffset else pos
val directionToLogo : Vector val directionToLogo: Vector
get() = (Kotlin.centre - position).normalized get() = (Kotlin.centre - position).normalized
//notice how the infix call can make some expressions extremely expressive //notice how the infix call can make some expressions extremely expressive
override fun contains(mousePos : Vector) = pos distanceTo mousePos < radius override fun contains(mousePos: Vector) = pos distanceTo mousePos < radius
// defining more nice extension functions // defining more nice extension functions
fun Context.circlePath(position : Vector, rad : Double) { fun CanvasContext.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 * Math.PI, false)
} }
//notice we can use an extension function we just defined inside another extension function //notice we can use an extension function we just defined inside another extension function
fun Context.fillCircle(position : Vector, rad : Double) { fun CanvasContext.fillCircle(position: Vector, rad: Double) {
fillPath { fillPath {
circlePath(position, rad) circlePath(position, rad)
} }
} }
override fun draw(state : CanvasState) { override fun draw(state: CanvasState) {
val context = state.context val context = state.context
if (!selected) { if (!selected) {
drawCreature(context) drawCreature(context)
@@ -124,7 +143,7 @@ class Creature(override var pos : Vector, val state : CanvasState) : Shape() {
} }
} }
fun drawCreature(context : Context) { fun drawCreature(context: CanvasContext) {
context.fillStyle = getGradient(context) context.fillStyle = getGradient(context)
context.fillPath { context.fillPath {
tailPath(context) tailPath(context)
@@ -133,22 +152,22 @@ class Creature(override var pos : Vector, val state : CanvasState) : Shape() {
drawEye(context) drawEye(context)
} }
fun getGradient(context : Context) : CanvasGradient { fun getGradient(context: CanvasContext): CanvasGradient {
val gradientCentre = position + directionToLogo * (radius / 4) val gradientCentre = position + directionToLogo * (radius / 4)
val gradient = context.createRadialGradient(gradientCentre.x, gradientCentre.y, 1.0, gradientCentre.x, gradientCentre.y, 2 * radius) val gradient = context.createRadialGradient(gradientCentre.x, gradientCentre.y, 1.0, gradientCentre.x, gradientCentre.y, 2 * radius)!!
for (colorStop in colorStops) { for (colorStop in colorStops) {
gradient.addColorStop(colorStop._1, colorStop._2) gradient.addColorStop(colorStop._1, colorStop._2)
} }
return gradient return gradient
} }
fun tailPath(context : Context) { fun tailPath(context: CanvasContext) {
val tailDirection = -directionToLogo val tailDirection = - directionToLogo
val tailPos = position + tailDirection * radius * 1.0 val tailPos = position + tailDirection * radius * 1.0
val tailSize = radius * 1.6 val tailSize = radius * 1.6
val angle = Math.PI / 6.0 val angle = Math.PI / 6.0
val p1 = tailPos + tailDirection.rotatedBy(angle) * tailSize val p1 = tailPos + tailDirection.rotatedBy(angle) * tailSize
val p2 = tailPos + tailDirection.rotatedBy(-angle) * tailSize val p2 = tailPos + tailDirection.rotatedBy(- angle) * tailSize
val middlePoint = position + tailDirection * radius * 1.0 val middlePoint = position + tailDirection * radius * 1.0
context.moveTo(tailPos.x.toInt(), tailPos.y.toInt()) context.moveTo(tailPos.x.toInt(), tailPos.y.toInt())
context.lineTo(p1.x.toInt(), p1.y.toInt()) context.lineTo(p1.x.toInt(), p1.y.toInt())
@@ -156,7 +175,7 @@ class Creature(override var pos : Vector, val state : CanvasState) : Shape() {
context.lineTo(tailPos.x.toInt(), tailPos.y.toInt()) context.lineTo(tailPos.x.toInt(), tailPos.y.toInt())
} }
fun drawEye(context : Context) { fun drawEye(context: CanvasContext) {
val eyePos = directionToLogo * radius * 0.6 + position val eyePos = directionToLogo * radius * 0.6 + position
val eyeRadius = radius / 3 val eyeRadius = radius / 3
val eyeLidRadius = eyeRadius / 2 val eyeLidRadius = eyeRadius / 2
@@ -166,7 +185,7 @@ class Creature(override var pos : Vector, val state : CanvasState) : Shape() {
context.fillCircle(eyePos, eyeLidRadius) context.fillCircle(eyePos, eyeLidRadius)
} }
fun drawCreatureWithShadow(context : Context) { fun drawCreatureWithShadow(context: CanvasContext) {
context.shadowed(shadowOffset, 0.7) { context.shadowed(shadowOffset, 0.7) {
context.fillStyle = getGradient(context) context.fillStyle = getGradient(context)
fillPath { fillPath {
@@ -178,15 +197,15 @@ class Creature(override var pos : Vector, val state : CanvasState) : Shape() {
} }
} }
class CanvasState(val canvas : Canvas) { class CanvasState(val canvas: HTMLCanvasElement) {
var width = canvas.width.toDouble() var width = canvas.width
var height = canvas.height.toDouble() var height = canvas.height
val size : Vector val size: Vector
get() = v(width, height) get() = v(width, height)
val context = getContext() val context = creatures.context
var valid = false var valid = false
var shapes = ArrayList<Shape>() var shapes = ArrayList<Shape>()
var selection : Shape? = null var selection: Shape? = null
var dragOff = Vector() var dragOff = Vector()
val interval = 1000 / 30 val interval = 1000 / 30
@@ -231,18 +250,18 @@ class CanvasState(val canvas : Canvas) {
}, interval) }, interval)
} }
fun mousePos(e : MouseEvent) : Vector { fun mousePos(e: MouseEvent): Vector {
var offset = Vector() var offset = Vector()
var element : DomElement? = canvas var element: HTMLElement? = canvas
while (element != null) { while (element != null) {
val el : DomElement = element.sure() val el: HTMLElement = element.sure()
offset += Vector(el.offsetLeft, el.offsetTop) offset += Vector(el.offsetLeft, el.offsetTop)
element = el.offsetParent element = el.offsetParent
} }
return Vector(e.pageX, e.pageY) - offset return Vector(e.pageX, e.pageY) - offset
} }
fun addShape(shape : Shape) { fun addShape(shape: Shape) {
shapes.add(shape) shapes.add(shape)
valid = false valid = false
} }
@@ -267,11 +286,11 @@ class CanvasState(val canvas : Canvas) {
} }
} }
class RadialGradientGenerator(val context : Context) { class RadialGradientGenerator(val context: CanvasContext) {
val gradients = ArrayList<Array<#(Double, String)>>() val gradients = ArrayList<Array<#(Double, String)>>()
var current = 0 var current = 0
fun newColorStops(vararg colorStops : #(Double, String)) { fun newColorStops(vararg colorStops: #(Double, String)) {
gradients.add(colorStops) gradients.add(colorStops)
} }
@@ -284,47 +303,49 @@ class RadialGradientGenerator(val context : Context) {
newColorStops(#(0.0, "rgb(250,147,250)"), #(0.5, "rgb(255,80,255)"), #(1.0, "rgb(250,0,217)")) newColorStops(#(0.0, "rgb(250,147,250)"), #(0.5, "rgb(255,80,255)"), #(1.0, "rgb(250,0,217)"))
} }
fun getNext() : Array<#(Double, String)> { fun getNext(): Array<#(Double, String)> {
val result = gradients.get(current) val result = gradients.get(current)
current = (current + 1) % gradients.size() current = (current + 1) % gradients.size()
return result return result
} }
} }
fun v(x : Double, y : Double) = Vector(x, y) fun v(x: Double, y: Double) = Vector(x, y)
class Vector(val x : Double = 0.0, val y : Double = 0.0) { class Vector(val x: Double = 0.0, val y: Double = 0.0) {
fun plus(v : Vector) = v(x + v.x, y + v.y) fun plus(v: Vector) = v(x + v.x, y + v.y)
fun minus() = v(-x, -y) fun minus() = v(- x, - y)
fun minus(v : Vector) = v(x - v.x, y - v.y) fun minus(v: Vector) = v(x - v.x, y - v.y)
fun times(koef : Double) = v(x * koef, y * koef) fun times(koef: Double) = v(x * koef, y * koef)
fun distanceTo(v : Vector) = Math.sqrt((this - v).sqr) fun distanceTo(v: Vector) = Math.sqrt((this - v).sqr)
fun rotatedBy(theta : Double) : Vector { fun rotatedBy(theta: Double): Vector {
val sin = Math.sin(theta) val sin = Math.sin(theta)
val cos = Math.cos(theta) val cos = Math.cos(theta)
return v(x * cos - y * sin, x * sin + y * cos) return v(x * cos - y * sin, x * sin + y * cos)
} }
fun isInRect(topLeft : Vector, size : Vector) = (x >= topLeft.x) && (x <= topLeft.x + size.x) && 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 val sqr: Double
get() = x * x + y * y get() = x * x + y * y
val normalized : Vector val normalized: Vector
get() = this * (1.0 / Math.sqrt(sqr)) get() = this * (1.0 / Math.sqrt(sqr))
} }
fun main(args : Array<String>) { fun main(args: Array<String>) {
val state = CanvasState(getCanvas()) jq {
state.addShape(Kotlin) val state = CanvasState(canvas)
state.addShape(Creature(state.size * 0.25, state)) state.addShape(Kotlin)
state.addShape(Creature(state.size * 0.75, state)) state.addShape(Creature(state.size * 0.25, state))
setTimeout({ state.addShape(Creature(state.size * 0.75, state))
state.valid = false setTimeout({
}) state.valid = false
})
}
} }
fun <T> List<T>.reversed() : List<T> { fun <T> List<T>.reversed(): List<T> {
val result = ArrayList<T>() val result = ArrayList<T>()
var i = size() var i = size()
while (i > 0) { while (i > 0) {