├── .gitignore ├── README.md ├── bin ├── kt ├── kt-emulator ├── kt-emulator.bat ├── kt.bat ├── ktc ├── ktc-dex ├── ktc-dex.bat ├── ktc-js ├── ktc-js.bat ├── ktc.bat ├── kts ├── kts.bat ├── kttest └── kttest.bat ├── buildlibs.sh ├── cs109ui ├── canvas.kt ├── cs109ui.kt ├── uitest-animation.kt ├── uitest-dialogs.kt ├── uitest-transparency.kt ├── uitest1.kt ├── uitest2.kt ├── uitest3.kt ├── uitest4.kt ├── uitest5.kt ├── uitest6.kt └── uitest7.kt ├── emulator └── emulator.kt ├── framework ├── canvas.kt ├── framework.kt └── utils.kt ├── js ├── canvas │ ├── canvas-nojscanvas.kt │ ├── canvas.html │ ├── canvas1.kt │ ├── canvas2.kt │ ├── canvas3.kt │ ├── canvas4.kt │ └── draw1.kt ├── framework │ ├── framework.html │ └── jsframework.kt └── jslib │ └── jscanvas.kt ├── mini-apps ├── animation.kt ├── basic.kt ├── dialogs.kt ├── drawing.kt ├── gravity1.kt ├── gravity2.kt ├── light.kt ├── menus.kt ├── tap1.kt ├── taps.kt └── transparency.kt ├── misc ├── kotlin.lang └── kotlin.xml ├── projects ├── 19-readable │ ├── input.txt │ └── readable.kts ├── 38-ledmatrix │ ├── ledmatrix.kt │ ├── simple.kt │ ├── square.kt │ ├── template.kt │ └── text.kt ├── 50-2048 │ ├── board.kt │ ├── check.kt │ ├── game.kt │ └── uigame.kt ├── 90-guiclock │ └── clock.kt └── 96-journey │ ├── chicken.kt │ ├── journey.html │ └── journey.kt └── tutorial ├── 05-basic ├── arguments.kts ├── drinks.kts ├── for1.kts ├── mastermind.kts ├── overloading.kts ├── triangle.kts ├── triangle1.kts ├── triangle2.kts ├── triangle3.kts ├── types.kt ├── when1.kts ├── when2.kts ├── when3.kts └── when4.kts ├── 07-collatz ├── collatz1.kts ├── collatz2.kts ├── collatz3.kts ├── collatz4.kts └── collatz5.kts ├── 10-objects └── test.kts ├── 11-null └── reverse.kts ├── 13-sets ├── sieve.kts ├── spell.kts └── words.txt ├── 16-exceptions ├── catch1.kts ├── catch2.kts ├── except1.kts ├── except2.kts ├── except3.kts ├── quiz.kts ├── read1.kts └── require1.kts ├── 18-stringbuilder ├── join1.kts ├── join2.kts └── join3.kts ├── 30-maps ├── cmudict.txt ├── cmudict1.kts ├── histogram1.kts ├── histogram2.kts ├── histogram3.kts ├── histogram4.kts └── text.txt ├── 40-lambda ├── higher1.kts ├── higher2.kts ├── higher3.kts ├── integrate.kts ├── primes.kts └── table.kts ├── 48-compiling ├── hello-app.kt ├── hello.kt ├── number-game.kt └── point.kt ├── 50-objects ├── days.kt ├── days1.kt ├── days2.kt ├── days3.kt ├── days4.kt ├── days5.kt ├── days6.kt └── days7.kt ├── 52-objects ├── accum1.kts ├── accum2.kts ├── blackjack-game.kt ├── blackjack1.kt ├── blackjack2.kt ├── rectangle1.kt └── rectangle2.kt ├── 60-junit ├── polynomial.kt ├── polynomial1.kt ├── polynomial2.kt ├── polytest.kt ├── test1.kt └── test2.kt ├── 90-image └── image.kts ├── 92-drawing └── drawing.kts └── 94-files ├── readfile1.kts ├── readfile2.kts ├── writefile1.kts └── writefile2.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.jar 3 | *.js 4 | *.dex 5 | classes 6 | build 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS109 Kotlin tutorial 2 | 3 | This repository contains example code for my Kotlin tutorial at 4 | http://otfried.org/courses/cs109/tutorial.html. 5 | 6 | Also included are: 7 | 8 | * modules to unify drawing graphics using Java.AWT, the HTML5 Canvas 9 | (in Javascript), and on Android. 10 | 11 | * A module that allows students to build simple programs with a 12 | graphical user interface without learning about Swing or event-based 13 | programming. 14 | 15 | See http://otfried.org/courses/cs109/tutorial-cs109ui.html for 16 | documentation. 17 | 18 | * A framework to allow students to develop simple Android apps (called 19 | "mini-apps") without using the Android SDK, including a simple 20 | emulator. 21 | 22 | -------------------------------------------------------------------------------- /bin/kt: -------------------------------------------------------------------------------- 1 | #!/bin/bash --posix 2 | 3 | # Based on findScalaHome() from scalac script 4 | findKotlinHome() { 5 | local source="${BASH_SOURCE[0]}" 6 | while [ -h "$source" ] ; do 7 | local linked="$(readlink "$source")" 8 | local dir="$(cd -P $(dirname "$source") && cd -P $(dirname "$linked") && pwd)" 9 | source="$dir/$(basename "$linked")" 10 | done 11 | (cd -P "$(dirname "$source")/.." && pwd) 12 | } 13 | 14 | KOTLIN_HOME="$(findKotlinHome)" 15 | 16 | CP="$KOTLIN_HOME/lib/kotlin-runtime.jar" 17 | if [ -e classes ]; then 18 | CP="$CP:classes" 19 | fi 20 | for f in $KOTLIN_HOME/ext/*.jar; do 21 | CP="$CP:$f" 22 | done 23 | 24 | java -ea -cp "$CP" "$@" 25 | -------------------------------------------------------------------------------- /bin/kt-emulator: -------------------------------------------------------------------------------- 1 | #!/bin/bash --posix 2 | 3 | # Based on findScalaHome() from scalac script 4 | findKotlinHome() { 5 | local source="${BASH_SOURCE[0]}" 6 | while [ -h "$source" ] ; do 7 | local linked="$(readlink "$source")" 8 | local dir="$(cd -P $(dirname "$source") && cd -P $(dirname "$linked") && pwd)" 9 | source="$dir/$(basename "$linked")" 10 | done 11 | (cd -P "$(dirname "$source")/.." && pwd) 12 | } 13 | 14 | KOTLIN_HOME="$(findKotlinHome)" 15 | 16 | CP="$KOTLIN_HOME/lib/kotlin-runtime.jar" 17 | for f in $KOTLIN_HOME/ext/*.jar; do 18 | CP="$CP:$f" 19 | done 20 | 21 | java -ea -cp "$CP" org.otfried.cs109emulator.EmulatorKt "$@" 22 | -------------------------------------------------------------------------------- /bin/kt-emulator.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | setlocal enabledelayedexpansion 5 | call :set_home 6 | call :set_cp 7 | 8 | java -ea %_CP% org.otfried.cs109emulator.EmulatorKt %* 9 | goto end 10 | 11 | :set_home 12 | set _BIN_DIR= 13 | for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi 14 | set _KOTLIN_HOME=%_BIN_DIR%.. 15 | goto :eof 16 | 17 | :set_cp 18 | set _CP=-cp %_KOTLIN_HOME%\lib\kotlin-runtime.jar 19 | 20 | if exist classes set _CP=!_CP!;classes 21 | 22 | for %%f in ( %_KOTLIN_HOME%\ext\*.jar ) do set _CP=!_CP!;%%f 23 | goto :eof 24 | 25 | :end 26 | -------------------------------------------------------------------------------- /bin/kt.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | setlocal enabledelayedexpansion 5 | call :set_home 6 | call :set_cp 7 | 8 | java -ea %_CP% %* 9 | goto end 10 | 11 | :set_home 12 | set _BIN_DIR= 13 | for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi 14 | set _KOTLIN_HOME=%_BIN_DIR%.. 15 | goto :eof 16 | 17 | :set_cp 18 | set _CP=-cp %_KOTLIN_HOME%\lib\kotlin-runtime.jar 19 | 20 | if exist classes set _CP=!_CP!;classes 21 | 22 | for %%f in ( %_KOTLIN_HOME%\ext\*.jar ) do set _CP=!_CP!;%%f 23 | goto :eof 24 | 25 | :end 26 | -------------------------------------------------------------------------------- /bin/ktc: -------------------------------------------------------------------------------- 1 | #!/bin/bash --posix 2 | 3 | # Based on findScalaHome() from scalac script 4 | findKotlinHome() { 5 | local source="${BASH_SOURCE[0]}" 6 | while [ -h "$source" ] ; do 7 | local linked="$(readlink "$source")" 8 | local dir="$(cd -P $(dirname "$source") && cd -P $(dirname "$linked") && pwd)" 9 | source="$dir/$(basename "$linked")" 10 | done 11 | (cd -P "$(dirname "$source")/.." && pwd) 12 | } 13 | 14 | KOTLIN_HOME="$(findKotlinHome)" 15 | 16 | CP="." 17 | if [ -e classes ]; then 18 | CP="classes" 19 | fi 20 | for f in $KOTLIN_HOME/ext/*.jar; do 21 | CP="$CP:$f" 22 | done 23 | 24 | kotlinc -cp "$CP" -d classes "$@" 25 | -------------------------------------------------------------------------------- /bin/ktc-dex: -------------------------------------------------------------------------------- 1 | #!/bin/bash --posix 2 | 3 | # Based on findScalaHome() from scalac script 4 | findKotlinHome() { 5 | local source="${BASH_SOURCE[0]}" 6 | while [ -h "$source" ] ; do 7 | local linked="$(readlink "$source")" 8 | local dir="$(cd -P $(dirname "$source") && cd -P $(dirname "$linked") && pwd)" 9 | source="$dir/$(basename "$linked")" 10 | done 11 | (cd -P "$(dirname "$source")/.." && pwd) 12 | } 13 | 14 | KOTLIN_HOME="$(findKotlinHome)" 15 | 16 | FIRST=$1 17 | DEX=${FIRST%%.kt}.dex 18 | JAR=${FIRST%%.kt}.jar 19 | 20 | CP="" 21 | SEP="" 22 | CPFLAG="" 23 | for f in $KOTLIN_HOME/ext/*.jar; do 24 | CP="$CP$SEP$f" 25 | SEP=":" 26 | CPFLAG="-cp" 27 | done 28 | 29 | kotlinc $CPFLAG $CP -d $JAR "$@" && \ 30 | java -jar $KOTLIN_HOME/dx/dx.jar --dex --output=$DEX $JAR 31 | 32 | -------------------------------------------------------------------------------- /bin/ktc-dex.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | setlocal enabledelayedexpansion 5 | call :set_home 6 | call :set_cp 7 | 8 | set _DEX=%~n1.dex 9 | set _JAR=%~n1.jar 10 | 11 | call %~dps0kotlinc.bat %_CP% -d %_JAR% %* && java -jar %_KOTLIN_HOME%\dx\dx.jar --dex --output=%_DEX% %_JAR% 12 | 13 | goto end 14 | 15 | :set_home 16 | set _BIN_DIR= 17 | for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi 18 | set _KOTLIN_HOME=%_BIN_DIR%.. 19 | goto :eof 20 | 21 | :set_cp 22 | set _CP= 23 | set "_SEP=-cp^ " 24 | 25 | if exist classes ( 26 | set _CP=-cp classes 27 | set _SEP=; 28 | ) 29 | 30 | for %%f in ( %_KOTLIN_HOME%\ext\*.jar ) do ( 31 | set _CP=!_CP!!_SEP!%%f 32 | set _SEP=; 33 | ) 34 | goto :eof 35 | 36 | :end 37 | -------------------------------------------------------------------------------- /bin/ktc-js: -------------------------------------------------------------------------------- 1 | #!/bin/bash --posix 2 | 3 | # Based on findScalaHome() from scalac script 4 | findKotlinHome() { 5 | local source="${BASH_SOURCE[0]}" 6 | while [ -h "$source" ] ; do 7 | local linked="$(readlink "$source")" 8 | local dir="$(cd -P $(dirname "$source") && cd -P $(dirname "$linked") && pwd)" 9 | source="$dir/$(basename "$linked")" 10 | done 11 | (cd -P "$(dirname "$source")/.." && pwd) 12 | } 13 | 14 | KOTLIN_HOME="$(findKotlinHome)" 15 | 16 | kotlinc-js -library-files "$KOTLIN_HOME/ext/cs109-jslib.jar" "$@" 17 | -------------------------------------------------------------------------------- /bin/ktc-js.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | setlocal enabledelayedexpansion 5 | call :set_home 6 | 7 | call %~dps0kotlinc-js.bat -library-files %_KOTLIN_HOME%\ext\cs109-jslib.jar %* 8 | goto end 9 | 10 | :set_home 11 | set _BIN_DIR= 12 | for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi 13 | set _KOTLIN_HOME=%_BIN_DIR%.. 14 | goto :eof 15 | 16 | :end 17 | -------------------------------------------------------------------------------- /bin/ktc.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | setlocal enabledelayedexpansion 5 | call :set_home 6 | call :set_cp 7 | 8 | call %~dps0kotlinc.bat %_CP% -d classes %* 9 | goto end 10 | 11 | :set_home 12 | set _BIN_DIR= 13 | for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi 14 | set _KOTLIN_HOME=%_BIN_DIR%.. 15 | goto :eof 16 | 17 | :set_cp 18 | set _CP= 19 | rem next line must end with a space! 20 | set _SEP=-cp^ 21 | 22 | if exist classes ( 23 | set _CP=-cp classes 24 | set _SEP=; 25 | ) 26 | 27 | for %%f in ( %_KOTLIN_HOME%\ext\*.jar ) do ( 28 | set _CP=!_CP!!_SEP!%%f 29 | set _SEP=; 30 | ) 31 | goto :eof 32 | 33 | :end 34 | -------------------------------------------------------------------------------- /bin/kts: -------------------------------------------------------------------------------- 1 | #!/bin/bash --posix 2 | 3 | # Based on findScalaHome() from scalac script 4 | findKotlinHome() { 5 | local source="${BASH_SOURCE[0]}" 6 | while [ -h "$source" ] ; do 7 | local linked="$(readlink "$source")" 8 | local dir="$(cd -P $(dirname "$source") && cd -P $(dirname "$linked") && pwd)" 9 | source="$dir/$(basename "$linked")" 10 | done 11 | (cd -P "$(dirname "$source")/.." && pwd) 12 | } 13 | 14 | KOTLIN_HOME="$(findKotlinHome)" 15 | 16 | CP="" 17 | SEP="-cp " 18 | if [ -e classes ]; then 19 | CP="-cp classes" 20 | SEP=":" 21 | fi 22 | for f in $KOTLIN_HOME/ext/*.jar; do 23 | CP="$CP$SEP$f" 24 | SEP=":" 25 | done 26 | 27 | kotlinc $CP -script "$@" 28 | -------------------------------------------------------------------------------- /bin/kts.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | setlocal enabledelayedexpansion 5 | call :set_home 6 | call :set_cp 7 | 8 | call %~dps0kotlinc.bat %_CP% -script %* 9 | 10 | goto :fin 11 | 12 | :set_home 13 | set _BIN_DIR= 14 | for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi 15 | set _KOTLIN_HOME=%_BIN_DIR%.. 16 | goto :eof 17 | 18 | :set_cp 19 | set _CP= 20 | rem next line must end with a space! 21 | set _SEP=-cp^ 22 | 23 | if exist classes ( 24 | set _CP=-cp classes 25 | set _SEP=; 26 | ) 27 | 28 | for %%f in ( %_KOTLIN_HOME%\ext\*.jar ) do ( 29 | set _CP=!_CP!!_SEP!%%f 30 | set _SEP=; 31 | ) 32 | goto :eof 33 | 34 | :fin 35 | -------------------------------------------------------------------------------- /bin/kttest: -------------------------------------------------------------------------------- 1 | #!/bin/bash --posix 2 | 3 | # Based on findScalaHome() from scalac script 4 | findKotlinHome() { 5 | local source="${BASH_SOURCE[0]}" 6 | while [ -h "$source" ] ; do 7 | local linked="$(readlink "$source")" 8 | local dir="$(cd -P $(dirname "$source") && cd -P $(dirname "$linked") && pwd)" 9 | source="$dir/$(basename "$linked")" 10 | done 11 | (cd -P "$(dirname "$source")/.." && pwd) 12 | } 13 | 14 | KOTLIN_HOME="$(findKotlinHome)" 15 | 16 | CP="$KOTLIN_HOME/lib/kotlin-runtime.jar" 17 | if [ -e classes ]; then 18 | CP="$CP:classes" 19 | fi 20 | for f in $KOTLIN_HOME/ext/*.jar; do 21 | CP="$CP:$f" 22 | done 23 | 24 | java -cp "$CP" org.junit.runner.JUnitCore "$@" 25 | -------------------------------------------------------------------------------- /bin/kttest.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | setlocal enabledelayedexpansion 5 | call :set_home 6 | call :set_cp 7 | 8 | java %_CP% org.junit.runner.JUnitCore %* 9 | goto end 10 | 11 | :set_home 12 | set _BIN_DIR= 13 | for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi 14 | set _KOTLIN_HOME=%_BIN_DIR%.. 15 | goto :eof 16 | 17 | :set_cp 18 | set _CP=-cp %_KOTLIN_HOME%\lib\kotlin-runtime.jar 19 | 20 | if exist classes set _CP=!_CP!;classes 21 | 22 | for %%f in ( %_KOTLIN_HOME%\ext\*.jar ) do set _CP=!_CP!;%%f 23 | goto :eof 24 | 25 | :end 26 | -------------------------------------------------------------------------------- /buildlibs.sh: -------------------------------------------------------------------------------- 1 | # 2 | # Build all additional files to be installed 3 | # 4 | 5 | # dx.jar is from `SDK/build-tools/23.0.3/lib/dx.jar 6 | 7 | BASE=`pwd` 8 | BUILD=$BASE/build/kotlinc 9 | EXT=$BUILD/ext 10 | SRC=$BASE/framework 11 | JSRC=$BASE/js/jslib 12 | UISRC=$BASE/cs109ui 13 | 14 | rm -fr $BASE/build 15 | mkdir -p $EXT 16 | 17 | echo "Building cs109.jar" 18 | kotlinc -d $EXT/cs109.jar $SRC/canvas.kt $SRC/framework.kt $SRC/utils.kt 19 | 20 | echo "Building cs109ui.jar" 21 | kotlinc -d $EXT/cs109ui.jar -cp $EXT/cs109.jar $UISRC/canvas.kt $UISRC/cs109ui.kt 22 | 23 | #echo "Building cs109-jslib.jar" 24 | #kotlinc-js -output jscanvas.js -meta-info $JSRC/jscanvas.kt $SRC/canvas.kt 25 | #jar cf $EXT/cs109-jslib.jar jscanvas.js jscanvas.meta.js 26 | #rm jscanvas.js jscanvas.meta.js 27 | 28 | echo "Building cs109-emulator.jar" 29 | kotlinc -d $EXT/cs109-emulator.jar -cp $EXT/cs109.jar:$EXT/cs109ui.jar $BASE/emulator/emulator.kt 30 | 31 | mkdir -p $BUILD/bin 32 | cp $BASE/bin/kt* $BUILD/bin 33 | rm $BUILD/bin/ktc-js* 34 | 35 | mkdir -p $BUILD/dx 36 | cp $HOME/Android/Sdk/build-tools/27.0.3/lib/dx.jar $BUILD/dx 37 | wget -O $EXT/junit-4.12.jar http://search.maven.org/remotecontent?filepath=junit/junit/4.12/junit-4.12.jar 38 | wget -O $EXT/hamcrest-core-1.3.jar http://search.maven.org/remotecontent?filepath=org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar 39 | 40 | echo "Making zipfile" 41 | pushd $BASE/build 42 | zip -r cs109-additions.zip kotlinc 43 | popd 44 | 45 | -------------------------------------------------------------------------------- /cs109ui/canvas.kt: -------------------------------------------------------------------------------- 1 | package org.otfried.cs109ui 2 | 3 | import org.otfried.cs109.Color 4 | import org.otfried.cs109.DrawStyle 5 | import org.otfried.cs109.TextAlign 6 | import org.otfried.cs109.Canvas 7 | 8 | import java.awt.image.BufferedImage 9 | import java.awt.Graphics2D 10 | import java.awt.Color as JColor 11 | import java.awt.BasicStroke 12 | import java.awt.AlphaComposite 13 | import java.awt.geom.* 14 | 15 | // -------------------------------------------------------------------- 16 | 17 | class ImageCanvas(val img: BufferedImage) : Canvas { 18 | private val g = img.createGraphics() 19 | private val ctm = mutableListOf(g.transform) 20 | private var path: Path2D.Double? = null 21 | 22 | init { 23 | g.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, 24 | java.awt.RenderingHints.VALUE_ANTIALIAS_ON) 25 | } 26 | 27 | private fun draw(s: DrawStyle, shape: java.awt.Shape) { 28 | if (s != DrawStyle.STROKE) 29 | g.fill(shape) 30 | if (s != DrawStyle.FILL) 31 | g.draw(shape) 32 | } 33 | 34 | private fun toRadians(degrees: Double) = degrees / 180.0 * Math.PI 35 | 36 | 37 | override val width: Int 38 | get() = img.width 39 | override val height: Int 40 | get() = img.height 41 | 42 | override fun clear(c: Color) { 43 | val tfm = g.transform 44 | g.setColor(JColor(c.r, c.g, c.b)) 45 | g.transform = ctm.first() 46 | g.fillRect(0, 0, width, height) 47 | g.transform = tfm 48 | } 49 | 50 | override fun setColor(c: Color) { 51 | g.setColor(JColor(c.r, c.g, c.b)) 52 | } 53 | 54 | override fun setAlpha(a: Int) { 55 | g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 56 | (a / 255f))) 57 | } 58 | 59 | override fun setLineWidth(w: Double) { 60 | g.setStroke(BasicStroke(w.toFloat())) 61 | } 62 | 63 | override fun setFont(size: Double, font: String) { 64 | g.setFont(java.awt.Font(font, java.awt.Font.PLAIN, size.toInt())) 65 | } 66 | 67 | override fun drawRectangle(x: Double, y: Double, w: Double, h: Double, 68 | s: DrawStyle) { 69 | draw(s, Rectangle2D.Double(x, y, w, h)) 70 | } 71 | 72 | override fun drawCircle(x: Double, y: Double, r: Double, s: DrawStyle) { 73 | draw(s, Ellipse2D.Double(x-r, y-r, 2*r, 2*r)) 74 | } 75 | 76 | override fun drawText(text: String, x: Double, y: Double, align: TextAlign) { 77 | var x0 = x.toFloat() 78 | if (align != TextAlign.LEFT) { 79 | val w = g.getFontMetrics().stringWidth(text) 80 | if (align == TextAlign.RIGHT) 81 | x0 -= w 82 | else 83 | x0 -= w/2f 84 | } 85 | g.drawString(text, x0, y.toFloat()) 86 | } 87 | 88 | override fun textWidth(s: String): Double { 89 | return g.getFontMetrics().stringWidth(s).toDouble() 90 | } 91 | 92 | override fun beginShape() { path = Path2D.Double() } 93 | override fun moveTo(x: Double, y: Double) { path?.moveTo(x, y) } 94 | override fun lineTo(x: Double, y: Double) { path?.lineTo(x, y) } 95 | override fun closePath() { path?.closePath() } 96 | override fun drawShape(s: DrawStyle) { draw(s, path!!) } 97 | 98 | override fun translate(x: Double, y: Double) { g.translate(x, y) } 99 | override fun rotate(degrees: Double) { g.rotate(toRadians(degrees)) } 100 | override fun scale(sx: Double, sy: Double) { g.scale(sx, sy) } 101 | 102 | override fun save() { 103 | ctm.add(g.transform) 104 | } 105 | override fun restore() { 106 | if (ctm.size == 1) 107 | throw IllegalArgumentException("Restore without save") 108 | g.transform = ctm.removeAt(ctm.lastIndex) 109 | } 110 | 111 | fun done() { g.dispose() } 112 | } 113 | 114 | // -------------------------------------------------------------------- 115 | -------------------------------------------------------------------------------- /cs109ui/cs109ui.kt: -------------------------------------------------------------------------------- 1 | package org.otfried.cs109ui 2 | 3 | import kotlin.concurrent.* 4 | import java.awt.Color 5 | import java.awt.Point 6 | import java.awt.Dimension 7 | import java.awt.image.BufferedImage 8 | import java.awt.event.WindowEvent 9 | import java.awt.event.KeyEvent 10 | import java.awt.event.ComponentEvent 11 | import java.awt.event.MouseEvent 12 | import javax.swing.JOptionPane as JOP 13 | 14 | // -------------------------------------------------------------------- 15 | 16 | interface Result 17 | 18 | data class Key(val ch: Char) : Result 19 | data class Mouse(val x: Int, val y: Int) : Result 20 | object TimeOut : Result 21 | 22 | val timeOutChar = '\u0000' 23 | 24 | // -------------------------------------------------------------------- 25 | 26 | private fun onEDT(op: () -> Unit) { 27 | javax.swing.SwingUtilities.invokeLater(op) 28 | } 29 | 30 | private fun onEDTWait(op: () -> Unit) { 31 | javax.swing.SwingUtilities.invokeAndWait(op) 32 | } 33 | 34 | private fun sendEvent(r: Result) { 35 | with (UI) { 36 | timer.stop() // key or mouse event cancels timeout 37 | lock.withLock { 38 | queue.add(r) 39 | eventCondition.signal() 40 | } 41 | } 42 | } 43 | 44 | // -------------------------------------------------------------------- 45 | 46 | private class JCanvas: javax.swing.JComponent() { 47 | private var image: BufferedImage? = null 48 | 49 | init { 50 | setPreferredSize(Dimension(480, 320)) 51 | setFocusable(true) 52 | setOpaque(true) 53 | setDoubleBuffered(false) 54 | 55 | addKeyListener(object: java.awt.event.KeyAdapter() { 56 | override fun keyPressed(e: KeyEvent) { 57 | sendEvent(Key(e.keyChar)) 58 | } 59 | }) 60 | addMouseListener(object: java.awt.event.MouseAdapter() { 61 | override fun mouseClicked(e: MouseEvent) { 62 | sendEvent(Mouse(e.x, e.y)) 63 | } 64 | }) 65 | } 66 | 67 | override fun paintComponent(gr: java.awt.Graphics) { 68 | val g = gr as java.awt.Graphics2D 69 | if (image == null) { 70 | val d = size 71 | g.setColor(Color.WHITE) 72 | g.fillRect(0,0, d.width, d.height) 73 | } else 74 | g.drawImage(image, null, 0, 0) 75 | } 76 | 77 | fun setImage(im: BufferedImage) { 78 | if (image?.width != im.width || image?.height != im.height) 79 | image = BufferedImage(im.width, im.height, BufferedImage.TYPE_INT_RGB) 80 | image!!.setData(im.getData()) 81 | setPreferredSize(Dimension(im.width, im.height)) 82 | } 83 | } 84 | 85 | // -------------------------------------------------------------------- 86 | 87 | private class Frame(canvas: JCanvas): java.awt.Frame() { 88 | init { 89 | setTitle("CS109 UI") 90 | setResizable(false) 91 | add(canvas) 92 | pack() 93 | addWindowListener(object: java.awt.event.WindowAdapter() { 94 | override fun windowClosing(e: WindowEvent) { 95 | kotlin.system.exitProcess(0) 96 | } 97 | }) 98 | } 99 | } 100 | 101 | // -------------------------------------------------------------------- 102 | 103 | private object UI { 104 | init { 105 | println("CS109 UI version 2017/04/03") 106 | } 107 | 108 | val canvas = JCanvas() 109 | val ui = Frame(canvas) 110 | val queue = java.util.concurrent.ConcurrentLinkedQueue() 111 | val lock = java.util.concurrent.locks.ReentrantLock() 112 | val eventCondition = lock.newCondition() 113 | val timer = javax.swing.Timer(1000, 114 | object : javax.swing.AbstractAction() { 115 | override fun actionPerformed(e : java.awt.event.ActionEvent) = 116 | sendEvent(TimeOut) 117 | }) 118 | } 119 | 120 | fun setTitle(s: String) { 121 | onEDTWait { UI.ui.setTitle(s) } 122 | } 123 | 124 | fun show(image: BufferedImage) { 125 | onEDT { 126 | UI.canvas.setImage(image) 127 | val insets = UI.ui.insets 128 | UI.ui.setSize(Dimension(image.width + insets.left + insets.right, 129 | image.height + insets.top + insets.bottom)) 130 | UI.ui.setVisible(true) 131 | UI.canvas.repaint() 132 | } 133 | } 134 | 135 | fun setTimeOut(ms: Int) { 136 | UI.timer.setRepeats(false) 137 | UI.timer.setInitialDelay(ms) 138 | UI.timer.restart() 139 | } 140 | 141 | fun waitEvent(): Result { 142 | return UI.lock.withLock { 143 | while (UI.queue.isEmpty()) { 144 | UI.eventCondition.await() 145 | } 146 | UI.queue.remove() 147 | } 148 | } 149 | 150 | fun waitKey(): Char { 151 | while (true) { 152 | val r = waitEvent() 153 | when(r) { 154 | is Key -> return r.ch 155 | is TimeOut -> return timeOutChar 156 | // is Mouse -> 157 | } 158 | } 159 | } 160 | 161 | fun waitMouse(): Pair { 162 | while (true) { 163 | val r = waitEvent() 164 | when(r) { 165 | // is Key -> 166 | is Mouse -> return Pair(r.x, r.y) 167 | is TimeOut -> return Pair(-1, -1) 168 | } 169 | } 170 | } 171 | 172 | fun waitForMs(ms: Int) { 173 | Thread.sleep(ms.toLong()) 174 | } 175 | 176 | fun close() { 177 | onEDTWait { UI.ui.setVisible(false) } 178 | kotlin.system.exitProcess(0) 179 | } 180 | 181 | // -------------------------------------------------------------------- 182 | // Dialogs 183 | // -------------------------------------------------------------------- 184 | 185 | fun showMessage(msg: String) { 186 | onEDTWait { 187 | UI.ui.setVisible(true) 188 | JOP.showMessageDialog(UI.canvas, msg, UI.ui.title, JOP.PLAIN_MESSAGE) 189 | } 190 | } 191 | 192 | fun askYesNo(question: String): Boolean { 193 | var result: Boolean = false 194 | onEDTWait { 195 | UI.ui.setVisible(true) 196 | val r = JOP.showConfirmDialog(UI.canvas, question, 197 | UI.ui.title, JOP.YES_NO_OPTION, JOP.PLAIN_MESSAGE) 198 | result = (r == JOP.YES_OPTION) 199 | } 200 | return result 201 | } 202 | 203 | fun inputString(msg: String): String { 204 | var result: String = "" 205 | onEDTWait { 206 | UI.ui.setVisible(true) 207 | val r = JOP.showInputDialog(UI.canvas, msg, UI.ui.title, 208 | JOP.PLAIN_MESSAGE, null, null, "") 209 | if (r is String) 210 | result = r 211 | } 212 | return result 213 | } 214 | 215 | // up to three buttons are okay 216 | fun askButtons(question: String, buttons: Array): Int { 217 | var result: Int = 0 218 | onEDTWait { 219 | UI.ui.setVisible(true) 220 | val r = JOP.showOptionDialog(UI.canvas, question, UI.ui.title, 221 | JOP.YES_NO_CANCEL_OPTION, JOP.PLAIN_MESSAGE, 222 | null, buttons, null) 223 | if (r != JOP.CLOSED_OPTION) 224 | result = r + 1 225 | } 226 | return result 227 | } 228 | 229 | fun askChoice(msg: String, choices: Array): String { 230 | var result: String = "" 231 | onEDTWait { 232 | UI.ui.setVisible(true) 233 | val r = JOP.showInputDialog(UI.canvas, msg, UI.ui.title, 234 | JOP.PLAIN_MESSAGE, null, 235 | choices, null) 236 | if (r is String) 237 | result = r 238 | } 239 | return result 240 | } 241 | 242 | // -------------------------------------------------------------------- 243 | -------------------------------------------------------------------------------- /cs109ui/uitest-animation.kt: -------------------------------------------------------------------------------- 1 | import org.otfried.cs109ui.* 2 | import org.otfried.cs109ui.ImageCanvas 3 | import org.otfried.cs109.Color 4 | import org.otfried.cs109.DrawStyle 5 | 6 | import java.awt.image.BufferedImage 7 | 8 | fun draw(image: BufferedImage, x: Double, y: Double) { 9 | val g = ImageCanvas(image) 10 | g.clear(Color.WHITE) 11 | g.setColor(Color.RED) 12 | g.drawCircle(x, y, 40.0) 13 | g.done() 14 | } 15 | 16 | fun main(args: Array) { 17 | setTitle("CS109 UI Animation test") 18 | 19 | val image = BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB) 20 | 21 | var x = 30.0 22 | var y = 30.0 23 | while (x < 500.0) { 24 | draw(image, x, y) 25 | x += 2 26 | y += 1 27 | show(image) 28 | Thread.sleep(10) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cs109ui/uitest-dialogs.kt: -------------------------------------------------------------------------------- 1 | import org.otfried.cs109ui.* 2 | 3 | fun main(args: Array) { 4 | setTitle("CS109 UI Dialog Test") 5 | 6 | while (true) { 7 | showMessage("This is a message") 8 | 9 | val yesno = askYesNo("Do you like this?") 10 | println("Answer: $yesno") 11 | 12 | val name = inputString("What is your name?") 13 | println("Name: $name") 14 | 15 | val drink = askChoice("What do you like best", 16 | arrayOf("Beer", "Wine", "Makkoli", "Soju", "Water")) 17 | println("Drink: $drink") 18 | 19 | val choice = askButtons("What do you want to do now?", 20 | arrayOf("Exit", "More dialogs", "Nothing")) 21 | println("Do next: $choice") 22 | when(choice) { 23 | 1 -> close() 24 | 2 -> { /* nothing */ } 25 | 3 -> return 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cs109ui/uitest-transparency.kt: -------------------------------------------------------------------------------- 1 | import org.otfried.cs109ui.* 2 | import org.otfried.cs109.Color 3 | import org.otfried.cs109.DrawStyle 4 | import org.otfried.cs109.TextAlign 5 | 6 | import java.awt.image.BufferedImage 7 | 8 | fun draw(image: BufferedImage) { 9 | val g = ImageCanvas(image) 10 | g.clear(Color.WHITE) 11 | 12 | g.setAlpha(128) // 50% transparency 13 | g.setColor(Color.CYAN) 14 | g.drawCircle(200.0, 200.0, 150.0) 15 | g.setColor(Color.YELLOW) 16 | g.drawCircle(300.0, 200.0, 150.0) 17 | g.setColor(Color.MAGENTA) 18 | g.drawCircle(250.0, 300.0, 150.0) 19 | 20 | g.setAlpha(32) // 12.5% transparency 21 | g.setColor(Color(0, 128, 0)) // a darker green 22 | g.setFont(128.0) 23 | g.translate(250.0, 250.0) 24 | g.rotate(-45.0) 25 | g.translate(0.0, 48.0) 26 | g.drawText("CS109", 0.0, 0.0, TextAlign.CENTER) 27 | 28 | g.done() 29 | } 30 | 31 | fun main(args: Array) { 32 | setTitle("CS109 UI Transparency Test") 33 | val image = BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB) 34 | draw(image) 35 | show(image) 36 | } 37 | -------------------------------------------------------------------------------- /cs109ui/uitest1.kt: -------------------------------------------------------------------------------- 1 | import org.otfried.cs109ui.* 2 | import org.otfried.cs109ui.ImageCanvas 3 | import org.otfried.cs109.Color 4 | import org.otfried.cs109.DrawStyle 5 | 6 | import java.awt.image.BufferedImage 7 | 8 | fun draw(image: BufferedImage) { 9 | // get ImageCanvas for the image 10 | val g = ImageCanvas(image) 11 | 12 | // clear background 13 | g.clear(Color.WHITE) 14 | 15 | // draw two filled circles 16 | g.setColor(Color.RED) 17 | g.drawCircle(50.0, 50.0, 20.0) // FILL is the default 18 | g.setColor(Color.ORANGE) 19 | g.drawCircle(250.0, 400.0, 20.0) 20 | 21 | // draw an unfilled circle with a pen of width 3 22 | g.setColor(Color.MAGENTA) 23 | g.setLineWidth(3.0) 24 | g.drawCircle(415.0, 50.0, 15.0, DrawStyle.STROKE) 25 | 26 | // draw a filled and an unfilled Rectangle 27 | g.setColor(Color.CYAN) 28 | g.drawRectangle(20.0, 400.0, 50.0, 20.0, DrawStyle.FILL) 29 | g.drawRectangle(400.0, 400.0, 50.0, 20.0, DrawStyle.STROKE) 30 | 31 | // draw a line 32 | g.setLineWidth(1.0) // reset to default 33 | g.setColor(Color(0, 0, 255)) // same as Color.BLUE 34 | g.beginShape() 35 | g.moveTo(50.0, 50.0) 36 | g.lineTo(250.0, 400.0) 37 | g.drawShape(DrawStyle.STROKE) 38 | 39 | // draw a non-convex quadrilateral: 40 | g.save() // save current coordinate system 41 | g.translate(360.0, 260.0) // move origin to here 42 | g.rotate(-30.0) // rotate 30 degrees counter-clockwise 43 | g.beginShape() 44 | g.moveTo(0.0, 0.0) 45 | g.lineTo(30.0, -40.0) 46 | g.lineTo(60.0, 0.0) 47 | g.lineTo(30.0, -100.0) 48 | g.closePath() 49 | g.drawShape() 50 | g.restore() // restore current coordinate system 51 | 52 | // draw some text 53 | g.setColor(Color(0, 128, 0)) // a darker green 54 | g.setFont(20.0, "Batang") 55 | g.drawText("Hello World!", 155.0, 225.0) 56 | g.drawText("안녕 하세요", 175.0, 245.0) 57 | 58 | // done with drawing 59 | g.done() 60 | } 61 | 62 | fun main(args: Array) { 63 | setTitle("CS109 UI Test #1") 64 | val image = BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB) 65 | draw(image) 66 | show(image) 67 | } 68 | -------------------------------------------------------------------------------- /cs109ui/uitest2.kt: -------------------------------------------------------------------------------- 1 | import org.otfried.cs109ui.* 2 | import org.otfried.cs109ui.ImageCanvas 3 | import org.otfried.cs109.Color 4 | import org.otfried.cs109.DrawStyle 5 | 6 | import java.awt.image.BufferedImage 7 | 8 | fun draw(image: BufferedImage, color: Color) { 9 | val g = ImageCanvas(image) 10 | g.clear(Color.WHITE) 11 | g.setColor(color) 12 | g.drawRectangle(100.0, 100.0, 300.0, 300.0) 13 | g.done() 14 | } 15 | 16 | fun showWait(image: BufferedImage, color: Color, ms: Int) { 17 | draw(image, color) // draw rectangle 18 | show(image) 19 | waitForMs(ms) // wait ms milliseconds 20 | } 21 | 22 | fun main(args: Array) { 23 | setTitle("CS109 UI Blinking Rectangle") 24 | 25 | val image = BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB) 26 | 27 | showWait(image, Color.WHITE, 500) // 0.5 sec white picture 28 | showWait(image, Color.RED, 1000) // 1 sec red rectangle 29 | showWait(image, Color.WHITE, 500) // 0.5 sec white picture 30 | showWait(image, Color.BLUE, 1000) // 1 sec blue rectangle 31 | showWait(image, Color.WHITE, 5000) // 5 secs white picture 32 | 33 | close() // close window and terminate program 34 | } 35 | -------------------------------------------------------------------------------- /cs109ui/uitest3.kt: -------------------------------------------------------------------------------- 1 | import org.otfried.cs109ui.* 2 | import org.otfried.cs109ui.ImageCanvas 3 | import org.otfried.cs109.Color 4 | import org.otfried.cs109.DrawStyle 5 | 6 | import java.awt.image.BufferedImage 7 | 8 | fun draw(image: BufferedImage, color: Color) { 9 | val g = ImageCanvas(image) 10 | g.clear(Color.WHITE) 11 | g.setColor(color) 12 | g.drawRectangle(100.0, 100.0, 300.0, 300.0) 13 | g.done() 14 | } 15 | 16 | fun main(args: Array) { 17 | setTitle("CS109 UI Keyboard Input Test") 18 | 19 | val image = BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB) 20 | 21 | draw(image, Color.RED) 22 | show(image) 23 | 24 | println("Now press some keys inside the CS109 UI windows") 25 | println("Pressing 'q' will terminate the program") 26 | 27 | while (true) { 28 | val ch = waitKey() 29 | println("Got character $ch") 30 | if (ch == 'q') 31 | close() // close window and terminate program 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /cs109ui/uitest4.kt: -------------------------------------------------------------------------------- 1 | import org.otfried.cs109ui.* 2 | import org.otfried.cs109ui.ImageCanvas 3 | import org.otfried.cs109.Color 4 | import org.otfried.cs109.DrawStyle 5 | 6 | import java.awt.image.BufferedImage 7 | 8 | fun draw(image: BufferedImage, color: Color) { 9 | val g = ImageCanvas(image) 10 | g.clear(Color.WHITE) 11 | g.setColor(color) 12 | g.drawRectangle(100.0, 100.0, 300.0, 300.0) 13 | g.done() 14 | } 15 | 16 | fun main(args: Array) { 17 | setTitle("CS109 UI Mouse Input Test") 18 | 19 | val image = BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB) 20 | 21 | draw(image, Color.RED) 22 | show(image) 23 | 24 | println("Now click the moused inside the CS109 UI windows") 25 | println("Close the window to terminate the program") 26 | 27 | while (true) { 28 | val (x,y) = waitMouse() 29 | println("Mouse click at ($x, $y)") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cs109ui/uitest5.kt: -------------------------------------------------------------------------------- 1 | import org.otfried.cs109ui.* 2 | import org.otfried.cs109ui.ImageCanvas 3 | import org.otfried.cs109.Color 4 | import org.otfried.cs109.DrawStyle 5 | 6 | import java.awt.image.BufferedImage 7 | 8 | fun draw(image: BufferedImage, color: Color) { 9 | val g = ImageCanvas(image) 10 | g.clear(Color.WHITE) 11 | g.setColor(color) 12 | g.drawRectangle(100.0, 100.0, 300.0, 300.0) 13 | g.done() 14 | } 15 | 16 | fun main(args: Array) { 17 | setTitle("CS109 UI Timer Test") 18 | 19 | val image = BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB) 20 | 21 | draw(image, Color.RED) 22 | show(image) 23 | 24 | println("You have 5 seconds to press a key inside the CS109 UI window") 25 | 26 | setTimeOut(5000) 27 | val ch = waitKey() 28 | if (ch == timeOutChar) 29 | println("You lost!") 30 | else 31 | println("You won by typing $ch") 32 | 33 | close() // close window and terminate program 34 | } 35 | -------------------------------------------------------------------------------- /cs109ui/uitest6.kt: -------------------------------------------------------------------------------- 1 | import org.otfried.cs109ui.* 2 | import org.otfried.cs109ui.ImageCanvas 3 | import org.otfried.cs109.Color 4 | import org.otfried.cs109.DrawStyle 5 | 6 | import java.awt.image.BufferedImage 7 | 8 | fun draw(image: BufferedImage) { 9 | val g = ImageCanvas(image) 10 | g.clear(Color.WHITE) 11 | g.setColor(Color.RED) 12 | g.drawRectangle(50.0, 150.0, 150.0, 150.0) 13 | g.setColor(Color.GREEN) 14 | g.drawRectangle(300.0, 150.0, 150.0, 150.0) 15 | g.done() 16 | } 17 | 18 | fun main(args: Array) { 19 | setTitle("CS109 UI Timer Test") 20 | 21 | val image = BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB) 22 | 23 | draw(image) 24 | show(image) 25 | 26 | println("You have 3 seconds to click a square") 27 | 28 | setTimeOut(3000) 29 | val (x, y) = waitMouse() 30 | 31 | if (x < 0) 32 | println("You are too slow.") 33 | else 34 | println("You clicked at $x $y") 35 | 36 | close() // close window and terminate program 37 | } 38 | -------------------------------------------------------------------------------- /cs109ui/uitest7.kt: -------------------------------------------------------------------------------- 1 | import org.otfried.cs109ui.* 2 | import org.otfried.cs109ui.ImageCanvas 3 | import org.otfried.cs109.Color 4 | import org.otfried.cs109.DrawStyle 5 | 6 | import org.otfried.cs109ui.TimeOut 7 | import org.otfried.cs109ui.Key 8 | import org.otfried.cs109ui.Mouse 9 | 10 | import java.awt.image.BufferedImage 11 | 12 | fun draw(image: BufferedImage, color: Color) { 13 | val g = ImageCanvas(image) 14 | g.clear(Color.WHITE) 15 | g.setColor(color) 16 | g.drawRectangle(100.0, 100.0, 300.0, 300.0) 17 | g.done() 18 | } 19 | 20 | fun main(args: Array) { 21 | setTitle("CS109 UI Timer Test") 22 | 23 | val image = BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB) 24 | 25 | draw(image, Color.RED) 26 | show(image) 27 | 28 | println("You have 5 seconds to press a key or click the mouse") 29 | 30 | setTimeOut(5000) 31 | val ev = waitEvent() 32 | when (ev) { 33 | is TimeOut -> 34 | println("You lost!") 35 | is Key -> 36 | println("You won by typing ${ev.ch}") 37 | is Mouse -> 38 | println("You won by clicking at (${ev.x}, ${ev.y})") 39 | } 40 | close() // close window and terminate program 41 | } 42 | -------------------------------------------------------------------------------- /emulator/emulator.kt: -------------------------------------------------------------------------------- 1 | package org.otfried.cs109emulator 2 | 3 | import org.otfried.cs109.MiniApp 4 | import org.otfried.cs109.Context 5 | import org.otfried.cs109.TextAlign 6 | import org.otfried.cs109.Color 7 | 8 | import org.otfried.cs109ui.ImageCanvas 9 | 10 | import java.awt.Dimension 11 | import java.awt.Color as JColor 12 | import java.awt.image.BufferedImage 13 | import java.awt.event.KeyEvent 14 | import java.awt.event.MouseEvent 15 | import java.awt.event.ActionListener 16 | 17 | import javax.swing.JOptionPane as JOP 18 | import javax.swing.JSlider 19 | import javax.swing.SwingConstants 20 | import javax.swing.event.ChangeListener 21 | 22 | import java.net.URLClassLoader 23 | import java.net.URL 24 | 25 | // -------------------------------------------------------------------- 26 | 27 | private class JCanvas(w: Int, h: Int, val handler: UI): javax.swing.JComponent() { 28 | private var image = BufferedImage(w, h, BufferedImage.TYPE_INT_RGB) 29 | var miniApp: MiniApp? = null 30 | var toast: String? = null 31 | var redrawApp = true 32 | val toastFont = java.awt.Font("sans-serif", java.awt.Font.PLAIN, 32) 33 | 34 | init { 35 | setPreferredSize(Dimension(w, h)) 36 | setFocusable(true) 37 | 38 | addKeyListener(object: java.awt.event.KeyAdapter() { 39 | override fun keyPressed(e: KeyEvent) { 40 | handler.handleKey(e.keyChar) 41 | } 42 | }) 43 | addMouseListener(object: java.awt.event.MouseAdapter() { 44 | override fun mouseClicked(e: MouseEvent) { 45 | handler.handleMouse(e.button, e.x.toDouble(), e.y.toDouble()) 46 | } 47 | }) 48 | } 49 | 50 | override fun paintComponent(gr: java.awt.Graphics) { 51 | val gc = ImageCanvas(image) 52 | if (redrawApp) 53 | miniApp?.onDraw(gc) 54 | redrawApp = false 55 | gc.done() 56 | val g = gr as java.awt.Graphics2D 57 | g.drawImage(image, null, 0, 0) 58 | val t = toast 59 | t?.let { 60 | g.setFont(toastFont) 61 | val x = width / 2 62 | val y = height - 60 63 | val w = g.getFontMetrics().stringWidth(t).toInt() / 2 64 | g.setColor(JColor.BLUE) 65 | g.drawString(t, x - w, y) 66 | } 67 | } 68 | } 69 | 70 | // -------------------------------------------------------------------- 71 | 72 | private class UI(override val width: Int, override val height: Int) : Context { 73 | val canvas = JCanvas(width, height, this) 74 | val ui = javax.swing.JFrame() 75 | var miniAppMenu: List Unit>>? = null 76 | 77 | val lightSlider = JSlider(SwingConstants.HORIZONTAL, 1, 1000, 20) 78 | val lightFrame = javax.swing.JFrame() 79 | 80 | val gravityXSlider = JSlider(SwingConstants.HORIZONTAL, -100, 100, 0) 81 | val gravityYSlider = JSlider(SwingConstants.HORIZONTAL, -100, 100, 0) 82 | val gravityFrame = javax.swing.JFrame() 83 | 84 | init { 85 | ui.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE) 86 | ui.setTitle("CS109 emulator") 87 | ui.setResizable(false) 88 | ui.add(canvas) 89 | ui.pack() 90 | lightSlider.setPreferredSize(Dimension(400, 100)) 91 | lightSlider.addChangeListener( ChangeListener { lightChange() } ) 92 | lightFrame.setTitle("Light sensor") 93 | lightFrame.add(lightSlider) 94 | lightFrame.pack() 95 | gravityXSlider.setPaintTicks(true) 96 | gravityYSlider.setPaintTicks(true) 97 | gravityXSlider.setMajorTickSpacing(100) 98 | gravityYSlider.setMajorTickSpacing(100) 99 | gravityXSlider.addChangeListener( ChangeListener { gravityChange() } ) 100 | gravityYSlider.addChangeListener( ChangeListener { gravityChange() } ) 101 | val gpanel = javax.swing.JPanel() 102 | gpanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(30, 10, 30, 10)); 103 | gpanel.setLayout(javax.swing.BoxLayout(gpanel, SwingConstants.VERTICAL)) 104 | gpanel.add(gravityXSlider) 105 | gpanel.add(javax.swing.Box.createRigidArea(Dimension(0,30))) 106 | gpanel.add(gravityYSlider) 107 | gravityFrame.setTitle("Gravity sensor") 108 | gravityFrame.add(gpanel) 109 | gravityFrame.pack() 110 | } 111 | 112 | val toastTimer = javax.swing.Timer(2000, { clearToast() } ) 113 | val afterTimer = javax.swing.Timer(2000, { handleTimeout() } ) 114 | 115 | var tapHandler: ((x: Double, y: Double) -> Unit)? = null 116 | var doubleTapHandler: ((x: Double, y: Double) -> Unit)? = null 117 | var flingHandler: ((x: Double, y: Double, 118 | dir: Char, dist: Double) -> Unit)? = null 119 | 120 | var afterHandler: (() -> Unit)? = null 121 | var gravityHandler: ((x: Double, y: Double, z: Double) -> Unit)? = null 122 | var lightHandler: ((lux: Double) -> Unit)? = null 123 | 124 | // ------------------------------------------------------------------ 125 | 126 | override fun update() { 127 | canvas.redrawApp = true 128 | canvas.repaint() 129 | } 130 | 131 | override fun setTitle(s: String) { 132 | ui.setTitle(s) 133 | } 134 | 135 | override fun toast(s: String) { 136 | canvas.toast = s 137 | toastTimer.start() 138 | canvas.repaint() 139 | } 140 | 141 | fun clearToast() { 142 | canvas.toast = null 143 | canvas.repaint() 144 | } 145 | 146 | override fun onTap(f: (x: Double, y: Double) -> Unit) { 147 | tapHandler = f 148 | } 149 | 150 | override fun onDoubleTap(f: (x: Double, y: Double) -> Unit) { 151 | doubleTapHandler = f 152 | } 153 | 154 | override fun onFling(f: (x: Double, y: Double, dir: Char, 155 | dist: Double) -> Unit) { 156 | flingHandler = f 157 | } 158 | 159 | override fun onGravity(f: (x: Double, y: Double, z: Double) -> Unit) { 160 | gravityHandler = f 161 | gravityFrame.setVisible(true) 162 | gravityChange() 163 | } 164 | 165 | fun gravityChange() { 166 | val x = gravityXSlider.value / 10.0 167 | val y = gravityYSlider.value / 10.0 168 | val z = 1.0 169 | val norm = Math.sqrt(x*x + y*y + z*z) 170 | val g = 9.81 171 | gravityHandler?.invoke(g*x/norm, g*y/norm, g*z/norm) 172 | } 173 | 174 | override fun onLight(f: (lux: Double) -> Unit) { 175 | lightHandler = f 176 | lightFrame.setVisible(true) 177 | lightChange() 178 | } 179 | 180 | fun lightChange() { 181 | val lux = lightSlider.value.toDouble() 182 | lightHandler?.invoke(lux) 183 | } 184 | 185 | override fun after(ms: Long, f: () -> Unit) { 186 | afterTimer.stop() 187 | afterTimer.setInitialDelay(ms.toInt()) 188 | afterHandler = f 189 | afterTimer.start() 190 | } 191 | 192 | fun handleTimeout() { 193 | afterHandler?.invoke() 194 | } 195 | 196 | override fun createMenu(items: List Unit>>) { 197 | miniAppMenu = items 198 | val menu = javax.swing.JMenu("Menu") 199 | for (i in items.indices) { 200 | val item = javax.swing.JMenuItem(items[i].first) 201 | item.addActionListener(ActionListener { handleMenuItem(i) }) 202 | menu.add(item) 203 | } 204 | val menubar = javax.swing.JMenuBar() 205 | menubar.add(menu) 206 | ui.setJMenuBar(menubar) 207 | } 208 | 209 | fun handleMenuItem(item: Int) { 210 | val m = miniAppMenu 211 | m?.let { m[item].second() } 212 | } 213 | 214 | // ------------------------------------------------------------------ 215 | 216 | fun handleKey(ch: Char) { 217 | val x = width / 2.0 218 | val y = height / 2.0 219 | val dist = 200.0 220 | if (ch in "udlr") 221 | flingHandler?.invoke(x, y, ch, dist) 222 | } 223 | 224 | fun handleMouse(button: Int, x: Double, y: Double) { 225 | when(button) { 226 | MouseEvent.BUTTON1 -> tapHandler?.invoke(x, y) 227 | MouseEvent.BUTTON3 -> doubleTapHandler?.invoke(x, y) 228 | else -> { /* ignore it */ } 229 | } 230 | } 231 | 232 | // ------------------------------------------------------------------ 233 | 234 | override fun showMessage(s: String) { 235 | JOP.showMessageDialog(canvas, s, ui.title, JOP.PLAIN_MESSAGE) 236 | } 237 | 238 | override fun askYesNo(s: String, handle: (Boolean) -> Unit) { 239 | val r = JOP.showConfirmDialog(canvas, s, ui.title, 240 | JOP.YES_NO_OPTION, JOP.PLAIN_MESSAGE) 241 | handle(r == JOP.YES_OPTION) 242 | } 243 | 244 | override fun inputString(prompt: String, handle: (String) -> Unit) { 245 | val r = JOP.showInputDialog(canvas, prompt, ui.title, 246 | JOP.PLAIN_MESSAGE, null, null, "") 247 | if (r is String) 248 | handle(r) 249 | } 250 | 251 | // up to three buttons are okay 252 | fun askButtons(question: String, buttons: Array): Int { 253 | var result: Int = 0 254 | val r = JOP.showOptionDialog(canvas, question, ui.title, 255 | JOP.YES_NO_CANCEL_OPTION, JOP.PLAIN_MESSAGE, 256 | null, buttons, null) 257 | if (r != JOP.CLOSED_OPTION) 258 | result = r + 1 259 | return result 260 | } 261 | 262 | fun askChoice(msg: String, choices: Array): String { 263 | var result: String = "" 264 | val r = JOP.showInputDialog(canvas, msg, ui.title, 265 | JOP.PLAIN_MESSAGE, null, 266 | choices, null) 267 | if (r is String) 268 | result = r 269 | return result 270 | } 271 | } 272 | 273 | // -------------------------------------------------------------------- 274 | 275 | fun loadJar(jar: String, ctx: Context): MiniApp { 276 | System.err.print("Loading '$jar' ...") 277 | val loader = URLClassLoader(arrayOf(URL("file:$jar"))) 278 | val klass = loader.loadClass("Main") 279 | @Suppress("UNCHECKED_CAST") 280 | val ctor = klass.getConstructor(Context::class.java) 281 | as java.lang.reflect.Constructor 282 | val miniApp = ctor.newInstance(ctx) 283 | System.err.println("succeeded") 284 | return miniApp 285 | } 286 | 287 | fun usage() { 288 | System.err.println("Usage: kt-emulator [ ]") 289 | kotlin.system.exitProcess(1) 290 | } 291 | 292 | fun main(args: Array) { 293 | var width = 480 294 | var height = 720 295 | if (args.size == 3) { 296 | try { 297 | width = args[1].toInt() 298 | height = args[2].toInt() 299 | } 300 | catch (e: NumberFormatException) { 301 | usage() 302 | } 303 | } 304 | if (args.size != 1 && args.size != 3) 305 | usage() 306 | val jar = args[0] 307 | if (!jar.endsWith(".jar")) 308 | usage() 309 | val ui = UI(width, height) 310 | val miniApp = loadJar(jar, ui) 311 | ui.canvas.miniApp = miniApp 312 | ui.ui.setVisible(true) 313 | } 314 | 315 | // -------------------------------------------------------------------- 316 | -------------------------------------------------------------------------------- /framework/canvas.kt: -------------------------------------------------------------------------------- 1 | package org.otfried.cs109 2 | 3 | // -------------------------------------------------------------------- 4 | 5 | data class Color(val r: Int, val g: Int, val b: Int) { 6 | companion object { 7 | val WHITE = Color(255, 255, 255) 8 | val BLACK = Color(0, 0, 0) 9 | val RED = Color(255, 0, 0) 10 | val GREEN = Color(0, 255, 0) 11 | val BLUE = Color(0, 0, 255) 12 | val MAGENTA = Color(255, 0, 255) 13 | val CYAN = Color(0, 255, 255) 14 | val YELLOW = Color(255, 255, 0) 15 | val ORANGE = Color(255, 200, 0) 16 | val GRAY = Color(128, 128, 128) 17 | val DARK_GRAY = Color(64, 64, 64) 18 | val LIGHT_GRAY = Color(192, 192, 192) 19 | val PINK = Color(255, 175, 175) 20 | } 21 | constructor(rgb: Int) : 22 | this(rgb shr 16, (rgb shr 8) and 0xff, rgb and 0xff) { } 23 | } 24 | 25 | enum class DrawStyle { 26 | STROKE, FILL, STROKE_AND_FILL 27 | } 28 | 29 | enum class TextAlign { 30 | LEFT, CENTER, RIGHT 31 | } 32 | 33 | interface Canvas { 34 | val width: Int 35 | val height: Int 36 | 37 | fun clear(c: Color) 38 | 39 | fun setColor(c: Color) 40 | fun setAlpha(a: Int) 41 | fun setLineWidth(w: Double) 42 | fun setFont(size: Double, font: String = "sans-serif") 43 | 44 | fun drawRectangle(x: Double, y: Double, w: Double, h: Double, 45 | s: DrawStyle = DrawStyle.FILL) 46 | fun drawCircle(x: Double, y: Double, r: Double, 47 | s: DrawStyle = DrawStyle.FILL) 48 | fun drawText(text: String, x: Double, y: Double, 49 | align: TextAlign = TextAlign.LEFT) 50 | 51 | fun textWidth(s: String): Double 52 | 53 | fun beginShape() 54 | fun moveTo(x: Double, y: Double) 55 | fun lineTo(x: Double, y: Double) 56 | fun closePath() 57 | fun drawShape(s: DrawStyle = DrawStyle.FILL) 58 | 59 | fun translate(x: Double, y: Double) 60 | fun rotate(degrees: Double) 61 | fun scale(sx: Double, sy: Double) 62 | fun save() 63 | fun restore() 64 | } 65 | 66 | // -------------------------------------------------------------------- 67 | -------------------------------------------------------------------------------- /framework/framework.kt: -------------------------------------------------------------------------------- 1 | package org.otfried.cs109 2 | 3 | // -------------------------------------------------------------------- 4 | 5 | interface Context { 6 | val width: Int 7 | val height: Int 8 | 9 | fun update() 10 | 11 | fun setTitle(s: String) 12 | 13 | fun toast(s: String) 14 | fun showMessage(s: String) 15 | fun askYesNo(s: String, handle: (Boolean) -> Unit) 16 | fun inputString(prompt: String, handle: (String) -> Unit) 17 | 18 | fun onTap(f: (x: Double, y: Double) -> Unit) 19 | fun onDoubleTap(f: (x: Double, y: Double) -> Unit) 20 | fun onFling(f: (x: Double, y: Double, dir: Char, dist: Double) -> Unit) 21 | 22 | fun onGravity(f: (x: Double, y: Double, z: Double) -> Unit) 23 | fun onLight(f: (lux: Double) -> Unit) 24 | 25 | fun after(ms: Long, f: () -> Unit) 26 | 27 | fun createMenu(items: List Unit>>) 28 | } 29 | 30 | interface MiniApp { 31 | fun onDraw(canvas: Canvas) 32 | } 33 | 34 | // -------------------------------------------------------------------- 35 | -------------------------------------------------------------------------------- /framework/utils.kt: -------------------------------------------------------------------------------- 1 | // Utility functions for the beginning 2 | 3 | package org.otfried.cs109 4 | 5 | fun readString(prompt: String): String { 6 | print(prompt) 7 | System.out.flush() 8 | return readLine() ?: "" 9 | } 10 | -------------------------------------------------------------------------------- /js/canvas/canvas-nojscanvas.kt: -------------------------------------------------------------------------------- 1 | package canvas 2 | 3 | import kotlin.browser.document 4 | import kotlin.browser.window 5 | import org.w3c.dom.* 6 | import org.w3c.dom.events.* 7 | 8 | object Canvas { 9 | val canvas = document.getElementById("canvas") as HTMLCanvasElement 10 | val ctx = canvas.getContext("2d")!! as CanvasRenderingContext2D 11 | init { 12 | canvas.width = window.innerWidth.toInt() - 20 13 | canvas.height = window.innerHeight.toInt() - 50 14 | } 15 | 16 | var x = canvas.width / 2.0 17 | var y = canvas.height / 2.0 18 | var alpha = 0.0 19 | var animate = false 20 | var timeStamp = 0.0 21 | 22 | fun draw() { 23 | ctx.save() 24 | ctx.clearRect(0.0, 0.0, canvas.width.toDouble(), canvas.height.toDouble()) 25 | ctx.fillStyle = "green" 26 | ctx.globalAlpha = 0.2 27 | ctx.fillRect(10.0, 10.0, 100.0, 100.0) 28 | ctx.globalAlpha = 1.0 29 | for (i in 0 .. 5) { 30 | for (j in 0 .. 5) { 31 | ctx.fillStyle = "rgb(${Math.floor(255-42.5*i)},${Math.floor(255-42.5*j)},0)" 32 | ctx.fillRect(150.0 + j*25.0, i*25.0, 25.0, 25.0) 33 | } 34 | } 35 | ctx.translate(x, y) 36 | ctx.globalAlpha = 0.5 37 | ctx.rotate(alpha) 38 | ctx.fillStyle = "red" 39 | ctx.font = "32px sans-serif" 40 | ctx.fillText("Lovely", 0.0, 0.0) 41 | ctx.restore() 42 | if (animate) 43 | window.requestAnimationFrame { animate(it) } 44 | } 45 | } 46 | 47 | fun animate(s: Double) { 48 | val delta = s - Canvas.timeStamp 49 | Canvas.timeStamp = s 50 | Canvas.x += delta / 2.0 51 | if (Canvas.x > Canvas.canvas.width) 52 | Canvas.x = 0.0 53 | Canvas.alpha += Math.PI / 360.0 * delta 54 | if (Canvas.alpha > 2 * Math.PI) 55 | Canvas.alpha = 0.0 56 | Canvas.draw() 57 | } 58 | 59 | fun keyDown(e: Event) { 60 | val ek = e as KeyboardEvent 61 | var k = ek.key 62 | if (k === undefined) 63 | k = "${ek.keyCode.toChar().toLowerCase()}" 64 | when (k) { 65 | "a" -> Canvas.x -= 3 66 | "s" -> Canvas.x += 3 67 | "w" -> Canvas.y -= 3 68 | "z" -> Canvas.y += 3 69 | "j" -> Canvas.alpha += Math.PI / 36.0 70 | "k" -> Canvas.alpha -= Math.PI / 36.0 71 | "g" -> { 72 | if (!Canvas.animate) 73 | Canvas.timeStamp = window.performance.now() 74 | Canvas.animate = !Canvas.animate 75 | } 76 | else -> return 77 | } 78 | Canvas.draw() 79 | e.preventDefault() 80 | } 81 | 82 | fun mouseDown(e: Event) { 83 | val em = e as MouseEvent 84 | Canvas.x = em.offsetX 85 | Canvas.y = em.offsetY 86 | Canvas.draw() 87 | } 88 | 89 | fun start() { 90 | println("Canvas4 starting...") 91 | println("Active keys are aswzjk and g for animation") 92 | Canvas.draw() 93 | window.addEventListener("keydown", { keyDown(it) }, true) 94 | window.addEventListener("mousedown", { mouseDown(it) }, true) 95 | } 96 | -------------------------------------------------------------------------------- /js/canvas/canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
This is a fun paragraph.
11 | 12 | 13 | -------------------------------------------------------------------------------- /js/canvas/canvas1.kt: -------------------------------------------------------------------------------- 1 | package canvas 2 | 3 | import org.otfried.cs109js.JsCanvas 4 | import org.otfried.cs109.Color 5 | 6 | import kotlin.browser.document 7 | import org.w3c.dom.* 8 | 9 | fun start() { 10 | println("Hello World from Javascript") 11 | 12 | val canvas = JsCanvas("canvas") 13 | canvas.clear(Color.GREEN) 14 | 15 | val text = document.getElementById("text") 16 | text?.appendChild(document.createTextNode("Was du hier liest ist kein Gedicht.")) 17 | } 18 | -------------------------------------------------------------------------------- /js/canvas/canvas2.kt: -------------------------------------------------------------------------------- 1 | package canvas 2 | 3 | import org.otfried.cs109js.JsCanvas 4 | import org.otfried.cs109.Color 5 | 6 | object Controller { 7 | val canvas = JsCanvas("canvas") 8 | var x = 30.0 9 | var y = 50.0 10 | var alpha = 45.0 11 | 12 | fun draw() { 13 | canvas.clear(Color.WHITE) 14 | canvas.setAlpha(48) 15 | canvas.setColor(Color.GREEN) 16 | canvas.drawRectangle(10.0, 10.0, 100.0, 100.0) 17 | canvas.setAlpha(255) // opaque 18 | for (i in 0 .. 5) { 19 | for (j in 0 .. 5) { 20 | canvas.setColor(Color(Math.floor(255-42.5*i), Math.floor(255-42.5*j), 0)) 21 | canvas.drawRectangle(150.0 + j*25.0, i*25.0, 25.0, 25.0) 22 | } 23 | } 24 | canvas.translate(x, y) 25 | canvas.setAlpha(128) 26 | canvas.rotate(alpha) 27 | canvas.setColor(Color.RED) 28 | canvas.setFont(32.0) 29 | canvas.drawText("Lovely", 0.0, 0.0) 30 | } 31 | } 32 | 33 | fun start() { 34 | println("Canvas2 starting...") 35 | Controller.draw() 36 | } 37 | -------------------------------------------------------------------------------- /js/canvas/canvas3.kt: -------------------------------------------------------------------------------- 1 | package canvas 2 | 3 | import org.otfried.cs109js.JsCanvas 4 | import org.otfried.cs109.Color 5 | 6 | import kotlin.browser.window 7 | import org.w3c.dom.events.* 8 | 9 | object Controller { 10 | val canvas = JsCanvas("canvas") 11 | var x = 30.0 12 | var y = 50.0 13 | var alpha = 45.0 14 | 15 | fun draw() { 16 | canvas.save() 17 | canvas.clear(Color.WHITE) 18 | canvas.setAlpha(48) 19 | canvas.setColor(Color.GREEN) 20 | canvas.drawRectangle(10.0, 10.0, 100.0, 100.0) 21 | canvas.setAlpha(255) // opaque 22 | for (i in 0 .. 5) { 23 | for (j in 0 .. 5) { 24 | canvas.setColor(Color(Math.floor(255-42.5*i), Math.floor(255-42.5*j), 0)) 25 | canvas.drawRectangle(150.0 + j*25.0, i*25.0, 25.0, 25.0) 26 | } 27 | } 28 | canvas.translate(x, y) 29 | canvas.setAlpha(128) 30 | canvas.rotate(alpha) 31 | canvas.setColor(Color.RED) 32 | canvas.setFont(32.0) 33 | canvas.drawText("Lovely", 0.0, 0.0) 34 | canvas.restore() 35 | } 36 | 37 | fun keyDown(e: Event) { 38 | val ek = e as KeyboardEvent 39 | var k = ek.key 40 | if (k === undefined) 41 | k = "${ek.keyCode.toChar().toLowerCase()}" 42 | when (k) { 43 | "a" -> x -= 3 44 | "s" -> x += 3 45 | "w" -> y -= 3 46 | "z" -> y += 3 47 | "j" -> alpha += 10.0 48 | "k" -> alpha -= 10.0 49 | else -> return 50 | } 51 | draw() 52 | e.preventDefault() 53 | } 54 | 55 | fun mouseDown(e: Event) { 56 | val em = e as MouseEvent 57 | x = em.offsetX 58 | y = em.offsetY 59 | draw() 60 | } 61 | } 62 | 63 | fun start() { 64 | println("Canvas3 starting...") 65 | println("Active keys are aswzjk") 66 | Controller.draw() 67 | window.addEventListener("keydown", { Controller.keyDown(it) }, true) 68 | window.addEventListener("mousedown", { Controller.mouseDown(it) }, true) 69 | } 70 | -------------------------------------------------------------------------------- /js/canvas/canvas4.kt: -------------------------------------------------------------------------------- 1 | package canvas 2 | 3 | import org.otfried.cs109js.JsCanvas 4 | import org.otfried.cs109.Color 5 | 6 | import kotlin.browser.window 7 | import org.w3c.dom.events.* 8 | 9 | object Controller { 10 | val canvas = JsCanvas("canvas") 11 | // change canvas size to fill entire browser window 12 | init { 13 | canvas.canvas.width = window.innerWidth.toInt() - 20 14 | canvas.canvas.height = window.innerHeight.toInt() - 50 15 | } 16 | 17 | var x = 30.0 18 | var y = 50.0 19 | var alpha = 45.0 20 | var animate = false 21 | var timeStamp = 0.0 22 | 23 | fun draw() { 24 | canvas.save() 25 | canvas.clear(Color.WHITE) 26 | canvas.setAlpha(48) 27 | canvas.setColor(Color.GREEN) 28 | canvas.drawRectangle(10.0, 10.0, 100.0, 100.0) 29 | canvas.setAlpha(255) // opaque 30 | for (i in 0 .. 5) { 31 | for (j in 0 .. 5) { 32 | canvas.setColor(Color(Math.floor(255-42.5*i), Math.floor(255-42.5*j), 0)) 33 | canvas.drawRectangle(150.0 + j*25.0, i*25.0, 25.0, 25.0) 34 | } 35 | } 36 | canvas.translate(x, y) 37 | canvas.setAlpha(128) 38 | canvas.rotate(alpha) 39 | canvas.setColor(Color.RED) 40 | canvas.setFont(32.0) 41 | canvas.drawText("Lovely", 0.0, 0.0) 42 | canvas.restore() 43 | if (animate) 44 | window.requestAnimationFrame { animate(it) } 45 | } 46 | 47 | fun animate(s: Double) { 48 | val delta = s - timeStamp 49 | timeStamp = s 50 | x += delta / 2.0 51 | if (x > canvas.width) 52 | x = 0.0 53 | alpha += 0.3 * delta 54 | if (alpha >= 360.0) 55 | alpha = 0.0 56 | draw() 57 | } 58 | 59 | fun keyDown(e: Event) { 60 | val ek = e as KeyboardEvent 61 | var k = ek.key 62 | if (k === undefined) 63 | k = "${ek.keyCode.toChar().toLowerCase()}" 64 | when (k) { 65 | "a" -> x -= 3 66 | "s" -> x += 3 67 | "w" -> y -= 3 68 | "z" -> y += 3 69 | "j" -> alpha += 10.0 70 | "k" -> alpha -= 10.0 71 | "g" -> { 72 | if (!animate) 73 | timeStamp = window.performance.now() 74 | animate = !animate 75 | } 76 | else -> return 77 | } 78 | draw() 79 | e.preventDefault() 80 | } 81 | 82 | fun mouseDown(e: Event) { 83 | val em = e as MouseEvent 84 | x = em.offsetX 85 | y = em.offsetY 86 | draw() 87 | } 88 | } 89 | 90 | fun start() { 91 | println("Canvas3 starting...") 92 | println("Active keys are aswzjk and g for animation") 93 | Controller.draw() 94 | window.addEventListener("keydown", { Controller.keyDown(it) }, true) 95 | window.addEventListener("mousedown", { Controller.mouseDown(it) }, true) 96 | } 97 | -------------------------------------------------------------------------------- /js/canvas/draw1.kt: -------------------------------------------------------------------------------- 1 | // This is the drawing code from "drawing.kts" and "uitest1.kt" 2 | 3 | package canvas 4 | 5 | import org.otfried.cs109js.JsCanvas 6 | import org.otfried.cs109.Color 7 | import org.otfried.cs109.Canvas 8 | import org.otfried.cs109.DrawStyle 9 | 10 | import kotlin.browser.document 11 | import org.w3c.dom.* 12 | 13 | fun draw(g: Canvas) { 14 | // clear background 15 | g.clear(Color.WHITE) 16 | 17 | // draw two filled circles 18 | g.setColor(Color.RED) 19 | g.drawCircle(50.0, 50.0, 20.0) // FILL is the default 20 | g.setColor(Color.ORANGE) 21 | g.drawCircle(250.0, 400.0, 20.0) 22 | 23 | // draw an unfilled circle with a pen of width 3 24 | g.setColor(Color.MAGENTA) 25 | g.setLineWidth(3.0) 26 | g.drawCircle(415.0, 50.0, 15.0, DrawStyle.STROKE) 27 | 28 | // draw a filled and an unfilled Rectangle 29 | g.setColor(Color.CYAN) 30 | g.drawRectangle(20.0, 400.0, 50.0, 20.0, DrawStyle.FILL) 31 | g.drawRectangle(400.0, 400.0, 50.0, 20.0, DrawStyle.STROKE) 32 | 33 | // draw a line 34 | g.setLineWidth(1.0) // reset to default 35 | g.setColor(Color(0, 0, 255)) // same as Color.BLUE 36 | g.beginShape() 37 | g.moveTo(50.0, 50.0) 38 | g.lineTo(250.0, 400.0) 39 | g.drawShape(DrawStyle.STROKE) 40 | 41 | // draw a non-convex quadrilateral: 42 | g.save() // save current coordinate system 43 | g.translate(360.0, 260.0) // move origin to here 44 | g.rotate(-30.0) // rotate 30 degrees counter-clockwise 45 | g.beginShape() 46 | g.moveTo(0.0, 0.0) 47 | g.lineTo(30.0, -40.0) 48 | g.lineTo(60.0, 0.0) 49 | g.lineTo(30.0, -100.0) 50 | g.closePath() 51 | g.drawShape() 52 | g.restore() // restore current coordinate system 53 | 54 | // draw some text 55 | g.setColor(Color(0, 128, 0)) // a darker green 56 | g.setFont(20.0, "Batang") 57 | g.drawText("Hello World!", 155.0, 225.0) 58 | g.drawText("안녕 하세요", 175.0, 245.0) 59 | } 60 | 61 | fun start() { 62 | println("Hello World from Javascript") 63 | 64 | val canvas = JsCanvas("canvas") 65 | canvas.canvas.width = 500 66 | canvas.canvas.height = 500 67 | draw(canvas) 68 | } 69 | -------------------------------------------------------------------------------- /js/framework/framework.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /js/framework/jsframework.kt: -------------------------------------------------------------------------------- 1 | package jsframework 2 | 3 | import kotlin.browser.document 4 | import kotlin.browser.window 5 | import org.w3c.dom.events.Event 6 | import org.w3c.dom.events.MouseEvent 7 | 8 | import org.otfried.cs109js.* 9 | import org.otfried.cs109.Context 10 | import org.otfried.cs109.Canvas 11 | 12 | object Controller : Context { 13 | val canvas = JsCanvas("canvas") 14 | 15 | val app = Main(this) 16 | 17 | fun draw() { 18 | app.onDraw(canvas) 19 | } 20 | 21 | fun mouseDown(e: Event) { 22 | val em = e as MouseEvent 23 | app.onTouch(em.offsetX, em.offsetY) 24 | } 25 | 26 | // ------------------------------------------------------------------ 27 | 28 | override fun update() { draw() } 29 | override fun toast(s: String) { 30 | window.alert(s) 31 | } 32 | override fun setTitle(s: String) { 33 | document.title = s 34 | } 35 | override fun showMessage(s: String) { 36 | window.alert(s) 37 | } 38 | override fun askYesNo(s: String, handle: (Boolean) -> Unit) { 39 | handle(window.confirm(s)) 40 | } 41 | override fun inputString(prompt: String, handle: (String) -> Unit) { 42 | val r = window.prompt(prompt) 43 | if (r != null) 44 | handle(r) 45 | } 46 | } 47 | 48 | fun start() { 49 | Controller.draw() 50 | window.addEventListener("mousedown", { Controller.mouseDown(it) }, true) 51 | } 52 | -------------------------------------------------------------------------------- /js/jslib/jscanvas.kt: -------------------------------------------------------------------------------- 1 | package org.otfried.cs109js 2 | 3 | import org.otfried.cs109.Color 4 | import org.otfried.cs109.DrawStyle 5 | import org.otfried.cs109.TextAlign 6 | import org.otfried.cs109.Canvas 7 | 8 | import kotlin.browser.document 9 | import org.w3c.dom.HTMLCanvasElement 10 | import org.w3c.dom.CanvasRenderingContext2D 11 | 12 | // -------------------------------------------------------------------- 13 | 14 | class JsCanvas(id: String) : Canvas { 15 | val canvas = document.getElementById(id) as HTMLCanvasElement 16 | val ctx = canvas.getContext("2d")!! as CanvasRenderingContext2D 17 | 18 | private var color = Color.BLACK 19 | 20 | private fun cols(c: Color): String = "rgb(${c.r},${c.g},${c.b})" 21 | private fun toRadians(degrees: Double) = degrees / 180.0 * Math.PI 22 | 23 | override val width: Int 24 | get() = canvas.width 25 | override val height: Int 26 | get() = canvas.height 27 | 28 | override fun clear(c: Color) { 29 | ctx.fillStyle = cols(c) 30 | ctx.fillRect(0.0, 0.0, width.toDouble(), height.toDouble()) 31 | } 32 | 33 | override fun setColor(c: Color) { color = c } 34 | override fun setAlpha(a: Int) { ctx.globalAlpha = a.toDouble() / 255.0 } 35 | override fun setLineWidth(w: Double) { ctx.lineWidth = w } 36 | override fun setFont(size: Double, font: String) { 37 | ctx.font = "${size}px $font" 38 | } 39 | 40 | override fun drawRectangle(x: Double, y: Double, 41 | w: Double, h: Double, s: DrawStyle) { 42 | if (s != DrawStyle.STROKE) { 43 | ctx.fillStyle = cols(color) 44 | ctx.fillRect(x, y, w, h) 45 | } 46 | if (s != DrawStyle.FILL) { 47 | ctx.strokeStyle = cols(color) 48 | ctx.strokeRect(x, y, w, h) 49 | } 50 | } 51 | 52 | override fun drawCircle(x: Double, y: Double, r: Double, s: DrawStyle) { 53 | ctx.beginPath() 54 | ctx.arc(x, y, r, 0.0, 2*Math.PI, false) 55 | ctx.closePath() 56 | drawShape(s) 57 | } 58 | 59 | override fun drawText(text: String, x: Double, y: Double, align: TextAlign) { 60 | ctx.fillStyle = cols(color) 61 | ctx.textAlign = align.toString().toLowerCase() 62 | ctx.fillText(text, x, y) 63 | } 64 | 65 | override fun textWidth(s: String): Double = ctx.measureText(s).width 66 | 67 | override fun beginShape() { ctx.beginPath() } 68 | override fun moveTo(x: Double, y: Double) { ctx.moveTo(x, y) } 69 | override fun lineTo(x: Double, y: Double) { ctx.lineTo(x, y) } 70 | override fun closePath() { ctx.closePath() } 71 | override fun drawShape(s: DrawStyle) { 72 | if (s != DrawStyle.STROKE) { 73 | ctx.fillStyle = cols(color) 74 | ctx.fill() 75 | } 76 | if (s != DrawStyle.FILL) { 77 | ctx.strokeStyle = cols(color) 78 | ctx.stroke() 79 | } 80 | } 81 | 82 | override fun translate(x: Double, y: Double) { ctx.translate(x, y) } 83 | override fun rotate(degrees: Double) { ctx.rotate(toRadians(degrees)) } 84 | override fun scale(sx: Double, sy: Double) { ctx.scale(sx, sy) } 85 | override fun save() { ctx.save() } 86 | override fun restore() { ctx.restore() } 87 | } 88 | 89 | // -------------------------------------------------------------------- 90 | -------------------------------------------------------------------------------- /mini-apps/animation.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Animation with "after" 3 | // 4 | 5 | import org.otfried.cs109.Context 6 | import org.otfried.cs109.MiniApp 7 | 8 | import org.otfried.cs109.Canvas 9 | import org.otfried.cs109.Color 10 | import org.otfried.cs109.DrawStyle 11 | import org.otfried.cs109.TextAlign 12 | 13 | class Main(val ctx: Context) : MiniApp { 14 | var y: Double = 0.0 15 | var dead = false 16 | 17 | init { 18 | ctx.setTitle("Animation demo") 19 | ctx.onFling { x0, y0, dir, dist -> if (dir == 'u') y = 0.0; ctx.update() } 20 | ctx.after(20) { animate() } 21 | } 22 | 23 | fun animate() { 24 | y += 6.0 25 | ctx.update() 26 | if (y > ctx.height) 27 | dead = true 28 | else 29 | ctx.after(20) { animate() } 30 | } 31 | 32 | override fun onDraw(canvas: Canvas) { 33 | if (dead) { 34 | canvas.clear(Color(172, 0, 0)) // blood-red 35 | } else { 36 | val x = canvas.width / 2.0 37 | canvas.clear(Color(255, 255, 192)) 38 | canvas.setColor(Color.BLUE) 39 | canvas.drawCircle(x, y, 30.0) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mini-apps/basic.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Tiny mini-app for the CS109 Android framework 3 | // 4 | 5 | import org.otfried.cs109.Color 6 | import org.otfried.cs109.TextAlign 7 | import org.otfried.cs109.Context 8 | import org.otfried.cs109.MiniApp 9 | import org.otfried.cs109.Canvas 10 | 11 | class Main(val ctx: Context) : MiniApp { 12 | init { 13 | ctx.setTitle("Demo #1") 14 | } 15 | 16 | override fun onDraw(canvas: Canvas) { 17 | canvas.clear(Color(255, 255, 192)) 18 | canvas.setColor(Color.BLUE) 19 | canvas.setFont(48.0) 20 | canvas.drawText("CS109", canvas.width / 2.0, 200.0, TextAlign.CENTER) 21 | canvas.drawCircle(canvas.width / 2.0, 400.0, 60.0) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mini-apps/dialogs.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Different dialogs 3 | // 4 | 5 | import org.otfried.cs109.Context 6 | import org.otfried.cs109.MiniApp 7 | 8 | import org.otfried.cs109.Canvas 9 | import org.otfried.cs109.Color 10 | import org.otfried.cs109.DrawStyle 11 | import org.otfried.cs109.TextAlign 12 | 13 | class Main(val ctx: Context) : MiniApp { 14 | init { 15 | ctx.setTitle("Dialog demo") 16 | ctx.onTap { x, y -> tapped(x, y) } 17 | } 18 | 19 | fun tapped(x: Double, y: Double) { 20 | if (y < 100) 21 | ctx.toast("This is a toast!") 22 | else if (y < 200) 23 | ctx.showMessage("This is a message. I can tell you a lot!") 24 | else if (y < 300) 25 | ctx.askYesNo("This is a question. Do you know the answer?") { 26 | answer -> if (answer) ctx.toast("Good!") } 27 | else 28 | ctx.inputString("Tell me your name!") { 29 | s -> ctx.setTitle("Hello $s") } 30 | } 31 | 32 | override fun onDraw(canvas: Canvas) { 33 | val x = canvas.width / 2.0 34 | canvas.clear(Color(255, 255, 192)) 35 | canvas.setColor(Color.BLUE) 36 | canvas.setFont(32.0) 37 | canvas.drawText("Toast", x, 50.0, TextAlign.CENTER) 38 | canvas.drawText("Message", x, 150.0, TextAlign.CENTER) 39 | canvas.drawText("Yes or No", x, 250.0, TextAlign.CENTER) 40 | canvas.drawText("Name", x, 350.0, TextAlign.CENTER) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mini-apps/drawing.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Drawing and toast 3 | // 4 | 5 | import org.otfried.cs109.Context 6 | import org.otfried.cs109.MiniApp 7 | 8 | import org.otfried.cs109.Canvas 9 | import org.otfried.cs109.Color 10 | import org.otfried.cs109.DrawStyle 11 | 12 | fun draw(g: Canvas) { 13 | // clear background 14 | g.clear(Color.WHITE) 15 | 16 | // draw two filled circles 17 | g.setColor(Color.RED) 18 | g.drawCircle(50.0, 50.0, 20.0) // FILL is the default 19 | g.setColor(Color.ORANGE) 20 | g.drawCircle(250.0, 400.0, 20.0) 21 | 22 | // draw an unfilled circle with a pen of width 3 23 | g.setColor(Color.MAGENTA) 24 | g.setLineWidth(3.0) 25 | g.drawCircle(415.0, 50.0, 15.0, DrawStyle.STROKE) 26 | 27 | // draw a filled and an unfilled Rectangle 28 | g.setColor(Color.CYAN) 29 | g.drawRectangle(20.0, 400.0, 50.0, 20.0, DrawStyle.FILL) 30 | g.drawRectangle(400.0, 400.0, 50.0, 20.0, DrawStyle.STROKE) 31 | 32 | // draw a line 33 | g.setLineWidth(1.0) // reset to default 34 | g.setColor(Color(0, 0, 255)) // same as Color.BLUE 35 | g.beginShape() 36 | g.moveTo(50.0, 50.0) 37 | g.lineTo(250.0, 400.0) 38 | g.drawShape(DrawStyle.STROKE) 39 | 40 | // draw a non-convex quadrilateral: 41 | g.save() // save current coordinate system 42 | g.translate(360.0, 260.0) // move origin to here 43 | g.rotate(-30.0) // rotate 30 degrees counter-clockwise 44 | g.beginShape() 45 | g.moveTo(0.0, 0.0) 46 | g.lineTo(30.0, -40.0) 47 | g.lineTo(60.0, 0.0) 48 | g.lineTo(30.0, -100.0) 49 | g.closePath() 50 | g.drawShape() 51 | g.restore() // restore current coordinate system 52 | 53 | // draw some text 54 | g.setColor(Color(0, 128, 0)) // a darker green 55 | g.setFont(20.0, "Batang") 56 | g.drawText("Hello World!", 155.0, 225.0) 57 | g.drawText("안녕 하세요", 175.0, 245.0) 58 | } 59 | 60 | class Main(val ctx: Context) : MiniApp { 61 | init { 62 | ctx.setTitle("Drawing demo") 63 | ctx.onTap { x, y -> ctx.toast("You are tickling me!") } 64 | } 65 | 66 | override fun onDraw(canvas: Canvas) { 67 | draw(canvas) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /mini-apps/gravity1.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Gravity sensor test #1 3 | // 4 | 5 | import org.otfried.cs109.Context 6 | import org.otfried.cs109.MiniApp 7 | 8 | import org.otfried.cs109.Canvas 9 | import org.otfried.cs109.Color 10 | import org.otfried.cs109.DrawStyle 11 | import org.otfried.cs109.TextAlign 12 | 13 | fun sq(x: Double) = x * x 14 | 15 | class Main(val ctx: Context) : MiniApp { 16 | var gravity = arrayOf(0.0, 0.0, 0.0) 17 | 18 | init { 19 | ctx.setTitle("Gravity sensor demo #1") 20 | ctx.onGravity { x, y, z -> updateGravity(x, y, z) } 21 | } 22 | 23 | fun updateGravity(x: Double, y: Double, z: Double) { 24 | gravity = arrayOf(x, y, z) 25 | ctx.update() 26 | } 27 | 28 | override fun onDraw(canvas: Canvas) { 29 | val x = canvas.width / 2.0 30 | canvas.clear(Color(255, 255, 192)) 31 | canvas.setColor(Color.BLUE) 32 | canvas.setFont(48.0) 33 | for (i in 0..2) 34 | canvas.drawText("%.3f".format(gravity[i]), x, 80.0 + i * 60.0, 35 | TextAlign.CENTER) 36 | val norm = Math.sqrt(sq(gravity[0]) + sq(gravity[1]) + sq(gravity[2])) 37 | canvas.drawText("%.3f".format(norm), x, 300.0, TextAlign.CENTER) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mini-apps/gravity2.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Gravity sensor test #2 3 | // 4 | 5 | import org.otfried.cs109.Context 6 | import org.otfried.cs109.MiniApp 7 | 8 | import org.otfried.cs109.Canvas 9 | import org.otfried.cs109.Color 10 | import org.otfried.cs109.DrawStyle 11 | import org.otfried.cs109.TextAlign 12 | 13 | fun sq(x: Double) = x * x 14 | 15 | class Main(val ctx: Context) : MiniApp { 16 | var angle: Double? = null 17 | 18 | init { 19 | ctx.setTitle("Gravity sensor demo #2") 20 | ctx.onGravity { x, y, z -> updateGravity(x, y, z) } 21 | } 22 | 23 | fun updateGravity(x: Double, y: Double, z: Double) { 24 | if (Math.abs(z) > 2.0) { 25 | angle = null 26 | } else { 27 | val n = Math.sqrt(x*x + y*y) 28 | val x0 = x/n 29 | val y0 = y/n 30 | angle = Math.atan2(x0, y0) 31 | } 32 | ctx.update() 33 | } 34 | 35 | override fun onDraw(canvas: Canvas) { 36 | val x = canvas.width / 2.0 37 | val y = canvas.height / 2.0 38 | val w = (canvas.width + canvas.height).toDouble() 39 | canvas.clear(Color(255, 255, 192)) 40 | canvas.translate(x, y) 41 | canvas.setColor(Color.BLUE) 42 | canvas.setFont(48.0) 43 | val a = angle 44 | if (a == null) { 45 | canvas.drawText("Hold phone vertical", 0.0, 0.0, TextAlign.CENTER) 46 | } else { 47 | canvas.rotate(a / Math.PI * 180.0) 48 | canvas.beginShape() 49 | canvas.moveTo(-w, 0.0) 50 | canvas.lineTo(-w, w) 51 | canvas.lineTo(w, w) 52 | canvas.lineTo(w, 0.0) 53 | canvas.closePath() 54 | canvas.drawShape() 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /mini-apps/light.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Light sensor test 3 | // 4 | 5 | import org.otfried.cs109.Context 6 | import org.otfried.cs109.MiniApp 7 | 8 | import org.otfried.cs109.Canvas 9 | import org.otfried.cs109.Color 10 | import org.otfried.cs109.DrawStyle 11 | import org.otfried.cs109.TextAlign 12 | 13 | class Main(val ctx: Context) : MiniApp { 14 | var light = 0.0 15 | 16 | init { 17 | ctx.setTitle("Light sensor demo") 18 | ctx.onLight { updateLight(it) } 19 | } 20 | 21 | fun updateLight(lx: Double) { 22 | if (lx != light) { 23 | light = lx 24 | ctx.update() 25 | } 26 | } 27 | 28 | override fun onDraw(canvas: Canvas) { 29 | val x = canvas.width / 2.0 30 | canvas.clear(Color(255, 255, 192)) 31 | canvas.setColor(Color.BLUE) 32 | canvas.setFont(48.0) 33 | canvas.drawText("$light", x, 80.0, TextAlign.CENTER) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mini-apps/menus.kt: -------------------------------------------------------------------------------- 1 | // 2 | // A menu for the mini-app 3 | // 4 | 5 | import org.otfried.cs109.Context 6 | import org.otfried.cs109.MiniApp 7 | 8 | import org.otfried.cs109.Canvas 9 | import org.otfried.cs109.Color 10 | import org.otfried.cs109.DrawStyle 11 | import org.otfried.cs109.TextAlign 12 | 13 | class Main(val ctx: Context) : MiniApp { 14 | init { 15 | ctx.setTitle("Menu demo") 16 | ctx.createMenu(listOf(Pair("Toast", { ctx.toast("Well done!") } ), 17 | Pair("Input", { askForName() } ))) 18 | } 19 | 20 | fun askForName() { 21 | ctx.inputString("Tell me your name!") { ctx.setTitle("Hello $it") } 22 | } 23 | 24 | override fun onDraw(canvas: Canvas) { 25 | val x = canvas.width / 2.0 26 | canvas.clear(Color(255, 255, 192)) 27 | canvas.setColor(Color.BLUE) 28 | canvas.setFont(32.0) 29 | canvas.drawText("Open the menu", x, 50.0, TextAlign.CENTER) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mini-apps/tap1.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Tap demo 3 | // 4 | 5 | import org.otfried.cs109.Context 6 | import org.otfried.cs109.MiniApp 7 | 8 | import org.otfried.cs109.Canvas 9 | import org.otfried.cs109.Color 10 | import org.otfried.cs109.DrawStyle 11 | 12 | class Main(val ctx: Context) : MiniApp { 13 | private var lastX = 0.0 14 | private var lastY = 0.0 15 | 16 | init { 17 | ctx.setTitle("Tap demo") 18 | ctx.onTap { x, y -> tapped(x, y) } 19 | } 20 | 21 | fun tapped(x: Double, y: Double) { 22 | lastX = x 23 | lastY = y 24 | ctx.update() 25 | println("Debugging: $x $y") 26 | } 27 | 28 | override fun onDraw(canvas: Canvas) { 29 | canvas.clear(Color(255, 255, 192)) 30 | canvas.setColor(Color.BLUE) 31 | canvas.drawCircle(lastX, lastY, 30.0) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mini-apps/taps.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Tap, Double-tap, and Fling 3 | // 4 | 5 | import org.otfried.cs109.Context 6 | import org.otfried.cs109.MiniApp 7 | 8 | import org.otfried.cs109.Canvas 9 | import org.otfried.cs109.Color 10 | import org.otfried.cs109.DrawStyle 11 | import org.otfried.cs109.TextAlign 12 | 13 | class Main(val ctx: Context) : MiniApp { 14 | private var lastX = 0.0 15 | private var lastY = 0.0 16 | private var lastT = 0 17 | private var flingDir = 0.0 18 | 19 | init { 20 | ctx.setTitle("Tap and fling demo") 21 | ctx.onTap { x, y -> tapped(x, y, 1) } 22 | ctx.onDoubleTap { x, y -> tapped(x, y, 2) } 23 | ctx.onFling { x, y, dir, d -> flinged(dir) } 24 | } 25 | 26 | fun tapped(x: Double, y: Double, t: Int) { 27 | lastX = x 28 | lastY = y 29 | lastT = t 30 | ctx.update() 31 | } 32 | 33 | fun flinged(dir: Char) { 34 | when(dir) { 35 | 'l' -> flingDir = 270.0 36 | 'd' -> flingDir = 180.0 37 | 'r' -> flingDir = 90.0 38 | else -> flingDir = 0.0 39 | } 40 | lastT = 4 41 | ctx.update() 42 | } 43 | 44 | 45 | override fun onDraw(canvas: Canvas) { 46 | canvas.clear(Color(255, 255, 192)) 47 | if (lastT == 0) 48 | return 49 | canvas.setColor(Color.BLUE) 50 | canvas.setFont(32.0) 51 | canvas.drawText(when(lastT) { 52 | 1 -> "tap" 53 | 2 -> "double tap" 54 | 3 -> "press" 55 | 4 -> "fling" 56 | else -> "" 57 | }, canvas.width / 2.0, 60.0 + 20.0 * lastT, TextAlign.CENTER) 58 | if (lastT != 4) 59 | canvas.drawCircle(lastX, lastY, 30.0) 60 | else { 61 | canvas.translate(canvas.width / 2.0, canvas.height / 2.0) 62 | canvas.rotate(flingDir) 63 | canvas.beginShape() 64 | canvas.moveTo(-30.0, 0.0) 65 | canvas.lineTo(0.0, -40.0) 66 | canvas.lineTo(30.0, 0.0) 67 | canvas.lineTo(0.0, -100.0) 68 | canvas.closePath() 69 | canvas.drawShape() 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /mini-apps/transparency.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Transparent drawing 3 | // 4 | 5 | import org.otfried.cs109.Context 6 | import org.otfried.cs109.MiniApp 7 | 8 | import org.otfried.cs109.Canvas 9 | import org.otfried.cs109.Color 10 | import org.otfried.cs109.DrawStyle 11 | import org.otfried.cs109.TextAlign 12 | 13 | fun draw(g: Canvas) { 14 | g.clear(Color.WHITE) 15 | 16 | g.setAlpha(128) // 50% transparency 17 | g.setColor(Color.CYAN) 18 | g.drawCircle(200.0, 200.0, 150.0) 19 | g.setColor(Color.YELLOW) 20 | g.drawCircle(300.0, 200.0, 150.0) 21 | g.setColor(Color.MAGENTA) 22 | g.drawCircle(250.0, 300.0, 150.0) 23 | 24 | g.setAlpha(32) // 12.5% transparency 25 | g.setColor(Color(0, 128, 0)) // a darker green 26 | g.setFont(128.0) 27 | g.translate(g.width/2.0, g.height/2.0) 28 | g.rotate(-45.0) 29 | g.translate(0.0, 48.0) 30 | g.drawText("CS109", 0.0, 0.0, TextAlign.CENTER) 31 | } 32 | 33 | class Main(val ctx: Context) : MiniApp { 34 | init { 35 | ctx.setTitle("Drawing demo") 36 | ctx.onTap { x, y -> ctx.toast("You are tickling me!") } 37 | } 38 | 39 | override fun onDraw(canvas: Canvas) { 40 | draw(canvas) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /misc/kotlin.lang: -------------------------------------------------------------------------------- 1 | 2 | 27 | 28 | 29 | 30 | text/x-kotlin 31 | *.kt;*.kts 32 | // 33 | /* 34 | */ 35 | 36 | 37 | 38 |