Skip to content

Instantly share code, notes, and snippets.

@lihaoyi
Last active August 29, 2015 13:57
Show Gist options
  • Save lihaoyi/9759723 to your computer and use it in GitHub Desktop.
Save lihaoyi/9759723 to your computer and use it in GitHub Desktop.
@JSExport
object ScalaJSExample{
@JSExport
def main(args: Array[String]): Unit = {
val xs = Seq(1, 2, 3)
println(xs)
val ys = Seq(4, 5, 6)
println(ys)
val zs = for{
x <- xs
y <- ys
} yield x * y
println(zs)
}
}
import org.scalajs.dom
import scala.util.Random
import scala.scalajs.js
case class Point(x: Double, y: Double){
def +(p: Point) = Point(x + p.x, y + p.y)
def -(p: Point) = Point(x - p.x, y - p.y)
def *(d: Double) = Point(x * d, y * d)
def /(d: Double) = Point(x / d, y / d)
def length = Math.sqrt(x * x + y * y)
}
class Enemy(var pos: Point, var vel: Point)
@JSExport
object ScalaJSExample {
var startTime = js.Date.now()
var player = Point(dom.innerWidth.toInt/2, dom.innerHeight.toInt/2)
var enemies = Seq.empty[Enemy]
var death: Option[(String, Int)] = None
def run() = {
enemies = enemies.filter(e =>
e.pos.x >= 0 && e.pos.x <= canvas.width &&
e.pos.y >= 0 && e.pos.y <= canvas.height
)
def randSpeed = Random.nextInt(5) - 3
enemies = enemies ++ Seq.fill(20 - enemies.length)(
new Enemy(
Point(Random.nextInt(canvas.width.toInt), 0),
Point(randSpeed, randSpeed)
)
)
for(enemy <- enemies){
enemy.pos = enemy.pos + enemy.vel
val delta = player - enemy.pos
enemy.vel = enemy.vel + delta / delta.length / 100
}
if(enemies.exists(e => (e.pos - player).length < 20)){
death = Some((s"You lasted $deltaT seconds", 100))
enemies = enemies.filter(e => (e.pos - player).length > 20)
}
}
def deltaT = ((js.Date.now() - startTime) / 1000).toInt
def draw() = {
renderer.clearRect(0, 0, canvas.width, canvas.height)
death match{
case None =>
renderer.fillStyle = "white"
renderer.fillRect(player.x - 10, player.y - 10, 20, 20)
renderer.fillText("player", player.x - 15, player.y - 30)
renderer.fillStyle = "red"
for (enemy <- enemies){
renderer.fillRect(enemy.pos.x - 10, enemy.pos.y - 10, 20, 20)
}
renderer.fillStyle = "white"
renderer.fillText(s"$deltaT seconds", canvas.width / 2 - 100, canvas.height / 5)
case Some((msg, time)) =>
renderer.fillStyle = "white"
renderer.fillText(msg, canvas.width / 2 - 100, canvas.height / 2)
if (time - 1 == 0){
death = None
startTime = js.Date.now()
}else{
death = Option((msg, time - 1))
}
}
}
@JSExport
def main(args: Array[String] ): Unit = {
dom.document.onmousemove = { (e: dom.MouseEvent) =>
player = Point(e.clientX.toInt, e.clientY.toInt)
(): js.Any
}
dom.setInterval(() => {run(); draw()}, 20)
}
}
import org.scalajs.dom
import scala.scalajs.js
/**
* A spiritual clone of Flappy Bird. Click to jump, don't hit the white barriers!
*
* High scores are saved using HTML5 Local Storage.
*/
@JSExport
object ScalaJSExample{
var game = new Game()
@JSExport
def main(args: Array[String]): Unit = {
dom.setInterval(() => game.run(), 20 * 800 / canvas.width)
canvas.onclick = { (e: dom.MouseEvent) =>
game.v += canvas.height / 200
}
}
class Game{
renderer.fillStyle = "black"
renderer.fillRect(0, 0, canvas.width, canvas.height)
var i = 0.0
var h = canvas.height / 2
var v = 0.0
val a = -0.05
def run() = {
clear()
println("Score: " + i.toInt)
val highScore = Option(dom.localStorage.getItem("highScore")).fold(0)(_.toString.toInt)
println("High Score: " + highScore)
i += 1
dom.localStorage.setItem("highScore", Math.max(i, highScore).toString)
v += a
h += v
val spotData = renderer.getImageData(canvas.width/2 + 3, canvas.height - h, 1, 1).data
if (spotData.take(3).map(_.toInt).sum != 0 || h > canvas.height || h < 0) {
game = new Game()
}
else{
val imageData = renderer.getImageData(1, 0, canvas.width-1, canvas.height)
renderer.putImageData(imageData, 0, 0)
renderer.fillStyle = "black"
renderer.fillRect(canvas.width-1, 0, 1, canvas.height)
if (i.toInt % (canvas.width / 3).toInt == 0){
val gapHeight = canvas.height / 5
val gap = util.Random.nextInt(canvas.height.toInt - gapHeight.toInt)
renderer.fillStyle = "white"
renderer.fillRect(canvas.width-2, 0, 2, gap)
renderer.fillRect(canvas.width-2, gap + gapHeight, 2, canvas.height - gap - gapHeight)
}
val r = (h / canvas.height * 255).toInt
val b = (255 / (1 + Math.pow(Math.E, -v))).toInt
renderer.fillStyle = s"rgb($r, 255, $b)"
renderer.fillRect(canvas.width / 2, canvas.height - h, 2, 2)
}
}
}
}
/**
* Simple, zero-dependency, self-contained JSON parser, serializer and AST
* taken from
*
* https://github.com/nestorpersist/json
*
* Notably, does not use the browser's native JSON capability, but re-implements
* it all in order to be 100% compatible with both Scala-JS and Scala-JVM
*/
@JSExport
object ScalaJSExample{
@JSExport
def main(args: Array[String]): Unit = {
val parsed = Json.read(ugly)
println(parsed(0))
println(parsed(8)("real"))
println(parsed(8)("comment"))
println(parsed(8)("jsontext"))
println(Json.write(parsed(8)))
}
val ugly =
"""
|[
| "JSON Test Pattern pass1",
| {"object with 1 member":["array with 1 element"]},
| {},
| [],
| -42,
| true,
| false,
| null,
| {
| "integer": 1234567890,
| "real": -9876.543210,
| "e": 0.123456789e-12,
| "E": 1.234567890E+34,
| "": 23456789012E66,
| "zero": 0,
| "one": 1,
| "space": " ",
| "quote": "\"",
| "backslash": "\\",
| "controls": "\b\f\n\r\t",
| "slash": "/ & \/",
| "alpha": "abcdefghijklmnopqrstuvwyz",
| "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
| "digit": "0123456789",
| "0123456789": "digit",
| "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
| "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
| "true": true,
| "false": false,
| "null": null,
| "array":[ ],
| "object":{ },
| "address": "50 St. James Street",
| "url": "http://www.JSON.org/",
| "comment": "// /* <!-- --",
| "# -- --> */": " ",
| " s p a c e d " :[1,2 , 3
|
|,
|
|4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7],
| "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
| "quotes": "&#34; \u005Cu0022 %22 0x22 034 &#x22;",
| "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
|: "A key can be any string"
| },
| 0.5 ,98.6
|,
|99.44
|,
|
|1066,
|1e1,
|0.1e1,
|1e-1,
|1e00,2e+00,2e-00
|,"rosebud"]
""".stripMargin
}
object Js {
sealed trait Value{
def value: Any
def apply(i: Int): Value = this.asInstanceOf[Array].value(i)
def apply(s: java.lang.String): Value = this.asInstanceOf[Object].value.find(_._1 == s).get._2
}
case class String(value: java.lang.String) extends Value
case class Object(value: Seq[(java.lang.String, Value)]) extends Value
case class Array(value: Seq[Value]) extends Value
case class Number(value: java.lang.String) extends Value
case object False extends Value{
def value = true
}
case object True extends Value{
def value = false
}
case object Null extends Value{
def value = null
}
}
object Json {
def write(v: Js.Value): String = v match {
case Js.String(s) =>
val out = s.flatMap {
case '\\' => "\\\\"
case '"' => "\\\""
case '/' => "\\/"
case '\b' => "\\b"
case '\t' => "\\t"
case '\n' => "\\n"
case '\f' => "\\f"
case '\r' => "\\r"
case c if c < ' ' =>
val t = "000" + Integer.toHexString(c)
"\\u" + t.takeRight(4)
case c => c.toString
}
'"' + out + '"'
case Js.Object(kv) =>
val contents = kv.toIterator.map{
case (k, v) => s"${write(Js.String(k))}: ${write(v)}"
}.mkString(", ")
s"{$contents}"
case Js.Array(vs) => s"[${vs.map(write).mkString(", ")}]"
case Js.Number(d) => d
case Js.False => "false"
case Js.True => "true"
case Js.Null => "null"
}
/**
* Self-contained JSON parser adapted from
*
* https://github.com/nestorpersist/json
*/
def read(s: String): Js.Value = {
// *** Character Kinds
type CharKind = Int
val Letter = 0
val Digit = 1
val Minus = 2
val Quote = 3
val Colon = 4
val Comma = 5
val Lbra = 6
val Rbra = 7
val Larr = 8
val Rarr = 9
val Blank = 10
val Other = 11
val Eof = 12
val Slash = 13
// *** Token Kinds
type TokenKind = Int
val ID = 0
val STRING = 1
val NUMBER = 2
val BIGNUMBER = 3
val FLOATNUMBER = 4
val COLON = 5
val COMMA = 6
val LOBJ = 7
val ROBJ = 8
val LARR = 9
val RARR = 10
val BLANK = 11
val EOF = 12
// *** Character => CharKind Map ***
val charKind = (0 to 255).toArray.map {
case c if 'a'.toInt <= c && c <= 'z'.toInt => Letter
case c if 'A'.toInt <= c && c <= 'Z'.toInt => Letter
case c if '0'.toInt <= c && c <= '9'.toInt => Digit
case '-' => Minus
case ',' => Comma
case '"' => Quote
case ':' => Colon
case '{' => Lbra
case '}' => Rbra
case '[' => Larr
case ']' => Rarr
case ' ' => Blank
case '\t' => Blank
case '\n' => Blank
case '\r' => Blank
case '/' => Slash
case _ => Other
}
// *** Character Escapes
val escapeMap = Map[Int, String](
'\\'.toInt -> "\\",
'/'.toInt -> "/",
'\"'.toInt -> "\"",
'b'.toInt -> "\b",
'f'.toInt -> "\f",
'n'.toInt -> "\n",
'r'.toInt -> "\r",
't'.toInt -> "\t"
)
// *** Import Shared Data ***
// *** INPUT STRING ***
// array faster than accessing string directly using charAt
//final val s1 = s.toCharArray()
val size = s.size
// *** CHARACTERS ***
var pos = 0
var ch: Int = 0
var chKind: CharKind = 0
var chLinePos: Int = 0
var chCharPos: Int = 0
def chNext() = {
if (pos < size) {
//ch = s1(pos).toInt
ch = s.charAt(pos)
chKind = if (ch < 255) {
charKind(ch)
} else {
Other
}
pos += 1
if (ch == '\n'.toInt) {
chLinePos += 1
chCharPos = 1
} else {
chCharPos += 1
}
} else {
ch = -1
pos = size + 1
chKind = Eof
}
}
def chError(msg: String): Nothing = {
throw new Json.Exception(msg, s, chLinePos, chCharPos)
}
def chMark = pos - 1
def chSubstr(first: Int, delta: Int = 0) = {
s.substring(first, pos - 1 - delta)
}
// *** LEXER ***
var tokenKind = BLANK
var tokenValue = ""
var linePos = 1
var charPos = 1
def getDigits() = {
while (chKind == Digit) chNext()
}
def handleDigit() {
val first = chMark
getDigits()
val k1 = if (ch == '.'.toInt) {
chNext()
getDigits()
BIGNUMBER
} else {
NUMBER
}
val k2 = if (ch == 'E'.toInt || ch == 'e'.toInt) {
chNext()
if (ch == '+'.toInt) {
chNext()
} else if (ch == '-'.toInt) {
chNext()
}
getDigits()
FLOATNUMBER
} else {
k1
}
tokenKind = k2
tokenValue = chSubstr(first)
}
def handleRaw() {
chNext()
val first = chMark
var state = 0
do {
if (chKind == Eof) chError("EOF encountered in raw string")
state = (ch, state) match {
case ('}', _) => 1
case ('"', 1) => 2
case ('"', 2) => 3
case ('"', 3) => 0
case _ => 0
}
chNext()
} while (state != 3)
tokenKind = STRING
tokenValue = chSubstr(first, 3)
}
def handle(i: Int) = {
chNext()
tokenKind = i
tokenValue = ""
}
def tokenNext() {
do {
linePos = chLinePos
charPos = chCharPos
val kind: Int = chKind
kind match {
case Letter =>
val first = chMark
while (chKind == Letter || chKind == Digit) {
chNext()
}
tokenKind = ID
tokenValue = chSubstr(first)
case Digit => handleDigit()
case Minus =>
chNext()
handleDigit()
tokenValue = "-" + tokenValue
case Quote =>
val sb = new StringBuilder(50)
chNext()
var first = chMark
while (ch != '"'.toInt && ch >= 32) {
if (ch == '\\'.toInt) {
sb.append(chSubstr(first))
chNext()
escapeMap.get(ch) match {
case Some(s) =>
sb.append(s)
chNext()
case None =>
if (ch != 'u'.toInt) chError("Illegal escape")
chNext()
var code = 0
for (i <- 1 to 4) {
val ch1 = ch.toChar.toString
val i = "0123456789abcdef".indexOf(ch1.toLowerCase)
if (i == -1) chError("Illegal hex character")
code = code * 16 + i
chNext()
}
sb.append(code.toChar.toString)
}
first = chMark
} else {
chNext()
}
}
if (ch != '"') chError("Unexpected string character: " + ch.toChar)
sb.append(chSubstr(first))
tokenKind = STRING
tokenValue = sb.toString()
chNext()
if (tokenValue.length() == 0 && ch == '{') {
handleRaw()
}
case Colon => handle(COLON)
case Comma => handle(COMMA)
case Lbra => handle(LOBJ)
case Rbra => handle(ROBJ)
case Larr => handle(LARR)
case Rarr => handle(RARR)
case Blank =>
do chNext() while (chKind == Blank)
tokenKind = BLANK
tokenValue = ""
case Other => chError("Unexpected character: " + ch.toChar + " " + ch)
case Eof =>
chNext()
tokenKind = EOF
tokenValue = ""
case Slash =>
if (chKind != Slash) chError("Expecting Slash")
do chNext() while (ch != '\n' && chKind != Eof)
tokenKind = BLANK
tokenValue = ""
}
} while (tokenKind == BLANK)
}
def tokenError(msg: String): Nothing = {
throw new Json.Exception(msg, s, linePos, charPos)
}
// *** PARSER ***
def handleEof() = tokenError("Unexpected eof")
def handleUnexpected(i: String) = tokenError(s"Unexpected input: [$i]")
def handleArray(): Js.Array = {
tokenNext()
var result = List.empty[Js.Value]
while (tokenKind != RARR) {
result = getJson() :: result
tokenKind match{
case COMMA => tokenNext()
case RARR => // do nothing
case _ => tokenError("Expecting , or ]")
}
}
tokenNext()
Js.Array(result.reverse)
}
def handleObject(): Js.Object = {
tokenNext()
var result = List.empty[(String, Js.Value)]
while (tokenKind != ROBJ) {
if (tokenKind != STRING && tokenKind != ID) tokenError("Expecting string or name")
val name = tokenValue
tokenNext()
if (tokenKind != COLON) tokenError("Expecting :")
tokenNext()
result = (name -> getJson()) :: result
tokenKind match{
case COMMA => tokenNext()
case ROBJ => // do nothing
case _ => tokenError("Expecting , or }")
}
}
tokenNext()
Js.Object(result.reverse)
}
def handleNumber(name: String, f: String => Unit) = {
val v = try {
f(tokenValue)
} catch {
case _: Throwable => tokenError("Bad " + name)
}
val old = tokenValue
tokenNext()
Js.Number(old)
}
def getJson(): Js.Value = {
val kind: Int = tokenKind
val result: Js.Value = kind match {
case ID =>
val result = tokenValue match {
case "true" => Js.True
case "false" => Js.False
case "null" => Js.Null
case _ => tokenError("Not true, false, or null")
}
tokenNext()
result
case STRING =>
val result = tokenValue
tokenNext()
Js.String(result)
case NUMBER => handleNumber("NUMBER", _.toLong)
case BIGNUMBER => handleNumber("BIGNUMBER", _.toDouble)
case FLOATNUMBER => handleNumber("FLOATNUMBER", _.toDouble)
case COLON => handleUnexpected(":")
case COMMA => handleUnexpected(",")
case LOBJ => handleObject()
case ROBJ => handleUnexpected("}")
case LARR => handleArray()
case RARR => handleUnexpected("]")
case EOF => handleEof()
}
result
}
def parse(): Js.Value = {
chNext()
tokenNext()
val result = getJson
if (tokenKind != EOF) tokenError("Excess input")
result
}
parse()
}
class Exception(val msg: String,
val input: String,
val line: Int,
val char: Int)
extends scala.Exception(s"JsonParse Error: $msg line $line [$char] in $input")
}
import org.scalajs.dom
case class Pt(x: Double, y: Double)
/**
* Splash page for www.scala-js-fiddle.com. Uses Scalatags to render a HTML page
* containing the text on the page, while using a HTML5 Canvas element to slowly
* render a Sierpinski Triangle behind the text pixel by pixel.
*/
@JSExport
object ScalaJSExample{
val sandbox = dom.document.getElementById("sandbox")
clear()
def render() = {
import scalatags.all._
import scalatags._
renderln(
div(
padding:="10px",
id:="page",
h1(
marginTop:="0px",
img(src:="/Shield.svg", height:="40px", marginBottom:="-10px"),
" Scala.jsFiddle"
),
p(
"Scala.jsFiddle is an online scratchpad to try out your ",
a(href:="http://www.scala-js.org/", "Scala.js "),
"snippets. Enter Scala code on the left and see the results on the right, ",
"after the Scala is compiled to Javascript using the Scala.js compiler."
),
ul(
li(green("Ctrl/Cmd-Enter"), ": compile and execute"),
li(green("Ctrl/Cmd-Alt-Enter"), ": compile and execute using ", b("preoptimize")),
li(green("Ctrl/Cmd-Shift-Enter"), ": compile and execute using ", b("optimize")),
li(green("Ctrl/Cmd-Space"), ": autocomplete at caret"),
li(green("Ctrl/Cmd-J"), ": show the generated Javascript code"),
li(green("Ctrl/Cmd-Alt-J"), ": show the generated code, packaged with dependencies"),
li(green("Ctrl/Cmd-Shift-J"), ": show the generated code, optimized by Google Closure"),
li(green("Ctrl/Cmd-S"), ": save the code to a public Gist"),
li(green("Ctrl/Cmd-E"), ": export your code to a stand-alone web page")
),
p(
"To load the code from an existing gist, simply go to "
),
ul(li(green("www.scala-js-fiddle.com/gist/<gist-id>"))),
p(
"Where ",green("<gist-id>"), " is the last section of the gist's URL.",
"You can also go to:"
),
ul(li(green("www.scala-js-fiddle.com/gist/<gist-id>/<file-name>"))),
p(
"If you want a particular file in a multi file gist."
),
p(
"If you need ideas of things you can make using Scala.jsFiddle, check ",
"out some of our examples:"
),
ul(
for {
(file, name) <- Seq(
"BasicOperations.scala" -> "Basic Operations",
"SierpinskiTriangle.scala" -> "Sierpinski Triangle",
"Turmites.scala" -> "Turmites",
"Oscilloscope.scala" -> "Oscilloscope",
"FlappyLine.scala" -> "Flappy Line",
"SquareRoot.scala" -> "Square Root solver",
"DodgeTheDots.scala" -> "Dodge the Dots",
"SpaceInvaders.scala" -> "Space Invaders",
"SquareRootRx.scala" -> "Square Root solver with Scala.rx",
"TodoMVC.scala" -> "TodoMVC",
"JsonParser.scala" -> "JSON Parser",
"ScalaAsyncPaintbrush.scala" -> "Scala Async Paintbrush",
"RayTracer.scala" -> "RayTracer"
)
} yield li(
a(href:=(dom.document.location.origin + "/gist/9759723/" + file), name)
)
),
p("Scala.jsFiddle provides some build-in variables to give you started quickly:"),
ul(
li(
blue("canvas"), ": a ", blue("dom.HTMLCanvasElement"), " that lets you draw ",
"on the right pane"
),
li(
blue("renderer"), ": a ", blue("dom.CanvasRenderingContext2D"), " that ",
"lets you draw on the ", blue("canvas")
),
li(
blue("println(s: Any*)"), ": prints the given values to the right pane as ",
"a string."
),
li(
blue("renderln(s: Any*)"), ": prints the given values to the right pane as ",
"HTML."
),
li(blue("clear()"), ": Removes all printed output from the right pane"),
li(
blue("scroll(px: Int)"), ": Scrolls the right pane up or down by ",
"the given number of pixels"
),
li(
"The colors ", red("red"), ", ", green("green"), " and ", blue("blue"),
" to help prettify your output"
)
),
p("Apart from these inbuilt helpers, available libraries include:"),
ul(
li(a(href:="https://github.com/lihaoyi/scalatags", "scalatags")),
li(a(href:="https://github.com/lihaoyi/scala.rx", "scala.rx")),
li(a(href:="https://github.com/scala-js/scala-js-dom", "scala-js-dom"))
),
p(
"Scala.jsFiddle is made by ", a(href:="https://github.com/lihaoyi")("Li Haoyi"),
" and can be found on ", a(href:="https://github.com/lihaoyi/scala-js-fiddle")("Github")
)
)
)
// scroll to top so if the users screen is too short, he sees the top of the
// rendered page rather than the bottom
scroll(-1000)
}
val corners = Seq(
Pt(canvas.width/2, 0),
Pt(0, canvas.height),
Pt(canvas.width, canvas.height)
)
var pt = corners(0)
val (w, h) = (canvas.height.toDouble, canvas.height.toDouble)
@JSExport
def main(args: Array[String]): Unit = {
render()
dom.setInterval(() => {
val c = corners(util.Random.nextInt(3))
pt = Pt((pt.x + c.x) / 2, (pt.y + c.y) / 2)
val m = (pt.y / h)
val r = 255 - (pt.x / w * m * 255).toInt
val g = 255 - ((w-pt.x) / w * m * 255).toInt
val b = 255 - ((h - pt.y) / h * 255).toInt
renderer.fillStyle = s"rgb($r, $g, $b)"
renderer.fillRect(pt.x, pt.y, 1, 1)
}, 20)
}
}
import org.scalajs.dom
import Math._
@JSExport
object ScalaJSExample {
@JSExport
def main(): Unit = {
val (h, w) = (canvas.height, canvas.width)
var x = 0
val graphs = Seq[(String, Double => Double)](
("red", sin),
("green", x => 2 - abs(x % 8 - 4)),
("blue", x => 3 * pow(sin(x / 12), 2) * sin(x))
).zipWithIndex
dom.setInterval(() => {
x = (x + 1) % w.toInt
if (x == 0) renderer.clearRect(0, 0, w, h)
else for (((color, func), i) <- graphs) {
val y = func(x/w * 75) * h/40 + h/3 * (i+0.5)
renderer.fillStyle = color
renderer.fillRect(x, y, 3, 3)
}
}, 10)
}
}
import scala.scalajs.js.annotation.JSExport
import org.scalajs.dom
import math._
import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import ScalaJSExample.{Color, Epsilon}
import scala.math._
import scala.async.Async._
import scala.concurrent.Future
import scalaxy.loops._
import scala.language.postfixOps
/**
* A simple ray tracer, taken from the PyPy benchmarks
*
* https://bitbucket.org/pypy/benchmarks/src/846fa56a282b/own/raytrace-simple.py?at=default
*
* Half the lines of code
*/
@JSExport
object ScalaJSExample {
val Epsilon = 0.00001
type Color = Vec
val Color = Vec
@JSExport
def main(): Unit = {
val r = new util.Random(16314302)
val spiral = for (i <- 0 until 11) yield {
val theta = i * (i + 5) * Pi / 100 + 0.3
val center = (0 - 4 * sin(theta), 1.5 - i / 2.0, 0 - 4 * cos(theta))
val form = Sphere(center, 0.3 + i * 0.1)
val surface = Flat((i / 6.0, 1 - i / 6.0, 0.5))
(form, surface)
}
def rand(d: Double) = (r.nextDouble() - 0.5) * d * 2
val drops = Array(
Sphere((2.5, 2.5, -8), 0.3),
Sphere((1.5, 2.2, -7), 0.25),
Sphere((-1.3, 0.8, -8.5), 0.15),
Sphere((0.5, -2.5, -7.5), 0.2),
Sphere((-1.8, 2.3, -7.5), 0.3),
Sphere((-1.8, -2.3, -7.5), 0.3),
Sphere((1.3, 0.0, -8), 0.25)
).map(_ -> Refractor())
val s = new Scene(
objects = Array(
Sphere((0, 0, 0), 2) -> Flat((1, 1, 1), specularC = 0.6, lambertC = 0.4),
Plane((0, 4, 0), (0, 1, 0)) -> Checked(),
Plane((0, -4, 0), (0, 1, 0)) -> Flat((0.9, 1, 1)),
Plane((6, 0, 0), (1, 0, 0)) -> Flat((1, 0.9, 1)),
Plane((-6, 0, 0), (1, 0, 0)) -> Flat((1, 1, 0.9)),
Plane((0, 0, 6), (0, 0, 1)) -> Flat((0.9, 0.9, 1))
) ++ spiral ++ drops,
lightPoints = Array(
Light((0, -3, 0), (3, 3, 0)),
Light((3, 3, 0), (0, 3, 3)),
Light((-3, 3, 0), (3, 0, 3))
),
position = (0, 0, -15),
lookingAt = (0, 0, 0),
fieldOfView = 45.0
)
val c = new Canvas{
val width = min(canvas.width.toInt, canvas.height.toInt)
val height = min(canvas.width.toInt, canvas.height.toInt)
val data = renderer.getImageData(0, 0, canvas.width, canvas.height)
def save(y: Int): Unit = {
renderer.putImageData(data, 0, 0, 0, y-1, width, 1)
}
def plot(x: Int, y: Int, rgb: ScalaJSExample.Color): Unit = {
val index = (y * data.width + x) * 4
data.data(index+0) = (rgb.x * 255).toInt
data.data(index+1) = (rgb.y * 255).toInt
data.data(index+2) = (rgb.z * 255).toInt
data.data(index+3) = 255
}
}
s.render(c)
}
}
final case class Vec(x: Double, y: Double, z: Double){
def magnitude = sqrt(this dot this)
def +(o: Vec) = Vec(x + o.x, y + o.y, z + o.z)
def -(o: Vec) = Vec(x - o.x, y - o.y, z - o.z)
def *(o: Vec) = Vec(x * o.x, y * o.y, z * o.z)
def *(f: Double) = Vec(x * f, y * f, z * f)
def /(f: Double) = Vec(x / f, y / f, z / f)
def dot(o: Vec) = x * o.x + y * o.y + z * o.z
def cross(o: Vec) = Vec(
y * o.z - z * o.y,
z * o.x - x * o.z,
x * o.y - y * o.x
)
def normalized = this / magnitude
def reflectThrough(normal: Vec) = this - normal * (this dot normal) * 2
}
object Vec{
case class Unit(x: Double, y: Double, z: Double)
implicit def normalizer(v: Vec) = {
val l = v.magnitude
new Unit(v.x / l, v.y / l, v.z / l)
}
implicit def denormalizer(v: Vec.Unit) = new Vec(v.x, v.y, v.z)
implicit def pointify[X: Numeric, Y: Numeric, Z: Numeric](x: (X, Y, Z)): Vec = Vec(
implicitly[Numeric[X]].toDouble(x._1),
implicitly[Numeric[Y]].toDouble(x._2),
implicitly[Numeric[Z]].toDouble(x._3)
)
implicit def pointify2[X: Numeric, Y: Numeric, Z: Numeric](x: (X, Y, Z)): Vec.Unit = Vec.normalizer(x)
}
abstract class Form{
def intersectionTime(ray: Ray): Double
def normalAt(p: Vec): Vec
}
case class Sphere(center: Vec, radius: Double) extends Form{
def intersectionTime(ray: Ray) = {
val cp = center - ray.point
val v = cp dot ray.vector
val d = radius * radius - ((cp dot cp) - v * v)
if (d < 0) -1
else v - sqrt(d)
}
def normalAt(p: Vec) = (p - center).normalized
}
case class Plane(point: Vec, normal: Vec.Unit) extends Form{
def intersectionTime(ray: Ray) = {
val v = ray.vector dot normal
if (v != 0) ((point - ray.point) dot normal) / v
else -1
}
def normalAt(p: Vec) = normal
}
case class Ray(point: Vec, vector: Vec.Unit){
def pointAtTime(t: Double) = point + vector * t
}
case class Light(center: Vec, color: Color)
abstract class Surface{
def colorAt(scene: Scene, ray: Ray, p: Vec, normal: Vec.Unit, depth: Int): Color
}
abstract class SolidSurface extends Surface{
def baseColorAt(p: Vec): Color
def specularC: Double
def lambertC: Double
val ambientC = 1.0 - specularC - lambertC
def colorAt(scene: Scene, ray: Ray, p: Vec, normal: Vec.Unit, depth: Int): Color = {
val b = baseColorAt(p)
val specular = {
val reflectedRay = Ray(p, ray.vector.reflectThrough(normal))
val reflectedColor = scene.rayColor(reflectedRay, depth)
reflectedColor * specularC
}
val lambert = {
var lambertAmount = Vec(0, 0, 0)
for (i <- 0 until scene.lightPoints.length optimized) {
val light = scene.lightPoints(i)
if (scene.lightIsVisible(light.center, p)) {
val d = p - light.center
val dLength = d.magnitude
val contribution = light.color * abs(d dot normal / (dLength * dLength))
lambertAmount += contribution
}
}
b * lambertAmount * lambertC
}
val ambient = b * ambientC
specular + lambert + ambient
}
}
case class Refractor(refractiveIndex: Double = 0.5) extends Surface{
def colorAt(scene: Scene, ray: Ray, p: Vec, normal: Vec.Unit, depth: Int): Color = {
val r = if ((normal dot ray.vector) < 0)
refractiveIndex
else
1.0 / refractiveIndex
val c = (normal * -1) dot ray.vector
val sqrtValue = 1 - r * r * (1 - c * c)
if (sqrtValue > 0){
val refracted = ray.vector * r + normal * (r * c - sqrt(sqrtValue))
scene.rayColor(Ray(p, refracted), depth)
}else{
val perp = ray.vector dot normal
val reflected: Vec = Vec.denormalizer(ray.vector) + normal * 2 * perp
scene.rayColor(Ray(p, reflected), depth)
}
}
}
case class Flat(baseColor: Color = Color(1, 1, 1),
specularC: Double = 0.3,
lambertC: Double = 0.6) extends SolidSurface{
def baseColorAt(p: Vec) = baseColor
}
case class Checked(baseColor: Color = Color(1, 1, 1),
specularC: Double = 0.3,
lambertC: Double = 0.6,
otherColor: Color = (0, 0, 0),
checkSize: Double = 1) extends SolidSurface{
override def baseColorAt(p: Vec) = {
val v = p * (1.0 / checkSize)
def f(x: Double) = (abs(x) + 0.5).toInt
if ((f(v.x) + f(v.y) + f(v.z)) % 2 == 1) otherColor
else baseColor
}
}
abstract class Canvas{
def width: Int
def height: Int
def save(y: Int): Unit
def plot(x: Int, y: Int, rgb: Color)
}
class Scene(objects: Array[(Form, Surface)],
val lightPoints: Array[Light],
position: Vec,
lookingAt: Vec,
fieldOfView: Double){
def lightIsVisible(l: Vec, p: Vec) = {
val ray = Ray(p, l - p)
val length = (l - p).magnitude
var visible = true
for (i <- 0 until objects.length optimized){
val (o, s) = objects(i)
val t = o.intersectionTime(ray)
if (t > Epsilon && t < length - Epsilon){
visible = false
}
}
visible
}
def rayColor(ray: Ray, depth: Int): Color = {
if (depth > 3) (0, 0, 0)
else{
var (minT, minO, minS) = (-1.0, null: Form, null: Surface)
for(i <- 0 until objects.length optimized){
val (o, s) = objects(i)
val t = o.intersectionTime(ray)
if (t > Epsilon && (t < minT || minT < 0)){
minT = t
minO = o
minS = s
}
}
minT match{
case -1 => (0, 0, 0)
case t =>
val p = ray.pointAtTime(minT)
minS.colorAt(this, ray, p, minO.normalAt(p), depth + 1)
}
}
}
def render(canvas: Canvas) = async{
val fovRadians = Pi * (fieldOfView / 2.0) / 180.0
val halfWidth = tan(fovRadians)
val halfHeight = halfWidth
val width = halfWidth * 2
val height= halfHeight * 2
val pixelWidth = width / (canvas.width - 1)
val pixelHeight = height / (canvas.height - 1)
val eye = Ray(position, lookingAt - position)
val vpRight = eye.vector.cross((0, 1, 0)).normalized
val vpUp = vpRight.cross(eye.vector).normalized
for(y <- 0 until canvas.height optimized){
await(Future())
canvas.save(y)
for (x <- 0 until canvas.width optimized){
val xcomp = vpRight * (x * pixelWidth - halfWidth)
val ycomp = vpUp * (y * pixelHeight - halfHeight)
val ray = Ray(eye.point, xcomp + ycomp + eye.vector)
val color = rayColor(ray, 0)
canvas.plot(x, y, color)
}
}
}
}
import org.scalajs.dom
import concurrent._
import async.Async._
import scalajs.concurrent.JSExecutionContext.Implicits.queue
/**
* Re-implementation of the mouse-drag example from Deprecating the Observer
* Pattern
*
* http://lampwww.epfl.ch/~imaier/pub/DeprecatingObserversTR2010.pdf
*
* Using Scala.Async instead of Scala.React and continuations. Click and drag
* around the canvas on the right to see pretty shapes appear.
*/
@JSExport
object ScalaJSExample{
@JSExport
def main(args: Array[String]): Unit = {
val command = Channel[dom.MouseEvent]()
canvas.onmousemove = command.update _
canvas.onmouseup = command.update _
canvas.onmousedown = command.update _
renderer.lineWidth = 5
renderer.strokeStyle = "red"
renderer.fillStyle = "cyan"
val flow = async{
while(true){
renderer.beginPath()
val start = await(command.filter(_.`type` == "mousedown")())
renderer.moveTo(start.clientX - canvas.width, start.clientY)
var m = await(command())
while(m.`type` == "mousemove"){
renderer.lineTo(m.clientX - canvas.width, m.clientY)
renderer.stroke()
m = await(command())
}
renderer.fill()
}
}
}
}
case class Channel[T](){
private[this] var value: Promise[T] = null
def apply(): Future[T] = {
value = Promise[T]()
value.future
}
def update(t: T) = {
if (value != null && !value.isCompleted) value.success(t)
}
def filter(p: T => Boolean) = {
val filtered = Channel[T]()
async{
while(true){
val t = await(this())
if (p(t)) filtered() = t
}
}
filtered
}
}
import org.scalajs.dom
case class Pt(x: Double, y: Double)
@JSExport
object ScalaJSExample{
println("Hello!!")
val corners = Seq(
Pt(canvas.width/2, 0),
Pt(0, canvas.height),
Pt(canvas.width, canvas.height)
)
var p = corners(0)
val (w, h) = (canvas.height.toDouble, canvas.height.toDouble)
@JSExport
def main(args: Array[String]): Unit = {
dom.setInterval(() => for(_ <- 0 until 10){
val c = corners(util.Random.nextInt(3))
p = Pt((p.x + c.x) / 2, (p.y + c.y) / 2)
val m = (p.y / h)
val r = 255 - (p.x / w * m * 255).toInt
val g = 255 - ((w-p.x) / w * m * 255).toInt
val b = 255 - ((h - p.y) / h * 255).toInt
renderer.fillStyle = s"rgb($r, $g, $b)"
renderer.fillRect(p.x, p.y, 1, 1)
}, 10)
}
}
import org.scalajs.dom
@JSExport
object ScalaJSExample{
@JSExport
def main(args: Array[String]): Unit = {
var i = 0
dom.setInterval(() => for(_ <- 0 until 5){
i += 1
val x = i % canvas.width
val y = Math.sin(i / canvas.width * 10) * canvas.height / 4 + canvas.height / 2
output.clear()
val r = math.abs(((y / canvas.height - 0.5) * 255 * 4).toInt)
println(x)
println(y)
renderer.fillStyle = s"rgb($r, 255, 255)"
renderer.fillRect(x, y, 2, 2)
}, 10)
}
}
import org.scalajs.dom
case class Point(x: Double, y: Double){
def +(p: Point) = Point(x + p.x, y + p.y)
def -(p: Point) = Point(x - p.x, y - p.y)
def /(d: Double) = Point(x / d, y / d)
def *(d: Double) = Point(x * d, y * d)
def length = Math.sqrt(x * x + y * y)
}
@JSExport
object ScalaJSExample {
var count = 0
var player = Point(dom.innerWidth / 2, dom.innerHeight / 2)
val corners = Seq(Point(255, 255), Point(0, 255), Point(128, 0))
var bullets = Seq.empty[Point]
var enemies = Seq.empty[Point]
var wave = 1
def run = {
count += 1
bullets = bullets.map(
p => Point(p.x, p.y - 5)
)
if (enemies.isEmpty){
enemies = for{
x <- (0 until canvas.width.toInt by 50)
y <- 0 until wave
} yield {
Point(x, 50 + y * 50)
}
wave += 1
}
enemies = enemies.filter( e =>
!bullets.exists(b =>
(e - b).length < 5
)
)
enemies = enemies.map{ e =>
val i = count % 200
if (i < 50) e.copy(x = e.x - 0.2)
else if (i < 100) e.copy(y = e.y + 0.2)
else if (i < 150) e.copy(x = e.x + 0.2)
else e.copy(y = e.y + 0.2)
}
if (keysDown(38)) player += Point(0, -2)
if (keysDown(37)) player += Point(-2, 0)
if (keysDown(39)) player += Point(2, 0)
if (keysDown(40)) player += Point(0, 2)
}
def draw = {
renderer.clearRect(0, 0, canvas.width, canvas.height)
renderer.fillStyle = "white"
renderer.fillRect(player.x - 5, player.y - 5, 10, 10)
renderer.fillStyle = "yellow"
for (enemy <- enemies){
renderer.fillRect(enemy.x - 5, enemy.y - 5, 10, 10)
}
renderer.fillStyle = "red"
for (bullet <- bullets){
renderer.fillRect(bullet.x - 2, bullet.y - 2, 4, 4)
}
}
val keysDown = collection.mutable.Set.empty[Int]
@JSExport
def main(args: Array[String]): Unit = {
dom.onkeypress = {(e: dom.KeyboardEvent) =>
if (e.keyCode.toInt == 32) bullets = player +: bullets
}
dom.onkeydown = {(e: dom.KeyboardEvent) =>
keysDown.add(e.keyCode.toInt)
}
dom.onkeyup = {(e: dom.KeyboardEvent) =>
keysDown.remove(e.keyCode.toInt)
}
dom.setInterval(() => {run; draw}, 20)
}
}
/**
* A simple square root solver, one step at a time
*/
import org.scalajs.dom
import scala.scalajs.js
@JSExport
object ScalaJSExample{
@JSExport
def main(args: Array[String]): Unit = {
val n = 1000.0
var guess = 1.0
lazy val id: js.Number = dom.setInterval(() => {
val newGuess = guess - (guess * guess - n) / (2 * guess)
if (newGuess == guess) dom.clearInterval(id)
else guess = newGuess
println(guess)
}, 1000)
id
}
}
/**
* A simple Scala.Rx dataflow graph which looks like:
*
* guess -> error
* ^ |
* | v
* --------o
*
* Where the changes propagate around and around until
* the magnitude of `error` drops below `epsilon`
*
*/
@JSExport
object ScalaJSExample{
@JSExport
def main(args: Array[String]): Unit = {
import rx._
val n = 10.0
val guess = Var(1.0)
val epsilon = 0.0000000001
val error = Rx{ guess() * guess() - n }
val o = Obs(error){
println("Guess: " + guess())
if (math.abs(error()) > epsilon) {
guess() = guess() - error() / (2 * guess())
}
}
}
}
/**
* Port of the Scala.js TodoMVC example application
*
* http://lihaoyi.github.io/workbench-example-app/todo.html
*
* To Scala.jsFiddle. Mostly involved sticking the two source files into one
* (since Scala.jsFiddle doesn't support multiple files) and inlining/tweaking
* the CSS so it looks right
*/
import scala.collection.mutable
import scalatags.{HtmlTag, Attr, Modifier}
import scala.util.Random
import scalatags.all._
import scalatags.HtmlTag
import rx._
import rx.core.{Propagator, Obs}
import org.scalajs.dom
import org.scalajs.dom.DOMParser
import scala.scalajs.js
import scalatags.HtmlTag
/**
* A minimal binding between Scala.Rx and Scalatags and Scala-Js-Dom
*/
object Framework {
/**
* Wraps reactive strings in spans, so they can be referenced/replaced
* when the Rx changes.
*/
implicit def RxStr(r: Rx[String]): Modifier = {
new RxMod(Rx(span(r())))
}
/**
* Sticks an ID to a HtmlTag if it doesnt already have one, so we can refer
* to it later.
*/
class DomRef[T](r0: HtmlTag) extends Modifier{
val elemId = r0.attrs.getOrElse("id", ""+Random.nextInt())
val r = r0(id := elemId)
def transform(tag: HtmlTag): HtmlTag = {
tag(r)
}
}
implicit def derefDomRef[T](d: DomRef[T]) = {
dom.document.getElementById(d.elemId).asInstanceOf[T]
}
/**
* Sticks some Rx into a Scalatags fragment, which means hooking up an Obs
* to propapgate changes into the DOM via the element's ID. Monkey-patches
* the Obs onto the element itself so we have a reference to kill it when
* the element leaves the DOM (e.g. it gets deleted).
*/
implicit class RxMod(r: Rx[HtmlTag]) extends Modifier{
val elemId = r.now.attrs.getOrElse("id", ""+Random.nextInt())
lazy val obs: Obs = Obs(r, skipInitial = true){
dom.console.log("Obs fire!", elemId)
val target = dom.document.getElementById(elemId)
val element = dom.document.createElement("div")
element.innerHTML = r.now(
id := elemId
).toString()
if (target != null){
target.parentElement.replaceChild(element.children(0), target)
}else{
obs.kill()
r.kill()
}
}
dom.setTimeout(() => {
target.asInstanceOf[js.Dynamic].obs = obs.asInstanceOf[js.Dynamic]
}, 10)
def transform(tag: HtmlTag): HtmlTag = {
tag(
r.now(
id := elemId
)
)
}
}
/**
* Lets you stick Scala callbacks onto onclick and other onXXXX
* attributes using Scala callbacks, monkey-patching the callback
* object onto the element itself to avoid it getting prematurely
* garbage-collected.
*/
implicit class Transformable(a: Attr){
class CallbackModifier(a: Attr, func: () => Unit) extends Modifier{
def transform(tag: HtmlTag): HtmlTag = {
val elemId = tag.attrs.getOrElse("id", ""+Random.nextInt())
val funcName = a.name + "Func"
dom.setTimeout(() => {
val target = dom.document
.getElementById(elemId)
.asInstanceOf[js.Dynamic]
if (target != null)
target.updateDynamic(funcName)(func: js.Function0[Unit])
}, 10)
tag(id:=elemId, a:=s"this.$funcName(); return false;")
}
}
def <~ (func: => Unit) = new CallbackModifier(a, () => func)
}
}
import org.scalajs.dom
import scalatags.all._
import scalatags.Tags2.section
import scalatags.ExtendedString
import rx._
import rx.core.Propagator
case class Task(txt: Var[String], done: Var[Boolean])
@JSExport
object ScalaJSExample {
import Framework._
val editing = Var[Option[Task]](None)
val tasks = Var(
Seq(
Task(Var("TodoMVC Task A"), Var(true)),
Task(Var("TodoMVC Task B"), Var(false)),
Task(Var("TodoMVC Task C"), Var(false))
)
)
val filters: Map[String, Task => Boolean] = Map(
("All", t => true),
("Active", !_.done()),
("Completed", _.done())
)
val filter = Var("All")
val inputBox = new DomRef[dom.HTMLInputElement](input(
id:="new-todo",
placeholder:="What needs to be done?",
autofocus:=true
))
@JSExport
def main(args: Array[String]): Unit = {
renderln(div(
scalatags.Tags2.style(inlineCss),
section(id:="todoapp", color:="black")(
header(id:="header")(
h1("todos"),
form(
inputBox,
onsubmit <~ {
tasks() = Task(Var(inputBox.value), Var(false)) +: tasks()
inputBox.value = ""
}
)
),
section(id:="main")(
input(
id:="toggle-all",
`type`:="checkbox",
cursor:="pointer",
onclick <~ {
val target = tasks().exists(_.done() == false)
Var.set(tasks().map(_.done -> target): _*)
}
),
label(`for`:="toggle-all", "Mark all as complete"),
Rx{
dom.console.log("A")
ul(id:="todo-list")(
for(task <- tasks() if filters(filter())(task)) yield {
dom.console.log("B", task.txt())
val inputRef = new DomRef[dom.HTMLInputElement](
input(`class`:="edit", value:=task.txt())
)
li(if(task.done()) `class`:="completed" else (), if(editing() == Some(task)) `class`:="editing" else ())(
div(`class`:="view")(
"ondblclick".attr <~ {editing() = Some(task)},
input(
`class`:="toggle",
`type`:="checkbox",
cursor:="pointer",
onchange <~ {task.done() = !task.done()},
if(task.done()) checked:=true else ()
),
label(task.txt()),
button(
`class`:="destroy",
cursor:="pointer",
onclick <~ (tasks() = tasks().filter(_ != task))
)
),
form(
onsubmit <~ {
task.txt() = inputRef.value
editing() = None
},
inputRef
)
)
}
)
},
footer(id:="footer")(
span(id:="todo-count")(strong(Rx(tasks().count(!_.done()).toString)), " item left"),
Rx{
ul(id:="filters")(
for ((name, pred) <- filters.toSeq) yield {
li(a(
if(name == filter()) `class`:="selected" else (),
name,
href:="#",
onclick <~ (filter() = name)
))
}
)
},
button(
id:="clear-completed",
onclick <~ {tasks() = tasks().filter(!_.done())},
"Clear completed (", Rx(tasks().count(_.done()).toString), ")"
)
)
),
footer(id:="info")(
p("Double-click to edit a todo"),
p(a(href:="https://github.com/lihaoyi/workbench-example-app/blob/todomvc/src/main/scala/example/ScalaJSExample.scala")("Source Code")),
p("Created by ", a(href:="http://github.com/lihaoyi")("Li Haoyi"))
)
)
))
}
def inlineCss = """
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
color: inherit;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
body{
background: url('https://raw.github.com/lihaoyi/workbench-example-app/todomvc/src/main/resources/css/bg.png');
}
#sandbox {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: url('https://raw.github.com/lihaoyi/workbench-example-app/todomvc/src/main/resources/css/bg.png');
color: #4d4d4d;
width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
button,
input[type="checkbox"] {
outline: none;
}
#todoapp {
background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
}
#todoapp:before {
content: '';
border-left: 1px solid #f5d6d6;
border-right: 1px solid #f5d6d6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
}
#todoapp input::-webkit-input-placeholder {
font-style: italic;
}
#todoapp input::-moz-placeholder {
font-style: italic;
color: #a9a9a9;
}
#todoapp h1 {
position: absolute;
top: -120px;
width: 100%;
font-size: 70px;
font-weight: bold;
text-align: center;
color: #b3b3b3;
color: rgba(255, 255, 255, 0.3);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
-o-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
#header {
padding-top: 15px;
border-radius: inherit;
}
#header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
#new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
#new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.02);
z-index: 100;
box-shadow: none;
}
#main {
position: relative;
z-index: 2;
border-top: 1px dotted #adadad;
}
label[for='toggle-all'] {
display: none;
}
#toggle-all {
position: absolute;
top: -42px;
left: -4px;
width: 40px;
text-align: center;
/* Mobile Safari */
border: none;
}
#toggle-all:before {
content: '»';
font-size: 28px;
color: #d9d9d9;
padding: 0 25px 7px;
}
#toggle-all:checked:before {
color: #737373;
}
#todo-list {
margin: 0;
padding: 0;
list-style: none;
}
#todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px dotted #ccc;
}
#todo-list li:last-child {
border-bottom: none;
}
#todo-list li.editing {
border-bottom: none;
padding: 0;
}
#todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
#todo-list li.editing .view {
display: none;
}
#todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
/* Mobile Safari */
border: none;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
#todo-list li .toggle:after {
content: '✔';
/* 40 + a couple of pixels visual adjustment */
line-height: 43px;
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
}
#todo-list li .toggle:checked:after {
color: #85ada7;
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
}
#todo-list li label {
white-space: pre;
word-break: break-word;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
-webkit-transition: color 0.4s;
transition: color 0.4s;
}
#todo-list li.completed label {
color: #a9a9a9;
text-decoration: line-through;
}
#todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 22px;
color: #a88a8a;
-webkit-transition: all 0.2s;
transition: all 0.2s;
}
#todo-list li .destroy:hover {
text-shadow: 0 0 1px #000,
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
-ms-transform: scale(1.3);
transform: scale(1.3);
}
#todo-list li .destroy:after {
content: '✖';
}
#todo-list li:hover .destroy {
display: block;
}
#todo-list li .edit {
display: none;
}
#todo-list li.editing:last-child {
margin-bottom: -1px;
}
#footer {
color: #777;
padding: 0 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
height: 20px;
text-align: center;
}
#footer:before {
content: '';
position: absolute;
right: 0;
bottom: 31px;
left: 0;
height: 50px;
z-index: -1;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
0 6px 0 -3px rgba(255, 255, 255, 0.8),
0 7px 1px -3px rgba(0, 0, 0, 0.3),
0 43px 0 -6px rgba(255, 255, 255, 0.8),
0 44px 2px -6px rgba(0, 0, 0, 0.2);
}
#todo-count {
float: left;
text-align: left;
}
#filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
#filters li {
display: inline;
}
#filters li a {
color: #83756f;
margin: 2px;
text-decoration: none;
}
#filters li a.selected {
font-weight: bold;
}
#clear-completed {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
font-size: 11px;
padding: 0 10px;
border-radius: 3px;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
}
#clear-completed:hover {
background: rgba(0, 0, 0, 0.15);
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
}
#info {
margin: 65px auto 0;
color: #a6a6a6;
font-size: 12px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
text-align: center;
}
#info a {
color: inherit;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all,
#todo-list li .toggle {
background: none;
}
#todo-list li .toggle {
height: 40px;
}
#toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
.hidden {
display: none;
}
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #C5C5C5;
border-bottom: 1px dashed #F7F7F7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
-webkit-transition-property: left;
transition-property: left;
-webkit-transition-duration: 500ms;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
margin: 0 0 0 300px;
}
.learn-bar > .learn {
left: 8px;
}
.learn-bar #todoapp {
width: 550px;
margin: 130px auto 40px auto;
}
}
"""
}
import org.scalajs.dom
case class Pt(x: Double, y: Double)
@JSExport
object ScalaJSExample{
import scalatags.all.{a, href, h1}
println(
"An implementation of ", a(href:="http://en.wikipedia.org/wiki/Turmites")("Turmites"),
", in particular ", a(href:="http://en.wikipedia.org/wiki/Langton's_ant")("Langton's Ant"),
", on the HTML canvas. Click anywhere to create more turmites."
)
val directions = Seq(
Pt(0, 1),
Pt(1, 0),
Pt(0, -1),
Pt(-1, 0)
)
val (w, h) = (canvas.width, canvas.height)
var turmites = Seq(
Pt(w / 2, h / 2) -> 0
)
@JSExport
def main(args: Array[String]): Unit = {
dom.setInterval(() => for(_ <- 0 until 10){
turmites = for((oldP, facing) <- turmites) yield {
val d = directions(facing)
val p = Pt((oldP.x + d.x + w) % w, (oldP.y + d.y + h) % h)
val data = renderer.getImageData(p.x, p.y, 1, 1).data
val newFacing = if (data(0).toInt == 0){
val m = (p.y / h)
val r = 255 - (p.x / w * m * 255).toInt
val g = 255 - ((w - p.x) / w * m * 255).toInt
val b = 255 - ((h - p.y) / h * 255).toInt
renderer.fillStyle = s"rgb($r, $g, $b)"
(facing + 3) % 4
}else{
renderer.fillStyle = "black"
(facing + 1) % 4
}
renderer.fillRect(p.x, p.y, 1, 1)
p -> newFacing
}
}, 20)
canvas.onclick = { (e: dom.MouseEvent) =>
val rect = canvas.getBoundingClientRect()
val newP = Pt(e.clientX.toInt - rect.left, e.clientY.toInt - rect.top)
turmites = turmites :+ (newP -> util.Random.nextInt(4))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment