HTML5 canvas drawing demo.
This commit is contained in:
committed by
alexander-gorshenev
parent
ce3ed52705
commit
b367a0d255
+6
-1
@@ -346,7 +346,12 @@ internal class LinkStage(val context: Context) {
|
||||
|
||||
private fun JavaScriptLinker(jsFiles: List<String>, executable: String): String {
|
||||
val linkedJavaScript = File("$executable.js")
|
||||
linkedJavaScript.writeBytes(ByteArray(0));
|
||||
|
||||
val jsLibsExceptLauncher = jsFiles.filter { it != "launcher.js" }.map { it.removeSuffix(".js") }
|
||||
|
||||
val linkerStub = "var konan = { libraries: [] };\n"
|
||||
|
||||
linkedJavaScript.writeBytes(linkerStub.toByteArray());
|
||||
|
||||
jsFiles.forEach {
|
||||
linkedJavaScript.appendBytes(File(it).readBytes())
|
||||
|
||||
+2
-2
@@ -260,7 +260,7 @@ targetList.each { target ->
|
||||
}
|
||||
if (target == 'wasm32') {
|
||||
from(project(':runtime').file('src/launcher/js')) {
|
||||
into('konan/nativelib')
|
||||
into('klib/stdlib/targets/wasm32/included')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,4 +376,4 @@ task clean {
|
||||
}
|
||||
delete bundle.outputs.files
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ fun handleExceptionContinuation(x: (Throwable) -> Unit): Continuation<Any?> = ob
|
||||
def targetToolchain = TargetPropertiesKt.hostTargetString(properties, "targetToolchain", target)
|
||||
def absoluteTargetToolchain = "$dependenciesDir/$targetToolchain"
|
||||
def d8 = "$absoluteTargetToolchain/bin/d8"
|
||||
def launcherJs = "${dist.absolutePath}/konan/nativelib/launcher.js"
|
||||
def launcherJs = "${exe}.js"
|
||||
return [d8, '--expose-wasm', launcherJs, '--', exe]
|
||||
} else {
|
||||
return [exe]
|
||||
|
||||
@@ -38,6 +38,12 @@ OBJ_GETTER(setupArgs, int argc, const char** argv) {
|
||||
//--- main --------------------------------------------------------------------//
|
||||
extern "C" KInt Konan_start(const ObjHeader*);
|
||||
|
||||
extern "C" KInt Konan_run_start(int argc, const char** argv) {
|
||||
ObjHolder args;
|
||||
setupArgs(argc, argv, args.slot());
|
||||
return Konan_start(args.obj());
|
||||
}
|
||||
|
||||
extern "C" RUNTIME_USED int Konan_main(int argc, const char** argv) {
|
||||
RuntimeState* state = InitRuntime();
|
||||
|
||||
@@ -45,12 +51,7 @@ extern "C" RUNTIME_USED int Konan_main(int argc, const char** argv) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
KInt exitStatus;
|
||||
{
|
||||
ObjHolder args;
|
||||
setupArgs(argc, argv, args.slot());
|
||||
exitStatus = Konan_start(args.obj());
|
||||
}
|
||||
KInt exitStatus = Konan_run_start(argc, argv);
|
||||
|
||||
DeinitRuntime(state);
|
||||
|
||||
@@ -63,14 +64,19 @@ extern "C" RUNTIME_USED int Konan_main(int argc, const char** argv) {
|
||||
extern "C" int Konan_js_arg_size(int index);
|
||||
extern "C" int Konan_js_fetch_arg(int index, char* ptr);
|
||||
|
||||
extern "C" int Konan_js_main(int argc) {
|
||||
extern "C" int Konan_js_main(int argc, int memoryInit) {
|
||||
char** argv = (char**)konan::calloc(1, argc);
|
||||
for (int i = 0; i< argc; ++i) {
|
||||
argv[i] = (char*)konan::calloc(1, Konan_js_arg_size(i));
|
||||
Konan_js_fetch_arg(i, argv[i]);
|
||||
}
|
||||
return Konan_main(argc, (const char**)argv);
|
||||
if (memoryInit) {
|
||||
return Konan_main(argc, (const char**)argv);
|
||||
} else {
|
||||
return Konan_run_start(argc, (const char**)argv);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var module;
|
||||
var instance;
|
||||
var heap;
|
||||
var memory;
|
||||
var global_arguments;
|
||||
var globalBase = 0; // TODO: Is there any way to obtain global_base from JavaScript?
|
||||
|
||||
var konanStackTop;
|
||||
|
||||
function isBrowser() {
|
||||
if (typeof window === 'undefined') {
|
||||
return false;
|
||||
@@ -29,6 +30,7 @@ function isBrowser() {
|
||||
};
|
||||
}
|
||||
|
||||
var runtime;
|
||||
if (isBrowser()) {
|
||||
runtime = {
|
||||
print: console.log,
|
||||
@@ -85,6 +87,14 @@ function toString(pointer) {
|
||||
return string;
|
||||
}
|
||||
|
||||
function toUTF16String(pointer, size) {
|
||||
var string = '';
|
||||
for (var i = pointer; i < pointer + size; i+=2) {
|
||||
string += String.fromCharCode(heap[i] + heap[i+1]*256);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
function int32ToHeap(value, pointer) {
|
||||
heap[pointer] = value & 0xff;
|
||||
heap[pointer+1] = (value & 0xff00) >>> 8;
|
||||
@@ -166,48 +176,80 @@ var konan_dependencies = {
|
||||
}
|
||||
};
|
||||
|
||||
function invokeModule(buffer, args) {
|
||||
function linkJavaScriptLibraries() {
|
||||
konan.libraries.forEach ( function (library) {
|
||||
for (var property in library) {
|
||||
konan_dependencies.env[property] = library[property];
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
function invokeModule(inst, args) {
|
||||
if (args.length < 1) print_usage();
|
||||
global_arguments = args;
|
||||
|
||||
module = new WebAssembly.Module(new Uint8Array(buffer));
|
||||
module.env = {};
|
||||
module.env.memoryBase = 0;
|
||||
module.env.tablebase = 0;
|
||||
instance = inst;
|
||||
|
||||
instance = new WebAssembly.Instance(module, konan_dependencies);
|
||||
memory = konan_dependencies.env.memory
|
||||
heap = new Uint8Array(konan_dependencies.env.memory.buffer);
|
||||
konanStackTop = stackTop();
|
||||
|
||||
var exit_status = 0;
|
||||
|
||||
try {
|
||||
runGlobalInitializers(instance.exports);
|
||||
exit_status = instance.exports.Konan_js_main(args.length);
|
||||
if (isBrowser()) {
|
||||
instance.memoryState = instance.exports.InitRuntime();
|
||||
}
|
||||
exit_status = instance.exports.Konan_js_main(args.length, isBrowser() ? 0 : 1);
|
||||
// TODO: so when should we deinit runtime?
|
||||
} catch (e) {
|
||||
runtime.print("Exception executing Konan_js_main: " + e);
|
||||
runtime.print("Exception executing entry point: " + e);
|
||||
runtime.print(e.stack);
|
||||
exit_status = 1;
|
||||
}
|
||||
runtime.flush();
|
||||
|
||||
return exit_status;
|
||||
}
|
||||
|
||||
// Invoke from the browser.
|
||||
function loadAndInvoke(filename) {
|
||||
function listener () {
|
||||
invokeModule(this.response, [filename]);
|
||||
}
|
||||
|
||||
var request = new XMLHttpRequest();
|
||||
request.addEventListener("load", listener);
|
||||
request.open("GET", filename);
|
||||
request.responseType = "arraybuffer";
|
||||
request.send();
|
||||
function setupModule(module) {
|
||||
module.env = {};
|
||||
module.env.memoryBase = 0;
|
||||
module.env.tablebase = 0;
|
||||
linkJavaScriptLibraries();
|
||||
}
|
||||
|
||||
// Invoke from d8.
|
||||
if (!isBrowser()) {
|
||||
quit(invokeModule(readbuffer(arguments[0]), arguments));
|
||||
// Instantiate module in Browser in a sequence of promises.
|
||||
function instantiateAndRun(arraybuffer, args) {
|
||||
WebAssembly.compile(arraybuffer)
|
||||
.then(function(module) {
|
||||
setupModule(module);
|
||||
return WebAssembly.instantiate(module, konan_dependencies);
|
||||
}).then(function(instance) {
|
||||
return invokeModule(instance, args);
|
||||
});
|
||||
}
|
||||
|
||||
// Instantiate module in d8 synchronously.
|
||||
function instantiateAndRunSync(arraybuffer, args) {
|
||||
var module = WebAssembly.Module(arraybuffer)
|
||||
setupModule(module);
|
||||
var instance = WebAssembly.Instance(module, konan_dependencies);
|
||||
return invokeModule(instance, args)
|
||||
}
|
||||
|
||||
if (isBrowser()) {
|
||||
var filename = document.currentScript.getAttribute("wasm");
|
||||
fetch(filename).then( function(response) {
|
||||
return response.arrayBuffer();
|
||||
}).then(function(arraybuffer) {
|
||||
instantiateAndRun(arraybuffer, [filename]);
|
||||
});
|
||||
} else {
|
||||
// Invoke from d8.
|
||||
var arrayBuffer = readbuffer(arguments[0]);
|
||||
var exitStatus = instantiateAndRunSync(arrayBuffer, arguments);
|
||||
quit(exitStatus);
|
||||
}
|
||||
|
||||
|
||||
@@ -1092,4 +1092,16 @@ OBJ_GETTER(Kotlin_String_subSequence, KString thiz, KInt startIndex, KInt endInd
|
||||
RETURN_OBJ(result->obj());
|
||||
}
|
||||
|
||||
const KChar* Kotlin_String_utf16pointer(KString message) {
|
||||
RuntimeAssert(message->type_info() == theStringTypeInfo, "Must use a string");
|
||||
const KChar* utf16 = CharArrayAddressOfElementAt(message, 0);
|
||||
return utf16;
|
||||
}
|
||||
|
||||
KInt Kotlin_String_utf16length(KString message) {
|
||||
RuntimeAssert(message->type_info() == theStringTypeInfo, "Must use a string");
|
||||
return message->count_ * sizeof(KChar);
|
||||
}
|
||||
|
||||
|
||||
} // extern "C"
|
||||
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DIR=$(cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
PATH=$DIR/../../dist/bin:$DIR/../../bin:$PATH
|
||||
|
||||
if [ x$TARGET == x ]; then
|
||||
case "$OSTYPE" in
|
||||
darwin*) TARGET=macbook ;;
|
||||
linux*) TARGET=linux ;;
|
||||
*) echo "unknown: $OSTYPE" && exit 1;;
|
||||
esac
|
||||
fi
|
||||
|
||||
var=CFLAGS_${TARGET}
|
||||
CFLAGS=${!var}
|
||||
var=COMPILER_ARGS_${TARGET}
|
||||
COMPILER_ARGS=${!var} # add -opt for an optimized build.
|
||||
|
||||
mkdir -p $DIR/build/bin
|
||||
|
||||
konanc $DIR/src/jsinterop/kotlin \
|
||||
-includeBinary $DIR/src/jsinterop/js/jsinterop.js \
|
||||
-p library -o $DIR/build/jsinterop -target wasm32 || exit 1
|
||||
|
||||
konanc $DIR/src/canvas/kotlin \
|
||||
-includeBinary $DIR/src/canvas/js/canvas.js \
|
||||
-l $DIR/build/jsinterop \
|
||||
-p library -o $DIR/build/canvas -target wasm32 || exit 1
|
||||
|
||||
konanc $DIR/src/main/kotlin \
|
||||
-l $DIR/build/jsinterop -l $DIR/build/canvas \
|
||||
-o $DIR/build/bin/html5Canvas -target wasm32 || exit 1
|
||||
|
||||
echo "Artifact path is $DIR/build/bin/html5Canvas.wasm"
|
||||
echo "Artifact path is $DIR/build/bin/html5Canvas.wasm.js"
|
||||
echo "Check out $DIR/index.html"
|
||||
echo "Serve $DIR/ by an http server for the demo"
|
||||
# For example run: python -m SimpleHTTPServer 8080
|
||||
@@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<script src="build/bin/html5Canvas.wasm.js" wasm="build/bin//html5Canvas.wasm"> </script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="myCanvas" width="640" height="480" style="border:1px solid #000000;">
|
||||
</canvas>
|
||||
<p>Draw something using the mouse!</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
konan.libraries.push ({ // This one will be auto generated.
|
||||
knjs_addDocumentToArena: function(arena) {
|
||||
// Should be resolved by jsinterop.
|
||||
return konan_dependencies.env.Konan_js_addObjectToArena(arena, document);
|
||||
},
|
||||
knjs_setInterval: function (func, interval) {
|
||||
setInterval(konan_dependencies.env.Konan_js_wrapLambda(func), interval);
|
||||
},
|
||||
knjs_getElementById: function (arena, obj, id, idLength) {
|
||||
var name = toUTF16String(id, idLength);
|
||||
var result = konan_dependencies.env.arenas[arena][obj].getElementById(name);
|
||||
return konan_dependencies.env.Konan_js_addObjectToArena(arena, result);
|
||||
},
|
||||
knjs_getContext: function (arena, obj, context, contextLength) {
|
||||
var name = toUTF16String(context, contextLength);
|
||||
var result = konan_dependencies.env.arenas[arena][obj].getContext(name);
|
||||
return konan_dependencies.env.Konan_js_addObjectToArena(arena, result);
|
||||
},
|
||||
knjs_getBoundingClientRect: function (arena, obj) {
|
||||
var result = konan_dependencies.env.arenas[arena][obj].getBoundingClientRect();
|
||||
return konan_dependencies.env.Konan_js_addObjectToArena(arena, result);
|
||||
},
|
||||
knjs_moveTo: function(arena, obj, x, y) {
|
||||
konan_dependencies.env.arenas[arena][obj].moveTo(x, y);
|
||||
},
|
||||
knjs_lineTo: function(arena, obj, x, y) {
|
||||
konan_dependencies.env.arenas[arena][obj].lineTo(x, y);
|
||||
},
|
||||
knjs_stroke: function(arena, obj) {
|
||||
konan_dependencies.env.arenas[arena][obj].stroke();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
package html5.minimal
|
||||
|
||||
// This file will be auto-generated.
|
||||
|
||||
import kotlinx.wasm.jsinterop.*
|
||||
|
||||
@SymbolName("knjs_addDocumentToArena")
|
||||
external public fun addDocumentToArena(arena: Arena): Int
|
||||
|
||||
@SymbolName("knjs_getElementById")
|
||||
external public fun knjs_getElementById(arena: Int, obj: Int, idPtr: Int, idLen: Int): Int;
|
||||
|
||||
@SymbolName("knjs_getContext")
|
||||
external public fun knjs_getContext(arena: Int, obj: Int, idPtr: Int, idLen: Int): Int;
|
||||
|
||||
@SymbolName("knjs_getBoundingClientRect")
|
||||
external public fun knjs_getBoundingClientRect(arena: Int, obj: Int): Int;
|
||||
|
||||
@SymbolName("knjs_stroke")
|
||||
external public fun knjs_stroke(arena: Int, obj: Int);
|
||||
|
||||
@SymbolName("knjs_moveTo")
|
||||
external public fun knjs_moveTo(arena: Int, obj: Int, x: Int, y: Int): Int;
|
||||
|
||||
@SymbolName("knjs_lineTo")
|
||||
external public fun knjs_lineTo(arena: Int, obj: Int, x: Int, y: Int): Int;
|
||||
|
||||
@SymbolName("knjs_setInterval")
|
||||
external public fun knjs_setInterval(functionIndex: Int, interval: Int);
|
||||
|
||||
open class Document(arena: Int, index: Int): JsValue(arena, index) {
|
||||
fun getElementById(id: String): JsValue {
|
||||
val resultIndex = knjs_getElementById(this.arena, this.index, stringPointer(id), stringLengthBytes(id))
|
||||
return JsValue(this.arena, resultIndex)
|
||||
}
|
||||
}
|
||||
|
||||
open class DOMRect(arena: Int, index: Int): JsValue(arena, index) {
|
||||
}
|
||||
|
||||
open class Canvas(arena: Int, index: Int): JsValue(arena, index) {
|
||||
fun getContext(context: String): Context {
|
||||
val resultIndex = knjs_getContext(this.arena, this.index, stringPointer(context), stringLengthBytes(context))
|
||||
return Context(this.arena, resultIndex)
|
||||
}
|
||||
fun getBoundingClientRect(): DOMRect {
|
||||
return DOMRect(this.arena, knjs_getBoundingClientRect(this.arena, this.index))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is awful.
|
||||
val JsValue.asCanvas: Canvas
|
||||
get() {
|
||||
return Canvas(this.arena, this.index)
|
||||
}
|
||||
|
||||
open class Context(arena: Int, index: Int): JsValue(arena, index) {
|
||||
fun lineTo(x: Int, y: Int) {
|
||||
knjs_lineTo(this.arena, this.index, x, y)
|
||||
}
|
||||
fun moveTo(x: Int, y: Int) {
|
||||
knjs_moveTo(this.arena, this.index, x, y)
|
||||
}
|
||||
fun stroke() {
|
||||
knjs_stroke(this.arena, this.index);
|
||||
}
|
||||
}
|
||||
fun setInterval(lambda: (ArrayList<JsValue>)->Unit, interval: Int) {
|
||||
knjs_setInterval(wrapFunction(lambda), interval);
|
||||
}
|
||||
|
||||
// TODO: The arguments are flipped to have lambda within the last position.
|
||||
fun setInterval(interval: Int, lambda: (ArrayList<JsValue>)->Unit)
|
||||
= setInterval(lambda, interval)
|
||||
|
||||
class html5 {
|
||||
val arena = allocateArena()
|
||||
val document = Document(arena, addDocumentToArena(arena))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
konan.libraries.push ({
|
||||
arenas: [],
|
||||
Konan_js_allocateArena: function (array) {
|
||||
konan_dependencies.env.arenas.push(array || []);
|
||||
return konan_dependencies.env.arenas.length - 1;
|
||||
|
||||
},
|
||||
Konan_js_freeArena: function(arenaIndex) {
|
||||
var arena = konan_dependencies.env.arenas[arenaIndex];
|
||||
arena.forEach(function(element, index) {
|
||||
arena[index] = null;
|
||||
});
|
||||
konan_dependencies.env.arenas[arenaIndex] = null;
|
||||
},
|
||||
Konan_js_addObjectToArena: function (arenaIndex, object) {
|
||||
var arena = konan_dependencies.env.arenas[arenaIndex];
|
||||
arena.push(object);
|
||||
return arena.length - 1;
|
||||
},
|
||||
Konan_js_wrapLambda: function (index) {
|
||||
return (function () {
|
||||
// convert Arguments to an array
|
||||
// to be provided by launcher.js
|
||||
var arenaIndex = konan_dependencies.env.Konan_js_allocateArena(Array.prototype.slice.call(arguments));
|
||||
// To be provided by Kotlin runtime.
|
||||
instance.exports.Konan_js_runLambda(index, arenaIndex, arguments.length);
|
||||
konan_dependencies.env.Konan_js_freeArena(arenaIndex);
|
||||
});
|
||||
},
|
||||
Konan_js_getInt: function(arenaIndex, objIndex, propertyNamePtr, propertyNameLength) {
|
||||
// TODO: The toUTF16String() is to be resolved by launcher.js runtime.
|
||||
var property = toUTF16String(propertyNamePtr, propertyNameLength);
|
||||
var value = konan_dependencies.env.arenas[arenaIndex][objIndex][property];
|
||||
return value;
|
||||
},
|
||||
Konan_js_setFunction: function (arena, obj, propertyName, propertyNameLength, func) {
|
||||
var name = toUTF16String(propertyName, propertyNameLength);
|
||||
konan_dependencies.env.arenas[arena][obj][name] = konan_dependencies.env.Konan_js_wrapLambda(func);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
package kotlinx.wasm.jsinterop
|
||||
|
||||
import konan.internal.ExportForCppRuntime
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
typealias Arena = Int
|
||||
typealias Object = Int
|
||||
typealias Pointer = Int
|
||||
|
||||
@SymbolName("Konan_js_allocateArena")
|
||||
external public fun allocateArena(): Arena
|
||||
|
||||
@SymbolName("Konan_js_freeArena")
|
||||
external public fun freeArena(arena: Arena)
|
||||
|
||||
@SymbolName("Kotlin_String_utf16pointer")
|
||||
external public fun stringPointer(message: String): Pointer
|
||||
|
||||
@SymbolName("Kotlin_String_utf16length")
|
||||
external public fun stringLengthBytes(message: String): Int
|
||||
|
||||
typealias KtFunction = (ArrayList<JsValue>)->Unit
|
||||
|
||||
fun wrapFunction(func: KtFunction): Int {
|
||||
val ptr: Long = StableObjPtr.create(func).value.toLong()
|
||||
return ptr.toInt() // TODO: LP64 unsafe.
|
||||
}
|
||||
|
||||
@ExportForCppRuntime("Konan_js_runLambda")
|
||||
fun runLambda(userData: Int, arena: Arena, arenaSize: Int) {
|
||||
val arguments = arrayListOf<JsValue>()
|
||||
for (i in 0..arenaSize-1) {
|
||||
arguments.add(JsValue(arena, i));
|
||||
}
|
||||
// TODO: LP64 unsafe: wasm32 passes Int, not Long.
|
||||
val func = StableObjPtr.fromValue(userData.toLong().toCPointer()!!).get() as KtFunction
|
||||
func(arguments);
|
||||
|
||||
|
||||
}
|
||||
|
||||
open class JsValue(val arena: Arena, val index: Object) {
|
||||
fun getInt(property: String): Int {
|
||||
return getInt(arena, index, stringPointer(property), stringLengthBytes(property))
|
||||
}
|
||||
}
|
||||
|
||||
@SymbolName("Konan_js_getInt")
|
||||
external public fun getInt(arena: Arena, obj: Object, propertyPtr: Pointer, propertyLen: Int): Int;
|
||||
|
||||
@SymbolName("Konan_js_setFunction")
|
||||
external public fun setFunction(arena: Arena, obj: Object, propertyName: Pointer, propertyLength: Int , function: Int)
|
||||
|
||||
fun setter(obj: JsValue, property: String, lambda: (arguments: ArrayList<JsValue>) -> Unit) {
|
||||
val index = wrapFunction(lambda);
|
||||
setFunction(obj.arena, obj.index, stringPointer(property), stringLengthBytes(property), index)
|
||||
}
|
||||
|
||||
fun JsValue.setter(property: String, lambda: (ArrayList<JsValue>) -> Unit) {
|
||||
setter(this, property, lambda)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import html5.minimal.*
|
||||
import kotlinx.wasm.jsinterop.*
|
||||
import konan.internal.ExportForCppRuntime
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val html5 = html5()
|
||||
val document = html5.document
|
||||
|
||||
val canvas = document.getElementById("myCanvas").asCanvas;
|
||||
val ctx = canvas.getContext("2d");
|
||||
val rect = canvas.getBoundingClientRect();
|
||||
val rectLeft = rect.getInt("left")
|
||||
val rectTop = rect.getInt("top")
|
||||
|
||||
var mouseX: Int = 0;
|
||||
var mouseY: Int = 0;
|
||||
var draw: Boolean = false;
|
||||
|
||||
document.setter("onmousemove") { args: ArrayList<JsValue> ->
|
||||
val event = args[0];
|
||||
mouseX = event.getInt("clientX") - rectLeft
|
||||
mouseY = event.getInt("clientY") - rectTop
|
||||
if (mouseX < 0) mouseX = 0;
|
||||
if (mouseX > 639) mouseX = 639;
|
||||
if (mouseY < 0) mouseY = 0;
|
||||
if (mouseY > 479) mouseY = 479;
|
||||
}
|
||||
|
||||
document.setter("onmousedown") {
|
||||
draw = true;
|
||||
}
|
||||
|
||||
document.setter("onmouseup") {
|
||||
draw = false;
|
||||
}
|
||||
|
||||
setInterval(10) {
|
||||
if (draw) {
|
||||
ctx.lineTo(mouseX, mouseY);
|
||||
ctx.stroke();
|
||||
} else {
|
||||
ctx.moveTo(mouseX, mouseY);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user