Unverified Commit f8968123 authored by Oleksandr Karpovich's avatar Oleksandr Karpovich Committed by GitHub

Refactor interop with js in k/js and k/wasm source sets (#843)

* Refactor interop with js in k/js and k/wasm source sets

* Make event conversion functions internal

The event conversion functions in the 'Convertors.js.kt' and 'Convertors.wasm.kt' files have been updated from public to internal.

* Disable local maven repository

* PR fixes

---------
Co-authored-by: 's avatarOleksandr.Karpovich <oleksandr.karpovich@jetbrains.com>
parent 70282bd6
kotlin.code.style=official kotlin.code.style=official
kotlin.version=1.9.20 kotlin.version=1.9.21
skiko.composite.build=1 #skiko.composite.build=1
\ No newline at end of file \ No newline at end of file
package org.jetbrains.skiko.sample.js package org.jetbrains.skiko.sample.js
import kotlinx.browser.document
import org.jetbrains.skiko.wasm.onWasmReady import org.jetbrains.skiko.wasm.onWasmReady
fun main() { fun main() {
onWasmReady(::runApp) onWasmReady(::runApp)
} }
actual fun findElementById(id: String): Any? = document.getElementById(id)
\ No newline at end of file
package org.jetbrains.skiko.sample.js package org.jetbrains.skiko.sample.js
import kotlinx.browser.document
import org.jetbrains.skia.Canvas import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Paint import org.jetbrains.skia.Paint
import org.jetbrains.skia.Rect import org.jetbrains.skia.Rect
import org.jetbrains.skiko.GenericSkikoView import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoView import org.jetbrains.skiko.SkikoView
import org.w3c.dom.HTMLCanvasElement
private class DemoApp: SkikoView { private class DemoApp: SkikoView {
private val paint = Paint() private val paint = Paint()
...@@ -25,7 +23,7 @@ private class DemoApp: SkikoView { ...@@ -25,7 +23,7 @@ private class DemoApp: SkikoView {
internal fun runApp() { internal fun runApp() {
for (index in 1 .. 3) { for (index in 1 .. 3) {
val skiaLayer = SkiaLayer() val skiaLayer = SkiaLayer()
val canvas = document.getElementById("c$index") as HTMLCanvasElement val canvas = findElementById("c$index")!!
val app = if (index == 3) { val app = if (index == 3) {
DemoApp() DemoApp()
} else { } else {
...@@ -36,3 +34,8 @@ internal fun runApp() { ...@@ -36,3 +34,8 @@ internal fun runApp() {
skiaLayer.needRedraw() skiaLayer.needRedraw()
} }
} }
// Note: despite the actual implementation looks identical, it can't be common - there is no W3C common API
// return type is Any because k/js and k/wasm don't have a common type for HTML elements
expect fun findElementById(id: String): Any?
\ No newline at end of file
package org.jetbrains.skiko.sample.js package org.jetbrains.skiko.sample.js
import kotlinx.browser.document
fun main() { fun main() {
runApp() runApp()
} }
actual fun findElementById(id: String): Any? = document.getElementById(id)
\ No newline at end of file
...@@ -22,6 +22,8 @@ repositories { ...@@ -22,6 +22,8 @@ repositories {
google() google()
mavenCentral() mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
// mavenLocal()
} }
val osName = System.getProperty("os.name") val osName = System.getProperty("os.name")
......
kotlin.code.style=official kotlin.code.style=official
org.gradle.jvmargs=-Xmx3G -XX:MaxMetaspaceSize=512m org.gradle.jvmargs=-Xmx3G -XX:MaxMetaspaceSize=512m
kotlin.version=1.9.20 kotlin.version=1.9.21
#skiko.composite.build=1 #skiko.composite.build=1
\ No newline at end of file
package org.jetbrains.skiko.sample package org.jetbrains.skiko.sample
import kotlinx.browser.document
import kotlinx.browser.window import kotlinx.browser.window
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.wasm.onWasmReady import org.jetbrains.skiko.wasm.onWasmReady
import org.w3c.dom.HTMLCanvasElement
fun main() { fun main() {
window.addEventListener("DOMContentLoaded", { window.addEventListener("DOMContentLoaded", {
onWasmReady(::runApp) onWasmReady(::runApp)
}) })
} }
internal fun runApp() {
val skiaLayer = SkiaLayer()
skiaLayer.onContentScaleChanged = { scale -> println(scale) }
val game = JsClocks(skiaLayer)
skiaLayer.skikoView = GenericSkikoView(skiaLayer, game)
val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement
canvas.setAttribute("tabindex", "0")
skiaLayer.attachTo(canvas)
skiaLayer.needRedraw()
}
\ No newline at end of file
package org.jetbrains.skiko.sample
import kotlinx.browser.document
import org.jetbrains.skiko.*
import org.w3c.dom.HTMLCanvasElement
internal fun runApp() {
val skiaLayer = SkiaLayer()
onContentScaleChanged = { scale -> println(scale) }
val game = JsClocks(skiaLayer)
skiaLayer.skikoView = GenericSkikoView(skiaLayer, game)
val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement
canvas.setAttribute("tabindex", "0")
skiaLayer.attachTo(canvas)
skiaLayer.needRedraw()
}
\ No newline at end of file
package org.jetbrains.skiko.sample package org.jetbrains.skiko.sample
import kotlinx.browser.document
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayer
import org.w3c.dom.HTMLCanvasElement
fun main() { fun main() {
runApp() runApp()
} }
internal fun runApp() {
val skiaLayer = SkiaLayer()
skiaLayer.onContentScaleChanged = { scale -> println(scale) }
val game = JsClocks(skiaLayer)
skiaLayer.skikoView = GenericSkikoView(skiaLayer, game)
val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement
canvas.setAttribute("tabindex", "0")
skiaLayer.attachTo(canvas)
skiaLayer.needRedraw()
}
\ No newline at end of file
...@@ -542,14 +542,6 @@ project.tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile>().config ...@@ -542,14 +542,6 @@ project.tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile>().config
) )
} }
if (supportJs && supportWasm) {
project.tasks.whenTaskAdded {
if (name == "compileJsWasmMainKotlinMetadata") {
enabled = false
}
}
}
tasks.findByName("publishSkikoWasmRuntimePublicationToComposeRepoRepository") tasks.findByName("publishSkikoWasmRuntimePublicationToComposeRepoRepository")
?.dependsOn("publishWasmJsPublicationToComposeRepoRepository") ?.dependsOn("publishWasmJsPublicationToComposeRepoRepository")
......
package org.jetbrains.skiko package org.jetbrains.skiko
import org.w3c.dom.events.MouseEvent import org.w3c.dom.HTMLElement
import org.w3c.dom.events.UIEvent import org.w3c.dom.events.UIEvent
internal actual fun getEventTimestamp(e: UIEvent): Long { internal fun getEventTimestamp(e: UIEvent): Long {
return e.timeStamp.toLong() return e.timeStamp.toLong()
} }
internal actual fun CursorManager_setCursor(component: Any, cursor: Cursor) {
(component as? HTMLElement)?.style?.cursor = cursor
}
internal actual fun CursorManager_getCursor(component: Any): Cursor? {
return (component as? HTMLElement)?.style?.cursor
}
internal actual fun getNavigatorInfo(): String =
js("navigator.userAgentData ? navigator.userAgentData.platform : navigator.platform") as String
\ No newline at end of file
package org.jetbrains.skiko
import org.jetbrains.skia.impl.NativePointer
import org.jetbrains.skiko.wasm.GL
internal actual fun makeGLContextCurrent(contextPointer: NativePointer) {
GL.makeContextCurrent(contextPointer)
}
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
import org.w3c.dom.events.* import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.MouseEvent
import org.w3c.dom.events.WheelEvent
private val SPECIAL_KEYS = setOf( internal fun toSkikoEvent(
"Unidentified",
"Alt",
"AltGraph",
"Backspace",
"CapsLock",
"Control",
"Fn",
"FnLock",
"Hyper",
"Meta",
"NumLock",
"ScrollLock",
"Shift",
"Super",
"Symbol",
"SymbolLock",
"F1",
"F2",
"F3",
"F4",
"F5",
"F6",
"F7",
"F8",
"F9",
"F10",
"F11",
"F12",
"F13",
"F14",
"F15",
"F16",
"F17",
"F18",
"F19",
"F20",
"F21",
"F22",
"ArrowLeft",
"ArrowUp",
"ArrowRight",
"ArrowDown",
"Help",
"Home",
"Delete",
"End",
"PageUp",
"PageDown",
"Escape",
"Clear",
"Clear"
)
internal expect fun getEventTimestamp(e: UIEvent): Long
fun toSkikoEvent(
event: MouseEvent, event: MouseEvent,
kind: SkikoPointerEventKind kind: SkikoPointerEventKind
): SkikoPointerEvent { ): SkikoPointerEvent {
...@@ -74,7 +20,7 @@ fun toSkikoEvent( ...@@ -74,7 +20,7 @@ fun toSkikoEvent(
) )
} }
fun toSkikoDragEvent( internal fun toSkikoDragEvent(
event: MouseEvent event: MouseEvent
): SkikoPointerEvent { ): SkikoPointerEvent {
return SkikoPointerEvent( return SkikoPointerEvent(
...@@ -89,7 +35,7 @@ fun toSkikoDragEvent( ...@@ -89,7 +35,7 @@ fun toSkikoDragEvent(
) )
} }
fun toSkikoTypeEvent( internal fun toSkikoTypeEvent(
character: String, character: String,
event: KeyboardEvent?, event: KeyboardEvent?,
): SkikoInputEvent? { ): SkikoInputEvent? {
...@@ -114,7 +60,7 @@ fun toSkikoTypeEvent( ...@@ -114,7 +60,7 @@ fun toSkikoTypeEvent(
} }
fun toSkikoEvent( internal fun toSkikoEvent(
event: KeyboardEvent, event: KeyboardEvent,
kind: SkikoKeyboardEventKind kind: SkikoKeyboardEventKind
): SkikoKeyboardEvent { ): SkikoKeyboardEvent {
...@@ -127,7 +73,7 @@ fun toSkikoEvent( ...@@ -127,7 +73,7 @@ fun toSkikoEvent(
) )
} }
fun toSkikoScrollEvent( internal fun toSkikoScrollEvent(
event: WheelEvent, event: WheelEvent,
): SkikoPointerEvent { ): SkikoPointerEvent {
return SkikoPointerEvent( return SkikoPointerEvent(
......
package org.jetbrains.skiko
import kotlinx.browser.window
import org.jetbrains.skiko.w3c.HTMLCanvasElement
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.MouseEvent
import org.w3c.dom.events.WheelEvent
internal actual fun SkiaLayer.bindCanvasEventsToSkikoView(
canvas: HTMLCanvasElement
) {
val htmlCanvas = canvas.unsafeCast<org.w3c.dom.HTMLCanvasElement>()
htmlCanvas.addEventListener("mousedown", { event ->
event as MouseEvent
isPointerPressed = true
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.DOWN))
})
htmlCanvas.addEventListener("mouseup", { event ->
event as MouseEvent
isPointerPressed = false
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.UP))
})
htmlCanvas.addEventListener("mousemove", { event ->
event as MouseEvent
if (isPointerPressed) {
skikoView?.onPointerEvent(toSkikoDragEvent(event))
} else {
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.MOVE))
}
})
htmlCanvas.addEventListener("wheel", { event ->
event as WheelEvent
skikoView?.onPointerEvent(toSkikoScrollEvent(event))
})
htmlCanvas.addEventListener("contextmenu", { event ->
event.preventDefault()
})
htmlCanvas.addEventListener("keydown", { event ->
event as KeyboardEvent
skikoView?.onKeyboardEvent(toSkikoEvent(event, SkikoKeyboardEventKind.DOWN))
toSkikoTypeEvent(event.key, event)?.let { inputEvent ->
skikoView?.input?.onInputEvent(inputEvent)
}
})
htmlCanvas.addEventListener("keyup", { event ->
event as KeyboardEvent
skikoView?.onKeyboardEvent(toSkikoEvent(event, SkikoKeyboardEventKind.UP))
})
}
actual typealias SkikoGesturePlatformEvent = Any
actual typealias SkikoPlatformInputEvent = KeyboardEvent
actual typealias SkikoPlatformKeyboardEvent = KeyboardEvent
// MouseEvent is base class of PointerEvent
actual typealias SkikoPlatformPointerEvent = MouseEvent
internal actual fun SkiaLayer.setOnChangeScaleNotifier() {
state?.initCanvas(desiredWidth, desiredHeight, contentScale, this.pixelGeometry)
window.matchMedia("(resolution: ${contentScale}dppx)")
.addEventListener("change", { setOnChangeScaleNotifier() }, true)
onContentScaleChanged?.invoke(contentScale)
}
\ No newline at end of file
package org.jetbrains.skiko.wasm package org.jetbrains.skiko.wasm
import org.jetbrains.skia.impl.NativePointer import org.jetbrains.skia.impl.NativePointer
import org.w3c.dom.HTMLCanvasElement import org.jetbrains.skiko.w3c.HTMLCanvasElement
import kotlin.js.* import kotlin.js.*
/** /**
...@@ -40,7 +40,6 @@ internal external object GL : GLInterface { ...@@ -40,7 +40,6 @@ internal external object GL : GLInterface {
override fun makeContextCurrent(contextPointer: NativePointer): Boolean = definedExternally override fun makeContextCurrent(contextPointer: NativePointer): Boolean = definedExternally
} }
internal actual fun createWebGLContext(canvas: HTMLCanvasElement, attr: ContextAttributes?): NativePointer { internal actual fun createWebGLContext(canvas: HTMLCanvasElement, attr: ContextAttributes?): NativePointer {
val contextAttributes = object : ContextAttributes { val contextAttributes = object : ContextAttributes {
override val alpha = attr?.alpha ?: 1 override val alpha = attr?.alpha ?: 1
......
package org.jetbrains.skia package org.jetbrains.skia
import kotlinx.browser.window import org.jetbrains.skiko.w3c.window
internal actual fun <R> commonSynchronized(lock: Any, block: () -> R) { internal actual fun <R> commonSynchronized(lock: Any, block: () -> R) {
block() block()
......
package org.jetbrains.skiko package org.jetbrains.skiko
import org.w3c.dom.HTMLElement import org.jetbrains.skiko.w3c.window
internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R = internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R =
block() block()
actual fun currentNanoTime(): Long = (kotlinx.browser.window.performance.now() * 1_000_000).toLong() actual fun currentNanoTime(): Long = (window.performance.now() * 1_000_000).toLong()
internal actual fun URIHandler_openUri(uri: String) { internal actual fun URIHandler_openUri(uri: String) {
kotlinx.browser.window.open(uri, target = "_blank") window.open(uri, target = "_blank")
} }
internal actual fun ClipboardManager_setText(text: String) { internal actual fun ClipboardManager_setText(text: String) {
kotlinx.browser.window.navigator.clipboard.writeText(text) window.navigator.clipboard.writeText(text)
} }
internal actual fun ClipboardManager_getText(): String? { internal actual fun ClipboardManager_getText(): String? {
...@@ -25,20 +25,6 @@ internal actual fun ClipboardManager_hasText(): Boolean = !ClipboardManager_getT ...@@ -25,20 +25,6 @@ internal actual fun ClipboardManager_hasText(): Boolean = !ClipboardManager_getT
actual typealias Cursor = String actual typealias Cursor = String
internal actual fun CursorManager_setCursor(component: Any, cursor: Cursor) {
if (component is HTMLElement) {
component.style.cursor = cursor
}
}
internal actual fun CursorManager_getCursor(component: Any): Cursor? {
return if (component is HTMLElement) {
component.style.cursor
} else {
null
}
}
internal actual fun getCursorById(id: PredefinedCursorsId): Cursor = internal actual fun getCursorById(id: PredefinedCursorsId): Cursor =
when (id) { when (id) {
PredefinedCursorsId.DEFAULT -> "default" PredefinedCursorsId.DEFAULT -> "default"
......
package org.jetbrains.skiko package org.jetbrains.skiko
import kotlinx.browser.window
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skia.impl.NativePointer
import org.jetbrains.skiko.w3c.HTMLCanvasElement
import org.jetbrains.skiko.w3c.window
import org.jetbrains.skiko.wasm.createWebGLContext import org.jetbrains.skiko.wasm.createWebGLContext
import org.jetbrains.skiko.wasm.GL
import org.w3c.dom.HTMLCanvasElement
/** /**
* CanvasRenderer takes an [HTMLCanvasElement] instance and initializes * CanvasRenderer takes an [HTMLCanvasElement] instance and initializes
...@@ -13,7 +13,9 @@ import org.w3c.dom.HTMLCanvasElement ...@@ -13,7 +13,9 @@ import org.w3c.dom.HTMLCanvasElement
* After initialization [needRedraw] can be used to schedule a call to [drawFrame]. * After initialization [needRedraw] can be used to schedule a call to [drawFrame].
* [drawFrame] has to be implemented to perform the actual drawing on [canvas]. * [drawFrame] has to be implemented to perform the actual drawing on [canvas].
*/ */
abstract class CanvasRenderer constructor(val htmlCanvas: HTMLCanvasElement) { internal abstract class CanvasRenderer(
private val htmlCanvas: HTMLCanvasElement
) {
private val contextPointer = createWebGLContext(htmlCanvas) private val contextPointer = createWebGLContext(htmlCanvas)
private val context: DirectContext private val context: DirectContext
private var surface: Surface? = null private var surface: Surface? = null
...@@ -39,7 +41,7 @@ abstract class CanvasRenderer constructor(val htmlCanvas: HTMLCanvasElement) { ...@@ -39,7 +41,7 @@ abstract class CanvasRenderer constructor(val htmlCanvas: HTMLCanvasElement) {
get() = htmlCanvas.height get() = htmlCanvas.height
init { init {
GL.makeContextCurrent(contextPointer) makeGLContextCurrent(contextPointer)
context = DirectContext.makeGL() context = DirectContext.makeGL()
} }
...@@ -93,7 +95,7 @@ abstract class CanvasRenderer constructor(val htmlCanvas: HTMLCanvasElement) { ...@@ -93,7 +95,7 @@ abstract class CanvasRenderer constructor(val htmlCanvas: HTMLCanvasElement) {
redrawScheduled = true redrawScheduled = true
window.requestAnimationFrame { timestamp -> window.requestAnimationFrame { timestamp ->
redrawScheduled = false redrawScheduled = false
GL.makeContextCurrent(contextPointer) makeGLContextCurrent(contextPointer)
// `clear` and `resetMatrix` make canvas not accumulate previous effects // `clear` and `resetMatrix` make canvas not accumulate previous effects
canvas?.clear(Color.WHITE) canvas?.clear(Color.WHITE)
canvas?.resetMatrix() canvas?.resetMatrix()
...@@ -103,3 +105,5 @@ abstract class CanvasRenderer constructor(val htmlCanvas: HTMLCanvasElement) { ...@@ -103,3 +105,5 @@ abstract class CanvasRenderer constructor(val htmlCanvas: HTMLCanvasElement) {
} }
} }
} }
internal expect fun makeGLContextCurrent(contextPointer: NativePointer)
\ No newline at end of file
package org.jetbrains.skiko
internal val SPECIAL_KEYS = setOf(
"Unidentified",
"Alt",
"AltGraph",
"Backspace",
"CapsLock",
"Control",
"Fn",
"FnLock",
"Hyper",
"Meta",
"NumLock",
"ScrollLock",
"Shift",
"Super",
"Symbol",
"SymbolLock",
"F1",
"F2",
"F3",
"F4",
"F5",
"F6",
"F7",
"F8",
"F9",
"F10",
"F11",
"F12",
"F13",
"F14",
"F15",
"F16",
"F17",
"F18",
"F19",
"F20",
"F21",
"F22",
"ArrowLeft",
"ArrowUp",
"ArrowRight",
"ArrowDown",
"Help",
"Home",
"Delete",
"End",
"PageUp",
"PageDown",
"Escape",
"Clear",
"Clear"
)
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
import kotlinx.browser.window import org.jetbrains.skiko.w3c.window
actual val hostOs: OS = detectHostOs() actual val hostOs: OS = detectHostOs()
...@@ -22,9 +22,7 @@ actual val kotlinBackend: KotlinBackend ...@@ -22,9 +22,7 @@ actual val kotlinBackend: KotlinBackend
* See https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/platform - new API, * See https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/platform - new API,
* but not supported in all browsers * but not supported in all browsers
*/ */
private fun getNavigatorInfo(): String = internal expect fun getNavigatorInfo(): String
js("navigator.userAgentData ? navigator.userAgentData.platform : navigator.platform") //as String // k/wasm requires only 1 expression in a function calling `js`
/** /**
* In a browser, user platform can be obtained from different places: * In a browser, user platform can be obtained from different places:
......
package org.jetbrains.skiko package org.jetbrains.skiko
import kotlinx.browser.window
import org.jetbrains.skia.Canvas import org.jetbrains.skia.Canvas
import org.jetbrains.skia.PixelGeometry import org.jetbrains.skia.PixelGeometry
import org.w3c.dom.HTMLCanvasElement import org.jetbrains.skiko.w3c.HTMLCanvasElement
import org.w3c.dom.events.InputEvent import org.jetbrains.skiko.w3c.window
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.MouseEvent
import org.w3c.dom.events.WheelEvent
/** /**
* Provides a way to render the content and to receive the input events. * Provides a way to render the content and to receive the input events.
...@@ -17,7 +13,7 @@ import org.w3c.dom.events.WheelEvent ...@@ -17,7 +13,7 @@ import org.w3c.dom.events.WheelEvent
* using [attachTo] method. * using [attachTo] method.
*/ */
actual open class SkiaLayer { actual open class SkiaLayer {
private var state: CanvasRenderer? = null internal var state: CanvasRenderer? = null
/** /**
* [GraphicsApi.WEBGL] is the only supported renderApi for k/js (browser). * [GraphicsApi.WEBGL] is the only supported renderApi for k/js (browser).
...@@ -72,10 +68,10 @@ actual open class SkiaLayer { ...@@ -72,10 +68,10 @@ actual open class SkiaLayer {
// TODO: when switch to the frame dispatcher - stop it here. // TODO: when switch to the frame dispatcher - stop it here.
} }
private var isPointerPressed = false internal var isPointerPressed = false
private var desiredWidth = 0 internal var desiredWidth = 0
private var desiredHeight = 0 internal var desiredHeight = 0
actual val component: Any? actual val component: Any?
get() = this.htmlCanvas get() = this.htmlCanvas
...@@ -86,7 +82,7 @@ actual open class SkiaLayer { ...@@ -86,7 +82,7 @@ actual open class SkiaLayer {
* Initializes the [CanvasRenderer] and events listeners. * Initializes the [CanvasRenderer] and events listeners.
* Delegates rendering and events processing to [skikoView]. * Delegates rendering and events processing to [skikoView].
*/ */
fun attachTo(htmlCanvas: HTMLCanvasElement, autoDetach: Boolean = true) { private fun attachTo(htmlCanvas: HTMLCanvasElement, autoDetach: Boolean = true) {
this.htmlCanvas = htmlCanvas this.htmlCanvas = htmlCanvas
// Scale canvas to allow high DPI rendering as suggested in // Scale canvas to allow high DPI rendering as suggested in
...@@ -105,49 +101,7 @@ actual open class SkiaLayer { ...@@ -105,49 +101,7 @@ actual open class SkiaLayer {
}.apply { initCanvas(desiredWidth, desiredHeight, contentScale, pixelGeometry) } }.apply { initCanvas(desiredWidth, desiredHeight, contentScale, pixelGeometry) }
// See https://www.w3schools.com/jsref/dom_obj_event.asp // See https://www.w3schools.com/jsref/dom_obj_event.asp
// https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events // https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events
htmlCanvas.addEventListener("mousedown", { event -> bindCanvasEventsToSkikoView(htmlCanvas)
event as MouseEvent
isPointerPressed = true
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.DOWN))
})
htmlCanvas.addEventListener("mouseup", { event ->
event as MouseEvent
isPointerPressed = false
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.UP))
})
htmlCanvas.addEventListener("mousemove", { event ->
event as MouseEvent
if (isPointerPressed) {
skikoView?.onPointerEvent(toSkikoDragEvent(event))
} else {
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.MOVE))
}
})
htmlCanvas.addEventListener("wheel", { event ->
event as WheelEvent
skikoView?.onPointerEvent(toSkikoScrollEvent(event))
})
htmlCanvas.addEventListener("contextmenu", { event ->
event.preventDefault()
})
htmlCanvas.addEventListener("keydown", { event ->
event as KeyboardEvent
skikoView?.onKeyboardEvent(toSkikoEvent(event, SkikoKeyboardEventKind.DOWN))
toSkikoTypeEvent(event.key, event)?.let { inputEvent ->
skikoView?.input?.onInputEvent(inputEvent)
}
})
htmlCanvas.addEventListener("keyup", { event ->
event as KeyboardEvent
skikoView?.onKeyboardEvent(toSkikoEvent(event, SkikoKeyboardEventKind.UP))
})
}
private fun setOnChangeScaleNotifier() {
state?.initCanvas(desiredWidth, desiredHeight, contentScale, this.pixelGeometry)
window.matchMedia("(resolution: ${contentScale}dppx)").addEventListener("change", { setOnChangeScaleNotifier() }, true)
onContentScaleChanged?.invoke(contentScale)
} }
internal actual fun draw(canvas: Canvas) { internal actual fun draw(canvas: Canvas) {
...@@ -156,12 +110,10 @@ actual open class SkiaLayer { ...@@ -156,12 +110,10 @@ actual open class SkiaLayer {
actual val pixelGeometry: PixelGeometry actual val pixelGeometry: PixelGeometry
get() = PixelGeometry.UNKNOWN get() = PixelGeometry.UNKNOWN
var onContentScaleChanged: ((Float) -> Unit)? = null
} }
var onContentScaleChanged: ((Float) -> Unit)? = null
actual typealias SkikoGesturePlatformEvent = Any internal expect fun SkiaLayer.bindCanvasEventsToSkikoView(canvas: HTMLCanvasElement)
actual typealias SkikoPlatformInputEvent = KeyboardEvent internal expect fun SkiaLayer.setOnChangeScaleNotifier()
actual typealias SkikoPlatformKeyboardEvent = KeyboardEvent
// MouseEvent is base class of PointerEvent
actual typealias SkikoPlatformPointerEvent = MouseEvent
package org.jetbrains.skiko.w3c
/**
* The purpose of this file is to facilitate shared W3C type declarations
* for use in both kotlin/js and kotlin/wasm code.
* Given that the Kotlin standard library does not provide common (shared between kotlin/js and kotlin/wasm) W3C API,
* this file serves to fill that void with custom partial declarations.
*
* These declarations do not cover all attributes and methods of the respective W3C types,
* but only those needed by our implementations.
*/
@Suppress("ClassName")
internal external object window {
val devicePixelRatio: Double
val navigator: Navigator
val performance: Performance
fun requestAnimationFrame(block: (Double) -> Unit)
fun open(url: String?, target: String?)
}
internal external interface Performance {
fun now(): Double
}
internal external interface Navigator {
val language: String
val clipboard: Clipboard
val userAgent: String
}
internal external interface Clipboard {
fun writeText(text: String)
}
internal abstract external class HTMLCanvasElement {
internal var width: Int
internal var height: Int
internal val style: CSSStyleDeclaration
}
internal abstract external class CSSStyleDeclaration {
var width: String
var height: String
}
\ No newline at end of file
package org.jetbrains.skiko.wasm package org.jetbrains.skiko.wasm
import org.jetbrains.skia.impl.NativePointer import org.jetbrains.skia.impl.NativePointer
import org.w3c.dom.HTMLCanvasElement import org.jetbrains.skiko.w3c.HTMLCanvasElement
import kotlin.js.*
internal external interface ContextAttributes { internal external interface ContextAttributes {
val alpha: Int? val alpha: Int?
......
package org.jetbrains.skiko package org.jetbrains.skiko
import org.w3c.dom.events.MouseEvent import org.w3c.dom.HTMLElement
import org.w3c.dom.events.UIEvent import org.w3c.dom.events.UIEvent
internal actual fun getEventTimestamp(e: UIEvent): Long { internal fun getEventTimestamp(e: UIEvent): Long {
// TODO: why can't do timestamp.toLong() // TODO: why can't do timestamp.toLong()
return e.timeStamp.toInt().toLong() return e.timeStamp.toInt().toLong()
} }
internal actual fun CursorManager_setCursor(component: Any, cursor: Cursor) {
(component as? HTMLElement)?.style?.cursor = cursor
}
internal actual fun CursorManager_getCursor(component: Any): Cursor? {
return (component as? HTMLElement)?.style?.cursor
}
internal actual fun getNavigatorInfo(): String =
js("navigator.userAgentData ? navigator.userAgentData.platform : navigator.platform")
\ No newline at end of file
package org.jetbrains.skiko
import org.jetbrains.skia.impl.NativePointer
import org.jetbrains.skiko.wasm.GL
internal actual fun makeGLContextCurrent(contextPointer: NativePointer) {
GL.makeContextCurrent(contextPointer)
}
\ No newline at end of file
package org.jetbrains.skiko
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.MouseEvent
import org.w3c.dom.events.WheelEvent
internal fun toSkikoEvent(
event: MouseEvent,
kind: SkikoPointerEventKind
): SkikoPointerEvent {
return SkikoPointerEvent(
x = event.offsetX,
y = event.offsetY,
pressedButtons = toSkikoPressedMouseButtons(event, kind),
button = toSkikoMouseButton(event),
modifiers = toSkikoModifiers(event),
kind = kind,
timestamp = getEventTimestamp(event),
platform = event
)
}
internal fun toSkikoDragEvent(
event: MouseEvent
): SkikoPointerEvent {
return SkikoPointerEvent(
x = event.offsetX,
y = event.offsetY,
pressedButtons = SkikoMouseButtons(buttonsFlags),
button = toSkikoMouseButton(event),
modifiers = toSkikoModifiers(event),
kind = SkikoPointerEventKind.DRAG,
timestamp = getEventTimestamp(event),
platform = event
)
}
internal fun toSkikoTypeEvent(
character: String,
event: KeyboardEvent?,
): SkikoInputEvent? {
return if (SPECIAL_KEYS.contains(character)) {
null
} else {
val input = when (character) {
"Enter" -> "\n"
"Tab" -> "\t"
else -> character
}
val key = if (event != null) SkikoKey.valueOf(event.keyCode) else SkikoKey.KEY_UNKNOWN
val modifiers = if (event != null) toSkikoModifiers(event) else SkikoInputModifiers.EMPTY
SkikoInputEvent(
input,
key,
modifiers,
SkikoKeyboardEventKind.TYPE,
event
)
}
}
internal fun toSkikoEvent(
event: KeyboardEvent,
kind: SkikoKeyboardEventKind
): SkikoKeyboardEvent {
return SkikoKeyboardEvent(
SkikoKey.valueOf(toSkikoKey(event)),
toSkikoModifiers(event),
kind,
getEventTimestamp(event),
event
)
}
internal fun toSkikoScrollEvent(
event: WheelEvent,
): SkikoPointerEvent {
return SkikoPointerEvent(
x = event.offsetX,
y = event.offsetY,
deltaX = event.deltaX,
deltaY = event.deltaY,
pressedButtons = SkikoMouseButtons(buttonsFlags),
button = SkikoMouseButtons.NONE,
modifiers = toSkikoModifiers(event),
kind = SkikoPointerEventKind.SCROLL,
timestamp = getEventTimestamp(event),
platform = event
)
}
private var buttonsFlags = 0
private fun toSkikoPressedMouseButtons(
event: MouseEvent,
kind: SkikoPointerEventKind
): SkikoMouseButtons {
// https://www.w3schools.com/jsref/event_button.asp
val button = event.button.toInt()
if (kind == SkikoPointerEventKind.DOWN) {
buttonsFlags = buttonsFlags.or(getSkikoButtonValue(button))
return SkikoMouseButtons(buttonsFlags)
}
buttonsFlags = buttonsFlags.xor(getSkikoButtonValue(button))
return SkikoMouseButtons(buttonsFlags)
}
private fun toSkikoMouseButton(event: MouseEvent): SkikoMouseButtons {
return SkikoMouseButtons(getSkikoButtonValue(event.button.toInt()))
}
private fun getSkikoButtonValue(button: Int): Int {
return when (button) {
0 -> SkikoMouseButtons.LEFT.value
1 -> SkikoMouseButtons.MIDDLE.value
2 -> SkikoMouseButtons.RIGHT.value
3 -> SkikoMouseButtons.BUTTON_4.value
4 -> SkikoMouseButtons.BUTTON_5.value
else -> 0
}
}
private fun toSkikoModifiers(event: MouseEvent): SkikoInputModifiers {
var result = 0
if (event.altKey) {
result = result.or(SkikoInputModifiers.ALT.value)
}
if (event.shiftKey) {
result = result.or(SkikoInputModifiers.SHIFT.value)
}
if (event.ctrlKey) {
result = result.or(SkikoInputModifiers.CONTROL.value)
}
if (event.metaKey) {
result = result.or(SkikoInputModifiers.META.value)
}
return SkikoInputModifiers(result)
}
private fun toSkikoModifiers(event: KeyboardEvent): SkikoInputModifiers {
var result = 0
if (event.altKey) {
result = result.or(SkikoInputModifiers.ALT.value)
}
if (event.shiftKey) {
result = result.or(SkikoInputModifiers.SHIFT.value)
}
if (event.ctrlKey) {
result = result.or(SkikoInputModifiers.CONTROL.value)
}
if (event.metaKey) {
result = result.or(SkikoInputModifiers.META.value)
}
return SkikoInputModifiers(result)
}
private fun toSkikoKey(event: KeyboardEvent): Int {
var key = event.keyCode
val side = event.location
if (side == KeyboardEvent.DOM_KEY_LOCATION_RIGHT) {
if (
key == SkikoKey.KEY_LEFT_CONTROL.platformKeyCode ||
key == SkikoKey.KEY_LEFT_SHIFT.platformKeyCode ||
key == SkikoKey.KEY_LEFT_META.platformKeyCode
)
key = key.or(0x80000000.toInt())
}
return key
}
\ No newline at end of file
package org.jetbrains.skiko
import kotlinx.browser.window
import org.jetbrains.skiko.w3c.HTMLCanvasElement
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.MouseEvent
import org.w3c.dom.events.WheelEvent
internal actual fun SkiaLayer.bindCanvasEventsToSkikoView(
canvas: HTMLCanvasElement
) {
val htmlCanvas = canvas as org.w3c.dom.HTMLCanvasElement
htmlCanvas.addEventListener("mousedown", { event ->
event as MouseEvent
isPointerPressed = true
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.DOWN))
})
htmlCanvas.addEventListener("mouseup", { event ->
event as MouseEvent
isPointerPressed = false
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.UP))
})
htmlCanvas.addEventListener("mousemove", { event ->
event as MouseEvent
if (isPointerPressed) {
skikoView?.onPointerEvent(toSkikoDragEvent(event))
} else {
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.MOVE))
}
})
htmlCanvas.addEventListener("wheel", { event ->
event as WheelEvent
skikoView?.onPointerEvent(toSkikoScrollEvent(event))
})
htmlCanvas.addEventListener("contextmenu", { event ->
event.preventDefault()
})
htmlCanvas.addEventListener("keydown", { event ->
event as KeyboardEvent
skikoView?.onKeyboardEvent(toSkikoEvent(event, SkikoKeyboardEventKind.DOWN))
toSkikoTypeEvent(event.key, event)?.let { inputEvent ->
skikoView?.input?.onInputEvent(inputEvent)
}
})
htmlCanvas.addEventListener("keyup", { event ->
event as KeyboardEvent
skikoView?.onKeyboardEvent(toSkikoEvent(event, SkikoKeyboardEventKind.UP))
})
}
actual typealias SkikoGesturePlatformEvent = Any
actual typealias SkikoPlatformInputEvent = KeyboardEvent
actual typealias SkikoPlatformKeyboardEvent = KeyboardEvent
// MouseEvent is base class of PointerEvent
actual typealias SkikoPlatformPointerEvent = MouseEvent
internal actual fun SkiaLayer.setOnChangeScaleNotifier() {
state?.initCanvas(desiredWidth, desiredHeight, contentScale, this.pixelGeometry)
window.matchMedia("(resolution: ${contentScale}dppx)")
.addEventListener("change", { setOnChangeScaleNotifier() }, true)
onContentScaleChanged?.invoke(contentScale)
}
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
package org.jetbrains.skiko.wasm package org.jetbrains.skiko.wasm
import org.jetbrains.skia.impl.NativePointer import org.jetbrains.skia.impl.NativePointer
import org.w3c.dom.HTMLCanvasElement import org.jetbrains.skiko.w3c.HTMLCanvasElement
import kotlin.js.* import kotlin.js.*
internal external object GL : GLInterface { internal external object GL : GLInterface {
...@@ -11,6 +11,6 @@ internal external object GL : GLInterface { ...@@ -11,6 +11,6 @@ internal external object GL : GLInterface {
} }
private external interface GLInterface { private external interface GLInterface {
fun createContext(context: HTMLCanvasElement, contextAttributes: ContextAttributes): NativePointer; fun createContext(context: HTMLCanvasElement, contextAttributes: ContextAttributes): NativePointer
fun makeContextCurrent(contextPointer: NativePointer): Boolean; fun makeContextCurrent(contextPointer: NativePointer): Boolean;
} }
\ No newline at end of file
package org.jetbrains.skiko.wasm package org.jetbrains.skiko.wasm
import org.jetbrains.skia.impl.NativePointer import org.jetbrains.skia.impl.NativePointer
import org.w3c.dom.HTMLCanvasElement import org.jetbrains.skiko.w3c.HTMLCanvasElement
@JsFun( @JsFun(
"""() => { """() => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment