fun main(args : Array) {
println("Hello Kotliner!")
println("Click this green button at the top right!")
}
沒錯,這是一段可以嵌入到部落格中獨立執行的kotlin程式碼。要注意的是,您還能修改其中的程式碼,而不只是讓它執行起來。
// The code below doesn't compile. Add only one char to make it runnable again
fun main(args : Array) {
val fix = "Kotlin "
val me = "is great"
println(fixme)
}
這很酷,不是嗎?讓上述程式碼也順利工作起來吧。
通常來說,您並不希望所有程式碼段都顯示出來,而只想展示當中最有趣和最重要的部分。這也是可以實現的。
fun main(args: Array) {
val hint = "Click the plus button to see the full code"
//sampleStart
println (hint.length)
println("yes, 42")
//sampleEnd
}
您也可以新增測試:
// Implement extension functions Int.r() and Pair.r()
// and make them convert Int and Pair to RationalNumber.
fun Int.r(): RationalNumber = TODO()
fun Pair<Int, Int>.r(): RationalNumber = TODO()
data class RationalNumber(val numerator: Int, val denominator: Int)
你也可以將JavaScript設定為目標平臺,甚至還能在canvas上繪製圖案
/**
* 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 org.w3c.dom.*
import kotlin.browser.document
import kotlin.browser.window
import kotlin.js.Math
fun getImage(path: String): HTMLImageElement {
val image = window.document.createElement("img") as HTMLImageElement
image.src = path
return image
}
val canvas = initalizeCanvas()
fun initalizeCanvas(): HTMLCanvasElement {
val canvas = document.createElement("canvas") as HTMLCanvasElement
val context = canvas.getContext("2d") as CanvasRenderingContext2D
context.canvas.width = window.innerWidth.toInt();
context.canvas.height = window.innerHeight.toInt();
document.body!!.appendChild(canvas)
return canvas
}
val context: CanvasRenderingContext2D
get() {
return canvas.getContext("2d") as CanvasRenderingContext2D
}
abstract class Shape() {
abstract fun draw(state: CanvasState)
// these two abstract methods defines that our shapes can be dragged
operator abstract fun contains(mousePos: Vector): Boolean
abstract var pos: Vector
var selected: Boolean = false
// a couple of helper extension methods we'll be using in the derived classes
fun CanvasRenderingContext2D.shadowed(shadowOffset: Vector, alpha: Double, render: CanvasRenderingContext2D.() -> Unit) {
save()
shadowColor = "rgba(100, 100, 100, $alpha)"
shadowBlur = 5.0
shadowOffsetX = shadowOffset.x
shadowOffsetY = shadowOffset.y
render()
restore()
}
fun CanvasRenderingContext2D.fillPath(constructPath: CanvasRenderingContext2D.() -> Unit) {
beginPath()
constructPath()
closePath()
fill()
}
}
val Kotlin = Logo(v(250.0, 75.0))
class Logo(override var pos: Vector) : Shape() {
val relSize: Double = 0.18
val shadowOffset = v(-3.0, 3.0)
val imageSize = v(150.0, 150.0)
var size: Vector = imageSize * relSize
// get-only properties like this saves you lots of typing and are very expressive
val position: Vector
get() = if (selected) pos - shadowOffset else pos
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://try.kotlinlang.org/static/images/kotlin_logo.svg"), 0.0, 0.0,
imageSize.x, imageSize.y,
position.x, position.y,
size.x, size.y)
}
override fun draw(state: CanvasState) {
val context = state.context
if (selected) {
// using helper we defined in Shape class
context.shadowed(shadowOffset, 0.2) {
drawLogo(state)
}
} else {
drawLogo(state)
}
}
override fun contains(mousePos: Vector): Boolean = mousePos.isInRect(pos, size)
val centre: Vector
get() = pos + size * 0.5
}
val gradientGenerator: RadialGradientGenerator? = null
get() {
if (field == null) {
field = RadialGradientGenerator(context)
}
return field
}
class Creature(override var pos: Vector, val state: CanvasState) : Shape() {
val shadowOffset = v(-5.0, 5.0)
val colorStops = gradientGenerator!!.getNext()
val relSize = 0.05
// these properties have no backing fields and in java/javascript they could be represented as little helper functions
val radius: Double
get() = state.width * relSize
val position: Vector
get() = if (selected) pos - shadowOffset else pos
val directionToLogo: Vector
get() = (Kotlin.centre - position).normalized
//notice how the infix call can make some expressions extremely expressive
override fun contains(mousePos: Vector) = pos distanceTo mousePos < radius
// defining more nice extension functions
fun CanvasRenderingContext2D.circlePath(position: Vector, rad: Double) {
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
fun CanvasRenderingContext2D.fillCircle(position: Vector, rad: Double) {
fillPath {
circlePath(position, rad)
}
}
override fun draw(state: CanvasState) {
val context = state.context
if (!selected) {
drawCreature(context)
} else {
drawCreatureWithShadow(context)
}
}
fun drawCreature(context: CanvasRenderingContext2D) {
context.fillStyle = getGradient(context)
context.fillPath {
tailPath(context)
circlePath(position, radius)
}
drawEye(context)
}
fun getGradient(context: CanvasRenderingContext2D): CanvasGradient {
val gradientCentre = position + directionToLogo * (radius / 4)
val gradient = context.createRadialGradient(gradientCentre.x, gradientCentre.y, 1.0, gradientCentre.x, gradientCentre.y, 2 * radius)
for (colorStop in colorStops) {
gradient.addColorStop(colorStop.first, colorStop.second)
}
return gradient
}
fun tailPath(context: CanvasRenderingContext2D) {
val tailDirection = -directionToLogo
val tailPos = position + tailDirection * radius * 1.0
val tailSize = radius * 1.6
val angle = Math.PI / 6.0
val p1 = tailPos + tailDirection.rotatedBy(angle) * tailSize
val p2 = tailPos + tailDirection.rotatedBy(-angle) * tailSize
val middlePoint = position + tailDirection * radius * 1.0
context.moveTo(tailPos.x, tailPos.y)
context.lineTo(p1.x, p1.y)
context.quadraticCurveTo(middlePoint.x, middlePoint.y, p2.x, p2.y)
context.lineTo(tailPos.x, tailPos.y)
}
fun drawEye(context: CanvasRenderingContext2D) {
val eyePos = directionToLogo * radius * 0.6 + position
val eyeRadius = radius / 3
val eyeLidRadius = eyeRadius / 2
context.fillStyle = "#FFFFFF"
context.fillCircle(eyePos, eyeRadius)
context.fillStyle = "#000000"
context.fillCircle(eyePos, eyeLidRadius)
}
fun drawCreatureWithShadow(context: CanvasRenderingContext2D) {
context.shadowed(shadowOffset, 0.7) {
context.fillStyle = getGradient(context)
fillPath {
tailPath(context)
context.circlePath(position, radius)
}
}
drawEye(context)
}
}
class CanvasState(val canvas: HTMLCanvasElement) {
var width = canvas.width
var height = canvas.height
val size: Vector
get() = v(width.toDouble(), height.toDouble())
val context = creatures.context
var valid = false
var shapes = mutableListOf()
var selection: Shape? = null
var dragOff = Vector()
val interval = 1000 / 30
init {
jq(canvas).mousedown {
valid = false
selection = null
val mousePos = mousePos(it)
for (shape in shapes) {
if (mousePos in shape) {
dragOff = mousePos - shape.pos
shape.selected = true
selection = shape
break
}
}
}
jq(canvas).mousemove {
if (selection != null) {
selection!!.pos = mousePos(it) - dragOff
valid = false
}
}
jq(canvas).mouseup {
if (selection != null) {
selection!!.selected = false
}
selection = null
valid = false
}
jq(canvas).dblclick {
val newCreature = Creature(mousePos(it), this@CanvasState)
addShape(newCreature)
valid = false
}
window.setInterval({
draw()
}, interval)
}
fun mousePos(e: MouseEvent): Vector {
var offset = Vector()
var element: HTMLElement? = canvas
while (element != null) {
val el: HTMLElement = element
offset += Vector(el.offsetLeft.toDouble(), el.offsetTop.toDouble())
element = el.offsetParent as HTMLElement?
}
return Vector(e.pageX, e.pageY) - offset
}
fun addShape(shape: Shape) {
shapes.add(shape)
valid = false
}
fun clear() {
context.fillStyle = "#D0D0D0"
context.fillRect(0.0, 0.0, width.toDouble(), height.toDouble())
context.strokeStyle = "#000000"
context.lineWidth = 4.0
context.strokeRect(0.0, 0.0, width.toDouble(), height.toDouble())
}
fun draw() {
if (valid) return
clear()
for (shape in shapes.reversed()) {
shape.draw(this)
}
Kotlin.draw(this)
valid = true
}
}
class RadialGradientGenerator(val context: CanvasRenderingContext2D) {
val gradients = mutableListOf<Array>>()
var current = 0
fun newColorStops(vararg colorStops: Pair<Double, String>) {
gradients.add(colorStops)
}
init {
newColorStops(Pair(0.0, "#F59898"), Pair(0.5, "#F57373"), Pair(1.0, "#DB6B6B"))
newColorStops(Pair(0.39, "rgb(140,167,209)"), Pair(0.7, "rgb(104,139,209)"), Pair(0.85, "rgb(67,122,217)"))
newColorStops(Pair(0.0, "rgb(255,222,255)"), Pair(0.5, "rgb(255,185,222)"), Pair(1.0, "rgb(230,154,185)"))
newColorStops(Pair(0.0, "rgb(255,209,114)"), Pair(0.5, "rgb(255,174,81)"), Pair(1.0, "rgb(241,145,54)"))
newColorStops(Pair(0.0, "rgb(132,240,135)"), Pair(0.5, "rgb(91,240,96)"), Pair(1.0, "rgb(27,245,41)"))
newColorStops(Pair(0.0, "rgb(250,147,250)"), Pair(0.5, "rgb(255,80,255)"), Pair(1.0, "rgb(250,0,217)"))
}
fun getNext(): Array> {
val result = gradients.get(current)
current = (current + 1) % gradients.size
return result
}
}
fun v(x: Double, y: Double) = Vector(x, y)
class Vector(val x: Double = 0.0, val y: Double = 0.0) {
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)
fun rotatedBy(theta: Double): Vector {
val sin = Math.sin(theta)
val cos = Math.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)
val sqr: Double
get() = x * x + y * y
val normalized: Vector
get() = this * (1.0 / Math.sqrt(sqr))
}
fun main(args: Array) {
//sampleStart
val state = CanvasState(canvas).apply {
addShape(Kotlin)
addShape(Creature(size * 0.25, this))
addShape(Creature(size * 0.75, this))
}
window.setTimeout({ state.valid = false }, 1000)
// You can drag this objects
//sampleEnd
}
fun List.reversed(): List {
val result = mutableListOf()
var i = size
while (i > 0) {
result.add(get(--i))
}
return result
}
有時候您並不需要或者不能建立一個可執行的示例。此時,您可以運用highlight-only
特性,在相同的樣式下顯示這段不會執行的程式碼。
val simpleText1 = "It's just an ordinary Kotlin snippet"
...
val simpleText239 = "It doesn't compile actually"
嵌入式Kotlin環境工作原理
一直以來,成千上萬的萌新透過try.kotlinlang.org這個網站作為一種互動方式來學習Kotlin。尤其是Kotlin Koans Online現在已經非常受歡迎。高階使用者可以直接利用這個開發環境來試執行小段程式碼,而無需開啟IDE,例如在StackOverflow上貼上程式碼來回答問題的時候。
Embedded Kotlin Playground在同樣的原理下工作,但是允許你在網頁中編寫及執行示例。編譯將在我們的後端伺服器上進行,然後執行在你的瀏覽器上(如果平臺是JS)或者執行在伺服器上(如果平臺是JVM)。
前端
只需要在header中新增簡單的一行程式碼,即可將該環境嵌入到你的頁面:
|
<script src="https://unpkg.com/kotlin-playground@1" data-selector="code"></script> |
現在頁面上所有程式碼塊都將被轉換為能執行的Kotlin程式碼片段。當然可以自定義data-selector
將指令碼應用到某些特定的類,也可以手動配置Kotlin執行環境。
|
<script src="https://unpkg.com/kotlin-playground@1"></script> <script> document.addEventListener('DOMContentLoaded', function() { KotlinPlayground('.code-blocks-selector'); }); </script> |
除此之外,還有很多其他的安裝及自定義選項,可以在文件中檢視。
後端
嵌入式環境將在後端編譯程式碼,並高亮顯示完成的資訊。通常情況下,你不需要擔心後端,直接使用我們的伺服器即可,除非你想引用自定義的JVM庫。
當編寫的示例需要引用到外部的庫,例如你在為自己的庫編寫可互動文件時,你需要配置及執行一個開發環境的例項。這是一件很容易的事:新增依賴,執行兩個預定製的gradle task,再執行docker-compose up
,看!伺服器已經跑起來了。更多的細節請檢視指引。
它正應用於這些地方
應用場景
Kotlin嵌入環境改善了閱讀體驗,並增加了程式碼示例的表現力。讀者不僅能看到程式碼,還可以執行它,更改它,並再次執行已更改的程式碼。特別是在以下場合中,我們鼓勵所有作者都編寫可執行的Kotlin程式碼片段:
- 課堂上
- 幻燈片和書籍上的補充資料
- 庫及框架的文件
- 部落格文章中的示例
在不久的將來,我們還會為嵌入式Kotlin環境上的指令碼編寫提供支援。
fun main(args: Array) {
//sampleStart
println("請盡情享受Kotlin!")
//sampleEnd
}
原文連結:https://blog.jetbrains.com/kotlin/2018/04/embedding-kotlin-playground/