Unverified Commit 16473284 authored by Ivan Matkov's avatar Ivan Matkov Committed by GitHub

Remove input handling (#893)

skiko is supposed to be a wrapper around skia, however it has unrelated
features like input. This PR removes it from this library - it's
supposed to be handled externally.

- Removed `SkikoInput`, `SkikoKey`, `SkikoPlatform*Event`
- Replaced `SkikoView` to `SkikoRenderDelegate` and related field in
`SkiaLayer`
- `SkiaLayer` on macOS native now receives `NSView` instead of creating
it internally and attaching it to the `NSWindow`. In theory, it allows
initializing non-full-window skia view there (not tested)
- Updated samples and readme
parent 4f226646
...@@ -55,7 +55,7 @@ Simple example for Kotlin/JVM ...@@ -55,7 +55,7 @@ Simple example for Kotlin/JVM
```kotlin ```kotlin
fun main() { fun main() {
val skiaLayer = SkiaLayer() val skiaLayer = SkiaLayer()
skiaLayer.skikoView = GenericSkikoView(skiaLayer, object : SkikoView { skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object : SkikoRenderDelegate {
val paint = Paint().apply { val paint = Paint().apply {
color = Color.RED color = Color.RED
} }
...@@ -108,8 +108,7 @@ class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol { ...@@ -108,8 +108,7 @@ class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
window!!.rootViewController = SkikoViewController( window!!.rootViewController = SkikoViewController(
SkikoUIView( SkikoUIView(
SkiaLayer().apply { SkiaLayer().apply {
gesturesToListen = SkikoGestureEventKind.values() renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object : SkikoRenderDelegate {
skikoView = GenericSkikoView(skiaLayer, object : SkikoView {
val paint = Paint().apply { color = Color.RED } val paint = Paint().apply { color = Color.RED }
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
canvas.clear(Color.CYAN) canvas.clear(Color.CYAN)
......
package org.jetbrains.skiko.sample package org.jetbrains.skiko.sample
import org.jetbrains.skia.* import android.view.View
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.FontMgr
import org.jetbrains.skia.Paint
import org.jetbrains.skia.PaintMode
import org.jetbrains.skia.Rect
import org.jetbrains.skia.paragraph.FontCollection import org.jetbrains.skia.paragraph.FontCollection
import org.jetbrains.skia.paragraph.ParagraphBuilder import org.jetbrains.skia.paragraph.ParagraphBuilder
import org.jetbrains.skia.paragraph.ParagraphStyle import org.jetbrains.skia.paragraph.ParagraphStyle
import org.jetbrains.skia.paragraph.TextStyle import org.jetbrains.skia.paragraph.TextStyle
import org.jetbrains.skiko.* import org.jetbrains.skiko.FPSCounter
import org.jetbrains.skiko.OS
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoRenderDelegate
import org.jetbrains.skiko.currentSystemTheme
import org.jetbrains.skiko.hostOs
import kotlin.math.PI
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
import kotlin.math.PI
class Clocks(private val layer: SkiaLayer): SkikoView { class Clocks(private val skiaLayer: SkiaLayer, view: View): SkikoRenderDelegate {
private val withFps = true private val withFps = true
private val fpsCounter = FPSCounter() private val fpsCounter = FPSCounter()
private val platformYOffset = if (hostOs == OS.Ios) 50f else 5f private val platformYOffset = if (hostOs == OS.Ios) 50f else 5f
private var frame = 0 private var frame = 0
private var xpos = 0.0 private var xpos = 0.0f
private var ypos = 0.0 private var ypos = 0.0f
private var xOffset = 0.0 private var xOffset = 0.0
private var yOffset = 0.0 private var yOffset = 0.0
private var scale = 1.0 private var scale = 1.0
...@@ -25,7 +35,14 @@ class Clocks(private val layer: SkiaLayer): SkikoView { ...@@ -25,7 +35,14 @@ class Clocks(private val layer: SkiaLayer): SkikoView {
private val fontCollection = FontCollection() private val fontCollection = FontCollection()
.setDefaultFontManager(FontMgr.default) .setDefaultFontManager(FontMgr.default)
private val style = ParagraphStyle() private val style = ParagraphStyle()
private var inputText = ""
init {
view.setOnTouchListener { _, event ->
xpos = event.x / skiaLayer.contentScale
ypos = event.y / skiaLayer.contentScale
true
}
}
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
if (withFps) fpsCounter.tick() if (withFps) fpsCounter.tick()
...@@ -92,20 +109,12 @@ class Clocks(private val layer: SkiaLayer): SkikoView { ...@@ -92,20 +109,12 @@ class Clocks(private val layer: SkiaLayer): SkikoView {
val renderInfo = ParagraphBuilder(style, fontCollection) val renderInfo = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFF000000.toInt())) .pushStyle(TextStyle().setColor(0xFF000000.toInt()))
.addText("Graphics API: ${layer.renderApi} ✿゚ ${currentSystemTheme}${maybeFps}") .addText("Graphics API: ${skiaLayer.renderApi} ✿゚ ${currentSystemTheme}${maybeFps}")
.popStyle() .popStyle()
.build() .build()
renderInfo.layout(Float.POSITIVE_INFINITY) renderInfo.layout(Float.POSITIVE_INFINITY)
renderInfo.paint(canvas, 5f, platformYOffset) renderInfo.paint(canvas, 5f, platformYOffset)
val input = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFF000000.toInt()))
.addText("TextInput: $inputText")
.popStyle()
.build()
input.layout(Float.POSITIVE_INFINITY)
input.paint(canvas, 5f, platformYOffset + 20f)
val frames = ParagraphBuilder(style, fontCollection) val frames = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xff9BC730L.toInt()).setFontSize(20f)) .pushStyle(TextStyle().setColor(0xff9BC730L.toInt()).setFontSize(20f))
.addText("Frames: ${frame++}\nAngle: $rotate") .addText("Frames: ${frame++}\nAngle: $rotate")
...@@ -116,116 +125,4 @@ class Clocks(private val layer: SkiaLayer): SkikoView { ...@@ -116,116 +125,4 @@ class Clocks(private val layer: SkiaLayer): SkikoView {
canvas.resetMatrix() canvas.resetMatrix()
} }
private fun reset() {
xOffset = 0.0
yOffset = 0.0
rotate = 0.0
scale = 1.0
}
override fun onPointerEvent(event: SkikoPointerEvent) {
when (event.kind) {
SkikoPointerEventKind.DOWN,
SkikoPointerEventKind.MOVE -> {
xpos = event.x
ypos = event.y
}
SkikoPointerEventKind.DRAG -> {
xOffset += event.x - xpos
yOffset += event.y - ypos
xpos = event.x
ypos = event.y
}
SkikoPointerEventKind.SCROLL -> {
when (event.modifiers) {
SkikoInputModifiers.CONTROL -> {
rotate += if (event.deltaY < 0) -5.0 else 5.0
}
else -> {
if (event.y != 0.0) {
scale *= if (event.deltaY < 0) 0.9 else 1.1
}
}
}
}
else -> {}
}
}
override fun onInputEvent(event: SkikoInputEvent) {
if (event.input != "\b") {
inputText += event.input
}
}
override fun onKeyboardEvent(event: SkikoKeyboardEvent) {
if (event.kind == SkikoKeyboardEventKind.DOWN) {
when (event.key) {
SkikoKey.KEY_NUMPAD_ADD -> scale *= 1.1
SkikoKey.KEY_I -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
scale *= 1.1
}
}
SkikoKey.KEY_NUMPAD_SUBTRACT -> scale *= 0.9
SkikoKey.KEY_O -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
scale *= 0.9
}
}
SkikoKey.KEY_R -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
rotate -= 5.0
}
}
SkikoKey.KEY_L -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
rotate += 5.0
}
}
SkikoKey.KEY_NUMPAD_4,
SkikoKey.KEY_LEFT -> xOffset -= 5.0
SkikoKey.KEY_NUMPAD_8,
SkikoKey.KEY_UP -> yOffset -= 5.0
SkikoKey.KEY_NUMPAD_6,
SkikoKey.KEY_RIGHT -> xOffset += 5.0
SkikoKey.KEY_NUMPAD_2,
SkikoKey.KEY_DOWN -> yOffset += 5.0
SkikoKey.KEY_SPACE -> { reset() }
SkikoKey.KEY_BACKSPACE -> {
if (inputText.isNotEmpty()) {
inputText = inputText.dropLast(1)
}
}
else -> {}
}
}
}
override fun onGestureEvent(event: SkikoGestureEvent) {
when (event.kind) {
SkikoGestureEventKind.TAP -> {
xpos = event.x
ypos = event.y
}
SkikoGestureEventKind.DOUBLETAP -> { reset() }
SkikoGestureEventKind.PINCH -> {
if (event.state == SkikoGestureEventState.STARTED) {
k = scale
}
scale = k * event.scale
}
SkikoGestureEventKind.PAN -> {
xOffset += event.x - xpos
yOffset += event.y - ypos
xpos = event.x
ypos = event.y
}
SkikoGestureEventKind.ROTATION -> {
rotate = event.rotation * 180.0 / PI
}
else -> {}
}
}
} }
\ No newline at end of file
...@@ -2,11 +2,9 @@ package org.jetbrains.skiko.sample ...@@ -2,11 +2,9 @@ package org.jetbrains.skiko.sample
import android.app.Activity import android.app.Activity
import android.os.Bundle import android.os.Bundle
import android.view.WindowManager
import android.widget.LinearLayout import android.widget.LinearLayout
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoGestureEventKind import org.jetbrains.skiko.SkiaLayerRenderDelegate
class MainActivity : Activity() { class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
...@@ -21,10 +19,8 @@ class MainActivity : Activity() { ...@@ -21,10 +19,8 @@ class MainActivity : Activity() {
//layoutParams = ViewGroup.LayoutParams(1000, 1200) //layoutParams = ViewGroup.LayoutParams(1000, 1200)
} }
val skiaLayer = SkiaLayer().apply { val skiaLayer = SkiaLayer()
gesturesToListen = SkikoGestureEventKind.values() skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, Clocks(skiaLayer, holder))
}
skiaLayer.skikoView = GenericSkikoView(skiaLayer, Clocks(skiaLayer))
skiaLayer.attachTo(holder) skiaLayer.attachTo(holder)
layout.addView(holder) layout.addView(holder)
......
package org.jetbrains.skiko.sample
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Color
import org.jetbrains.skia.Paint
import org.jetbrains.skia.Rect
import org.jetbrains.skiko.SkikoInputEvent
import org.jetbrains.skiko.SkikoKeyboardEvent
import org.jetbrains.skiko.SkikoPointerEvent
import org.jetbrains.skiko.SkikoView
class RotatingSquare : SkikoView {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val angleDeg = (nanoTime / 5_000_000) % 360
val paint = Paint().apply { color = Color.GREEN }
canvas.clear(Color.RED)
canvas.save();
canvas.translate(128.0f, 128.0f)
canvas.rotate(angleDeg.toFloat())
val rect = Rect.makeXYWH(-90.5f, -90.5f, 181.0f, 181.0f)
canvas.drawRect(rect, paint)
canvas.restore()
}
override fun onInputEvent(event: SkikoInputEvent) {
println("onInput: $event")
}
override fun onKeyboardEvent(event: SkikoKeyboardEvent) {
println("onKeyboard: $event")
}
override fun onPointerEvent(event: SkikoPointerEvent) {
println("onMouse: $event")
}
}
...@@ -115,7 +115,8 @@ fun createWindow(title: String, exitOnClose: Boolean) = SwingUtilities.invokeLat ...@@ -115,7 +115,8 @@ fun createWindow(title: String, exitOnClose: Boolean) = SwingUtilities.invokeLat
println("Changed renderer for $layer: new value is ${layer.renderApi}") println("Changed renderer for $layer: new value is ${layer.renderApi}")
} }
skiaLayer.addView(GenericSkikoView(skiaLayer, clocks)) skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, clocks)
skiaLayer.addMouseMotionListener(clocks)
// Window transparency // Window transparency
if (System.getProperty("skiko.transparency") == "true") { if (System.getProperty("skiko.transparency") == "true") {
......
...@@ -3,11 +3,13 @@ package SkiaAwtSample ...@@ -3,11 +3,13 @@ package SkiaAwtSample
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skia.paragraph.* import org.jetbrains.skia.paragraph.*
import org.jetbrains.skiko.* import org.jetbrains.skiko.*
import java.awt.event.MouseEvent
import java.awt.event.MouseMotionListener
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
open class ClocksAwt(private val scaleProvider: () -> Float) : SkikoView { open class ClocksAwt(private val scaleProvider: () -> Float) : SkikoRenderDelegate, MouseMotionListener {
constructor(layer: SkiaLayer) : this({ layer.contentScale }) constructor(layer: SkiaLayer) : this({ layer.contentScale })
private val typeface = Typeface.makeFromFile("fonts/JetBrainsMono-Regular.ttf") private val typeface = Typeface.makeFromFile("fonts/JetBrainsMono-Regular.ttf")
...@@ -22,8 +24,8 @@ open class ClocksAwt(private val scaleProvider: () -> Float) : SkikoView { ...@@ -22,8 +24,8 @@ open class ClocksAwt(private val scaleProvider: () -> Float) : SkikoView {
} }
private var frame = 0 private var frame = 0
private var xpos = 0.0 private var xpos = 0
private var ypos = 0.0 private var ypos = 0
private val fontCollection = FontCollection() private val fontCollection = FontCollection()
.setDefaultFontManager(FontMgr.default) .setDefaultFontManager(FontMgr.default)
...@@ -108,23 +110,11 @@ open class ClocksAwt(private val scaleProvider: () -> Float) : SkikoView { ...@@ -108,23 +110,11 @@ open class ClocksAwt(private val scaleProvider: () -> Float) : SkikoView {
canvas.drawLine(left, top + rectH, left + rectW, top, Paint()) canvas.drawLine(left, top + rectH, left + rectW, top, Paint())
} }
override fun onPointerEvent(event: SkikoPointerEvent) { override fun mouseDragged(e: MouseEvent) {
when (event.kind) {
SkikoPointerEventKind.DOWN,
SkikoPointerEventKind.MOVE -> {
xpos = event.x
ypos = event.y
}
else -> {}
}
// TODO: provide example that covers all features of pointer event
}
override fun onInputEvent(event: SkikoInputEvent) {
// TODO: provide example that covers all features of text input event
} }
override fun onKeyboardEvent(event: SkikoKeyboardEvent) { override fun mouseMoved(e: MouseEvent) {
// TODO: provide example that covers all features of keyboard event xpos = e.x
ypos = e.y
} }
} }
\ No newline at end of file
...@@ -23,6 +23,7 @@ fun swingSkiaResize() = SwingUtilities.invokeLater { ...@@ -23,6 +23,7 @@ fun swingSkiaResize() = SwingUtilities.invokeLater {
} }
skiaPanel = SkiaSwingPanel(skikoView) skiaPanel = SkiaSwingPanel(skikoView)
skiaPanel.addMouseMotionListener(skikoView)
val leftPanel = JPanel().apply { val leftPanel = JPanel().apply {
background = Color.CYAN background = Color.CYAN
......
package SkiaAwtSample package SkiaAwtSample
import org.jetbrains.skiko.GenericSkikoView import org.jetbrains.skiko.SkiaLayerRenderDelegate
import java.awt.BorderLayout import java.awt.BorderLayout
import java.awt.Color import java.awt.Color
import java.awt.Dimension import java.awt.Dimension
...@@ -85,7 +85,9 @@ fun swingSkia() = SwingUtilities.invokeLater { ...@@ -85,7 +85,9 @@ fun swingSkia() = SwingUtilities.invokeLater {
private fun getSkiaPanel(): SkiaPanel { private fun getSkiaPanel(): SkiaPanel {
return SkiaPanel().apply { return SkiaPanel().apply {
layer.addView(GenericSkikoView(layer, ClocksAwt(layer))) val clocks = ClocksAwt(layer)
layer.renderDelegate = SkiaLayerRenderDelegate(layer, clocks)
layer.addMouseMotionListener(clocks)
val btnPanelOK = JPanel() val btnPanelOK = JPanel()
btnPanelOK.layout = BorderLayout(0, 0) btnPanelOK.layout = BorderLayout(0, 0)
btnPanelOK.background = Color.white btnPanelOK.background = Color.white
......
...@@ -72,19 +72,19 @@ kotlin { ...@@ -72,19 +72,19 @@ kotlin {
} }
} }
val jsWasmMain by creating { val webMain by creating {
dependsOn(commonMain) dependsOn(commonMain)
resources.setSrcDirs(resources.srcDirs) resources.setSrcDirs(resources.srcDirs)
resources.srcDirs(unzipTask.map { it.destinationDir }) resources.srcDirs(unzipTask.map { it.destinationDir })
} }
val jsMain by getting { val jsMain by getting {
dependsOn(jsWasmMain) dependsOn(webMain)
} }
val wasmJsMain by getting { val wasmJsMain by getting {
dependsOn(jsWasmMain) dependsOn(webMain)
} }
} }
} }
......
...@@ -3,11 +3,11 @@ package org.jetbrains.skiko.sample.js ...@@ -3,11 +3,11 @@ package org.jetbrains.skiko.sample.js
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.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoView import org.jetbrains.skiko.SkiaLayerRenderDelegate
import org.jetbrains.skiko.SkikoRenderDelegate
private class DemoApp: SkikoView { private class DemoApp: SkikoRenderDelegate {
private val paint = Paint() private val paint = Paint()
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
...@@ -29,7 +29,7 @@ internal fun runApp() { ...@@ -29,7 +29,7 @@ internal fun runApp() {
} else { } else {
BouncingBalls() BouncingBalls()
} }
skiaLayer.skikoView = GenericSkikoView(skiaLayer, app) skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, app)
skiaLayer.attachTo(canvas) skiaLayer.attachTo(canvas)
skiaLayer.needRedraw() skiaLayer.needRedraw()
} }
......
...@@ -4,12 +4,12 @@ import org.jetbrains.skia.Canvas ...@@ -4,12 +4,12 @@ import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Color4f import org.jetbrains.skia.Color4f
import org.jetbrains.skia.Paint import org.jetbrains.skia.Paint
import org.jetbrains.skia.PaintMode import org.jetbrains.skia.PaintMode
import org.jetbrains.skiko.SkikoView import org.jetbrains.skiko.SkikoRenderDelegate
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
class BouncingBalls: SkikoView { class BouncingBalls: SkikoRenderDelegate {
private data class Circle(var x: Float, var y: Float, var r: Float) private data class Circle(var x: Float, var y: Float, var r: Float)
companion object { companion object {
......
...@@ -21,9 +21,8 @@ plugins { ...@@ -21,9 +21,8 @@ plugins {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
mavenLocal()
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")
...@@ -137,18 +136,18 @@ kotlin { ...@@ -137,18 +136,18 @@ kotlin {
} }
} }
val jsWasmMain by creating { val webMain by creating {
dependsOn(commonMain) dependsOn(commonMain)
resources.setSrcDirs(resources.srcDirs) resources.setSrcDirs(resources.srcDirs)
resources.srcDirs(unzipTask.map { it.destinationDir }) resources.srcDirs(unzipTask.map { it.destinationDir })
} }
val jsMain by getting { val jsMain by getting {
dependsOn(jsWasmMain) dependsOn(webMain)
} }
val wasmJsMain by getting { val wasmJsMain by getting {
dependsOn(jsWasmMain) dependsOn(webMain)
} }
val darwinMain by creating { val darwinMain by creating {
......
package org.jetbrains.skiko.sample package org.jetbrains.skiko.sample
import org.jetbrains.skiko.* import org.jetbrains.skiko.*
import java.awt.Dimension import java.awt.Dimension
import javax.swing.* import javax.swing.*
fun main() { fun main() {
val skiaLayer = SkiaLayer() val skiaLayer = SkiaLayer()
skiaLayer.addView(GenericSkikoView(skiaLayer, AwtClocks(skiaLayer))) val clocks = AwtClocks(skiaLayer)
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, clocks)
SwingUtilities.invokeLater { SwingUtilities.invokeLater {
val window = JFrame("Skiko example").apply { val window = JFrame("Skiko example").apply {
defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
......
package org.jetbrains.skiko.sample package org.jetbrains.skiko.sample
import org.jetbrains.skiko.CursorManager
import org.jetbrains.skiko.PredefinedCursors
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoInputEvent import java.awt.event.MouseEvent
import java.awt.event.MouseMotionListener
import java.awt.event.MouseWheelEvent
import java.awt.event.MouseWheelListener
class AwtClocks(layer: SkiaLayer) : Clocks(layer) { class AwtClocks(private val layer: SkiaLayer) : Clocks(layer.renderApi), MouseMotionListener, MouseWheelListener {
override var inputText: String = "Hello, AwtClocks" private val cursorManager = CursorManager()
override fun handleBackspace() { init {
if (inputText.isNotEmpty()) { layer.addMouseMotionListener(this)
inputText = inputText.dropLast(1)
} }
override fun mouseDragged(event: MouseEvent) {
xOffset += event.x - xpos
yOffset += event.y - ypos
xpos = event.x.toDouble()
ypos = event.y.toDouble()
} }
override fun onInputEvent(event: SkikoInputEvent) { override fun mouseMoved(event: MouseEvent) {
if (event.input != "\b") { if (event.x > 200) {
inputText += event.input cursorManager.setCursor(layer.component, PredefinedCursors.HAND)
} else {
cursorManager.setCursor(layer.component, PredefinedCursors.DEFAULT)
} }
xpos = event.x.toDouble()
ypos = event.y.toDouble()
} }
override fun mouseWheelMoved(event: MouseWheelEvent) {
if (event.isControlDown) {
rotate += if (event.wheelRotation < 0) -5.0 else 5.0
} else {
scale *= if (event.wheelRotation < 0) 0.9 else 1.1
}
}
} }
package org.jetbrains.skiko.sample
import org.jetbrains.skia.*
import org.jetbrains.skiko.*
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
class BouncingBalls(private val withFps: Boolean = false): SkikoView {
private data class Circle(var x: Float, var y: Float, var r: Float)
private val fpsCounter = FPSCounter(2.0, true)
companion object {
private fun moveCircle(c: Circle, s: Float, angle: Float, width: Int, height: Int, r: Float) {
c.x = (c.x + s * sin(angle)).coerceAtLeast(r).coerceAtMost(width.toFloat() - r)
c.y = (c.y + s * cos(angle)).coerceAtLeast(r).coerceAtMost(height.toFloat() - r)
}
private fun calculatePosition(circle: Circle, boundingWidth: Int, boundingHeight: Int): Position {
val southmost = circle.y + circle.r
val northmost = circle.y - circle.r
val westmost = circle.x - circle.r
val eastmost = circle.x + circle.r
return when {
southmost >= boundingHeight -> Position.TOUCHES_SOUTH
northmost <= 0 -> Position.TOUCHES_NORTH
eastmost >= boundingWidth -> Position.TOUCHES_EAST
westmost <= 0 -> Position.TOUCHES_WEST
else -> Position.INSIDE
}
}
}
private enum class Position {
INSIDE,
TOUCHES_SOUTH,
TOUCHES_NORTH,
TOUCHES_WEST,
TOUCHES_EAST
}
private class BouncingBall(
val circle: Circle,
val velocity: Float,
var angle: Double
) {
fun recalculate(width: Int, height: Int, dt: Float) {
val position = calculatePosition(circle, width, height)
val dtMillis = dt / 1000000
when (position) {
Position.TOUCHES_SOUTH -> angle = PI - angle
Position.TOUCHES_EAST -> angle = -angle
Position.TOUCHES_WEST -> angle = -angle
Position.TOUCHES_NORTH -> angle = PI - angle
Position.INSIDE -> angle
}
moveCircle(circle, velocity * (dtMillis.coerceAtMost(500f) / 1000), angle.toFloat(), width, height, circle.r)
}
}
private var prevTimestamp: Long = 0L
private fun Color4f.asPaint() = Paint().apply {
color4f = this@asPaint
mode = PaintMode.FILL
isAntiAlias = true
}
private val data = listOf(
BouncingBall(Circle(200f, 50f, 25f), 172f, PI / 4),
BouncingBall(Circle(100f, 100f, 10f), 162f, -PI / 3),
BouncingBall(Circle(150f, 120f, 30f), 168f, 3 * PI / 4),
BouncingBall(Circle(100f, 10f, 25f), 208f, -PI / 6),
BouncingBall(Circle(120f, 100f, 40f), 120f, -1.1 * PI)
).zip(listOf(
Color4f(0f, 1f, 0f, 0.8f).asPaint(),
Color4f(0f, 0f, 1f, 0.8f).asPaint(),
Color4f(1f, 0f, 0f, 0.8f).asPaint(),
Color4f(1f, 0f, 1f, 0.8f).asPaint(),
Color4f(0f, 1f, 1f, 0.8f).asPaint()
)).toMutableList()
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
if (withFps) fpsCounter.tick()
canvas.clear(-1)
val dtime = (nanoTime - prevTimestamp)
prevTimestamp = nanoTime
data.forEach { (ball, paint) ->
ball.recalculate(width, height, dtime.toFloat())
canvas.drawCircle(ball.circle.x, ball.circle.y, ball.circle.r, paint)
}
}
override fun onInputEvent(event: SkikoInputEvent) {
println("onInput: $event")
}
override fun onKeyboardEvent(event: SkikoKeyboardEvent) {
println("onKeyboard: $event")
}
override fun onPointerEvent(event: SkikoPointerEvent) {
if (event.isLeftClick) {
val paint = Color4f(1f, 0f, 0f, 0.8f).asPaint()
data += BouncingBall(
Circle(event.x.toFloat(), event.y.toFloat(), 25f), 172f, PI / 4) to paint
}
if (event.isRightClick && data.size > 0) {
data.removeLast()
}
// To avoid log spamming
if (event.kind != SkikoPointerEventKind.MOVE)
println("onMouse: $event")
}
}
\ No newline at end of file
package org.jetbrains.skiko.sample package org.jetbrains.skiko.sample
import org.jetbrains.skia.* import org.jetbrains.skia.Canvas
import org.jetbrains.skia.FontMgr
import org.jetbrains.skia.Paint
import org.jetbrains.skia.PaintMode
import org.jetbrains.skia.Rect
import org.jetbrains.skia.paragraph.FontCollection import org.jetbrains.skia.paragraph.FontCollection
import org.jetbrains.skia.paragraph.ParagraphBuilder import org.jetbrains.skia.paragraph.ParagraphBuilder
import org.jetbrains.skia.paragraph.ParagraphStyle import org.jetbrains.skia.paragraph.ParagraphStyle
import org.jetbrains.skia.paragraph.TextStyle import org.jetbrains.skia.paragraph.TextStyle
import org.jetbrains.skiko.* import org.jetbrains.skiko.FPSCounter
import org.jetbrains.skiko.GraphicsApi
import org.jetbrains.skiko.OS
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoRenderDelegate
import org.jetbrains.skiko.currentSystemTheme
import org.jetbrains.skiko.hostOs
import kotlin.math.PI
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
import kotlin.math.PI
abstract class Clocks(private val layer: SkiaLayer): SkikoView { abstract class Clocks(private val renderApi: GraphicsApi): SkikoRenderDelegate {
private val cursorManager = CursorManager()
private val withFps = true private val withFps = true
private val fpsCounter = FPSCounter() private val fpsCounter = FPSCounter()
private val platformYOffset = if (hostOs == OS.Ios) 50f else 5f private val platformYOffset = if (hostOs == OS.Ios) 50f else 5f
private var frame = 0 private var frame = 0
private var xpos = 0.0
private var ypos = 0.0 var xpos = 0.0
private var xOffset = 0.0 var ypos = 0.0
private var yOffset = 0.0 var xOffset = 0.0
private var scale = 1.0 var yOffset = 0.0
private var k = scale var scale = 1.0
private var rotate = 0.0 var rotate = 0.0
private val fontCollection = FontCollection() private val fontCollection = FontCollection()
.setDefaultFontManager(FontMgr.default) .setDefaultFontManager(FontMgr.default)
private val style = ParagraphStyle() private val style = ParagraphStyle()
abstract val inputText:String
abstract fun handleBackspace()
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
if (withFps) fpsCounter.tick() if (withFps) fpsCounter.tick()
canvas.translate(xOffset.toFloat(), yOffset.toFloat()) canvas.translate(xOffset.toFloat(), yOffset.toFloat())
...@@ -95,20 +102,12 @@ abstract class Clocks(private val layer: SkiaLayer): SkikoView { ...@@ -95,20 +102,12 @@ abstract class Clocks(private val layer: SkiaLayer): SkikoView {
val renderInfo = ParagraphBuilder(style, fontCollection) val renderInfo = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFF000000.toInt())) .pushStyle(TextStyle().setColor(0xFF000000.toInt()))
.addText("Graphics API: ${layer.renderApi} ✿゚ ${currentSystemTheme}${maybeFps}") .addText("Graphics API: $renderApi ✿゚ ${currentSystemTheme}${maybeFps}")
.popStyle() .popStyle()
.build() .build()
renderInfo.layout(Float.POSITIVE_INFINITY) renderInfo.layout(Float.POSITIVE_INFINITY)
renderInfo.paint(canvas, 5f, platformYOffset) renderInfo.paint(canvas, 5f, platformYOffset)
val input = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFF000000.toInt()))
.addText("TextInput: $inputText")
.popStyle()
.build()
input.layout(Float.POSITIVE_INFINITY)
input.paint(canvas, 5f, platformYOffset + 20f)
val frames = ParagraphBuilder(style, fontCollection) val frames = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xff9BC730L.toInt()).setFontSize(20f)) .pushStyle(TextStyle().setColor(0xff9BC730L.toInt()).setFontSize(20f))
.addText("Frames: ${frame++}\nAngle: $rotate") .addText("Frames: ${frame++}\nAngle: $rotate")
...@@ -119,113 +118,4 @@ abstract class Clocks(private val layer: SkiaLayer): SkikoView { ...@@ -119,113 +118,4 @@ abstract class Clocks(private val layer: SkiaLayer): SkikoView {
canvas.resetMatrix() canvas.resetMatrix()
} }
private fun reset() {
xOffset = 0.0
yOffset = 0.0
rotate = 0.0
scale = 1.0
}
override fun onPointerEvent(event: SkikoPointerEvent) {
when (event.kind) {
SkikoPointerEventKind.DOWN,
SkikoPointerEventKind.MOVE -> {
if (event.x > 200) {
cursorManager.setCursor(layer.component, PredefinedCursors.HAND)
} else {
cursorManager.setCursor(layer.component, PredefinedCursors.DEFAULT)
}
xpos = event.x
ypos = event.y
}
SkikoPointerEventKind.DRAG -> {
xOffset += event.x - xpos
yOffset += event.y - ypos
xpos = event.x
ypos = event.y
}
SkikoPointerEventKind.SCROLL -> {
when (event.modifiers) {
SkikoInputModifiers.CONTROL -> {
rotate += if (event.deltaY < 0) -5.0 else 5.0
}
else -> {
if (event.y != 0.0) {
scale *= if (event.deltaY < 0) 0.9 else 1.1
}
}
}
}
else -> {}
}
}
override fun onKeyboardEvent(event: SkikoKeyboardEvent) {
if (event.kind == SkikoKeyboardEventKind.DOWN) {
when (event.key) {
SkikoKey.KEY_NUMPAD_ADD -> scale *= 1.1
SkikoKey.KEY_I -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
scale *= 1.1
}
}
SkikoKey.KEY_NUMPAD_SUBTRACT -> scale *= 0.9
SkikoKey.KEY_O -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
scale *= 0.9
}
}
SkikoKey.KEY_R -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
rotate -= 5.0
}
}
SkikoKey.KEY_L -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
rotate += 5.0
}
}
SkikoKey.KEY_NUMPAD_4,
SkikoKey.KEY_LEFT -> xOffset -= 5.0
SkikoKey.KEY_NUMPAD_8,
SkikoKey.KEY_UP -> yOffset -= 5.0
SkikoKey.KEY_NUMPAD_6,
SkikoKey.KEY_RIGHT -> xOffset += 5.0
SkikoKey.KEY_NUMPAD_2,
SkikoKey.KEY_DOWN -> yOffset += 5.0
SkikoKey.KEY_SPACE -> { reset() }
SkikoKey.KEY_BACKSPACE -> {
handleBackspace()
}
else -> {}
}
}
}
override fun onGestureEvent(event: SkikoGestureEvent) {
when (event.kind) {
SkikoGestureEventKind.TAP -> {
xpos = event.x
ypos = event.y
}
SkikoGestureEventKind.DOUBLETAP -> { reset() }
SkikoGestureEventKind.PINCH -> {
if (event.state == SkikoGestureEventState.STARTED) {
k = scale
}
scale = k * event.scale
}
SkikoGestureEventKind.PAN -> {
xOffset += event.x - xpos
yOffset += event.y - ypos
xpos = event.x
ypos = event.y
}
SkikoGestureEventKind.ROTATION -> {
rotate = event.rotation * 180.0 / PI
}
else -> {}
}
}
} }
package org.jetbrains.skiko.sample
import org.jetbrains.skia.*
import org.jetbrains.skiko.*
class RotatingSquare : SkikoView {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val angleDeg = (nanoTime / 5_000_000) % 360
val paint = Paint().apply { color = Color.GREEN }
canvas.clear(Color.RED)
canvas.save();
canvas.translate(128.0f, 128.0f)
canvas.rotate(angleDeg.toFloat())
val rect = Rect.makeXYWH(-90.5f, -90.5f, 181.0f, 181.0f)
canvas.drawRect(rect, paint)
canvas.restore()
}
override fun onInputEvent(event: SkikoInputEvent) {
println("onInput: $event")
}
override fun onKeyboardEvent(event: SkikoKeyboardEvent) {
println("onKeyboard: $event")
}
override fun onPointerEvent(event: SkikoPointerEvent) {
println("onMouse: $event")
}
}
\ No newline at end of file
// Use `xcodegen` first, then `open ./SkikoSample.xcodeproj` and then Run button in XCode. // Use `xcodegen` first, then `open ./SkikoSample.xcodeproj` and then Run button in XCode.
package org.jetbrains.skiko.sample package org.jetbrains.skiko.sample
import kotlinx.cinterop.* import kotlinx.cinterop.autoreleasepool
import platform.UIKit.* import kotlinx.cinterop.cstr
import platform.Foundation.* import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toCValues
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerRenderDelegate
import platform.Foundation.NSStringFromClass
import platform.UIKit.UIApplication
import platform.UIKit.UIApplicationDelegateProtocol
import platform.UIKit.UIApplicationDelegateProtocolMeta
import platform.UIKit.UIApplicationMain
import platform.UIKit.UIResponder
import platform.UIKit.UIResponderMeta
import platform.UIKit.UIScreen
import platform.UIKit.UIWindow
import platform.darwin.dispatch_async
import platform.darwin.dispatch_get_main_queue
fun main() { fun main() {
val args = emptyArray<String>() val args = emptyArray<String>()
...@@ -19,7 +33,7 @@ fun main() { ...@@ -19,7 +33,7 @@ fun main() {
class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol { class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta
@ObjCObjectBase.OverrideInit @OverrideInit
constructor() : super() constructor() : super()
private var _window: UIWindow? = null private var _window: UIWindow? = null
...@@ -28,10 +42,21 @@ class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol { ...@@ -28,10 +42,21 @@ class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
_window = window _window = window
} }
override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map<Any?, *>?): Boolean { override fun application(
window = UIWindow(frame = UIScreen.mainScreen.bounds) application: UIApplication,
window!!.rootViewController = getSkikoViewContoller() didFinishLaunchingWithOptions: Map<Any?, *>?
window!!.makeKeyAndVisible() ): Boolean {
val skiaLayer = SkiaLayer()
val clocks = IosClocks(skiaLayer)
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, clocks)
dispatch_async(dispatch_get_main_queue()) {
skiaLayer.needRedraw()
}
window = UIWindow(frame = UIScreen.mainScreen.bounds).also {
it.rootViewController = clocks.viewController
it.makeKeyAndVisible()
}
return true return true
} }
} }
...@@ -2,24 +2,21 @@ package org.jetbrains.skiko.sample ...@@ -2,24 +2,21 @@ package org.jetbrains.skiko.sample
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoUIView import org.jetbrains.skiko.SkikoUIView
import org.jetbrains.skiko.SkikoInput import platform.UIKit.NSLayoutConstraint
import platform.UIKit.UIViewController
class IosClocks(layer: SkiaLayer) : Clocks(layer) { class IosClocks(skiaLayer: SkiaLayer) : Clocks(skiaLayer.renderApi) {
override var inputText: String = "Hello, IosClocks" val viewController: UIViewController
init {
override fun handleBackspace() { val view = SkikoUIView(skiaLayer)
if (inputText.isNotEmpty()) { view.translatesAutoresizingMaskIntoConstraints = false
inputText = inputText.dropLast(1) viewController = UIViewController()
} viewController.view.addSubview(view)
} NSLayoutConstraint.activateConstraints(listOf(
view.topAnchor.constraintEqualToAnchor(viewController.view.topAnchor),
override val input: SkikoInput = object : SkikoInput by SkikoInput.Empty { view.bottomAnchor.constraintEqualToAnchor(viewController.view.bottomAnchor),
override fun insertText(text: String) { view.leadingAnchor.constraintEqualToAnchor(viewController.view.leadingAnchor),
inputText += text view.trailingAnchor.constraintEqualToAnchor(viewController.view.trailingAnchor)
} ))
override fun deleteBackward() {
handleBackspace()
}
} }
} }
package org.jetbrains.skiko.sample
import org.jetbrains.skiko.SkiaLayer
class SwiftHelper {
fun getViewController() = getSkikoViewContoller()
}
package org.jetbrains.skiko.sample
import kotlinx.cinterop.*
import org.jetbrains.skiko.*
import org.jetbrains.skiko.sample.*
import platform.Foundation.*
import platform.UIKit.*
import platform.darwin.dispatch_async
import platform.darwin.dispatch_get_main_queue
fun makeApp(skiaLayer: SkiaLayer) = IosClocks(skiaLayer)
fun getSkikoViewContoller(): UIViewController {
val view = SkikoUIView(
SkiaLayer().apply {
skikoView = GenericSkikoView(this, makeApp(this))
dispatch_async(dispatch_get_main_queue()) {
needRedraw()
}
}
)
view.translatesAutoresizingMaskIntoConstraints = false
val viewController = UIViewController()
viewController.view.addSubview(view)
NSLayoutConstraint.activateConstraints(listOf(
view.topAnchor.constraintEqualToAnchor(viewController.view.topAnchor),
view.bottomAnchor.constraintEqualToAnchor(viewController.view.bottomAnchor),
view.leadingAnchor.constraintEqualToAnchor(viewController.view.leadingAnchor),
view.trailingAnchor.constraintEqualToAnchor(viewController.view.trailingAnchor)
))
return viewController
}
...@@ -2,8 +2,8 @@ package org.jetbrains.skiko.sample ...@@ -2,8 +2,8 @@ package org.jetbrains.skiko.sample
import kotlinx.browser.document 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.SkiaLayer
import org.jetbrains.skiko.SkiaLayerRenderDelegate
import org.jetbrains.skiko.wasm.onWasmReady import org.jetbrains.skiko.wasm.onWasmReady
import org.w3c.dom.HTMLCanvasElement import org.w3c.dom.HTMLCanvasElement
...@@ -14,11 +14,10 @@ fun main() { ...@@ -14,11 +14,10 @@ fun main() {
} }
internal fun runApp() { internal fun runApp() {
val skiaLayer = SkiaLayer()
val game = JsClocks(skiaLayer)
skiaLayer.skikoView = GenericSkikoView(skiaLayer, game)
val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement
canvas.setAttribute("tabindex", "0") canvas.setAttribute("tabindex", "0")
skiaLayer.attachTo(canvas) val skiaLayer = SkiaLayer()
val clocks = WebClocks(skiaLayer, canvas)
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, clocks)
skiaLayer.needRedraw() skiaLayer.needRedraw()
} }
package org.jetbrains.skiko.sample
actual val greetings: String = "Hello, JsClocks"
\ No newline at end of file
package org.jetbrains.skiko.sample
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoInput
import org.jetbrains.skiko.SkikoInputEvent
expect val greetings: String
class JsClocks(layer: SkiaLayer) : Clocks(layer) {
override var inputText: String = greetings
override fun handleBackspace() {
if (inputText.isNotEmpty()) {
inputText = inputText.dropLast(1)
}
}
override val input: SkikoInput = object : SkikoInput {
override fun onInputEvent(event: SkikoInputEvent) {
inputText += event.input
}
}
}
package org.jetbrains.skiko.sample package org.jetbrains.skiko.sample
import platform.AppKit.* import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerRenderDelegate
import org.jetbrains.skiko.* import platform.AppKit.NSApplication
import platform.AppKit.NSApplicationActivationPolicy
import platform.AppKit.NSApplicationDelegateProtocol
import platform.AppKit.NSBackingStoreBuffered
import platform.AppKit.NSMenu
import platform.AppKit.NSWindow
import platform.AppKit.NSWindowStyleMaskClosable
import platform.AppKit.NSWindowStyleMaskMiniaturizable
import platform.AppKit.NSWindowStyleMaskResizable
import platform.AppKit.NSWindowStyleMaskTitled
import platform.Foundation.NSMakeRect import platform.Foundation.NSMakeRect
import platform.Foundation.NSSelectorFromString import platform.Foundation.NSSelectorFromString
import platform.darwin.NSObject import platform.darwin.NSObject
fun makeApp(skiaLayer: SkiaLayer) = MacosClocks(skiaLayer)
fun main() { fun main() {
val app = NSApplication.sharedApplication() val app = NSApplication.sharedApplication()
app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular) app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular)
...@@ -30,7 +37,7 @@ fun main() { ...@@ -30,7 +37,7 @@ fun main() {
NSWindowStyleMaskMiniaturizable or NSWindowStyleMaskMiniaturizable or
NSWindowStyleMaskClosable or NSWindowStyleMaskClosable or
NSWindowStyleMaskResizable NSWindowStyleMaskResizable
val window = object: NSWindow( val window = object : NSWindow(
contentRect = NSMakeRect(0.0, 0.0, 640.0, 480.0), contentRect = NSMakeRect(0.0, 0.0, 640.0, 480.0),
styleMask = windowStyle, styleMask = windowStyle,
backing = NSBackingStoreBuffered, backing = NSBackingStoreBuffered,
...@@ -40,8 +47,8 @@ fun main() { ...@@ -40,8 +47,8 @@ fun main() {
override fun canBecomeMainWindow() = true override fun canBecomeMainWindow() = true
} }
val skiaLayer = SkiaLayer() val skiaLayer = SkiaLayer()
skiaLayer.skikoView = GenericSkikoView(skiaLayer, makeApp(skiaLayer)) val clocks = MacosClocks(skiaLayer, window)
skiaLayer.attachTo(window) skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, clocks)
window.makeKeyAndOrderFront(app) window.makeKeyAndOrderFront(app)
app.run() app.run()
} }
package org.jetbrains.skiko.sample package org.jetbrains.skiko.sample
import kotlinx.cinterop.useContents
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoInputEvent import platform.AppKit.NSEvent
import platform.AppKit.NSTrackingActiveInActiveApp
import platform.AppKit.NSTrackingArea
import platform.AppKit.NSTrackingMouseMoved
import platform.AppKit.NSView
import platform.AppKit.NSViewHeightSizable
import platform.AppKit.NSViewWidthSizable
import platform.AppKit.NSWindow
class MacosClocks(layer: SkiaLayer): Clocks(layer) { class MacosClocks(skiaLayer: SkiaLayer, window: NSWindow) : Clocks(skiaLayer.renderApi) {
override var inputText: String = "" init {
val nsView = object : NSView(window.frame) {
private var trackingArea : NSTrackingArea? = null
override fun handleBackspace() { override fun updateTrackingAreas() {
if (inputText.isNotEmpty()) { trackingArea?.let { removeTrackingArea(it) }
inputText = inputText.dropLast(1) trackingArea = NSTrackingArea(
rect = bounds,
options = NSTrackingActiveInActiveApp or NSTrackingMouseMoved,
owner = this,
userInfo = null
)
addTrackingArea(trackingArea!!)
}
override fun mouseMoved(event: NSEvent) {
val height = frame.useContents { size.height }
event.locationInWindow.useContents {
xpos = x
ypos = height - y
}
} }
} }
override fun onInputEvent(event: SkikoInputEvent) { val contentView = window.contentView!!
inputText += event.input nsView.autoresizingMask = NSViewHeightSizable or NSViewWidthSizable
contentView.addSubview(nsView)
skiaLayer.attachTo(nsView)
} }
} }
package org.jetbrains.skiko.sample package org.jetbrains.skiko.sample
import kotlinx.browser.document import kotlinx.browser.document
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerRenderDelegate
import org.w3c.dom.HTMLCanvasElement import org.w3c.dom.HTMLCanvasElement
fun main() { fun main() {
...@@ -10,11 +10,10 @@ fun main() { ...@@ -10,11 +10,10 @@ fun main() {
} }
internal fun runApp() { internal fun runApp() {
val skiaLayer = SkiaLayer()
val game = JsClocks(skiaLayer)
skiaLayer.skikoView = GenericSkikoView(skiaLayer, game)
val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement
canvas.setAttribute("tabindex", "0") canvas.setAttribute("tabindex", "0")
skiaLayer.attachTo(canvas) val skiaLayer = SkiaLayer()
val clocks = WebClocks(skiaLayer, canvas)
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, clocks)
skiaLayer.needRedraw() skiaLayer.needRedraw()
} }
package org.jetbrains.skiko.sample
actual val greetings: String = "Hello, WasmClocks"
\ No newline at end of file
package org.jetbrains.skiko.sample
import kotlinx.browser.window
import org.jetbrains.skiko.SkiaLayer
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.TouchEvent
import org.w3c.dom.events.Event
import org.w3c.dom.events.MouseEvent
import org.w3c.dom.get
class WebClocks(skiaLayer: SkiaLayer, canvas: HTMLCanvasElement) : Clocks(skiaLayer.renderApi) {
init {
val scale = window.devicePixelRatio.toFloat()
val bounds = canvas.getBoundingClientRect()
canvas.addTypedEvent<TouchEvent>("touchmove") { event ->
event.preventDefault()
event.touches[0]?.let {
xpos = (it.clientX - bounds.left) / scale
ypos = (it.clientY - bounds.top) / scale
}
}
canvas.addTypedEvent<MouseEvent>("mousemove") { event ->
xpos = event.offsetX / scale
ypos = event.offsetY / scale
}
skiaLayer.attachTo(canvas)
}
}
@Suppress("UNCHECKED_CAST")
private fun <T : Event> HTMLCanvasElement.addTypedEvent(
type: String,
handler: (event: T) -> Unit
) {
this.addEventListener(type, { event -> handler(event as T) })
}
package org.jetbrains.skiko
import android.view.MotionEvent
import android.view.KeyEvent
import kotlin.math.abs
fun toSkikoKeyboardEvent(
event: KeyEvent,
keyCode: Int,
kind: SkikoKeyboardEventKind
): SkikoKeyboardEvent {
return SkikoKeyboardEvent(
key = SkikoKey.valueOf(keyCode),
modifiers = toSkikoModifiers(event),
kind = kind,
timestamp = event.eventTime,
platform = event
)
}
fun toSkikoTypeEvent(
event: KeyEvent,
keyCode: Int
): SkikoInputEvent {
return SkikoInputEvent(
input = event.unicodeChar.toChar().toString(),
key = SkikoKey.valueOf(keyCode),
modifiers = toSkikoModifiers(event),
SkikoKeyboardEventKind.TYPE,
platform = event
)
}
private fun toSkikoModifiers(event: KeyEvent): SkikoInputModifiers {
var result = 0
if (event.isAltPressed) {
result = result.or(SkikoInputModifiers.ALT.value)
}
if (event.isShiftPressed) {
result = result.or(SkikoInputModifiers.SHIFT.value)
}
if (event.isCtrlPressed) {
result = result.or(SkikoInputModifiers.CONTROL.value)
}
if (event.isMetaPressed) {
result = result.or(SkikoInputModifiers.META.value)
}
return SkikoInputModifiers(result)
}
fun toSkikoPointerEvent(event: MotionEvent, density: Float): SkikoPointerEvent {
val upIndex = when (event.action) {
MotionEvent.ACTION_UP -> 0
MotionEvent.ACTION_POINTER_UP -> event.actionIndex
else -> -1
}
val pointers = (0 until event.pointerCount).map {
SkikoPointer(
x = event.getX(it).toDouble() / density,
y = event.getY(it).toDouble() / density,
// Same as in Jetpack Compose for Android
// https://github.com/androidx/androidx/blob/58597f0eba31b89f57b6605b7ed4977cd48ed38d/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt#L126
// (Mouse isn't supported for Android yet, so hover cannot be checked)
pressed = it != upIndex,
device = SkikoPointerDevice.TOUCH,
id = event.getPointerId(it).toLong(),
pressure = event.getPressure(it).toDouble()
)
}
return SkikoPointerEvent(
x = pointers.centroidX,
y = pointers.centroidY,
kind = toSkikoPointerEventKind(event),
timestamp = event.eventTime,
pointers = pointers,
platform = event
)
}
fun toSkikoScaleGestureEvent(
event: MotionEvent,
scale: Double,
state: SkikoGestureEventState,
density: Float
): SkikoGestureEvent {
return SkikoGestureEvent(
x = (event.x / density).toDouble(),
y = (event.y / density).toDouble(),
scale = scale,
kind = SkikoGestureEventKind.PINCH,
state = state,
platform = event
)
}
private val swipeThreshold = 100
private val swipeVelocityThreshold = 100
internal fun toSkikoGestureDirection(
event1: MotionEvent?,
event2: MotionEvent,
velocityX: Float,
velocityY: Float
): SkikoGestureEventDirection {
if (event1 == null) {
return SkikoGestureEventDirection.UNKNOWN
}
val dx = event2.x - event1.x
val dy = event2.y - event1.y
if (abs(dx) > abs(dy)) {
if (abs(dx) > swipeThreshold && abs(velocityX) > swipeVelocityThreshold) {
if (dx > 0) {
return SkikoGestureEventDirection.RIGHT
} else {
return SkikoGestureEventDirection.LEFT
}
}
} else {
if (abs(dy) > swipeThreshold && abs(velocityY) > swipeVelocityThreshold) {
if (dy > 0) {
return SkikoGestureEventDirection.DOWN
} else {
return SkikoGestureEventDirection.UP
}
}
}
return SkikoGestureEventDirection.UNKNOWN
}
internal fun toSkikoPointerEventKind(event: MotionEvent): SkikoPointerEventKind {
return when (event.action) {
MotionEvent.ACTION_POINTER_DOWN,
MotionEvent.ACTION_DOWN -> SkikoPointerEventKind.DOWN
MotionEvent.ACTION_POINTER_UP,
MotionEvent.ACTION_UP -> SkikoPointerEventKind.UP
MotionEvent.ACTION_MOVE -> SkikoPointerEventKind.MOVE
else -> SkikoPointerEventKind.UNKNOWN
}
}
private fun toSkikoGestureEventKind(event: MotionEvent): SkikoGestureEventKind {
return when (event.action) {
MotionEvent.ACTION_UP -> SkikoGestureEventKind.TAP
MotionEvent.ACTION_MOVE -> SkikoGestureEventKind.PAN
else -> SkikoGestureEventKind.UNKNOWN
}
}
\ No newline at end of file
...@@ -43,32 +43,6 @@ class SkikoSurfaceView(context: Context, val layer: SkiaLayer) : GLSurfaceView(c ...@@ -43,32 +43,6 @@ class SkikoSurfaceView(context: Context, val layer: SkiaLayer) : GLSurfaceView(c
fun scheduleFrame() { fun scheduleFrame() {
frameDispatcher.scheduleFrame() frameDispatcher.scheduleFrame()
} }
internal val gesturesDetector = SkikoGesturesDetector(context, layer)
override fun onTouchEvent(event: MotionEvent): Boolean {
layer.skikoView?.onPointerEvent(toSkikoPointerEvent(event, layer.contentScale))
return true
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
layer.skikoView?.onKeyboardEvent(
toSkikoKeyboardEvent(event, keyCode, SkikoKeyboardEventKind.DOWN)
)
if (event.unicodeChar != 0) {
layer.skikoView?.onInputEvent(
toSkikoTypeEvent(event, keyCode)
)
}
return true
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
layer.skikoView?.onKeyboardEvent(
toSkikoKeyboardEvent(event, keyCode, SkikoKeyboardEventKind.UP)
)
return true
}
} }
private class SkikoSurfaceRender(private val layer: SkiaLayer, private val manager: FrameManager) : GLSurfaceView.Renderer { private class SkikoSurfaceRender(private val layer: SkiaLayer, private val manager: FrameManager) : GLSurfaceView.Renderer {
...@@ -93,7 +67,7 @@ private class SkikoSurfaceRender(private val layer: SkiaLayer, private val manag ...@@ -93,7 +67,7 @@ private class SkikoSurfaceRender(private val layer: SkiaLayer, private val manag
// This method is called from the main thread. // This method is called from the main thread.
fun update() { fun update() {
layer.skikoView?.let { layer.renderDelegate?.let {
val bounds = Rect.makeWH(width.toFloat(), width.toFloat()) val bounds = Rect.makeWH(width.toFloat(), width.toFloat())
val canvas = pictureRecorder.beginRecording(bounds) val canvas = pictureRecorder.beginRecording(bounds)
try { try {
......
...@@ -2,34 +2,10 @@ package org.jetbrains.skiko ...@@ -2,34 +2,10 @@ package org.jetbrains.skiko
import android.content.Context import android.content.Context
import android.view.* import android.view.*
import android.view.inputmethod.InputMethodManager
import org.jetbrains.skia.Canvas import org.jetbrains.skia.Canvas
import org.jetbrains.skia.PixelGeometry import org.jetbrains.skia.PixelGeometry
actual typealias SkikoGesturePlatformEvent = MotionEvent
actual typealias SkikoPlatformPointerEvent = MotionEvent
actual typealias SkikoPlatformInputEvent = KeyEvent
actual typealias SkikoPlatformKeyboardEvent = KeyEvent
actual open class SkiaLayer { actual open class SkiaLayer {
var gesturesToListen: Array<SkikoGestureEventKind>? = null
set(value) {
field = value
initGestures()
}
private fun initGestures() {
glView?.let { view ->
view.gesturesDetector.setGesturesToListen(gesturesToListen)
if (gesturesToListen != null) {
view.setOnTouchListener { _, event ->
view.gesturesDetector.onTouchEvent(event)
}
}
}
}
private var glView: SkikoSurfaceView? = null private var glView: SkikoSurfaceView? = null
private var container: ViewGroup? = null private var container: ViewGroup? = null
...@@ -49,7 +25,7 @@ actual open class SkiaLayer { ...@@ -49,7 +25,7 @@ actual open class SkiaLayer {
if (value) throw IllegalArgumentException("transparency unsupported") if (value) throw IllegalArgumentException("transparency unsupported")
} }
actual var skikoView: SkikoView? = null actual var renderDelegate: SkikoRenderDelegate? = null
actual fun attachTo(container: Any) { actual fun attachTo(container: Any) {
when (container) { when (container) {
...@@ -60,27 +36,6 @@ actual open class SkiaLayer { ...@@ -60,27 +36,6 @@ actual open class SkiaLayer {
} }
} }
private var _isKeyboardVisible = false
fun showScreenKeyboard() {
if (glView != null) {
val imm = glView!!.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
_isKeyboardVisible = true
}
}
fun hideScreenKeyboard() {
if (glView != null) {
val imm = glView!!.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(glView!!.windowToken, 0)
_isKeyboardVisible = false
}
}
fun isScreenKeyboardOpen() = _isKeyboardVisible
fun attachTo(container: ViewGroup) { fun attachTo(container: ViewGroup) {
initDefaultContext(container.context) initDefaultContext(container.context)
...@@ -89,7 +44,6 @@ actual open class SkiaLayer { ...@@ -89,7 +44,6 @@ actual open class SkiaLayer {
this.container = container this.container = container
this.glView = view this.glView = view
initGestures()
view.setFocusableInTouchMode(true) view.setFocusableInTouchMode(true)
......
package org.jetbrains.skiko
import android.content.Context
import android.view.MotionEvent
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.ScaleGestureDetector
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener
import org.jetbrains.skia.toRadians
internal class SkikoGesturesDetector(
private val context: Context,
private val layer: SkiaLayer
) {
private var gesturesToListen: Array<SkikoGestureEventKind>? = null
fun setGesturesToListen(gestures: Array<SkikoGestureEventKind>?) {
gesturesToListen = gestures
}
private fun containsGesture(
gesture: SkikoGestureEventKind
): Boolean {
gesturesToListen?.let { list ->
return list.contains(gesture)
}
return false
}
fun onTouchEvent(event: MotionEvent): Boolean {
if (containsGesture(SkikoGestureEventKind.PINCH)) {
scaleGesture.onTouchEvent(event)
}
if (containsGesture(SkikoGestureEventKind.ROTATION)) {
rotationGesture.onTouchEvent(event)
}
return simpleGestures.onTouchEvent(event)
}
private val simpleGestures = GestureDetector(context, object: WorkaroundSimpleOnGestureListener() {
override fun onSingleTapUp(event: MotionEvent): Boolean {
if (!containsGesture(SkikoGestureEventKind.TAP)) return false
val density = layer.contentScale
layer.skikoView?.onGestureEvent(
SkikoGestureEvent(
x = (event.x / density).toDouble(),
y = (event.y / density).toDouble(),
kind = SkikoGestureEventKind.TAP,
platform = event
)
)
return true
}
override fun onDoubleTap(event: MotionEvent): Boolean {
if (!containsGesture(SkikoGestureEventKind.DOUBLETAP)) return false
val density = layer.contentScale
layer.skikoView?.onGestureEvent(
SkikoGestureEvent(
x = (event.x / density).toDouble(),
y = (event.y / density).toDouble(),
kind = SkikoGestureEventKind.DOUBLETAP,
platform = event
)
)
return true
}
override fun onLongPress(event: MotionEvent) {
if (!containsGesture(SkikoGestureEventKind.LONGPRESS)) return
val density = layer.contentScale
layer.skikoView?.onGestureEvent(
SkikoGestureEvent(
x = (event.x / density).toDouble(),
y = (event.y / density).toDouble(),
kind = SkikoGestureEventKind.LONGPRESS,
platform = event
)
)
}
override fun onScrollWorkaround(
event1: MotionEvent?,
event2: MotionEvent,
distanceX: Float,
distanceY: Float,
): Boolean {
if (!containsGesture(SkikoGestureEventKind.PAN)) return false
val density = layer.contentScale
layer.skikoView?.onGestureEvent(
SkikoGestureEvent(
x = (event2.x / density).toDouble(),
y = (event2.y / density).toDouble(),
kind = SkikoGestureEventKind.PAN,
platform = event2
)
)
return true
}
override fun onFlingWorkaround(
event1: MotionEvent?,
event2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
if (!containsGesture(SkikoGestureEventKind.SWIPE)) return false
var direction = toSkikoGestureDirection(event1, event2, velocityX, velocityY)
if (direction == SkikoGestureEventDirection.UNKNOWN) {
return false
}
val density = layer.contentScale
layer.skikoView?.onGestureEvent(
SkikoGestureEvent(
x = (event2.x / density).toDouble(),
y = (event2.y / density).toDouble(),
direction = direction,
kind = SkikoGestureEventKind.SWIPE,
platform = event2
)
)
return true
}
})
private val scaleGesture = SkikoScaleGestureDetector(context, SkikoScaleGestureListener(layer))
private class SkikoScaleGestureDetector(
context: Context,
private val scaleListener: SkikoScaleGestureListener
) : ScaleGestureDetector(context, scaleListener) {
override fun onTouchEvent(event: MotionEvent): Boolean {
scaleListener.event = event
return super.onTouchEvent(event)
}
}
private class SkikoScaleGestureListener(val layer: SkiaLayer) : SimpleOnScaleGestureListener() {
var event: MotionEvent? = null
var scale: Double = 1.0
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
if (event != null) {
scale *= detector.scaleFactor.toDouble()
layer.skikoView?.onGestureEvent(
toSkikoScaleGestureEvent(
event = event!!,
scale = scale,
state = SkikoGestureEventState.STARTED,
layer.contentScale
)
)
return true
}
return false
}
override fun onScale(detector: ScaleGestureDetector): Boolean {
if (event != null) {
scale *= detector.scaleFactor.toDouble()
layer.skikoView?.onGestureEvent(
toSkikoScaleGestureEvent(
event = event!!,
scale = scale,
state = SkikoGestureEventState.CHANGED,
layer.contentScale
)
)
return true
}
return false
}
override fun onScaleEnd(detector: ScaleGestureDetector) {
if (event != null) {
scale *= detector.scaleFactor.toDouble()
layer.skikoView?.onGestureEvent(
toSkikoScaleGestureEvent(
event = event!!,
scale = scale,
state = SkikoGestureEventState.ENDED,
layer.contentScale
)
)
}
scale = 1.0
}
}
private val rotationGesture = SkikoRotationGestureDetector(SkikoRotationGestureListener(layer))
private class SkikoRotationGestureListener(val layer: SkiaLayer) {
var event: MotionEvent? = null
fun onRotation(detector: SkikoRotationGestureDetector) {
if (event != null) {
val density = layer.contentScale
layer.skikoView?.onGestureEvent(
SkikoGestureEvent(
x = (event!!.x / density).toDouble(),
y = (event!!.y / density).toDouble(),
rotation = detector.angle,
kind = SkikoGestureEventKind.ROTATION,
state = SkikoGestureEventState.CHANGED,
platform = event
)
)
}
}
}
private class SkikoRotationGestureDetector(
private val rotationListener: SkikoRotationGestureListener
) {
private var fX = 0f
private var fY = 0f
private var sX = 0f
private var sY = 0f
private var ptrID1: Int
private var ptrID2: Int
var angle = 0.0
private set
companion object {
private const val INVALID_POINTER_ID = -1
}
init {
ptrID1 = INVALID_POINTER_ID
ptrID2 = INVALID_POINTER_ID
}
fun onTouchEvent(event: MotionEvent): Boolean {
rotationListener.event = event
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> ptrID1 = event.getPointerId(event.actionIndex)
MotionEvent.ACTION_POINTER_DOWN -> {
ptrID2 = event.getPointerId(event.actionIndex)
sX = event.getX(event.findPointerIndex(ptrID1))
sY = event.getY(event.findPointerIndex(ptrID1))
fX = event.getX(event.findPointerIndex(ptrID2))
fY = event.getY(event.findPointerIndex(ptrID2))
}
MotionEvent.ACTION_MOVE -> if (ptrID1 != INVALID_POINTER_ID && ptrID2 != INVALID_POINTER_ID) {
val nfX: Float
val nfY: Float
val nsX: Float
val nsY: Float
nsX = event.getX(event.findPointerIndex(ptrID1))
nsY = event.getY(event.findPointerIndex(ptrID1))
nfX = event.getX(event.findPointerIndex(ptrID2))
nfY = event.getY(event.findPointerIndex(ptrID2))
angle = angleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY)
rotationListener.onRotation(this)
}
MotionEvent.ACTION_UP -> ptrID1 = INVALID_POINTER_ID
MotionEvent.ACTION_POINTER_UP -> ptrID2 = INVALID_POINTER_ID
MotionEvent.ACTION_CANCEL -> {
ptrID1 = INVALID_POINTER_ID
ptrID2 = INVALID_POINTER_ID
}
}
return true
}
private fun angleBetweenLines(
fX: Float,
fY: Float,
sX: Float,
sY: Float,
nfX: Float,
nfY: Float,
nsX: Float,
nsY: Float
): Double {
val angle1 = Math.atan2((fY - sY).toDouble(), (fX - sX).toDouble())
val angle2 = Math.atan2((nfY - nsY).toDouble(), (nfX - nsX).toDouble())
return (Math.toDegrees(angle2 - angle1) % 360).toFloat().toRadians()
}
}
}
typealias MotionEventNullableOrNo = MotionEvent?
\ No newline at end of file
package org.jetbrains.skiko
actual interface SkikoInput {
actual object Empty : SkikoInput
}
package org.jetbrains.skiko
import android.view.KeyEvent.*
// See https://developer.android.com/reference/android/view/KeyEvent
actual enum class SkikoKey(actual val platformKeyCode: Int) {
KEY_UNKNOWN(KEYCODE_UNKNOWN),
KEY_A(KEYCODE_A),
KEY_S(KEYCODE_S),
KEY_D(KEYCODE_D),
KEY_F(KEYCODE_F),
KEY_H(KEYCODE_H),
KEY_G(KEYCODE_G),
KEY_Z(KEYCODE_Z),
KEY_X(KEYCODE_X),
KEY_C(KEYCODE_C),
KEY_V(KEYCODE_V),
KEY_B(KEYCODE_B),
KEY_Q(KEYCODE_Q),
KEY_W(KEYCODE_W),
KEY_E(KEYCODE_E),
KEY_R(KEYCODE_R),
KEY_Y(KEYCODE_Y),
KEY_T(KEYCODE_T),
KEY_U(KEYCODE_U),
KEY_I(KEYCODE_I),
KEY_P(KEYCODE_P),
KEY_L(KEYCODE_L),
KEY_J(KEYCODE_J),
KEY_K(KEYCODE_K),
KEY_N(KEYCODE_N),
KEY_M(KEYCODE_M),
KEY_O(KEYCODE_O),
KEY_1(KEYCODE_1),
KEY_2(KEYCODE_2),
KEY_3(KEYCODE_3),
KEY_4(KEYCODE_4),
KEY_5(KEYCODE_5),
KEY_6(KEYCODE_6),
KEY_7(KEYCODE_7),
KEY_8(KEYCODE_8),
KEY_9(KEYCODE_9),
KEY_0(KEYCODE_0),
KEY_CLOSE_BRACKET(KEYCODE_RIGHT_BRACKET),
KEY_OPEN_BRACKET(KEYCODE_LEFT_BRACKET),
KEY_QUOTE(KEYCODE_UNKNOWN),
KEY_SEMICOLON(KEYCODE_SEMICOLON),
KEY_SLASH(KEYCODE_SLASH),
KEY_COMMA(KEYCODE_COMMA),
KEY_BACKSLASH(KEYCODE_BACKSLASH),
KEY_PERIOD(KEYCODE_PERIOD),
KEY_BACK_QUOTE(KEYCODE_UNKNOWN),
KEY_EQUALS(KEYCODE_EQUALS),
KEY_MINUS(KEYCODE_MINUS),
KEY_ENTER(KEYCODE_ENTER),
KEY_ESCAPE(KEYCODE_ESCAPE),
KEY_TAB(KEYCODE_TAB),
KEY_BACKSPACE(KEYCODE_DEL),
KEY_SPACE(KEYCODE_SPACE),
KEY_CAPSLOCK(KEYCODE_CAPS_LOCK),
KEY_LEFT_META(KEYCODE_META_LEFT),
KEY_LEFT_SHIFT(KEYCODE_SHIFT_LEFT),
KEY_LEFT_ALT(KEYCODE_ALT_LEFT),
KEY_LEFT_CONTROL(KEYCODE_CTRL_LEFT),
KEY_RIGHT_META(KEYCODE_META_RIGHT),
KEY_RIGHT_SHIFT(KEYCODE_SHIFT_RIGHT),
KEY_RIGHT_ALT(KEYCODE_ALT_RIGHT),
KEY_RIGHT_CONTROL(KEYCODE_CTRL_RIGHT),
KEY_MENU(KEYCODE_UNKNOWN),
KEY_UP(KEYCODE_DPAD_UP),
KEY_DOWN(KEYCODE_DPAD_DOWN),
KEY_LEFT(KEYCODE_DPAD_LEFT),
KEY_RIGHT(KEYCODE_DPAD_RIGHT),
KEY_F1(KEYCODE_F1),
KEY_F2(KEYCODE_F2),
KEY_F3(KEYCODE_F3),
KEY_F4(KEYCODE_F4),
KEY_F5(KEYCODE_F5),
KEY_F6(KEYCODE_F6),
KEY_F7(KEYCODE_F7),
KEY_F8(KEYCODE_F8),
KEY_F9(KEYCODE_F9),
KEY_F10(KEYCODE_F10),
KEY_F11(KEYCODE_F11),
KEY_F12(KEYCODE_F12),
KEY_PRINTSCEEN(KEYCODE_UNKNOWN),
KEY_SCROLL_LOCK(KEYCODE_UNKNOWN),
KEY_PAUSE(KEYCODE_UNKNOWN),
KEY_INSERT(KEYCODE_INSERT),
KEY_HOME(KEYCODE_HOME),
KEY_PGUP(KEYCODE_PAGE_UP),
KEY_DELETE(KEYCODE_FORWARD_DEL),
KEY_END(KEYCODE_MOVE_END),
KEY_PGDOWN(KEYCODE_PAGE_DOWN),
KEY_NUM_LOCK(KEYCODE_UNKNOWN),
KEY_NUMPAD_0(KEYCODE_NUMPAD_0),
KEY_NUMPAD_1(KEYCODE_NUMPAD_1),
KEY_NUMPAD_2(KEYCODE_NUMPAD_2),
KEY_NUMPAD_3(KEYCODE_NUMPAD_3),
KEY_NUMPAD_4(KEYCODE_NUMPAD_4),
KEY_NUMPAD_5(KEYCODE_NUMPAD_5),
KEY_NUMPAD_6(KEYCODE_NUMPAD_6),
KEY_NUMPAD_7(KEYCODE_NUMPAD_7),
KEY_NUMPAD_8(KEYCODE_NUMPAD_8),
KEY_NUMPAD_9(KEYCODE_NUMPAD_9),
KEY_NUMPAD_ENTER(KEYCODE_NUMPAD_ENTER),
KEY_NUMPAD_ADD(KEYCODE_NUMPAD_ADD),
KEY_NUMPAD_SUBTRACT(KEYCODE_NUMPAD_SUBTRACT),
KEY_NUMPAD_MULTIPLY(KEYCODE_NUMPAD_MULTIPLY),
KEY_NUMPAD_DIVIDE(KEYCODE_NUMPAD_DIVIDE),
KEY_NUMPAD_DECIMAL(KEYCODE_UNKNOWN);
companion object {
fun valueOf(platformKeyCode: Int): SkikoKey {
val key = SkikoKey.values().firstOrNull { it.platformKeyCode == platformKeyCode }
return key ?: SkikoKey.KEY_UNKNOWN
}
}
}
\ No newline at end of file
...@@ -78,173 +78,3 @@ fun BufferedImage.toBitmap(): Bitmap { ...@@ -78,173 +78,3 @@ fun BufferedImage.toBitmap(): Bitmap {
fun BufferedImage.toImage(): Image { fun BufferedImage.toImage(): Image {
return Image.makeFromBitmap(toBitmap()) return Image.makeFromBitmap(toBitmap())
} }
private val MouseEventButton4 get() = 4
private val MouseEventButton5 get() = 5
fun toSkikoEvent(event: MouseEvent): SkikoPointerEvent {
return SkikoPointerEvent(
x = event.x.toDouble(),
y = event.y.toDouble(),
pressedButtons = toSkikoPressedMouseButtons(event),
button = toSkikoMouseButton(event),
modifiers = toSkikoModifiers(event.modifiersEx),
kind = when (event.id) {
MouseEvent.MOUSE_PRESSED -> SkikoPointerEventKind.DOWN
MouseEvent.MOUSE_RELEASED -> SkikoPointerEventKind.UP
MouseEvent.MOUSE_DRAGGED -> SkikoPointerEventKind.DRAG
MouseEvent.MOUSE_MOVED -> SkikoPointerEventKind.MOVE
MouseEvent.MOUSE_ENTERED -> SkikoPointerEventKind.ENTER
MouseEvent.MOUSE_EXITED -> SkikoPointerEventKind.EXIT
else -> SkikoPointerEventKind.UNKNOWN
},
timestamp = event.`when`,
platform = event
)
}
fun toSkikoEvent(event: MouseWheelEvent): SkikoPointerEvent {
val scrollAmount = event.preciseWheelRotation
val modifiers = toSkikoModifiers(event.modifiersEx)
val isShiftPressed = modifiers.has(SkikoInputModifiers.SHIFT)
val deltaX = if (isShiftPressed) scrollAmount else 0.0
val deltaY = if (isShiftPressed) 0.0 else scrollAmount
return SkikoPointerEvent(
x = event.x.toDouble(),
y = event.y.toDouble(),
deltaX = deltaX,
deltaY = deltaY,
pressedButtons = toSkikoPressedMouseButtons(event),
button = toSkikoMouseButton(event),
modifiers = modifiers,
kind = when (event.id) {
MouseEvent.MOUSE_WHEEL -> SkikoPointerEventKind.SCROLL
else -> SkikoPointerEventKind.UNKNOWN
},
timestamp = event.`when`,
platform = event
)
}
fun toSkikoEvent(event: KeyEvent): SkikoKeyboardEvent {
return SkikoKeyboardEvent(
SkikoKey.valueOf(toSkikoKey(event)),
toSkikoModifiers(event.modifiersEx),
when (event.id) {
KEY_PRESSED -> SkikoKeyboardEventKind.DOWN
KEY_RELEASED -> SkikoKeyboardEventKind.UP
else -> SkikoKeyboardEventKind.UNKNOWN
},
event.`when`,
event
)
}
fun toSkikoTypeEvent(typeEvent: KeyEvent, keyEvent: KeyEvent?): SkikoInputEvent {
var key: Int = -1
var modifiers = SkikoInputModifiers.EMPTY
if (keyEvent != null) {
key = toSkikoKey(keyEvent)
modifiers = toSkikoModifiers(keyEvent.modifiersEx)
}
return SkikoInputEvent(
typeEvent.keyChar.toString(),
SkikoKey.valueOf(key),
modifiers,
SkikoKeyboardEventKind.TYPE,
typeEvent
)
}
fun toSkikoTypeEvent(event: InputMethodEvent, keyEvent: KeyEvent?): SkikoInputEvent {
var key: Int = -1
var modifiers = SkikoInputModifiers.EMPTY
if (keyEvent != null) {
key = toSkikoKey(keyEvent)
modifiers = toSkikoModifiers(keyEvent.modifiersEx)
}
return SkikoInputEvent(
"",
SkikoKey.valueOf(key),
modifiers,
SkikoKeyboardEventKind.TYPE,
event
)
}
private fun toSkikoPressedMouseButtons(event: MouseEvent): SkikoMouseButtons {
val mask = event.modifiersEx
var result = 0
// We should check [event.button] because of case where [event.modifiersEx] does not provide
// info about the pressed mouse button when using touchpad on MacOS 12 (AWT only)
// see: https://youtrack.jetbrains.com/issue/COMPOSE-36
if (mask and InputEvent.BUTTON1_DOWN_MASK != 0
|| (event.id == MouseEvent.MOUSE_PRESSED && event.button == MouseEvent.BUTTON1)) {
result = result.or(SkikoMouseButtons.LEFT.value)
}
if (mask and InputEvent.BUTTON2_DOWN_MASK != 0
|| (event.id == MouseEvent.MOUSE_PRESSED && event.button == MouseEvent.BUTTON2)) {
result = result.or(SkikoMouseButtons.MIDDLE.value)
}
if (mask and InputEvent.BUTTON3_DOWN_MASK != 0
|| (event.id == MouseEvent.MOUSE_PRESSED && event.button == MouseEvent.BUTTON3)) {
result = result.or(SkikoMouseButtons.RIGHT.value)
}
if (mask and MouseEvent.getMaskForButton(MouseEventButton4) != 0
|| (event.id == MouseEvent.MOUSE_PRESSED && event.button == MouseEventButton4)) {
result = result.or(SkikoMouseButtons.BUTTON_4.value)
}
if (mask and MouseEvent.getMaskForButton(MouseEventButton5) != 0
|| (event.id == MouseEvent.MOUSE_PRESSED && event.button == MouseEventButton5)) {
result = result.or(SkikoMouseButtons.BUTTON_5.value)
}
return SkikoMouseButtons(result)
}
private fun toSkikoMouseButton(event: MouseEvent): SkikoMouseButtons {
return when (event.button) {
MouseEvent.BUTTON1 -> SkikoMouseButtons.LEFT
MouseEvent.BUTTON2 -> SkikoMouseButtons.MIDDLE
MouseEvent.BUTTON3 -> SkikoMouseButtons.RIGHT
MouseEventButton4 -> SkikoMouseButtons.BUTTON_4
MouseEventButton5 -> SkikoMouseButtons.BUTTON_5
else -> SkikoMouseButtons(event.button)
}
}
private fun toSkikoModifiers(modifiers: Int): SkikoInputModifiers {
var result = 0
if (modifiers and InputEvent.ALT_DOWN_MASK != 0) {
result = SkikoInputModifiers.ALT.value
}
if (modifiers and InputEvent.SHIFT_DOWN_MASK != 0) {
result = result.or(SkikoInputModifiers.SHIFT.value)
}
if (modifiers and InputEvent.CTRL_DOWN_MASK != 0) {
result = result.or(SkikoInputModifiers.CONTROL.value)
}
if (modifiers and InputEvent.META_DOWN_MASK != 0) {
result = result.or(SkikoInputModifiers.META.value)
}
return SkikoInputModifiers(result)
}
private fun toSkikoKey(event: KeyEvent): Int {
var key = event.keyCode
val side = event.keyLocation
if (side == 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())
}
}
if (side == KEY_LOCATION_NUMPAD) {
if (key == SkikoKey.KEY_ENTER.platformKeyCode) {
key = key.or(0x80000000.toInt())
}
}
return key
}
...@@ -219,7 +219,7 @@ actual open class SkiaLayer internal constructor( ...@@ -219,7 +219,7 @@ actual open class SkiaLayer internal constructor(
actual val component: Any? actual val component: Any?
get() = backedLayer get() = backedLayer
actual var skikoView: SkikoView? = null actual var renderDelegate: SkikoRenderDelegate? = null
actual fun attachTo(container: Any) { actual fun attachTo(container: Any) {
attachTo(container as JComponent) attachTo(container as JComponent)
...@@ -231,69 +231,6 @@ actual open class SkiaLayer internal constructor( ...@@ -231,69 +231,6 @@ actual open class SkiaLayer internal constructor(
private var keyEvent: KeyEvent? = null private var keyEvent: KeyEvent? = null
fun addView(view: SkikoView) {
skikoView = view
addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent) {
skikoView?.onPointerEvent(toSkikoEvent(e))
}
override fun mouseReleased(e: MouseEvent) {
skikoView?.onPointerEvent(toSkikoEvent(e))
}
override fun mouseEntered(e: MouseEvent) {
skikoView?.onPointerEvent(toSkikoEvent(e))
}
override fun mouseExited(e: MouseEvent) {
skikoView?.onPointerEvent(toSkikoEvent(e))
}
})
addMouseMotionListener(object : MouseMotionAdapter() {
override fun mouseDragged(e: MouseEvent) {
skikoView?.onPointerEvent(toSkikoEvent(e))
}
override fun mouseMoved(e: MouseEvent) {
skikoView?.onPointerEvent(toSkikoEvent(e))
}
})
addMouseWheelListener(object : MouseWheelListener {
override fun mouseWheelMoved(e: MouseWheelEvent) {
skikoView?.onPointerEvent(toSkikoEvent(e))
}
})
addKeyListener(object : KeyAdapter() {
override fun keyPressed(e: KeyEvent) {
keyEvent = e
skikoView?.onKeyboardEvent(toSkikoEvent(e))
}
override fun keyReleased(e: KeyEvent) {
keyEvent = e
skikoView?.onKeyboardEvent(toSkikoEvent(e))
}
override fun keyTyped(e: KeyEvent) {
skikoView?.onInputEvent(toSkikoTypeEvent(e, keyEvent))
}
})
addInputMethodListener(object : InputMethodListener {
override fun caretPositionChanged(e: InputMethodEvent) {
skikoView?.onInputEvent(toSkikoTypeEvent(e, keyEvent))
}
override fun inputMethodTextChanged(e: InputMethodEvent) {
skikoView?.onInputEvent(toSkikoTypeEvent(e, keyEvent))
}
})
}
val clipComponents = mutableListOf<ClipRectangle>() val clipComponents = mutableListOf<ClipRectangle>()
@Volatile @Volatile
...@@ -545,7 +482,7 @@ actual open class SkiaLayer internal constructor( ...@@ -545,7 +482,7 @@ actual open class SkiaLayer internal constructor(
try { try {
isRendering = true isRendering = true
skikoView?.onRender(canvas, pictureWidth, pictureHeight, nanoTime) renderDelegate?.onRender(canvas, pictureWidth, pictureHeight, nanoTime)
} finally { } finally {
isRendering = false isRendering = false
} }
...@@ -670,9 +607,3 @@ internal fun Canvas.clipRectBy(rectangle: ClipRectangle, scale: Float) { ...@@ -670,9 +607,3 @@ internal fun Canvas.clipRectBy(rectangle: ClipRectangle, scale: Float) {
true true
) )
} }
// InputEvent is abstract, so we wrap to match modality.
actual typealias SkikoGesturePlatformEvent = Any
actual typealias SkikoPlatformInputEvent = Any
actual typealias SkikoPlatformKeyboardEvent = KeyEvent
actual typealias SkikoPlatformPointerEvent = MouseEvent
package org.jetbrains.skiko
actual interface SkikoInput {
actual object Empty : SkikoInput
}
package org.jetbrains.skiko
import java.awt.event.KeyEvent.*
actual enum class SkikoKey(actual val platformKeyCode: Int) {
KEY_UNKNOWN(-1),
KEY_A(VK_A),
KEY_S(VK_S),
KEY_D(VK_D),
KEY_F(VK_F),
KEY_H(VK_H),
KEY_G(VK_G),
KEY_Z(VK_Z),
KEY_X(VK_X),
KEY_C(VK_C),
KEY_V(VK_V),
KEY_B(VK_B),
KEY_Q(VK_Q),
KEY_W(VK_W),
KEY_E(VK_E),
KEY_R(VK_R),
KEY_Y(VK_Y),
KEY_T(VK_T),
KEY_U(VK_U),
KEY_I(VK_I),
KEY_P(VK_P),
KEY_L(VK_L),
KEY_J(VK_J),
KEY_K(VK_K),
KEY_N(VK_N),
KEY_M(VK_M),
KEY_O(VK_O),
KEY_1(VK_1),
KEY_2(VK_2),
KEY_3(VK_3),
KEY_4(VK_4),
KEY_5(VK_5),
KEY_6(VK_6),
KEY_7(VK_7),
KEY_8(VK_8),
KEY_9(VK_9),
KEY_0(VK_0),
KEY_CLOSE_BRACKET(VK_CLOSE_BRACKET),
KEY_OPEN_BRACKET(VK_OPEN_BRACKET),
KEY_QUOTE(VK_QUOTE),
KEY_SEMICOLON(VK_SEMICOLON),
KEY_SLASH(VK_SLASH),
KEY_COMMA(VK_COMMA),
KEY_BACKSLASH(VK_BACK_SLASH),
KEY_PERIOD(VK_PERIOD),
KEY_BACK_QUOTE(VK_BACK_QUOTE),
KEY_EQUALS(VK_EQUALS),
KEY_MINUS(VK_MINUS),
KEY_ENTER(VK_ENTER),
KEY_ESCAPE(VK_ESCAPE),
KEY_TAB(VK_TAB),
KEY_BACKSPACE(VK_BACK_SPACE),
KEY_SPACE(VK_SPACE),
KEY_CAPSLOCK(VK_CAPS_LOCK),
KEY_LEFT_META(VK_META),
KEY_LEFT_SHIFT(VK_SHIFT),
KEY_LEFT_ALT(VK_ALT),
KEY_LEFT_CONTROL(VK_CONTROL),
KEY_RIGHT_META(0x80000000.toInt() or VK_META),
KEY_RIGHT_SHIFT(0x80000000.toInt() or VK_SHIFT),
KEY_RIGHT_ALT(VK_ALT_GRAPH),
KEY_RIGHT_CONTROL(0x80000000.toInt() or VK_CONTROL),
KEY_MENU(VK_CONTEXT_MENU),
KEY_UP(VK_UP),
KEY_DOWN(VK_DOWN),
KEY_LEFT(VK_LEFT),
KEY_RIGHT(VK_RIGHT),
KEY_F1(VK_F1),
KEY_F2(VK_F2),
KEY_F3(VK_F3),
KEY_F4(VK_F4),
KEY_F5(VK_F5),
KEY_F6(VK_F6),
KEY_F7(VK_F7),
KEY_F8(VK_F8),
KEY_F9(VK_F9),
KEY_F10(VK_F10),
KEY_F11(VK_F11),
KEY_F12(VK_F12),
KEY_PRINTSCEEN(VK_PRINTSCREEN),
KEY_SCROLL_LOCK(VK_SCROLL_LOCK),
KEY_PAUSE(VK_PAUSE),
KEY_INSERT(VK_INSERT),
KEY_HOME(VK_HOME),
KEY_PGUP(VK_PAGE_UP),
KEY_DELETE(VK_DELETE),
KEY_END(VK_END),
KEY_PGDOWN(VK_PAGE_DOWN),
KEY_NUM_LOCK(VK_NUM_LOCK),
KEY_NUMPAD_0(VK_NUMPAD0),
KEY_NUMPAD_1(VK_NUMPAD1),
KEY_NUMPAD_2(VK_NUMPAD2),
KEY_NUMPAD_3(VK_NUMPAD3),
KEY_NUMPAD_4(VK_NUMPAD4),
KEY_NUMPAD_5(VK_NUMPAD5),
KEY_NUMPAD_6(VK_NUMPAD6),
KEY_NUMPAD_7(VK_NUMPAD7),
KEY_NUMPAD_8(VK_NUMPAD8),
KEY_NUMPAD_9(VK_NUMPAD9),
KEY_NUMPAD_ENTER(0x80000000.toInt() or VK_ENTER),
KEY_NUMPAD_ADD(VK_ADD),
KEY_NUMPAD_SUBTRACT(VK_SUBTRACT),
KEY_NUMPAD_MULTIPLY(VK_MULTIPLY),
KEY_NUMPAD_DIVIDE(VK_DIVIDE),
KEY_NUMPAD_DECIMAL(VK_DECIMAL);
companion object {
fun valueOf(platformKeyCode: Int): SkikoKey {
val key = SkikoKey.values().firstOrNull { it.platformKeyCode == platformKeyCode }
return if (key == null) SkikoKey.KEY_UNKNOWN else key
}
}
}
...@@ -6,7 +6,7 @@ import java.awt.Graphics2D ...@@ -6,7 +6,7 @@ import java.awt.Graphics2D
internal class Direct3DSwingRedrawer( internal class Direct3DSwingRedrawer(
swingLayerProperties: SwingLayerProperties, swingLayerProperties: SwingLayerProperties,
private val skikoView: SkikoView, private val renderDelegate: SkikoRenderDelegate,
analytics: SkiaLayerAnalytics analytics: SkiaLayerAnalytics
) : SwingRedrawerBase(swingLayerProperties, analytics, GraphicsApi.DIRECT3D) { ) : SwingRedrawerBase(swingLayerProperties, analytics, GraphicsApi.DIRECT3D) {
companion object { companion object {
...@@ -49,7 +49,7 @@ internal class Direct3DSwingRedrawer( ...@@ -49,7 +49,7 @@ internal class Direct3DSwingRedrawer(
// Calculate aligned width that is needed for performance optimization, // Calculate aligned width that is needed for performance optimization,
// since DirectX uses aligned bytebuffer. // since DirectX uses aligned bytebuffer.
// So we will have [Surface] with width == [alignedWidth], // So we will have [Surface] with width == [alignedWidth],
// but imitate (for SkikoView and Swing) like it has width == [width]. // but imitate (for SkikoRenderDelegate and Swing) like it has width == [width].
val alignedWidth = if (width % widthSizeAlignment != 0) { val alignedWidth = if (width % widthSizeAlignment != 0) {
width + widthSizeAlignment - (width % widthSizeAlignment); width + widthSizeAlignment - (width % widthSizeAlignment);
} else { } else {
...@@ -73,7 +73,7 @@ internal class Direct3DSwingRedrawer( ...@@ -73,7 +73,7 @@ internal class Direct3DSwingRedrawer(
val canvas = surface.canvas val canvas = surface.canvas
canvas.clear(Color.TRANSPARENT) canvas.clear(Color.TRANSPARENT)
skikoView.onRender(canvas, width, height, nanoTime) renderDelegate.onRender(canvas, width, height, nanoTime)
flush(surface, g) flush(surface, g)
} }
} }
......
...@@ -6,7 +6,7 @@ import java.awt.Graphics2D ...@@ -6,7 +6,7 @@ import java.awt.Graphics2D
internal class LinuxOpenGLSwingRedrawer( internal class LinuxOpenGLSwingRedrawer(
swingLayerProperties: SwingLayerProperties, swingLayerProperties: SwingLayerProperties,
private val skikoView: SkikoView, private val renderDelegate: SkikoRenderDelegate,
analytics: SkiaLayerAnalytics analytics: SkiaLayerAnalytics
) : SwingRedrawerBase(swingLayerProperties, analytics, GraphicsApi.OPENGL) { ) : SwingRedrawerBase(swingLayerProperties, analytics, GraphicsApi.OPENGL) {
init { init {
...@@ -76,7 +76,7 @@ internal class LinuxOpenGLSwingRedrawer( ...@@ -76,7 +76,7 @@ internal class LinuxOpenGLSwingRedrawer(
val canvas = surface.canvas val canvas = surface.canvas
canvas.clear(Color.TRANSPARENT) canvas.clear(Color.TRANSPARENT)
skikoView.onRender(canvas, width, height, nanoTime) renderDelegate.onRender(canvas, width, height, nanoTime)
flush(surface, g) flush(surface, g)
unbindAndDisposeTexture(texturePtr) unbindAndDisposeTexture(texturePtr)
} }
......
...@@ -10,7 +10,7 @@ import java.awt.Graphics2D ...@@ -10,7 +10,7 @@ import java.awt.Graphics2D
* *
* For now, it uses drawing to [java.awt.image.BufferedImage] that cause VRAM <-> RAM memory transfer and so increased CPU usage. * For now, it uses drawing to [java.awt.image.BufferedImage] that cause VRAM <-> RAM memory transfer and so increased CPU usage.
* *
* Content to draw is provided by [SkikoView]. * Content to draw is provided by [SkikoRenderDelegate].
* *
* For on-screen rendering see [org.jetbrains.skiko.redrawer.MetalRedrawer]. * For on-screen rendering see [org.jetbrains.skiko.redrawer.MetalRedrawer].
* *
...@@ -19,7 +19,7 @@ import java.awt.Graphics2D ...@@ -19,7 +19,7 @@ import java.awt.Graphics2D
*/ */
internal class MetalSwingRedrawer( internal class MetalSwingRedrawer(
swingLayerProperties: SwingLayerProperties, swingLayerProperties: SwingLayerProperties,
private val skikoView: SkikoView, private val renderDelegate: SkikoRenderDelegate,
analytics: SkiaLayerAnalytics analytics: SkiaLayerAnalytics
) : SwingRedrawerBase(swingLayerProperties, analytics, GraphicsApi.METAL) { ) : SwingRedrawerBase(swingLayerProperties, analytics, GraphicsApi.METAL) {
companion object { companion object {
...@@ -70,7 +70,7 @@ internal class MetalSwingRedrawer( ...@@ -70,7 +70,7 @@ internal class MetalSwingRedrawer(
val canvas = surface.canvas val canvas = surface.canvas
canvas.clear(Color.TRANSPARENT) canvas.clear(Color.TRANSPARENT)
skikoView.onRender(canvas, width, height, nanoTime) renderDelegate.onRender(canvas, width, height, nanoTime)
flush(surface, g) flush(surface, g)
} }
} }
......
...@@ -10,7 +10,7 @@ import javax.swing.JComponent ...@@ -10,7 +10,7 @@ import javax.swing.JComponent
import javax.swing.SwingUtilities.isEventDispatchThread import javax.swing.SwingUtilities.isEventDispatchThread
/** /**
* Swing component that draws content provided by [skikoView] with GPU acceleration using Skia engine. * Swing component that draws content provided by [renderDelegate] with GPU acceleration using Skia engine.
* *
* Drawn content can be clipped by providing [ClipRectangle] to [clipComponents]. * Drawn content can be clipped by providing [ClipRectangle] to [clipComponents].
* *
...@@ -22,7 +22,7 @@ import javax.swing.SwingUtilities.isEventDispatchThread ...@@ -22,7 +22,7 @@ import javax.swing.SwingUtilities.isEventDispatchThread
@Suppress("unused") // used in Compose Multiplatform @Suppress("unused") // used in Compose Multiplatform
@ExperimentalSkikoApi @ExperimentalSkikoApi
open class SkiaSwingLayer( open class SkiaSwingLayer(
skikoView: SkikoView, renderDelegate: SkikoRenderDelegate,
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty, analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
) : JComponent() { ) : JComponent() {
internal companion object { internal companion object {
...@@ -40,14 +40,14 @@ open class SkiaSwingLayer( ...@@ -40,14 +40,14 @@ open class SkiaSwingLayer(
val clipComponents: MutableList<ClipRectangle> get() = mutableListOf() val clipComponents: MutableList<ClipRectangle> get() = mutableListOf()
private val skikoViewWithClipping = object : SkikoView by skikoView { private val renderDelegateWithClipping = object : SkikoRenderDelegate by renderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val scale = graphicsConfiguration.defaultTransform.scaleX.toFloat() val scale = graphicsConfiguration.defaultTransform.scaleX.toFloat()
// clipping // clipping
for (component in clipComponents) { for (component in clipComponents) {
canvas.clipRectBy(component, scale) canvas.clipRectBy(component, scale)
} }
skikoView.onRender(canvas, width, height, nanoTime) renderDelegate.onRender(canvas, width, height, nanoTime)
} }
} }
...@@ -64,7 +64,7 @@ open class SkiaSwingLayer( ...@@ -64,7 +64,7 @@ open class SkiaSwingLayer(
private val redrawerManager = RedrawerManager<SwingRedrawer>(properties.renderApi) { renderApi, oldRedrawer -> private val redrawerManager = RedrawerManager<SwingRedrawer>(properties.renderApi) { renderApi, oldRedrawer ->
oldRedrawer?.dispose() oldRedrawer?.dispose()
createSwingRedrawer(swingLayerProperties, skikoViewWithClipping, renderApi, analytics) createSwingRedrawer(swingLayerProperties, renderDelegateWithClipping, renderApi, analytics)
} }
private val redrawer: SwingRedrawer? private val redrawer: SwingRedrawer?
......
...@@ -3,21 +3,21 @@ package org.jetbrains.skiko.swing ...@@ -3,21 +3,21 @@ package org.jetbrains.skiko.swing
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skiko.GraphicsApi import org.jetbrains.skiko.GraphicsApi
import org.jetbrains.skiko.SkiaLayerAnalytics import org.jetbrains.skiko.SkiaLayerAnalytics
import org.jetbrains.skiko.SkikoView import org.jetbrains.skiko.SkikoRenderDelegate
import org.jetbrains.skiko.autoCloseScope import org.jetbrains.skiko.autoCloseScope
import java.awt.Graphics2D import java.awt.Graphics2D
/** /**
* Provides a way to draw on Skia canvas using software rendering without GPU acceleration and then draw it on [java.awt.Graphics2D]. * Provides a way to draw on Skia canvas using software rendering without GPU acceleration and then draw it on [java.awt.Graphics2D].
* *
* Content to draw is provided by [SkikoView]. * Content to draw is provided by [SkikoRenderDelegate].
* *
* @see SwingRedrawerBase * @see SwingRedrawerBase
* @see SwingOffscreenDrawer * @see SwingOffscreenDrawer
*/ */
internal class SoftwareSwingRedrawer( internal class SoftwareSwingRedrawer(
swingLayerProperties: SwingLayerProperties, swingLayerProperties: SwingLayerProperties,
private val skikoView: SkikoView, private val renderDelegate: SkikoRenderDelegate,
analytics: SkiaLayerAnalytics analytics: SkiaLayerAnalytics
) : SwingRedrawerBase( ) : SwingRedrawerBase(
swingLayerProperties, swingLayerProperties,
...@@ -48,7 +48,7 @@ internal class SoftwareSwingRedrawer( ...@@ -48,7 +48,7 @@ internal class SoftwareSwingRedrawer(
val canvas = Canvas(storage, SurfaceProps(pixelGeometry = PixelGeometry.UNKNOWN)).autoClose() val canvas = Canvas(storage, SurfaceProps(pixelGeometry = PixelGeometry.UNKNOWN)).autoClose()
canvas.clear(Color.TRANSPARENT) canvas.clear(Color.TRANSPARENT)
skikoView.onRender(canvas, width, height, nanoTime) renderDelegate.onRender(canvas, width, height, nanoTime)
flush(g) flush(g)
} }
......
...@@ -22,25 +22,25 @@ internal interface SwingRedrawer { ...@@ -22,25 +22,25 @@ internal interface SwingRedrawer {
} }
/** /**
* Creates a [SwingRedrawer] that will draw content provided by [skikoView] * Creates a [SwingRedrawer] that will draw content provided by [renderDelegate]
*/ */
internal fun createSwingRedrawer( internal fun createSwingRedrawer(
swingLayerProperties: SwingLayerProperties, swingLayerProperties: SwingLayerProperties,
skikoView: SkikoView, renderDelegate: SkikoRenderDelegate,
renderApi: GraphicsApi, renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics, analytics: SkiaLayerAnalytics,
): SwingRedrawer { ): SwingRedrawer {
if (renderApi == GraphicsApi.SOFTWARE_COMPAT || renderApi == GraphicsApi.SOFTWARE_FAST) { if (renderApi == GraphicsApi.SOFTWARE_COMPAT || renderApi == GraphicsApi.SOFTWARE_FAST) {
return SoftwareSwingRedrawer( return SoftwareSwingRedrawer(
swingLayerProperties, swingLayerProperties,
skikoView, renderDelegate,
analytics analytics
) )
} }
return when (hostOs) { return when (hostOs) {
OS.MacOS -> MetalSwingRedrawer(swingLayerProperties, skikoView, analytics) OS.MacOS -> MetalSwingRedrawer(swingLayerProperties, renderDelegate, analytics)
OS.Windows -> Direct3DSwingRedrawer(swingLayerProperties, skikoView, analytics) OS.Windows -> Direct3DSwingRedrawer(swingLayerProperties, renderDelegate, analytics)
OS.Linux -> LinuxOpenGLSwingRedrawer(swingLayerProperties, skikoView, analytics) OS.Linux -> LinuxOpenGLSwingRedrawer(swingLayerProperties, renderDelegate, analytics)
else -> SoftwareSwingRedrawer(swingLayerProperties, skikoView, analytics) else -> SoftwareSwingRedrawer(swingLayerProperties, renderDelegate, analytics)
} }
} }
\ No newline at end of file
...@@ -8,12 +8,12 @@ import java.util.concurrent.CancellationException ...@@ -8,12 +8,12 @@ import java.util.concurrent.CancellationException
import javax.swing.SwingUtilities import javax.swing.SwingUtilities
/** /**
* Provides a base implementation of drawing [SkikoView] content on [java.awt.Graphics2D] * Provides a base implementation of drawing [SkikoRenderDelegate] content on [java.awt.Graphics2D]
* *
* Each [redraw] request is handled in a following way: * Each [redraw] request is handled in a following way:
* 1. For the first request initialize native GPU context using [createDirectContext] * 1. For the first request initialize native GPU context using [createDirectContext]
* 2. Create [org.jetbrains.skia.Canvas] where content should be drawn using [initCanvas] * 2. Create [org.jetbrains.skia.Canvas] where content should be drawn using [initCanvas]
* 3. Acquire drawing "commands" using [SkikoView] * 3. Acquire drawing "commands" using [SkikoRenderDelegate]
* 4. Flush these commands on [java.awt.Graphics2D] using [flush] * 4. Flush these commands on [java.awt.Graphics2D] using [flush]
* *
* All the steps are performed synchronously on EDT. * All the steps are performed synchronously on EDT.
......
...@@ -52,7 +52,7 @@ class SkiaLayerPerformanceTest { ...@@ -52,7 +52,7 @@ class SkiaLayerPerformanceTest {
init { init {
window.setSize(width, height) window.setSize(width, height)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
window.layer.skikoView = object : SkikoView { window.layer.renderDelegate = object : SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
scene(canvas, width, height, nanoTime) scene(canvas, width, height, nanoTime)
if (canCollect && frameTimes.size < frameCount) { if (canCollect && frameTimes.size < frameCount) {
......
...@@ -81,7 +81,7 @@ class SkiaLayerTest { ...@@ -81,7 +81,7 @@ class SkiaLayerTest {
window.setLocation(200, 200) window.setLocation(200, 200)
window.setSize(400, 600) window.setSize(400, 600)
window.defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE window.defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
window.layer.skikoView = object : SkikoView { window.layer.renderDelegate = object : SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val c1 = counter1 val c1 = counter1
val c2 = counter2 val c2 = counter2
...@@ -164,7 +164,7 @@ class SkiaLayerTest { ...@@ -164,7 +164,7 @@ class SkiaLayerTest {
window.setSize(400, 200) window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val app = RectRenderer(window.layer, 200, 100, Color.RED) val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app window.layer.renderDelegate = app
window.isUndecorated = true window.isUndecorated = true
window.isVisible = true window.isVisible = true
...@@ -188,7 +188,7 @@ class SkiaLayerTest { ...@@ -188,7 +188,7 @@ class SkiaLayerTest {
window.preferredSize = Dimension(400, 200) window.preferredSize = Dimension(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val app = RectRenderer(window.layer, 200, 100, Color.RED) val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app window.layer.renderDelegate = app
window.isUndecorated = true window.isUndecorated = true
window.pack() window.pack()
window.paint(window.graphics) window.paint(window.graphics)
...@@ -213,7 +213,7 @@ class SkiaLayerTest { ...@@ -213,7 +213,7 @@ class SkiaLayerTest {
properties = SkiaLayerProperties(renderApi = renderApi) properties = SkiaLayerProperties(renderApi = renderApi)
) )
var renderedWidth = -1 var renderedWidth = -1
layer.skikoView = object : SkikoView { layer.renderDelegate = object : SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
renderedWidth = width renderedWidth = width
} }
...@@ -268,7 +268,7 @@ class SkiaLayerTest { ...@@ -268,7 +268,7 @@ class SkiaLayerTest {
window.setLocation(200, 200) window.setLocation(200, 200)
window.setSize(40, 20) window.setSize(40, 20)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
window.layer.skikoView = RectRenderer(window.layer, 20, 10, Color.RED) window.layer.renderDelegate = RectRenderer(window.layer, 20, 10, Color.RED)
window.isUndecorated = true window.isUndecorated = true
window.isVisible = true window.isVisible = true
delay(1000) delay(1000)
...@@ -288,7 +288,7 @@ class SkiaLayerTest { ...@@ -288,7 +288,7 @@ class SkiaLayerTest {
setLocation(200, 200) setLocation(200, 200)
setSize(400, 200) setSize(400, 200)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.skikoView = RectRenderer(layer, 200, 100, color) layer.renderDelegate = RectRenderer(layer, 200, 100, color)
isUndecorated = true isUndecorated = true
isVisible = true isVisible = true
} }
...@@ -355,7 +355,7 @@ class SkiaLayerTest { ...@@ -355,7 +355,7 @@ class SkiaLayerTest {
window.setLocation(200, 200) window.setLocation(200, 200)
window.setSize(40, 20) window.setSize(40, 20)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
window.layer.skikoView = object : SkikoView { window.layer.renderDelegate = object : SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
renderCount++ renderCount++
} }
...@@ -386,7 +386,7 @@ class SkiaLayerTest { ...@@ -386,7 +386,7 @@ class SkiaLayerTest {
setLocation(200, 200) setLocation(200, 200)
setSize(40, 20) setSize(40, 20)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.skikoView = if (isAnimated) { layer.renderDelegate = if (isAnimated) {
AnimatedBoxRenderer(layer, pixelsPerSecond = 20.0, size = 20.0) AnimatedBoxRenderer(layer, pixelsPerSecond = 20.0, size = 20.0)
} else { } else {
RectRenderer(layer, 20, 10, Color.RED) RectRenderer(layer, 20, 10, Color.RED)
...@@ -430,7 +430,7 @@ class SkiaLayerTest { ...@@ -430,7 +430,7 @@ class SkiaLayerTest {
setLocation(200, 200) setLocation(200, 200)
setSize(400, 200) setSize(400, 200)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.skikoView = AnimatedBoxRenderer(layer, pixelsPerSecond = 20.0, size = 20.0) layer.renderDelegate = AnimatedBoxRenderer(layer, pixelsPerSecond = 20.0, size = 20.0)
isVisible = true isVisible = true
} }
...@@ -454,7 +454,7 @@ class SkiaLayerTest { ...@@ -454,7 +454,7 @@ class SkiaLayerTest {
setSize(400, 200) setSize(400, 200)
preferredSize = Dimension(400, 200) preferredSize = Dimension(400, 200)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.skikoView = object : SkikoView { layer.renderDelegate = object : SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
} }
} }
...@@ -539,7 +539,7 @@ class SkiaLayerTest { ...@@ -539,7 +539,7 @@ class SkiaLayerTest {
window.setSize(400, 200) window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val app = RectRenderer(window.layer, 200, 100, Color.RED) val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app window.layer.renderDelegate = app
window.isUndecorated = true window.isUndecorated = true
window.isVisible = true window.isVisible = true
...@@ -591,7 +591,7 @@ class SkiaLayerTest { ...@@ -591,7 +591,7 @@ class SkiaLayerTest {
window.setLocation(200, 200) window.setLocation(200, 200)
window.setSize(400, 200) window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
window.layer.skikoView = object : SkikoView { window.layer.renderDelegate = object : SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
drawCount++ drawCount++
...@@ -623,7 +623,7 @@ class SkiaLayerTest { ...@@ -623,7 +623,7 @@ class SkiaLayerTest {
try { try {
window.setLocation(200, 200) window.setLocation(200, 200)
window.setSize(400, 400) window.setSize(400, 400)
window.layer.skikoView = object : SkikoView { window.layer.renderDelegate = object : SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
window.dispose() window.dispose()
SwingUtilities.invokeLater { SwingUtilities.invokeLater {
...@@ -646,7 +646,7 @@ class SkiaLayerTest { ...@@ -646,7 +646,7 @@ class SkiaLayerTest {
window.setSize(400, 200) window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val app = RectRenderer(window.layer, 200, 100, Color.RED) val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app window.layer.renderDelegate = app
window.isUndecorated = true window.isUndecorated = true
window.isVisible = true window.isVisible = true
...@@ -680,7 +680,7 @@ class SkiaLayerTest { ...@@ -680,7 +680,7 @@ class SkiaLayerTest {
panel.background = Color.GREEN panel.background = Color.GREEN
contentPane.add(panel, BorderLayout.WEST) contentPane.add(panel, BorderLayout.WEST)
layer.skikoView = RectRenderer(layer, 100, 100, Color.RED) layer.renderDelegate = RectRenderer(layer, 100, 100, Color.RED)
contentPane.add(layer, BorderLayout.CENTER) contentPane.add(layer, BorderLayout.CENTER)
}) })
try { try {
...@@ -699,13 +699,13 @@ class SkiaLayerTest { ...@@ -699,13 +699,13 @@ class SkiaLayerTest {
excludeRenderApis = listOf(GraphicsApi.SOFTWARE_COMPAT) excludeRenderApis = listOf(GraphicsApi.SOFTWARE_COMPAT)
) { ) {
val renderTimes = mutableListOf<Long>() val renderTimes = mutableListOf<Long>()
val renderer = object: SkikoView { val renderer = object: SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
renderTimes.add(System.currentTimeMillis()) renderTimes.add(System.currentTimeMillis())
} }
} }
val window = UiTestWindow { val window = UiTestWindow {
layer.skikoView = renderer layer.renderDelegate = renderer
contentPane.add(layer, BorderLayout.CENTER) contentPane.add(layer, BorderLayout.CENTER)
} }
try { try {
...@@ -834,7 +834,7 @@ class SkiaLayerTest { ...@@ -834,7 +834,7 @@ class SkiaLayerTest {
window.setSize(400, 200) window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val app = RectRenderer(window.layer, 200, 100, Color.RED) val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app window.layer.renderDelegate = app
window.isUndecorated = true window.isUndecorated = true
window.isVisible = true window.isVisible = true
...@@ -868,7 +868,7 @@ class SkiaLayerTest { ...@@ -868,7 +868,7 @@ class SkiaLayerTest {
val paragraph by lazy { paragraph(window.layer.contentScale * 40, "=-+Нп") } val paragraph by lazy { paragraph(window.layer.contentScale * 40, "=-+Нп") }
window.layer.skikoView = object : SkikoView { window.layer.renderDelegate = object : SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
canvas.clear(Color.WHITE.rgb) canvas.clear(Color.WHITE.rgb)
paragraph.layout(Float.POSITIVE_INFINITY) paragraph.layout(Float.POSITIVE_INFINITY)
...@@ -901,7 +901,7 @@ class SkiaLayerTest { ...@@ -901,7 +901,7 @@ class SkiaLayerTest {
var rectWidth: Int, var rectWidth: Int,
var rectHeight: Int, var rectHeight: Int,
private val rectColor: Color private val rectColor: Color
) : SkikoView { ) : SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val dpi = layer.contentScale val dpi = layer.contentScale
canvas.drawRect(Rect(0f, 0f, width.toFloat(), height.toFloat()), Paint().apply { canvas.drawRect(Rect(0f, 0f, width.toFloat(), height.toFloat()), Paint().apply {
...@@ -917,7 +917,7 @@ class SkiaLayerTest { ...@@ -917,7 +917,7 @@ class SkiaLayerTest {
private val layer: SkiaLayer, private val layer: SkiaLayer,
private val pixelsPerSecond: Double, private val pixelsPerSecond: Double,
private val size: Double private val size: Double
) : SkikoView { ) : SkikoRenderDelegate {
private var oldNanoTime = Long.MAX_VALUE private var oldNanoTime = Long.MAX_VALUE
private var x = 0.0 private var x = 0.0
......
package org.jetbrains.skiko
import kotlin.jvm.JvmInline
// Clicked mouse buttons bitmask.
@JvmInline
value class SkikoMouseButtons(val value: Int) {
companion object {
val NONE = SkikoMouseButtons(0)
val LEFT = SkikoMouseButtons(1)
val RIGHT = SkikoMouseButtons(2)
val MIDDLE = SkikoMouseButtons(4)
val BUTTON_1 = SkikoMouseButtons(1)
val BUTTON_2 = SkikoMouseButtons(2)
val BUTTON_3 = SkikoMouseButtons(4)
val BUTTON_4 = SkikoMouseButtons(8)
val BUTTON_5 = SkikoMouseButtons(16)
val BUTTON_6 = SkikoMouseButtons(32)
val BUTTON_7 = SkikoMouseButtons(64)
val BUTTON_8 = SkikoMouseButtons(128)
}
fun has(value: SkikoMouseButtons): Boolean {
if (value.value and this.value != 0) {
return true
}
return false
}
override fun toString(): String {
val result = mutableListOf<String>().apply {
if (has(SkikoMouseButtons.LEFT)) {
add("LEFT")
}
if (has(SkikoMouseButtons.RIGHT)) {
add("RIGHT")
}
if (has(SkikoMouseButtons.MIDDLE)) {
add("MIDDLE")
}
if (has(SkikoMouseButtons.BUTTON_4)) {
add("BUTTON_4")
}
if (has(SkikoMouseButtons.BUTTON_5)) {
add("BUTTON_5")
}
if (has(SkikoMouseButtons.BUTTON_6)) {
add("BUTTON_6")
}
if (has(SkikoMouseButtons.BUTTON_7)) {
add("BUTTON_7")
}
if (has(SkikoMouseButtons.BUTTON_8)) {
add("BUTTON_8")
}
}
return if (!result.isEmpty()) result.toString() else ""
}
}
@JvmInline
value class SkikoInputModifiers(val value: Int) {
companion object {
val EMPTY = SkikoInputModifiers(0)
val META = SkikoInputModifiers(1)
val CONTROL = SkikoInputModifiers(2)
val ALT = SkikoInputModifiers(4)
val SHIFT = SkikoInputModifiers(8)
}
fun has(value: SkikoInputModifiers): Boolean {
if (value.value and this.value != 0) {
return true
}
return false
}
override fun toString(): String {
val result = mutableListOf<String>().apply {
if (has(SkikoInputModifiers.META)) {
add("META")
}
if (has(SkikoInputModifiers.CONTROL)) {
add("CONTROL")
}
if (has(SkikoInputModifiers.ALT)) {
add("ALT")
}
if (has(SkikoInputModifiers.SHIFT)) {
add("SHIFT")
}
}
return if (!result.isEmpty()) result.toString() else ""
}
}
enum class SkikoGestureEventKind {
UNKNOWN, TAP, DOUBLETAP, LONGPRESS, PAN, PINCH, ROTATION, SWIPE
}
enum class SkikoGestureEventDirection {
UNKNOWN, UP, DOWN, LEFT, RIGHT
}
enum class SkikoGestureEventState {
UNKNOWN, PRESSED, STARTED, CHANGED, ENDED
}
expect class SkikoGesturePlatformEvent
data class SkikoGestureEvent(
val x: Double,
val y: Double,
val velocity: Double = 0.0,
val direction: SkikoGestureEventDirection = SkikoGestureEventDirection.UNKNOWN,
val rotation: Double = 0.0,
val scale: Double = 1.0,
val kind: SkikoGestureEventKind,
val state: SkikoGestureEventState = SkikoGestureEventState.UNKNOWN,
val platform: SkikoGesturePlatformEvent? = null
)
expect class SkikoPlatformInputEvent
data class SkikoInputEvent(
val input: String,
val key: SkikoKey,
val modifiers: SkikoInputModifiers = SkikoInputModifiers.EMPTY,
val kind: SkikoKeyboardEventKind,
val platform: SkikoPlatformInputEvent?
)
enum class SkikoKeyboardEventKind {
UNKNOWN, UP, DOWN, TYPE
}
expect class SkikoPlatformKeyboardEvent
data class SkikoKeyboardEvent(
val key: SkikoKey,
val modifiers: SkikoInputModifiers = SkikoInputModifiers.EMPTY,
val kind: SkikoKeyboardEventKind,
val timestamp: Long = 0,
val platform: SkikoPlatformKeyboardEvent?
)
enum class SkikoPointerEventKind {
UNKNOWN, UP, DOWN, MOVE, DRAG, SCROLL, ENTER, EXIT
}
expect class SkikoPlatformPointerEvent
// TODO(https://github.com/JetBrains/skiko/issues/680) refactor API
data class SkikoPointerEvent(
/**
* X position in points (scaled pixels that depend on the scale factor of the current display).
*
* If the event contains multiple pointers, it represents the center of all pointers.
*/
val x: Double,
/**
* Y position in points (scaled pixels that depend on the scale factor of the current display)
*
* If the event contains multiple pointers, it represents the center of all pointers.
*/
val y: Double,
val kind: SkikoPointerEventKind,
/**
* Scroll delta along the X axis
*/
val deltaX: Double = 0.0,
/**
* Scroll delta along the Y axis
*/
val deltaY: Double = 0.0,
val pressedButtons: SkikoMouseButtons = SkikoMouseButtons.NONE,
val button: SkikoMouseButtons = SkikoMouseButtons.NONE,
val modifiers: SkikoInputModifiers = SkikoInputModifiers.EMPTY,
/**
* Timestamp in milliseconds
*/
val timestamp: Long = 0,
val pointers: List<SkikoPointer> = listOf(
SkikoPointer(0, x, y, pressedButtons.has(SkikoMouseButtons.LEFT))
),
val platform: SkikoPlatformPointerEvent? = null
)
val SkikoPointerEvent.isLeftClick: Boolean
get() = button.has(SkikoMouseButtons.LEFT) && (kind == SkikoPointerEventKind.UP)
val SkikoPointerEvent.isRightClick: Boolean
get() = button.has(SkikoMouseButtons.RIGHT) && (kind == SkikoPointerEventKind.UP)
val SkikoPointerEvent.isMiddleClick: Boolean
get() = button.has(SkikoMouseButtons.MIDDLE) && (kind == SkikoPointerEventKind.UP)
/**
* The device type that produces pointer events, such as a mouse or stylus.
*/
enum class SkikoPointerDevice {
UNKNOWN, MOUSE, TOUCH
}
/**
* Represents pointer such as mouse cursor, or touch/stylus press.
* There can be multiple pointers on the screen at the same time.
*/
data class SkikoPointer(
/**
* Unique id associated with the pointer. Used to distinguish between multiple pointers that can exist
* at the same time (i.e. multiple pressed touches).
*
* If there is only on pointer in the system (for example, one mouse), it should always
* have the same id across multiple events.
*/
val id: Long,
/**
* X position in points (scaled pixels that depend on the scale factor of the current display)
*/
val x: Double,
/**
* Y position in points (scaled pixels that depend on the scale factor of the current display)
*/
val y: Double,
/**
* `true` if the pointer event is considered "pressed." For example, finger
* touching the screen or a mouse button is pressed [pressed] would be `true`.
* During the up event, pointer is considered not pressed.
*/
val pressed: Boolean,
/**
* The device type associated with the pointer, such as [mouse][SkikoPointerDevice.MOUSE],
* or [touch][SkikoPointerDevice.TOUCH].
*/
val device: SkikoPointerDevice = SkikoPointerDevice.MOUSE,
/**
* Pressure of the pointer. 0.0 - no pressure, 1.0 - average pressure
*/
val pressure: Double = 1.0,
)
internal val Iterable<SkikoPointer>.centroidX get() = asSequence().map { it.x }.average()
internal val Iterable<SkikoPointer>.centroidY get() = asSequence().map { it.y }.average()
\ No newline at end of file
...@@ -41,16 +41,16 @@ expect open class SkiaLayer { ...@@ -41,16 +41,16 @@ expect open class SkiaLayer {
/** /**
* Current view used for rendering. * Current view used for rendering.
*/ */
var skikoView: SkikoView? var renderDelegate: SkikoRenderDelegate?
/** /**
* Attach this SkikoView to platform container. * Attach this [SkikoRenderDelegate] to platform container.
* Actual type of attach container is platform-specific. * Actual type of attach container is platform-specific.
*/ */
fun attachTo(container: Any) fun attachTo(container: Any)
/** /**
* Detach this SkikoView from platform container. * Detach this [SkikoRenderDelegate] from platform container.
*/ */
fun detach() fun detach()
......
package org.jetbrains.skiko
expect interface SkikoInput {
object Empty : SkikoInput
}
package org.jetbrains.skiko
expect enum class SkikoKey {
KEY_UNKNOWN,
KEY_A,
KEY_S,
KEY_D,
KEY_F,
KEY_H,
KEY_G,
KEY_Z,
KEY_X,
KEY_C,
KEY_V,
KEY_B,
KEY_Q,
KEY_W,
KEY_E,
KEY_R,
KEY_Y,
KEY_T,
KEY_U,
KEY_I,
KEY_P,
KEY_L,
KEY_J,
KEY_K,
KEY_N,
KEY_M,
KEY_O,
KEY_1,
KEY_2,
KEY_3,
KEY_4,
KEY_5,
KEY_6,
KEY_7,
KEY_8,
KEY_9,
KEY_0,
KEY_CLOSE_BRACKET,
KEY_OPEN_BRACKET,
KEY_QUOTE,
KEY_SEMICOLON,
KEY_SLASH,
KEY_COMMA,
KEY_BACKSLASH,
KEY_PERIOD,
KEY_BACK_QUOTE,
KEY_EQUALS,
KEY_MINUS,
KEY_ENTER,
KEY_ESCAPE,
KEY_TAB,
KEY_BACKSPACE,
KEY_SPACE,
KEY_CAPSLOCK,
KEY_LEFT_META,
KEY_LEFT_SHIFT,
KEY_LEFT_ALT,
KEY_LEFT_CONTROL,
KEY_RIGHT_META,
KEY_RIGHT_SHIFT,
KEY_RIGHT_ALT,
KEY_RIGHT_CONTROL,
KEY_MENU,
KEY_UP,
KEY_DOWN,
KEY_LEFT,
KEY_RIGHT,
KEY_F1,
KEY_F2,
KEY_F3,
KEY_F4,
KEY_F5,
KEY_F6,
KEY_F7,
KEY_F8,
KEY_F9,
KEY_F10,
KEY_F11,
KEY_F12,
KEY_PRINTSCEEN,
KEY_SCROLL_LOCK,
KEY_PAUSE,
KEY_INSERT,
KEY_HOME,
KEY_PGUP,
KEY_DELETE,
KEY_END,
KEY_PGDOWN,
KEY_NUM_LOCK,
KEY_NUMPAD_0,
KEY_NUMPAD_1,
KEY_NUMPAD_2,
KEY_NUMPAD_3,
KEY_NUMPAD_4,
KEY_NUMPAD_5,
KEY_NUMPAD_6,
KEY_NUMPAD_7,
KEY_NUMPAD_8,
KEY_NUMPAD_9,
KEY_NUMPAD_ENTER,
KEY_NUMPAD_ADD,
KEY_NUMPAD_SUBTRACT,
KEY_NUMPAD_MULTIPLY,
KEY_NUMPAD_DIVIDE,
KEY_NUMPAD_DECIMAL;
val platformKeyCode: Int
}
package org.jetbrains.skiko
import org.jetbrains.skia.Canvas
interface SkikoRenderDelegate {
fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long)
}
open class SkiaLayerRenderDelegate(
val layer: SkiaLayer,
val renderDelegate: SkikoRenderDelegate
): SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val contentScale = layer.contentScale
canvas.scale(contentScale, contentScale)
renderDelegate.onRender(canvas, (width / contentScale).toInt(), (height / contentScale).toInt(), nanoTime)
// Request next frame immediately.
layer.needRedraw()
}
}
package org.jetbrains.skiko package org.jetbrains.skiko
import org.jetbrains.skia.Canvas @Deprecated(
message = "Replaced with SkikoRenderDelegate",
interface SkikoView { replaceWith = ReplaceWith("SkikoRenderDelegate")
// Input )
fun onKeyboardEvent(event: SkikoKeyboardEvent) = Unit typealias SkikoView = SkikoRenderDelegate
fun onPointerEvent(event: SkikoPointerEvent) = Unit
@Deprecated(
@Deprecated("This method will be removed. Use override val input: SkikoInput") message = "Replaced with SkiaLayerRenderDelegate",
fun onInputEvent(event: SkikoInputEvent) = Unit replaceWith = ReplaceWith("SkiaLayerRenderDelegate")
val input: SkikoInput get() = SkikoInput.Empty )
fun onGestureEvent(event: SkikoGestureEvent) = Unit typealias GenericSkikoView = SkiaLayerRenderDelegate
// Rendering
fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long)
}
open class GenericSkikoView(
val layer: SkiaLayer,
val app: SkikoView
): SkikoView {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val contentScale = layer.contentScale
canvas.scale(contentScale, contentScale)
app.onRender(canvas, (width / contentScale).toInt(), (height / contentScale).toInt(), nanoTime)
// Request next frame immediately.
layer.needRedraw()
}
override fun onInputEvent(event: SkikoInputEvent) {
app.onInputEvent(event)
}
override val input: SkikoInput get() = app.input
override fun onKeyboardEvent(event: SkikoKeyboardEvent) {
app.onKeyboardEvent(event)
}
override fun onPointerEvent(event: SkikoPointerEvent) {
app.onPointerEvent(event)
}
override fun onGestureEvent(event: SkikoGestureEvent) {
app.onGestureEvent(event)
}
}
package org.jetbrains.skiko
import platform.UIKit.*
fun toSkikoGestureDirection(direction: UISwipeGestureRecognizerDirection) : SkikoGestureEventDirection {
return when(direction) {
UISwipeGestureRecognizerDirectionUp -> SkikoGestureEventDirection.UP
UISwipeGestureRecognizerDirectionDown -> SkikoGestureEventDirection.DOWN
UISwipeGestureRecognizerDirectionLeft -> SkikoGestureEventDirection.LEFT
UISwipeGestureRecognizerDirectionRight -> SkikoGestureEventDirection.RIGHT
else -> SkikoGestureEventDirection.UNKNOWN
}
}
fun toSkikoGestureState(state: UIGestureRecognizerState ) : SkikoGestureEventState {
return when(state) {
UIGestureRecognizerStatePossible -> SkikoGestureEventState.PRESSED
UIGestureRecognizerStateBegan -> SkikoGestureEventState.STARTED
UIGestureRecognizerStateChanged -> SkikoGestureEventState.CHANGED
UIGestureRecognizerStateEnded -> SkikoGestureEventState.ENDED
else -> SkikoGestureEventState.UNKNOWN
}
}
fun toSkikoTypeEvent(character: String, event: UIPress?): SkikoInputEvent {
val key = if (event != null) SkikoKey.valueOf(event.key!!.keyCode) else SkikoKey.KEY_UNKNOWN
val modifiers = if (event != null) toSkikoModifiers(event) else SkikoInputModifiers.EMPTY
return SkikoInputEvent(
character,
key,
modifiers,
SkikoKeyboardEventKind.TYPE,
event
)
}
fun toSkikoKeyboardEvent(
event: UIPress,
kind: SkikoKeyboardEventKind
): SkikoKeyboardEvent {
val timestamp = (event.timestamp * 1_000).toLong()
return SkikoKeyboardEvent(
SkikoKey.valueOf(event.key!!.keyCode),
toSkikoModifiers(event),
kind,
timestamp,
event
)
}
private fun toSkikoModifiers(event: UIPress): SkikoInputModifiers {
var result = 0
val modifiers = event.key!!.modifierFlags
if (modifiers and UIKeyModifierAlternate != 0L) {
result = result.or(SkikoInputModifiers.ALT.value)
}
if (modifiers and UIKeyModifierShift != 0L) {
result = result.or(SkikoInputModifiers.SHIFT.value)
}
if (modifiers and UIKeyModifierControl != 0L) {
result = result.or(SkikoInputModifiers.CONTROL.value)
}
if (modifiers and UIKeyModifierCommand != 0L) {
result = result.or(SkikoInputModifiers.META.value)
}
return SkikoInputModifiers(result)
}
...@@ -53,25 +53,19 @@ actual open class SkiaLayer { ...@@ -53,25 +53,19 @@ actual open class SkiaLayer {
// GC bug? fixes leak on iOS // GC bug? fixes leak on iOS
view = null view = null
skikoView = null renderDelegate = null
} }
actual var skikoView: SkikoView? = null actual var renderDelegate: SkikoRenderDelegate? = null
internal actual fun draw(canvas: Canvas) { internal actual fun draw(canvas: Canvas) {
throw UnsupportedOperationException("Don't call it, artifact of wrong abstraction") throw UnsupportedOperationException("Don't call it, artifact of wrong abstraction")
} }
internal fun draw(surface: Surface) { internal fun draw(surface: Surface) {
skikoView?.onRender(surface.canvas, surface.width, surface.height, getTimeNanos()) renderDelegate?.onRender(surface.canvas, surface.width, surface.height, getTimeNanos())
} }
actual val pixelGeometry: PixelGeometry actual val pixelGeometry: PixelGeometry
get() = PixelGeometry.UNKNOWN get() = PixelGeometry.UNKNOWN
} }
// TODO: do properly
actual typealias SkikoGesturePlatformEvent = UIEvent
actual typealias SkikoPlatformInputEvent = UIPress
actual typealias SkikoPlatformKeyboardEvent = UIPress
actual typealias SkikoPlatformPointerEvent = UIEvent
package org.jetbrains.skiko
actual interface SkikoInput {
/**
* A Boolean value that indicates whether the text-entry object has any text.
* https://developer.apple.com/documentation/uikit/uikeyinput/1614457-hastext
*/
fun hasText(): Boolean
/**
* Inserts a character into the displayed text.
* Add the character text to your class’s backing store at the index corresponding to the cursor and redisplay the text.
* https://developer.apple.com/documentation/uikit/uikeyinput/1614543-inserttext
* @param text A string object representing the character typed on the system keyboard.
*/
fun insertText(text: String)
/**
* Deletes a character from the displayed text.
* Remove the character just before the cursor from your class’s backing store and redisplay the text.
* https://developer.apple.com/documentation/uikit/uikeyinput/1614572-deletebackward
*/
fun deleteBackward()
/**
* The text position for the end of a document.
* https://developer.apple.com/documentation/uikit/uitextinput/1614555-endofdocument
*/
fun endOfDocument(): Long
/**
* The range of selected text in a document.
* If the text range has a length, it indicates the currently selected text.
* If it has zero length, it indicates the caret (insertion point).
* If the text-range object is nil, it indicates that there is no current selection.
* https://developer.apple.com/documentation/uikit/uitextinput/1614541-selectedtextrange
*/
fun getSelectedTextRange(): IntRange?
fun setSelectedTextRange(range: IntRange?)
fun selectAll()
/**
* Returns the text in the specified range.
* https://developer.apple.com/documentation/uikit/uitextinput/1614527-text
* @param range A range of text in a document.
* @return A substring of a document that falls within the specified range.
*/
fun textInRange(range: IntRange): String
/**
* Replaces the text in a document that is in the specified range.
* https://developer.apple.com/documentation/uikit/uitextinput/1614558-replace
* @param range A range of text in a document.
* @param text A string to replace the text in range.
*/
fun replaceRange(range: IntRange, text: String)
/**
* Inserts the provided text and marks it to indicate that it is part of an active input session.
* Setting marked text either replaces the existing marked text or,
* if none is present, inserts it in place of the current selection.
* https://developer.apple.com/documentation/uikit/uitextinput/1614465-setmarkedtext
* @param markedText The text to be marked.
* @param selectedRange A range within markedText that indicates the current selection.
* This range is always relative to markedText.
*/
fun setMarkedText(markedText: String?, selectedRange: IntRange)
/**
* The range of currently marked text in a document.
* If there is no marked text, the value of the property is nil.
* Marked text is provisionally inserted text that requires user confirmation;
* it occurs in multistage text input.
* The current selection, which can be a caret or an extended range, always occurs within the marked text.
* https://developer.apple.com/documentation/uikit/uitextinput/1614489-markedtextrange
*/
fun markedTextRange(): IntRange?
/**
* Unmarks the currently marked text.
* After this method is called, the value of markedTextRange is nil.
* https://developer.apple.com/documentation/uikit/uitextinput/1614512-unmarktext
*/
fun unmarkText()
actual object Empty : SkikoInput {
override fun hasText(): Boolean = false
override fun insertText(text: String) = Unit
override fun deleteBackward() = Unit
override fun endOfDocument(): Long = 0L
override fun getSelectedTextRange(): IntRange? = null
override fun setSelectedTextRange(range: IntRange?) = Unit
override fun selectAll() = Unit
override fun textInRange(range: IntRange): String = ""
override fun replaceRange(range: IntRange, text: String) = Unit
override fun setMarkedText(markedText: String?, selectedRange: IntRange) = Unit
override fun markedTextRange(): IntRange? = null
override fun unmarkText() = Unit
}
}
package org.jetbrains.skiko
import platform.UIKit.*
actual enum class SkikoKey(val value: UIKeyboardHIDUsage) {
KEY_UNKNOWN(-1),
KEY_A(UIKeyboardHIDUsageKeyboardA),
KEY_S(UIKeyboardHIDUsageKeyboardS),
KEY_D(UIKeyboardHIDUsageKeyboardD),
KEY_F(UIKeyboardHIDUsageKeyboardF),
KEY_H(UIKeyboardHIDUsageKeyboardH),
KEY_G(UIKeyboardHIDUsageKeyboardG),
KEY_Z(UIKeyboardHIDUsageKeyboardZ),
KEY_X(UIKeyboardHIDUsageKeyboardX),
KEY_C(UIKeyboardHIDUsageKeyboardC),
KEY_V(UIKeyboardHIDUsageKeyboardV),
KEY_B(UIKeyboardHIDUsageKeyboardB),
KEY_Q(UIKeyboardHIDUsageKeyboardQ),
KEY_W(UIKeyboardHIDUsageKeyboardW),
KEY_E(UIKeyboardHIDUsageKeyboardE),
KEY_R(UIKeyboardHIDUsageKeyboardR),
KEY_Y(UIKeyboardHIDUsageKeyboardY),
KEY_T(UIKeyboardHIDUsageKeyboardT),
KEY_U(UIKeyboardHIDUsageKeyboardU),
KEY_I(UIKeyboardHIDUsageKeyboardI),
KEY_P(UIKeyboardHIDUsageKeyboardP),
KEY_L(UIKeyboardHIDUsageKeyboardL),
KEY_J(UIKeyboardHIDUsageKeyboardJ),
KEY_K(UIKeyboardHIDUsageKeyboardK),
KEY_N(UIKeyboardHIDUsageKeyboardN),
KEY_M(UIKeyboardHIDUsageKeyboardM),
KEY_O(UIKeyboardHIDUsageKeyboardO),
KEY_1(UIKeyboardHIDUsageKeyboard1),
KEY_2(UIKeyboardHIDUsageKeyboard2),
KEY_3(UIKeyboardHIDUsageKeyboard3),
KEY_4(UIKeyboardHIDUsageKeyboard4),
KEY_5(UIKeyboardHIDUsageKeyboard5),
KEY_6(UIKeyboardHIDUsageKeyboard6),
KEY_7(UIKeyboardHIDUsageKeyboard7),
KEY_8(UIKeyboardHIDUsageKeyboard8),
KEY_9(UIKeyboardHIDUsageKeyboard9),
KEY_0(UIKeyboardHIDUsageKeyboard0),
KEY_CLOSE_BRACKET(UIKeyboardHIDUsageKeyboardCloseBracket),
KEY_OPEN_BRACKET(UIKeyboardHIDUsageKeyboardOpenBracket),
KEY_QUOTE(UIKeyboardHIDUsageKeyboardQuote),
KEY_SEMICOLON(UIKeyboardHIDUsageKeyboardSemicolon),
KEY_SLASH(UIKeyboardHIDUsageKeyboardSlash),
KEY_COMMA(UIKeyboardHIDUsageKeyboardComma),
KEY_BACKSLASH(UIKeyboardHIDUsageKeyboardBackslash),
KEY_PERIOD(UIKeyboardHIDUsageKeyboardPeriod),
KEY_BACK_QUOTE(53),
KEY_EQUALS(UIKeyboardHIDUsageKeyboardEqualSign),
KEY_MINUS(UIKeyboardHIDUsageKeyboardHyphen),
KEY_ENTER(40),
KEY_ESCAPE(UIKeyboardHIDUsageKeyboardEscape),
KEY_TAB(UIKeyboardHIDUsageKeyboardTab),
KEY_BACKSPACE(UIKeyboardHIDUsageKeyboardDeleteOrBackspace),
KEY_SPACE(UIKeyboardHIDUsageKeyboardSpacebar),
KEY_CAPSLOCK(UIKeyboardHIDUsageKeyboardCapsLock),
KEY_LEFT_META(227),
KEY_LEFT_SHIFT(UIKeyboardHIDUsageKeyboardLeftShift),
KEY_LEFT_ALT(UIKeyboardHIDUsageKeyboardLeftAlt),
KEY_LEFT_CONTROL(UIKeyboardHIDUsageKeyboardLeftControl),
KEY_RIGHT_META(231),
KEY_RIGHT_SHIFT(UIKeyboardHIDUsageKeyboardRightShift),
KEY_RIGHT_ALT(UIKeyboardHIDUsageKeyboardRightAlt),
KEY_RIGHT_CONTROL(UIKeyboardHIDUsageKeyboardRightControl),
KEY_MENU(101),
KEY_UP(UIKeyboardHIDUsageKeyboardUpArrow),
KEY_DOWN(UIKeyboardHIDUsageKeyboardDownArrow),
KEY_LEFT(UIKeyboardHIDUsageKeyboardLeftArrow),
KEY_RIGHT(UIKeyboardHIDUsageKeyboardRightArrow),
KEY_F1(UIKeyboardHIDUsageKeyboardF1),
KEY_F2(UIKeyboardHIDUsageKeyboardF2),
KEY_F3(UIKeyboardHIDUsageKeyboardF3),
KEY_F4(UIKeyboardHIDUsageKeyboardF4),
KEY_F5(UIKeyboardHIDUsageKeyboardF5),
KEY_F6(UIKeyboardHIDUsageKeyboardF6),
KEY_F7(UIKeyboardHIDUsageKeyboardF7),
KEY_F8(UIKeyboardHIDUsageKeyboardF8),
KEY_F9(UIKeyboardHIDUsageKeyboardF9),
KEY_F10(UIKeyboardHIDUsageKeyboardF10),
KEY_F11(UIKeyboardHIDUsageKeyboardF11),
KEY_F12(UIKeyboardHIDUsageKeyboardF12),
KEY_PRINTSCEEN(104),
KEY_SCROLL_LOCK(UIKeyboardHIDUsageKeyboardScrollLock),
KEY_PAUSE(UIKeyboardHIDUsageKeyboardPause),
KEY_INSERT(117),
KEY_HOME(UIKeyboardHIDUsageKeyboardHome),
KEY_PGUP(UIKeyboardHIDUsageKeyboardPageUp),
KEY_DELETE(UIKeyboardHIDUsageKeyboardDeleteForward),
KEY_END(UIKeyboardHIDUsageKeyboardEnd),
KEY_PGDOWN(UIKeyboardHIDUsageKeyboardPageDown),
KEY_NUM_LOCK(UIKeyboardHIDUsageKeypadNumLock),
KEY_NUMPAD_0(UIKeyboardHIDUsageKeypad0),
KEY_NUMPAD_1(UIKeyboardHIDUsageKeypad1),
KEY_NUMPAD_2(90),
KEY_NUMPAD_3(UIKeyboardHIDUsageKeypad3),
KEY_NUMPAD_4(92),
KEY_NUMPAD_5(UIKeyboardHIDUsageKeypad5),
KEY_NUMPAD_6(94),
KEY_NUMPAD_7(UIKeyboardHIDUsageKeypad7),
KEY_NUMPAD_8(96),
KEY_NUMPAD_9(UIKeyboardHIDUsageKeypad9),
KEY_NUMPAD_ENTER(UIKeyboardHIDUsageKeypadEnter),
KEY_NUMPAD_ADD(87),
KEY_NUMPAD_SUBTRACT(86),
KEY_NUMPAD_MULTIPLY(UIKeyboardHIDUsageKeypadAsterisk),
KEY_NUMPAD_DIVIDE(UIKeyboardHIDUsageKeypadSlash),
KEY_NUMPAD_DECIMAL(99);
init {
if (value > Int.MAX_VALUE) {
error("iOS SkikoKey, init value = $value > Int.MAX_VALUE")
}
}
actual val platformKeyCode get() = value.toInt()
companion object {
fun valueOf(value: UIKeyboardHIDUsage): SkikoKey {
if (value > Int.MAX_VALUE) {
error("iOS SkikoKey, valueOf value = $value > Int.MAX_VALUE")
}
val key = SkikoKey.values().firstOrNull { it.value == value }
return key ?: SkikoKey.KEY_UNKNOWN
}
}
}
...@@ -19,7 +19,7 @@ import kotlin.native.ref.WeakReference ...@@ -19,7 +19,7 @@ import kotlin.native.ref.WeakReference
@Suppress("CONFLICTING_OVERLOADS") @Suppress("CONFLICTING_OVERLOADS")
@ExportObjCClass @ExportObjCClass
class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol { class SkikoUIView : UIView {
companion object : UIViewMeta() { companion object : UIViewMeta() {
override fun layerClass() = CAMetalLayer override fun layerClass() = CAMetalLayer
} }
...@@ -34,25 +34,8 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol { ...@@ -34,25 +34,8 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol {
MTLCreateSystemDefaultDevice() ?: throw IllegalStateException("Metal is not supported on this system") MTLCreateSystemDefaultDevice() ?: throw IllegalStateException("Metal is not supported on this system")
private val _metalLayer: CAMetalLayer get() = layer as CAMetalLayer private val _metalLayer: CAMetalLayer get() = layer as CAMetalLayer
private var _skiaLayer: SkiaLayer? = null private var _skiaLayer: SkiaLayer? = null
private var _pointInside: (Point, UIEvent?) -> Boolean = { _, _ -> true }
private var _skikoUITextInputTrains: SkikoUITextInputTraits = object : SkikoUITextInputTraits {}
private var _inputDelegate: UITextInputDelegateProtocol? = null
private var _currentTextMenuActions: TextActions? = null
private lateinit var _redrawer: MetalRedrawer private lateinit var _redrawer: MetalRedrawer
/*
* When there at least one tracked touch, we need notify redrawer about it. It should schedule CADisplayLink which
* affects frequency of polling UITouch events on high frequency display and forces it to match display refresh rate.
*/
private var _touchesCount = 0
set(value) {
field = value
val needHighFrequencyPolling = value > 0
_redrawer.needsProactiveDisplayLink = needHighFrequencyPolling
}
init { init {
multipleTouchEnabled = true multipleTouchEnabled = true
opaque = false // For UIKit interop through a "Hole" opaque = false // For UIKit interop through a "Hole"
...@@ -71,26 +54,12 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol { ...@@ -71,26 +54,12 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol {
} }
} }
// merging two constructors might cause a binary incompatibility, which will result in a unclear linking error,
// if a project using newer compose depends on an older compose transitively
// https://youtrack.jetbrains.com/issue/KT-60399
@Suppress("UNUSED") // public API
constructor(
skiaLayer: SkiaLayer,
frame: CValue<CGRect> = CGRectNull.readValue(),
pointInside: (Point, UIEvent?) -> Boolean = { _, _ -> true }
) : this(skiaLayer, frame, pointInside, skikoUITextInputTrains = object : SkikoUITextInputTraits {})
@OptIn(kotlin.experimental.ExperimentalNativeApi::class) @OptIn(kotlin.experimental.ExperimentalNativeApi::class)
constructor( constructor(
skiaLayer: SkiaLayer, skiaLayer: SkiaLayer,
frame: CValue<CGRect> = CGRectNull.readValue(), frame: CValue<CGRect> = CGRectNull.readValue()
pointInside: (Point, UIEvent?) -> Boolean = { _, _ -> true },
skikoUITextInputTrains: SkikoUITextInputTraits
) : super(frame) { ) : super(frame) {
_skiaLayer = skiaLayer _skiaLayer = skiaLayer
_pointInside = pointInside
_skikoUITextInputTrains = skikoUITextInputTrains
val weakSkiaLayer = WeakReference(skiaLayer) val weakSkiaLayer = WeakReference(skiaLayer)
...@@ -105,60 +74,6 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol { ...@@ -105,60 +74,6 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol {
skiaLayer.view = this skiaLayer.view = this
} }
/**
* Show copy/paste text menu
* @param targetRect - rectangle of selected text area
* @param textActions - available (not null) actions in text menu
*/
fun showTextMenu(targetRect: Rect, textActions: TextActions) {
_currentTextMenuActions = textActions
val menu: UIMenuController = UIMenuController.sharedMenuController()
val cgRect = CGRectMake(
x = targetRect.left.toDouble(),
y = targetRect.top.toDouble(),
width = targetRect.width.toDouble(),
height = targetRect.height.toDouble()
)
val isTargetVisible = CGRectIntersectsRect(bounds, cgRect)
if (isTargetVisible) {
if (menu.isMenuVisible()) {
menu.setTargetRect(cgRect, this)
} else {
menu.showMenuFromView(this, cgRect)
}
} else {
if (menu.isMenuVisible()) {
menu.hideMenu()
}
}
}
fun hideTextMenu() {
_currentTextMenuActions = null
val menu: UIMenuController = UIMenuController.sharedMenuController()
menu.hideMenu()
}
fun isTextMenuShown(): Boolean {
return _currentTextMenuActions != null
}
override fun copy(sender: Any?) {
_currentTextMenuActions?.copy?.invoke()
}
override fun paste(sender: Any?) {
_currentTextMenuActions?.paste?.invoke()
}
override fun cut(sender: Any?) {
_currentTextMenuActions?.cut?.invoke()
}
override fun selectAll(sender: Any?) {
_currentTextMenuActions?.selectAll?.invoke()
}
internal fun detach() { internal fun detach() {
_redrawer.dispose() _redrawer.dispose()
} }
...@@ -186,448 +101,4 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol { ...@@ -186,448 +101,4 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol {
_metalLayer.drawableSize = scaledSize _metalLayer.drawableSize = scaledSize
} }
fun showScreenKeyboard() = becomeFirstResponder()
fun hideScreenKeyboard() = resignFirstResponder()
fun isScreenKeyboardOpen() = isFirstResponder
/**
* A Boolean value that indicates whether the text-entry object has any text.
* https://developer.apple.com/documentation/uikit/uikeyinput/1614457-hastext
*/
override fun hasText(): Boolean {
return _skiaLayer?.skikoView?.input?.hasText() ?: false
}
/**
* Inserts a character into the displayed text.
* Add the character text to your class’s backing store at the index corresponding to the cursor and redisplay the text.
* https://developer.apple.com/documentation/uikit/uikeyinput/1614543-inserttext
* @param text A string object representing the character typed on the system keyboard.
*/
override fun insertText(text: String) {
_skiaLayer?.skikoView?.input?.insertText(text)
}
/**
* Deletes a character from the displayed text.
* Remove the character just before the cursor from your class’s backing store and redisplay the text.
* https://developer.apple.com/documentation/uikit/uikeyinput/1614572-deletebackward
*/
override fun deleteBackward() {
_skiaLayer?.skikoView?.input?.deleteBackward()
}
override fun canBecomeFirstResponder() = true
override fun pressesBegan(presses: Set<*>, withEvent: UIPressesEvent?) {
if (withEvent != null) {
for (press in withEvent.allPresses) {
val uiPress = press as? UIPress
if (uiPress != null) {
_skiaLayer?.skikoView?.onKeyboardEvent(
toSkikoKeyboardEvent(press, SkikoKeyboardEventKind.DOWN)
)
}
}
}
super.pressesBegan(presses, withEvent)
}
override fun pressesEnded(presses: Set<*>, withEvent: UIPressesEvent?) {
if (withEvent != null) {
for (press in withEvent.allPresses) {
val uiPress = press as? UIPress
if (uiPress != null) {
_skiaLayer?.skikoView?.onKeyboardEvent(
toSkikoKeyboardEvent(press, SkikoKeyboardEventKind.UP)
)
}
}
}
super.pressesEnded(presses, withEvent)
}
/**
* https://developer.apple.com/documentation/uikit/uiview/1622533-point
*/
override fun pointInside(point: CValue<CGPoint>, withEvent: UIEvent?): Boolean {
val skiaPoint: Point = point.useContents { Point(x.toFloat(), y.toFloat()) }
return _pointInside(skiaPoint, withEvent)
}
override fun touchesBegan(touches: Set<*>, withEvent: UIEvent?) {
super.touchesBegan(touches, withEvent)
_touchesCount += touches.size
sendTouchEventToSkikoView(withEvent!!, SkikoPointerEventKind.DOWN)
}
override fun touchesEnded(touches: Set<*>, withEvent: UIEvent?) {
super.touchesEnded(touches, withEvent)
_touchesCount -= touches.size
sendTouchEventToSkikoView(withEvent!!, SkikoPointerEventKind.UP)
}
override fun touchesMoved(touches: Set<*>, withEvent: UIEvent?) {
super.touchesMoved(touches, withEvent)
sendTouchEventToSkikoView(withEvent!!, SkikoPointerEventKind.MOVE)
}
override fun touchesCancelled(touches: Set<*>, withEvent: UIEvent?) {
super.touchesCancelled(touches, withEvent)
_touchesCount -= touches.size
sendTouchEventToSkikoView(withEvent!!, SkikoPointerEventKind.UP)
}
private fun sendTouchEventToSkikoView(event: UIEvent, kind: SkikoPointerEventKind) {
val pointers = event.touchesForView(this).orEmpty().map {
val touch = it as UITouch
val (x, y) = touch.locationInView(this).useContents { x to y }
SkikoPointer(
x = x,
y = y,
pressed = touch.isPressed,
device = SkikoPointerDevice.TOUCH,
id = touch.hashCode().toLong(),
pressure = touch.force
)
}
_skiaLayer?.skikoView?.onPointerEvent(
SkikoPointerEvent(
x = pointers.centroidX,
y = pointers.centroidY,
kind = kind,
timestamp = (event.timestamp * 1_000).toLong(),
pointers = pointers,
platform = event
)
)
}
override fun inputDelegate(): UITextInputDelegateProtocol? {
return _inputDelegate
}
override fun setInputDelegate(inputDelegate: UITextInputDelegateProtocol?) {
_inputDelegate = inputDelegate
}
/**
* Returns the text in the specified range.
* https://developer.apple.com/documentation/uikit/uitextinput/1614527-text
* @param range A range of text in a document.
* @return A substring of a document that falls within the specified range.
*/
override fun textInRange(range: UITextRange): String? {
return _skiaLayer?.skikoView?.input?.textInRange(range.toIntRange())
}
/**
* Replaces the text in a document that is in the specified range.
* https://developer.apple.com/documentation/uikit/uitextinput/1614558-replace
* @param range A range of text in a document.
* @param withText A string to replace the text in range.
*/
override fun replaceRange(range: UITextRange, withText: String) {
_skiaLayer?.skikoView?.input?.replaceRange(range.toIntRange(), withText)
}
override fun setSelectedTextRange(selectedTextRange: UITextRange?) {
_skiaLayer?.skikoView?.input?.setSelectedTextRange(selectedTextRange?.toIntRange())
}
/**
* The range of selected text in a document.
* If the text range has a length, it indicates the currently selected text.
* If it has zero length, it indicates the caret (insertion point).
* If the text-range object is nil, it indicates that there is no current selection.
* https://developer.apple.com/documentation/uikit/uitextinput/1614541-selectedtextrange
*/
override fun selectedTextRange(): UITextRange? {
return _skiaLayer?.skikoView?.input?.getSelectedTextRange()?.toUITextRange()
}
/**
* The range of currently marked text in a document.
* If there is no marked text, the value of the property is nil.
* Marked text is provisionally inserted text that requires user confirmation;
* it occurs in multistage text input.
* The current selection, which can be a caret or an extended range, always occurs within the marked text.
* https://developer.apple.com/documentation/uikit/uitextinput/1614489-markedtextrange
*/
override fun markedTextRange(): UITextRange? {
return _skiaLayer?.skikoView?.input?.markedTextRange()?.toUITextRange()
}
override fun setMarkedTextStyle(markedTextStyle: Map<Any?, *>?) {
// do nothing
}
override fun markedTextStyle(): Map<Any?, *>? {
return null
}
/**
* Inserts the provided text and marks it to indicate that it is part of an active input session.
* Setting marked text either replaces the existing marked text or,
* if none is present, inserts it in place of the current selection.
* https://developer.apple.com/documentation/uikit/uitextinput/1614465-setmarkedtext
* @param markedText The text to be marked.
* @param selectedRange A range within markedText that indicates the current selection.
* This range is always relative to markedText.
*/
override fun setMarkedText(markedText: String?, selectedRange: CValue<NSRange>) {
val (locationRelative, lengthRelative) = selectedRange.useContents {
location.toInt() to length.toInt()
}
val relativeTextRange = locationRelative until locationRelative + lengthRelative
_skiaLayer?.skikoView?.input?.setMarkedText(markedText, relativeTextRange)
}
/**
* Unmarks the currently marked text.
* After this method is called, the value of markedTextRange is nil.
* https://developer.apple.com/documentation/uikit/uitextinput/1614512-unmarktext
*/
override fun unmarkText() {
_skiaLayer?.skikoView?.input?.unmarkText()
}
override fun beginningOfDocument(): UITextPosition {
return IntermediateTextPosition(0)
}
/**
* The text position for the end of a document.
* https://developer.apple.com/documentation/uikit/uitextinput/1614555-endofdocument
*/
override fun endOfDocument(): UITextPosition {
return IntermediateTextPosition(_skiaLayer?.skikoView?.input?.endOfDocument() ?: 0)
}
/**
* Attention! fromPosition and toPosition may be null
*/
override fun textRangeFromPosition(fromPosition: UITextPosition, toPosition: UITextPosition): UITextRange? {
val from = (fromPosition as? IntermediateTextPosition)?.position ?: 0
val to = (toPosition as? IntermediateTextPosition)?.position ?: 0
return IntermediateTextRange(
IntermediateTextPosition(minOf(from, to)),
IntermediateTextPosition(maxOf(from, to))
)
}
/**
* Attention! position may be null
*/
override fun positionFromPosition(position: UITextPosition, offset: NSInteger): UITextPosition? {
val p = (position as? IntermediateTextPosition)?.position ?: return null
val endOfDocument = _skiaLayer?.skikoView?.input?.endOfDocument()
return if (endOfDocument != null) {
IntermediateTextPosition(max(min(p + offset, endOfDocument), 0))
} else {
null
}
}
override fun positionFromPosition(
position: UITextPosition,
inDirection: UITextLayoutDirection,
offset: NSInteger
): UITextPosition? {
return when (inDirection) {
UITextLayoutDirectionLeft, UITextLayoutDirectionUp -> {
positionFromPosition(position, -offset)
}
else -> positionFromPosition(position, offset)
}
}
/**
* Attention! position and toPosition may be null
*/
override fun comparePosition(position: UITextPosition, toPosition: UITextPosition): NSComparisonResult {
val from = (position as? IntermediateTextPosition)?.position ?: 0
val to = (toPosition as? IntermediateTextPosition)?.position ?: 0
val result = if (from < to) {
NSOrderedAscending
} else if (from > to) {
NSOrderedDescending
} else {
NSOrderedSame
}
return result
}
override fun offsetFromPosition(from: UITextPosition, toPosition: UITextPosition): NSInteger {
val fromPosition = from as IntermediateTextPosition
val to = toPosition as IntermediateTextPosition
return to.position - fromPosition.position
}
override fun tokenizer(): UITextInputTokenizerProtocol {
return UITextInputStringTokenizer()
}
override fun positionWithinRange(range: UITextRange, atCharacterOffset: NSInteger): UITextPosition? =
TODO("positionWithinRange range: $range, atCharacterOffset: $atCharacterOffset")
override fun positionWithinRange(range: UITextRange, farthestInDirection: UITextLayoutDirection): UITextPosition? =
TODO("positionWithinRange, farthestInDirection: ${farthestInDirection.directionToStr()}")
override fun characterRangeByExtendingPosition(
position: UITextPosition,
inDirection: UITextLayoutDirection
): UITextRange? {
TODO("characterRangeByExtendingPosition, inDirection: ${inDirection.directionToStr()}")
}
override fun baseWritingDirectionForPosition(
position: UITextPosition,
inDirection: UITextStorageDirection
): NSWritingDirection {
return NSWritingDirectionLeftToRight // TODO support RTL text direction
}
override fun setBaseWritingDirection(writingDirection: NSWritingDirection, forRange: UITextRange) {
// TODO support RTL text direction
}
//Working with Geometry and Hit-Testing. All methods return stubs for now.
override fun firstRectForRange(range: UITextRange): CValue<CGRect> = CGRectNull.readValue()
override fun caretRectForPosition(position: UITextPosition): CValue<CGRect> = CGRectNull.readValue()
override fun selectionRectsForRange(range: UITextRange): List<*> = listOf<UITextSelectionRect>()
override fun closestPositionToPoint(point: CValue<CGPoint>): UITextPosition? = null
override fun closestPositionToPoint(point: CValue<CGPoint>, withinRange: UITextRange): UITextPosition? = null
override fun characterRangeAtPoint(point: CValue<CGPoint>): UITextRange? = null
override fun textStylingAtPosition(position: UITextPosition, inDirection: UITextStorageDirection): Map<Any?, *>? {
return NSDictionary.dictionary()
}
override fun characterOffsetOfPosition(position: UITextPosition, withinRange: UITextRange): NSInteger {
TODO("characterOffsetOfPosition")
}
override fun shouldChangeTextInRange(range: UITextRange, replacementText: String): Boolean {
// Here we should decide to replace text in range or not.
// By default, this method returns true.
return true
}
override fun textInputView(): UIView {
return this
}
override fun canPerformAction(action: COpaquePointer?, withSender: Any?): Boolean =
when (action) {
NSSelectorFromString(UIResponderStandardEditActionsProtocol::copy.name + ":") ->
_currentTextMenuActions?.copy != null
NSSelectorFromString(UIResponderStandardEditActionsProtocol::cut.name + ":") ->
_currentTextMenuActions?.cut != null
NSSelectorFromString(UIResponderStandardEditActionsProtocol::paste.name + ":") ->
_currentTextMenuActions?.paste != null
NSSelectorFromString(UIResponderStandardEditActionsProtocol::selectAll.name + ":") ->
_currentTextMenuActions?.selectAll != null
else -> false
}
override fun keyboardType(): UIKeyboardType = _skikoUITextInputTrains.keyboardType()
override fun keyboardAppearance(): UIKeyboardAppearance = _skikoUITextInputTrains.keyboardAppearance()
override fun returnKeyType(): UIReturnKeyType = _skikoUITextInputTrains.returnKeyType()
override fun textContentType(): UITextContentType? = _skikoUITextInputTrains.textContentType()
override fun isSecureTextEntry(): Boolean = _skikoUITextInputTrains.isSecureTextEntry()
override fun enablesReturnKeyAutomatically(): Boolean = _skikoUITextInputTrains.enablesReturnKeyAutomatically()
override fun autocapitalizationType(): UITextAutocapitalizationType =
_skikoUITextInputTrains.autocapitalizationType()
override fun autocorrectionType(): UITextAutocorrectionType = _skikoUITextInputTrains.autocorrectionType()
override fun dictationRecognitionFailed() {
//todo may be useful
}
override fun dictationRecordingDidEnd() {
//todo may be useful
}
/**
* Call when something changes in text data
*/
fun textWillChange() {
_inputDelegate?.textWillChange(this)
}
/**
* Call when something changes in text data
*/
fun textDidChange() {
_inputDelegate?.textDidChange(this)
}
/**
* Call when something changes in text data
*/
fun selectionWillChange() {
_inputDelegate?.selectionWillChange(this)
}
/**
* Call when something changes in text data
*/
fun selectionDidChange() {
_inputDelegate?.selectionDidChange(this)
}
}
private class IntermediateTextPosition(val position: Long = 0) : UITextPosition()
private fun IntermediateTextRange(start: Int, end: Int) =
IntermediateTextRange(
_start = IntermediateTextPosition(start.toLong()),
_end = IntermediateTextPosition(end.toLong())
)
private class IntermediateTextRange(
private val _start: IntermediateTextPosition,
private val _end: IntermediateTextPosition
) : UITextRange() {
override fun isEmpty() = (_end.position - _start.position) <= 0
override fun start(): UITextPosition = _start
override fun end(): UITextPosition = _end
}
private fun UITextRange.toIntRange(): IntRange {
val start = (start() as IntermediateTextPosition).position.toInt()
val end = (end() as IntermediateTextPosition).position.toInt()
return start until end
} }
private fun IntRange.toUITextRange(): UITextRange =
IntermediateTextRange(start = start, end = endInclusive + 1)
private fun NSWritingDirection.directionToStr() =
when (this) {
UITextLayoutDirectionLeft -> "Left"
UITextLayoutDirectionRight -> "Right"
UITextLayoutDirectionUp -> "Up"
UITextLayoutDirectionDown -> "Down"
else -> "unknown direction"
}
private val UITouch.isPressed
get() =
phase != UITouchPhase.UITouchPhaseEnded &&
phase != UITouchPhase.UITouchPhaseCancelled
package org.jetbrains.skiko
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.UIEvent
actual typealias SkikoGesturePlatformEvent = Any
actual typealias SkikoPlatformInputEvent = KeyboardEvent
actual typealias SkikoPlatformKeyboardEvent = KeyboardEvent
// MouseEvent is base class of PointerEvent
actual typealias SkikoPlatformPointerEvent = UIEvent
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
...@@ -8,7 +8,7 @@ import org.jetbrains.skiko.wasm.createWebGLContext ...@@ -8,7 +8,7 @@ import org.jetbrains.skiko.wasm.createWebGLContext
/** /**
* 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.
* Rendering and events processing should be implemented in [skikoView]. * Rendering and events processing should be implemented in [renderDelegate].
* *
* SkikoLayer needs to be initialized with [HTMLCanvasElement] instance * SkikoLayer needs to be initialized with [HTMLCanvasElement] instance
* using [attachTo] method. * using [attachTo] method.
...@@ -53,10 +53,10 @@ actual open class SkiaLayer { ...@@ -53,10 +53,10 @@ actual open class SkiaLayer {
} }
/** /**
* An implementation of SkikoView with content rendering and * An implementation of [SkikoRenderDelegate] with content rendering and
* event processing logic. * event processing logic.
*/ */
actual var skikoView: SkikoView? = null actual var renderDelegate: SkikoRenderDelegate? = null
/** /**
* @param container - should be an instance of [HTMLCanvasElement] * @param container - should be an instance of [HTMLCanvasElement]
...@@ -76,7 +76,7 @@ actual open class SkiaLayer { ...@@ -76,7 +76,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 [renderDelegate].
*/ */
private fun attachTo(htmlCanvas: HTMLCanvasElement) { private fun attachTo(htmlCanvas: HTMLCanvasElement) {
this.htmlCanvas = htmlCanvas this.htmlCanvas = htmlCanvas
...@@ -85,13 +85,13 @@ actual open class SkiaLayer { ...@@ -85,13 +85,13 @@ actual open class SkiaLayer {
override fun drawFrame(currentTimestamp: Double) { override fun drawFrame(currentTimestamp: Double) {
// currentTimestamp is in milliseconds. // currentTimestamp is in milliseconds.
val currentNanos = currentTimestamp * 1_000_000 val currentNanos = currentTimestamp * 1_000_000
skikoView?.onRender(canvas!!, width, height, currentNanos.toLong()) renderDelegate?.onRender(canvas!!, width, height, currentNanos.toLong())
} }
} }
} }
internal actual fun draw(canvas: Canvas) { internal actual fun draw(canvas: Canvas) {
skikoView?.onRender(canvas, state!!.width, state!!.height, currentNanoTime()) renderDelegate?.onRender(canvas, state!!.width, state!!.height, currentNanoTime())
} }
actual val pixelGeometry: PixelGeometry actual val pixelGeometry: PixelGeometry
......
package org.jetbrains.skiko
actual interface SkikoInput {
fun onInputEvent(event: SkikoInputEvent)
actual object Empty : SkikoInput {
override fun onInputEvent(event: SkikoInputEvent) = Unit
}
}
package org.jetbrains.skiko
actual enum class SkikoKey(actual val platformKeyCode: Int) {
KEY_UNKNOWN(-1),
KEY_A(65),
KEY_S(83),
KEY_D(68),
KEY_F(70),
KEY_H(72),
KEY_G(71),
KEY_Z(90),
KEY_X(88),
KEY_C(67),
KEY_V(86),
KEY_B(66),
KEY_Q(81),
KEY_W(87),
KEY_E(69),
KEY_R(82),
KEY_Y(89),
KEY_T(54),
KEY_U(85),
KEY_I(73),
KEY_P(80),
KEY_L(76),
KEY_J(74),
KEY_K(75),
KEY_N(78),
KEY_M(77),
KEY_O(79),
KEY_1(49),
KEY_2(50),
KEY_3(51),
KEY_4(52),
KEY_5(53),
KEY_6(54),
KEY_7(55),
KEY_8(56),
KEY_9(57),
KEY_0(48),
KEY_CLOSE_BRACKET(221),
KEY_OPEN_BRACKET(219),
KEY_QUOTE(222),
KEY_SEMICOLON(59),
KEY_SLASH(191),
KEY_COMMA(188),
KEY_BACKSLASH(220),
KEY_PERIOD(190),
KEY_BACK_QUOTE(192),
KEY_EQUALS(61),
KEY_MINUS(173),
KEY_ENTER(13),
KEY_ESCAPE(27),
KEY_TAB(9),
KEY_BACKSPACE(8),
KEY_SPACE(32),
KEY_CAPSLOCK(20),
KEY_LEFT_META(224),
KEY_LEFT_SHIFT(16),
KEY_LEFT_ALT(18),
KEY_LEFT_CONTROL(17),
KEY_RIGHT_META(0x80000000.toInt() or 224),
KEY_RIGHT_SHIFT(0x80000000.toInt() or 16),
KEY_RIGHT_ALT(225),
KEY_RIGHT_CONTROL(0x80000000.toInt() or 17),
KEY_MENU(93),
KEY_UP(38),
KEY_DOWN(40),
KEY_LEFT(37),
KEY_RIGHT(39),
KEY_F1(112),
KEY_F2(113),
KEY_F3(114),
KEY_F4(115),
KEY_F5(116),
KEY_F6(117),
KEY_F7(118),
KEY_F8(119),
KEY_F9(120),
KEY_F10(121),
KEY_F11(122),
KEY_F12(123),
KEY_PRINTSCEEN(44),
KEY_SCROLL_LOCK(145),
KEY_PAUSE(19),
KEY_INSERT(45),
KEY_HOME(36),
KEY_PGUP(33),
KEY_DELETE(46),
KEY_END(35),
KEY_PGDOWN(34),
KEY_NUM_LOCK(144),
KEY_NUMPAD_0(96),
KEY_NUMPAD_1(97),
KEY_NUMPAD_2(98),
KEY_NUMPAD_3(99),
KEY_NUMPAD_4(100),
KEY_NUMPAD_5(101),
KEY_NUMPAD_6(102),
KEY_NUMPAD_7(103),
KEY_NUMPAD_8(104),
KEY_NUMPAD_9(105),
KEY_NUMPAD_ENTER(14),
KEY_NUMPAD_ADD(107),
KEY_NUMPAD_SUBTRACT(109),
KEY_NUMPAD_MULTIPLY(106),
KEY_NUMPAD_DIVIDE(111),
KEY_NUMPAD_DECIMAL(110);
companion object {
fun valueOf(platformKeyCode: Int): SkikoKey {
return values().firstOrNull { it.platformKeyCode == platformKeyCode } ?: KEY_UNKNOWN
}
}
}
...@@ -31,16 +31,10 @@ actual open class SkiaLayer { ...@@ -31,16 +31,10 @@ actual open class SkiaLayer {
TODO("unimplemented") TODO("unimplemented")
} }
actual var skikoView: SkikoView? = null actual var renderDelegate: SkikoRenderDelegate? = null
actual val pixelGeometry: PixelGeometry actual val pixelGeometry: PixelGeometry
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
} }
// TODO: do properly
actual typealias SkikoGesturePlatformEvent = Any
actual typealias SkikoPlatformInputEvent = Any
actual typealias SkikoPlatformKeyboardEvent = Any
actual typealias SkikoPlatformPointerEvent = Any
actual val currentSystemTheme: SystemTheme actual val currentSystemTheme: SystemTheme
get() = SystemTheme.UNKNOWN get() = SystemTheme.UNKNOWN
\ No newline at end of file
package org.jetbrains.skiko
actual interface SkikoInput {
actual object Empty : SkikoInput
}
package org.jetbrains.skiko
actual enum class SkikoKey(actual val platformKeyCode: Int) {
KEY_UNKNOWN(-1),
KEY_A(0),
KEY_S(1),
KEY_D(2),
KEY_F(3),
KEY_H(4),
KEY_G(5),
KEY_Z(6),
KEY_X(7),
KEY_C(8),
KEY_V(9),
KEY_B(11),
KEY_Q(12),
KEY_W(13),
KEY_E(14),
KEY_R(15),
KEY_Y(16),
KEY_T(17),
KEY_U(32),
KEY_I(34),
KEY_P(35),
KEY_L(37),
KEY_J(38),
KEY_K(40),
KEY_N(45),
KEY_M(46),
KEY_O(31),
KEY_1(18),
KEY_2(19),
KEY_3(20),
KEY_4(21),
KEY_5(23),
KEY_6(22),
KEY_7(26),
KEY_8(28),
KEY_9(25),
KEY_0(29),
KEY_CLOSE_BRACKET(30),
KEY_OPEN_BRACKET(33),
KEY_QUOTE(39),
KEY_SEMICOLON(41),
KEY_SLASH(42),
KEY_COMMA(43),
KEY_BACKSLASH(44),
KEY_PERIOD(47),
KEY_BACK_QUOTE(50),
KEY_EQUALS(24),
KEY_MINUS(27),
KEY_ENTER(36),
KEY_ESCAPE(53),
KEY_TAB(48),
KEY_BACKSPACE(51),
KEY_SPACE(49),
KEY_CAPSLOCK(57),
KEY_LEFT_META(55),
KEY_LEFT_SHIFT(56),
KEY_LEFT_ALT(58),
KEY_LEFT_CONTROL(59),
KEY_RIGHT_META(54),
KEY_RIGHT_SHIFT(60),
KEY_RIGHT_ALT(61),
KEY_RIGHT_CONTROL(62),
KEY_MENU(110),
KEY_UP(126),
KEY_DOWN(125),
KEY_LEFT(123),
KEY_RIGHT(124),
KEY_F1(122),
KEY_F2(120),
KEY_F3(99),
KEY_F4(118),
KEY_F5(96),
KEY_F6(97),
KEY_F7(98),
KEY_F8(100),
KEY_F9(101),
KEY_F10(109),
KEY_F11(103),
KEY_F12(111),
KEY_PRINTSCEEN(105),
KEY_SCROLL_LOCK(107),
KEY_PAUSE(113),
KEY_INSERT(114),
KEY_HOME(115),
KEY_PGUP(116),
KEY_DELETE(117),
KEY_END(119),
KEY_PGDOWN(121),
KEY_NUM_LOCK(71),
KEY_NUMPAD_0(82),
KEY_NUMPAD_1(83),
KEY_NUMPAD_2(84),
KEY_NUMPAD_3(85),
KEY_NUMPAD_4(86),
KEY_NUMPAD_5(87),
KEY_NUMPAD_6(88),
KEY_NUMPAD_7(88),
KEY_NUMPAD_8(91),
KEY_NUMPAD_9(92),
KEY_NUMPAD_ENTER(76),
KEY_NUMPAD_ADD(69),
KEY_NUMPAD_SUBTRACT(78),
KEY_NUMPAD_MULTIPLY(67),
KEY_NUMPAD_DIVIDE(75),
KEY_NUMPAD_DECIMAL(65);
companion object {
fun valueOf(platformKeyCode: Int): SkikoKey {
val key = SkikoKey.values().firstOrNull { it.platformKeyCode == platformKeyCode }
return if (key == null) SkikoKey.KEY_UNKNOWN else key
}
}
}
package org.jetbrains.skiko
import kotlinx.cinterop.useContents
import platform.AppKit.*
fun toSkikoEvent(
event: NSEvent,
kind: SkikoPointerEventKind,
view: NSView
): SkikoPointerEvent {
var (xpos, ypos) = event.locationInWindow.useContents {
x to y
}
// Translate.
view.frame.useContents {
ypos = size.height - ypos
}
val timestamp = (event.timestamp * 1_000).toLong()
return SkikoPointerEvent(
x = xpos,
y = ypos,
pressedButtons = toSkikoPressedMouseButtons(event, kind),
button = toSkikoMouseButton(event),
modifiers = toSkikoModifiers(event),
kind = kind,
timestamp = timestamp,
platform = event
)
}
fun toSkikoEvent(
event: NSEvent,
button: SkikoMouseButtons,
kind: SkikoPointerEventKind,
view: NSView
): SkikoPointerEvent {
var (xpos, ypos) = event.locationInWindow.useContents {
x to y
}
// Translate.
view.frame.useContents {
ypos = size.height - ypos
}
val timestamp = (event.timestamp * 1_000).toLong()
var buttons: SkikoMouseButtons
if (kind == SkikoPointerEventKind.DOWN) {
buttonsFlags = buttonsFlags.or(button.value)
} else {
buttonsFlags = buttonsFlags.xor(button.value)
}
buttons = SkikoMouseButtons(buttonsFlags)
return SkikoPointerEvent(
x = xpos,
y = ypos,
pressedButtons = buttons,
button = button,
modifiers = toSkikoModifiers(event),
kind = kind,
timestamp = timestamp,
platform = event
)
}
fun toSkikoScrollEvent(
event: NSEvent,
view: NSView
): SkikoPointerEvent {
var (xpos, ypos) = event.locationInWindow.useContents {
x to y
}
// Translate.
view.frame.useContents {
ypos = size.height - ypos
}
val timestamp = (event.timestamp * 1_000).toLong()
return SkikoPointerEvent(
x = xpos,
y = ypos,
deltaX = event.deltaX,
deltaY = event.deltaY,
pressedButtons = SkikoMouseButtons(buttonsFlags),
button = SkikoMouseButtons.NONE,
modifiers = toSkikoModifiers(event),
kind = SkikoPointerEventKind.SCROLL,
timestamp = timestamp,
platform = event
)
}
fun toSkikoEvent(
event: NSEvent,
kind: SkikoKeyboardEventKind
): SkikoKeyboardEvent {
val timestamp = (event.timestamp * 1_000).toLong()
return SkikoKeyboardEvent(
SkikoKey.valueOf(event.keyCode.toInt()),
toSkikoModifiers(event),
kind,
timestamp,
event
)
}
fun toSkikoTypeEvent(
character: String,
event: NSEvent?,
): SkikoInputEvent {
val key = if (event != null) SkikoKey.valueOf(event.keyCode.toInt()) else SkikoKey.KEY_UNKNOWN
val modifiers = if (event != null) toSkikoModifiers(event) else SkikoInputModifiers.EMPTY
return SkikoInputEvent(
character,
key,
modifiers,
SkikoKeyboardEventKind.TYPE,
event
)
}
private val actionAllKeyUpFlag = 256.toULong() // MacOS specific
private var modifierKeyUpFlag = actionAllKeyUpFlag
fun toSkikoEvent(
event: NSEvent
): SkikoKeyboardEvent {
// NSView.keyDown(NSEvent)/keyUp(NSEvent) does not work with modifier keys,
// so we need to use NSView.flagsChanged(NSEvent)
// and convert modifier key event to regular key event.
// Unfortunately, NSEvent does not give us any information about the type of action,
// so we need to check it manually.
val action = event.modifierFlags and NSEventModifierFlagDeviceIndependentFlagsMask.inv()
var kind = SkikoKeyboardEventKind.DOWN
val key = SkikoKey.valueOf(event.keyCode.toInt())
if (action >= modifierKeyUpFlag) {
kind = SkikoKeyboardEventKind.DOWN
} else {
kind = SkikoKeyboardEventKind.UP
}
modifierKeyUpFlag = action
if (modifierKeyUpFlag == actionAllKeyUpFlag) {
kind = SkikoKeyboardEventKind.UP
}
val timestamp = (event.timestamp * 1_000).toLong()
return SkikoKeyboardEvent(
SkikoKey.valueOf(event.keyCode.toInt()),
toSkikoModifiers(event),
kind,
timestamp,
event
)
}
private var buttonsFlags = 0
private fun toSkikoPressedMouseButtons(
event: NSEvent,
kind: SkikoPointerEventKind
): SkikoMouseButtons {
val button = event.buttonNumber.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: NSEvent): SkikoMouseButtons {
return SkikoMouseButtons(getSkikoButtonValue(event.buttonNumber.toInt()))
}
private fun getSkikoButtonValue(button: Int): Int {
return when (button) {
2 -> SkikoMouseButtons.MIDDLE.value
3 -> SkikoMouseButtons.BUTTON_4.value
4 -> SkikoMouseButtons.BUTTON_5.value
5 -> SkikoMouseButtons.BUTTON_6.value
6 -> SkikoMouseButtons.BUTTON_7.value
7 -> SkikoMouseButtons.BUTTON_8.value
else -> 0
}
}
private fun toSkikoModifiers(event: NSEvent): SkikoInputModifiers {
var result = 0
val modifiers = event.modifierFlags
if (modifiers and NSEventModifierFlagOption != 0.toULong()) {
result = result.or(SkikoInputModifiers.ALT.value)
}
if (modifiers and NSEventModifierFlagShift != 0.toULong()) {
result = result.or(SkikoInputModifiers.SHIFT.value)
}
if (modifiers and NSEventModifierFlagControl != 0.toULong()) {
result = result.or(SkikoInputModifiers.CONTROL.value)
}
if (modifiers and NSEventModifierFlagCommand != 0.toULong()) {
result = result.or(SkikoInputModifiers.META.value)
}
return SkikoInputModifiers(result)
}
private fun keyToModifier(key: SkikoKey): ULong {
return when (key) {
SkikoKey.KEY_LEFT_ALT,
SkikoKey.KEY_RIGHT_ALT -> {
NSEventModifierFlagOption
}
SkikoKey.KEY_LEFT_SHIFT,
SkikoKey.KEY_RIGHT_SHIFT -> {
NSEventModifierFlagShift
}
SkikoKey.KEY_LEFT_CONTROL,
SkikoKey.KEY_RIGHT_CONTROL -> {
NSEventModifierFlagControl
}
SkikoKey.KEY_LEFT_META,
SkikoKey.KEY_RIGHT_META -> {
NSEventModifierFlagCommand
}
else -> 0.toULong()
}
}
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
import kotlinx.cinterop.BetaInteropApi
import kotlinx.cinterop.ObjCAction import kotlinx.cinterop.ObjCAction
import kotlinx.cinterop.useContents import kotlinx.cinterop.useContents
import kotlinx.cinterop.CValue
import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.convert
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skiko.redrawer.MacOsOpenGLRedrawer
import org.jetbrains.skiko.redrawer.Redrawer import org.jetbrains.skiko.redrawer.Redrawer
import platform.AppKit.* import platform.AppKit.*
import platform.Foundation.NSAttributedString
import platform.Foundation.NSMutableAttributedString
import platform.Foundation.NSMakeRect
import platform.Foundation.NSMakeRange
import platform.Foundation.NSRange
import platform.Foundation.NSRangePointer
import platform.Foundation.NSPoint
import platform.Foundation.NSRect
import platform.Foundation.NSNotFound
import platform.Foundation.NSNotification import platform.Foundation.NSNotification
import platform.Foundation.NSNotificationCenter import platform.Foundation.NSNotificationCenter
import platform.Foundation.NSSelectorFromString import platform.Foundation.NSSelectorFromString
import platform.Foundation.addObserver
import platform.darwin.NSObject import platform.darwin.NSObject
import platform.CoreGraphics.CGRectMake
/** /**
* SkiaLayer implementation for macOS. * SkiaLayer implementation for macOS.
* Supports only [GraphicsApi.METAL] * Supports only [GraphicsApi.METAL]
*/ */
@OptIn(BetaInteropApi::class)
actual open class SkiaLayer { actual open class SkiaLayer {
fun isShowing(): Boolean { fun isShowing(): Boolean {
return true return true
...@@ -75,6 +62,7 @@ actual open class SkiaLayer { ...@@ -75,6 +62,7 @@ actual open class SkiaLayer {
* Underlying [NSView] * Underlying [NSView]
*/ */
lateinit var nsView: NSView lateinit var nsView: NSView
private set
actual val component: Any? actual val component: Any?
get() = this.nsView get() = this.nsView
...@@ -82,7 +70,7 @@ actual open class SkiaLayer { ...@@ -82,7 +70,7 @@ actual open class SkiaLayer {
/** /**
* Implements rendering logic and events processing. * Implements rendering logic and events processing.
*/ */
actual var skikoView: SkikoView? = null actual var renderDelegate: SkikoRenderDelegate? = null
internal var redrawer: Redrawer? = null internal var redrawer: Redrawer? = null
...@@ -93,154 +81,50 @@ actual open class SkiaLayer { ...@@ -93,154 +81,50 @@ actual open class SkiaLayer {
private var picture: PictureHolder? = null private var picture: PictureHolder? = null
private val pictureRecorder = PictureRecorder() private val pictureRecorder = PictureRecorder()
/** private val nsViewObserver = object : NSObject() {
* @param container - should be an instance of [NSWindow]
*/
actual fun attachTo(container: Any) {
attachTo(container as NSWindow)
}
/**
* Initializes the [nsView] then adds it to [window]. Initializes events listeners.
* Delegates events processing to [skikoView].
*/
fun attachTo(window: NSWindow) {
val (width, height) = window.contentLayoutRect.useContents {
this.size.width to this.size.height
}
nsView = object : NSView(NSMakeRect(0.0, 0.0, width, height)), NSTextInputClientProtocol {
private var trackingArea : NSTrackingArea? = null
override fun wantsUpdateLayer() = true
override fun acceptsFirstResponder() = true
override fun viewWillMoveToWindow(newWindow: NSWindow?) {
updateTrackingAreas()
}
override fun updateTrackingAreas() {
trackingArea?.let { removeTrackingArea(it) }
trackingArea = NSTrackingArea(
rect = bounds,
options = NSTrackingActiveAlways or
NSTrackingMouseEnteredAndExited or
NSTrackingMouseMoved or
NSTrackingActiveInKeyWindow or
NSTrackingAssumeInside or
NSTrackingInVisibleRect,
owner = nsView, userInfo = null)
nsView.addTrackingArea(trackingArea!!)
}
override fun mouseDown(event: NSEvent) {
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoMouseButtons.LEFT, SkikoPointerEventKind.DOWN, nsView))
}
override fun mouseUp(event: NSEvent) {
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoMouseButtons.LEFT, SkikoPointerEventKind.UP, nsView))
}
override fun rightMouseDown(event: NSEvent) {
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoMouseButtons.RIGHT, SkikoPointerEventKind.DOWN, nsView))
}
override fun rightMouseUp(event: NSEvent) {
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoMouseButtons.RIGHT, SkikoPointerEventKind.UP, nsView))
}
override fun otherMouseDown(event: NSEvent) {
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.DOWN, nsView))
}
override fun otherMouseUp(event: NSEvent) {
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.UP, nsView))
}
override fun mouseMoved(event: NSEvent) {
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.MOVE, nsView))
}
override fun mouseDragged(event: NSEvent) {
skikoView?.onPointerEvent(toSkikoEvent(event, SkikoPointerEventKind.DRAG, nsView))
}
override fun scrollWheel(event: NSEvent) {
skikoView?.onPointerEvent(toSkikoScrollEvent(event, nsView))
}
override fun keyDown(event: NSEvent) {
keyEvent = event
skikoView?.onKeyboardEvent(toSkikoEvent(event, SkikoKeyboardEventKind.DOWN))
interpretKeyEvents(listOf(event))
}
override fun flagsChanged(event: NSEvent) {
keyEvent = event
skikoView?.onKeyboardEvent(toSkikoEvent(event))
}
override fun keyUp(event: NSEvent) {
keyEvent = event
skikoView?.onKeyboardEvent(toSkikoEvent(event, SkikoKeyboardEventKind.UP))
}
private var keyEvent: NSEvent? = null
private var markedText: String = ""
private val kEmptyRange = NSMakeRange(NSNotFound.convert(), 0)
override fun attributedSubstringForProposedRange(range: CValue<NSRange>, actualRange: NSRangePointer?) = null
override fun hasMarkedText(): Boolean {
return markedText.length > 0
}
override fun markedRange(): CValue<NSRange> {
if (markedText.length > 0) {
return NSMakeRange(0, (markedText.length - 1).convert())
}
return kEmptyRange
}
override fun selectedRange() = kEmptyRange
override fun characterIndexForPoint(point: CValue<NSPoint>) = 0.toULong()
override fun doCommandBySelector(selector: COpaquePointer?) {}
override fun firstRectForCharacterRange(range: CValue<NSRange>, actualRange: NSRangePointer?): CValue<NSRect> {
val (x, y) = window.frame.useContents {
origin.x to origin.y
}
return NSMakeRect(x, y, 0.0, 0.0)
}
override fun insertText(string: Any, replacementRange: CValue<NSRange>) {
val character = when(string) {
is NSAttributedString -> string.string
else -> string as String
}
skikoView?.onInputEvent(toSkikoTypeEvent(character, keyEvent))
}
override fun setMarkedText(string: Any, selectedRange: CValue<NSRange>, replacementRange: CValue<NSRange>) {
if (string is NSAttributedString) {
markedText = string.string
} else {
markedText = string as String
}
}
override fun unmarkText() {
markedText = ""
}
override fun validAttributesForMarkedText(): List<*> {
return listOf<Any?>()
}
@ObjCAction @ObjCAction
open fun onWindowClose(arg: NSObject?) { fun frameDidChange(notification: NSNotification) {
detach()
val center = NSNotificationCenter.defaultCenter()
center.removeObserver(nsView)
}
}
val center = NSNotificationCenter.defaultCenter()
center.addObserver(nsView, NSSelectorFromString("onWindowClose:"),
NSWindowWillCloseNotification!!, window)
window.delegate = object : NSObject(), NSWindowDelegateProtocol {
override fun windowDidResize(notification: NSNotification) {
val (w, h) = window.contentView!!.frame.useContents {
size.width to size.height
}
nsView.frame = CGRectMake(0.0, 0.0, w, h)
redrawer?.syncSize() redrawer?.syncSize()
redrawer?.redrawImmediately() redrawer?.redrawImmediately()
} }
override fun windowDidChangeBackingProperties(notification: NSNotification) { @ObjCAction
fun windowDidChangeBackingProperties(notification: NSNotification) {
redrawer?.syncSize() redrawer?.syncSize()
redrawer?.redrawImmediately() redrawer?.redrawImmediately()
} }
fun addObserver() {
val center = NSNotificationCenter.defaultCenter()
center.addObserver(
observer = this,
selector = NSSelectorFromString("frameDidChange:"),
name = NSViewFrameDidChangeNotification,
`object` = nsView,
)
center.addObserver(
observer = this,
selector = NSSelectorFromString("windowDidChangeBackingProperties:"),
name = NSWindowDidChangeBackingPropertiesNotification,
`object` = nsView.window,
)
}
fun removeObserver() {
val center = NSNotificationCenter.defaultCenter()
center.removeObserver(this)
} }
window.contentView!!.addSubview(nsView) }
window.makeFirstResponder(nsView)
/**
* @param container - should be an instance of [NSView]
*/
actual fun attachTo(container: Any) {
check(!this::nsView.isInitialized) { "Already attached to another NSView" }
check(container is NSView) { "container should be an instance of NSView" }
nsView = container
nsView.postsFrameChangedNotifications = true
nsViewObserver.addObserver()
redrawer = createNativeRedrawer(this, renderApi).apply { redrawer = createNativeRedrawer(this, renderApi).apply {
syncSize() syncSize()
needRedraw() needRedraw()
...@@ -248,6 +132,7 @@ actual open class SkiaLayer { ...@@ -248,6 +132,7 @@ actual open class SkiaLayer {
} }
actual fun detach() { actual fun detach() {
nsViewObserver.removeObserver()
redrawer?.dispose() redrawer?.dispose()
redrawer = null redrawer = null
} }
...@@ -271,7 +156,7 @@ actual open class SkiaLayer { ...@@ -271,7 +156,7 @@ actual open class SkiaLayer {
val bounds = Rect.makeWH(pictureWidth.toFloat(), pictureHeight.toFloat()) val bounds = Rect.makeWH(pictureWidth.toFloat(), pictureHeight.toFloat())
val canvas = pictureRecorder.beginRecording(bounds) val canvas = pictureRecorder.beginRecording(bounds)
skikoView?.onRender(canvas, pictureWidth.toInt(), pictureHeight.toInt(), nanoTime) renderDelegate?.onRender(canvas, pictureWidth.toInt(), pictureHeight.toInt(), nanoTime)
val picture = pictureRecorder.finishRecordingAsPicture() val picture = pictureRecorder.finishRecordingAsPicture()
this.picture = PictureHolder(picture, pictureWidth.toInt(), pictureHeight.toInt()) this.picture = PictureHolder(picture, pictureWidth.toInt(), pictureHeight.toInt())
...@@ -286,9 +171,3 @@ actual open class SkiaLayer { ...@@ -286,9 +171,3 @@ actual open class SkiaLayer {
actual val pixelGeometry: PixelGeometry actual val pixelGeometry: PixelGeometry
get() = PixelGeometry.UNKNOWN get() = PixelGeometry.UNKNOWN
} }
// TODO: do properly
actual typealias SkikoGesturePlatformEvent = NSEvent
actual typealias SkikoPlatformInputEvent = NSEvent
actual typealias SkikoPlatformKeyboardEvent = NSEvent
actual typealias SkikoPlatformPointerEvent = NSEvent
package org.jetbrains.skiko
actual interface SkikoInput {
actual object Empty : SkikoInput
}
package org.jetbrains.skiko
actual enum class SkikoKey(actual val platformKeyCode: Int) {
KEY_UNKNOWN(-1),
KEY_A(0),
KEY_S(1),
KEY_D(2),
KEY_F(3),
KEY_H(4),
KEY_G(5),
KEY_Z(6),
KEY_X(7),
KEY_C(8),
KEY_V(9),
KEY_B(11),
KEY_Q(12),
KEY_W(13),
KEY_E(14),
KEY_R(15),
KEY_Y(16),
KEY_T(17),
KEY_U(32),
KEY_I(34),
KEY_P(35),
KEY_L(37),
KEY_J(38),
KEY_K(40),
KEY_N(45),
KEY_M(46),
KEY_O(31),
KEY_1(18),
KEY_2(19),
KEY_3(20),
KEY_4(21),
KEY_5(23),
KEY_6(22),
KEY_7(26),
KEY_8(28),
KEY_9(25),
KEY_0(29),
KEY_CLOSE_BRACKET(30),
KEY_OPEN_BRACKET(33),
KEY_QUOTE(39),
KEY_SEMICOLON(41),
KEY_SLASH(42),
KEY_COMMA(43),
KEY_BACKSLASH(44),
KEY_PERIOD(47),
KEY_BACK_QUOTE(50),
KEY_EQUALS(24),
KEY_MINUS(27),
KEY_ENTER(36),
KEY_ESCAPE(53),
KEY_TAB(48),
KEY_BACKSPACE(51),
KEY_SPACE(49),
KEY_CAPSLOCK(57),
KEY_LEFT_META(55),
KEY_LEFT_SHIFT(56),
KEY_LEFT_ALT(58),
KEY_LEFT_CONTROL(59),
KEY_RIGHT_META(54),
KEY_RIGHT_SHIFT(60),
KEY_RIGHT_ALT(61),
KEY_RIGHT_CONTROL(62),
KEY_MENU(110),
KEY_UP(126),
KEY_DOWN(125),
KEY_LEFT(123),
KEY_RIGHT(124),
KEY_F1(122),
KEY_F2(120),
KEY_F3(99),
KEY_F4(118),
KEY_F5(96),
KEY_F6(97),
KEY_F7(98),
KEY_F8(100),
KEY_F9(101),
KEY_F10(109),
KEY_F11(103),
KEY_F12(111),
KEY_PRINTSCEEN(105),
KEY_SCROLL_LOCK(107),
KEY_PAUSE(113),
KEY_INSERT(114),
KEY_HOME(115),
KEY_PGUP(116),
KEY_DELETE(117),
KEY_END(119),
KEY_PGDOWN(121),
KEY_NUM_LOCK(71),
KEY_NUMPAD_0(82),
KEY_NUMPAD_1(83),
KEY_NUMPAD_2(84),
KEY_NUMPAD_3(85),
KEY_NUMPAD_4(86),
KEY_NUMPAD_5(87),
KEY_NUMPAD_6(88),
KEY_NUMPAD_7(88),
KEY_NUMPAD_8(91),
KEY_NUMPAD_9(92),
KEY_NUMPAD_ENTER(76),
KEY_NUMPAD_ADD(69),
KEY_NUMPAD_SUBTRACT(78),
KEY_NUMPAD_MULTIPLY(67),
KEY_NUMPAD_DIVIDE(75),
KEY_NUMPAD_DECIMAL(65);
companion object {
fun valueOf(platformKeyCode: Int): SkikoKey {
val key = SkikoKey.values().firstOrNull { it.platformKeyCode == platformKeyCode }
return if (key == null) SkikoKey.KEY_UNKNOWN else key
}
}
}
package org.jetbrains.skiko
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.UIEvent
actual typealias SkikoGesturePlatformEvent = Any
actual typealias SkikoPlatformInputEvent = KeyboardEvent
actual typealias SkikoPlatformKeyboardEvent = KeyboardEvent
actual typealias SkikoPlatformPointerEvent = UIEvent
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