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
```kotlin
fun main() {
val skiaLayer = SkiaLayer()
skiaLayer.skikoView = GenericSkikoView(skiaLayer, object : SkikoView {
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object : SkikoRenderDelegate {
val paint = Paint().apply {
color = Color.RED
}
......@@ -108,8 +108,7 @@ class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
window!!.rootViewController = SkikoViewController(
SkikoUIView(
SkiaLayer().apply {
gesturesToListen = SkikoGestureEventKind.values()
skikoView = GenericSkikoView(skiaLayer, object : SkikoView {
renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object : SkikoRenderDelegate {
val paint = Paint().apply { color = Color.RED }
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
canvas.clear(Color.CYAN)
......
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.ParagraphBuilder
import org.jetbrains.skia.paragraph.ParagraphStyle
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.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 fpsCounter = FPSCounter()
private val platformYOffset = if (hostOs == OS.Ios) 50f else 5f
private var frame = 0
private var xpos = 0.0
private var ypos = 0.0
private var xpos = 0.0f
private var ypos = 0.0f
private var xOffset = 0.0
private var yOffset = 0.0
private var scale = 1.0
......@@ -25,7 +35,14 @@ class Clocks(private val layer: SkiaLayer): SkikoView {
private val fontCollection = FontCollection()
.setDefaultFontManager(FontMgr.default)
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) {
if (withFps) fpsCounter.tick()
......@@ -92,20 +109,12 @@ class Clocks(private val layer: SkiaLayer): SkikoView {
val renderInfo = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFF000000.toInt()))
.addText("Graphics API: ${layer.renderApi} ✿゚ ${currentSystemTheme}${maybeFps}")
.addText("Graphics API: ${skiaLayer.renderApi} ✿゚ ${currentSystemTheme}${maybeFps}")
.popStyle()
.build()
renderInfo.layout(Float.POSITIVE_INFINITY)
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)
.pushStyle(TextStyle().setColor(0xff9BC730L.toInt()).setFontSize(20f))
.addText("Frames: ${frame++}\nAngle: $rotate")
......@@ -116,116 +125,4 @@ class Clocks(private val layer: SkiaLayer): SkikoView {
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
import android.app.Activity
import android.os.Bundle
import android.view.WindowManager
import android.widget.LinearLayout
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoGestureEventKind
import org.jetbrains.skiko.SkiaLayerRenderDelegate
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
......@@ -21,10 +19,8 @@ class MainActivity : Activity() {
//layoutParams = ViewGroup.LayoutParams(1000, 1200)
}
val skiaLayer = SkiaLayer().apply {
gesturesToListen = SkikoGestureEventKind.values()
}
skiaLayer.skikoView = GenericSkikoView(skiaLayer, Clocks(skiaLayer))
val skiaLayer = SkiaLayer()
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, Clocks(skiaLayer, holder))
skiaLayer.attachTo(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
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
if (System.getProperty("skiko.transparency") == "true") {
......
......@@ -3,11 +3,13 @@ package SkiaAwtSample
import org.jetbrains.skia.*
import org.jetbrains.skia.paragraph.*
import org.jetbrains.skiko.*
import java.awt.event.MouseEvent
import java.awt.event.MouseMotionListener
import kotlin.math.PI
import kotlin.math.cos
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 })
private val typeface = Typeface.makeFromFile("fonts/JetBrainsMono-Regular.ttf")
......@@ -22,8 +24,8 @@ open class ClocksAwt(private val scaleProvider: () -> Float) : SkikoView {
}
private var frame = 0
private var xpos = 0.0
private var ypos = 0.0
private var xpos = 0
private var ypos = 0
private val fontCollection = FontCollection()
.setDefaultFontManager(FontMgr.default)
......@@ -108,23 +110,11 @@ open class ClocksAwt(private val scaleProvider: () -> Float) : SkikoView {
canvas.drawLine(left, top + rectH, left + rectW, top, Paint())
}
override fun onPointerEvent(event: SkikoPointerEvent) {
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 mouseDragged(e: MouseEvent) {
}
override fun onKeyboardEvent(event: SkikoKeyboardEvent) {
// TODO: provide example that covers all features of keyboard event
override fun mouseMoved(e: MouseEvent) {
xpos = e.x
ypos = e.y
}
}
\ No newline at end of file
......@@ -23,6 +23,7 @@ fun swingSkiaResize() = SwingUtilities.invokeLater {
}
skiaPanel = SkiaSwingPanel(skikoView)
skiaPanel.addMouseMotionListener(skikoView)
val leftPanel = JPanel().apply {
background = Color.CYAN
......
package SkiaAwtSample
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayerRenderDelegate
import java.awt.BorderLayout
import java.awt.Color
import java.awt.Dimension
......@@ -85,7 +85,9 @@ fun swingSkia() = SwingUtilities.invokeLater {
private fun getSkiaPanel(): SkiaPanel {
return SkiaPanel().apply {
layer.addView(GenericSkikoView(layer, ClocksAwt(layer)))
val clocks = ClocksAwt(layer)
layer.renderDelegate = SkiaLayerRenderDelegate(layer, clocks)
layer.addMouseMotionListener(clocks)
val btnPanelOK = JPanel()
btnPanelOK.layout = BorderLayout(0, 0)
btnPanelOK.background = Color.white
......
......@@ -72,19 +72,19 @@ kotlin {
}
}
val jsWasmMain by creating {
val webMain by creating {
dependsOn(commonMain)
resources.setSrcDirs(resources.srcDirs)
resources.srcDirs(unzipTask.map { it.destinationDir })
}
val jsMain by getting {
dependsOn(jsWasmMain)
dependsOn(webMain)
}
val wasmJsMain by getting {
dependsOn(jsWasmMain)
dependsOn(webMain)
}
}
}
......
......@@ -3,11 +3,11 @@ package org.jetbrains.skiko.sample.js
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Paint
import org.jetbrains.skia.Rect
import org.jetbrains.skiko.GenericSkikoView
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()
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
......@@ -29,7 +29,7 @@ internal fun runApp() {
} else {
BouncingBalls()
}
skiaLayer.skikoView = GenericSkikoView(skiaLayer, app)
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, app)
skiaLayer.attachTo(canvas)
skiaLayer.needRedraw()
}
......
......@@ -4,12 +4,12 @@ import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Color4f
import org.jetbrains.skia.Paint
import org.jetbrains.skia.PaintMode
import org.jetbrains.skiko.SkikoView
import org.jetbrains.skiko.SkikoRenderDelegate
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
class BouncingBalls: SkikoView {
class BouncingBalls: SkikoRenderDelegate {
private data class Circle(var x: Float, var y: Float, var r: Float)
companion object {
......
......@@ -21,9 +21,8 @@ plugins {
repositories {
google()
mavenCentral()
mavenLocal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
// mavenLocal()
}
val osName = System.getProperty("os.name")
......@@ -137,18 +136,18 @@ kotlin {
}
}
val jsWasmMain by creating {
val webMain by creating {
dependsOn(commonMain)
resources.setSrcDirs(resources.srcDirs)
resources.srcDirs(unzipTask.map { it.destinationDir })
}
val jsMain by getting {
dependsOn(jsWasmMain)
dependsOn(webMain)
}
val wasmJsMain by getting {
dependsOn(jsWasmMain)
dependsOn(webMain)
}
val darwinMain by creating {
......
package org.jetbrains.skiko.sample
import org.jetbrains.skiko.*
import java.awt.Dimension
import javax.swing.*
fun main() {
val skiaLayer = SkiaLayer()
skiaLayer.addView(GenericSkikoView(skiaLayer, AwtClocks(skiaLayer)))
val clocks = AwtClocks(skiaLayer)
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, clocks)
SwingUtilities.invokeLater {
val window = JFrame("Skiko example").apply {
defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
......
package org.jetbrains.skiko.sample
import org.jetbrains.skiko.CursorManager
import org.jetbrains.skiko.PredefinedCursors
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) {
override var inputText: String = "Hello, AwtClocks"
class AwtClocks(private val layer: SkiaLayer) : Clocks(layer.renderApi), MouseMotionListener, MouseWheelListener {
private val cursorManager = CursorManager()
override fun handleBackspace() {
if (inputText.isNotEmpty()) {
inputText = inputText.dropLast(1)
}
init {
layer.addMouseMotionListener(this)
}
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) {
if (event.input != "\b") {
inputText += event.input
override fun mouseMoved(event: MouseEvent) {
if (event.x > 200) {
cursorManager.setCursor(layer.component, PredefinedCursors.HAND)
} else {
cursorManager.setCursor(layer.component, PredefinedCursors.DEFAULT)
}
xpos = event.x.toDouble()
ypos = event.y.toDouble()
}
}
\ No newline at end of file
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
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.ParagraphBuilder
import org.jetbrains.skia.paragraph.ParagraphStyle
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.sin
import kotlin.math.PI
abstract class Clocks(private val layer: SkiaLayer): SkikoView {
private val cursorManager = CursorManager()
abstract class Clocks(private val renderApi: GraphicsApi): SkikoRenderDelegate {
private val withFps = true
private val fpsCounter = FPSCounter()
private val platformYOffset = if (hostOs == OS.Ios) 50f else 5f
private var frame = 0
private var xpos = 0.0
private var ypos = 0.0
private var xOffset = 0.0
private var yOffset = 0.0
private var scale = 1.0
private var k = scale
private var rotate = 0.0
var xpos = 0.0
var ypos = 0.0
var xOffset = 0.0
var yOffset = 0.0
var scale = 1.0
var rotate = 0.0
private val fontCollection = FontCollection()
.setDefaultFontManager(FontMgr.default)
private val style = ParagraphStyle()
abstract val inputText:String
abstract fun handleBackspace()
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
if (withFps) fpsCounter.tick()
canvas.translate(xOffset.toFloat(), yOffset.toFloat())
......@@ -95,20 +102,12 @@ abstract class Clocks(private val layer: SkiaLayer): SkikoView {
val renderInfo = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFF000000.toInt()))
.addText("Graphics API: ${layer.renderApi} ✿゚ ${currentSystemTheme}${maybeFps}")
.addText("Graphics API: $renderApi ✿゚ ${currentSystemTheme}${maybeFps}")
.popStyle()
.build()
renderInfo.layout(Float.POSITIVE_INFINITY)
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)
.pushStyle(TextStyle().setColor(0xff9BC730L.toInt()).setFontSize(20f))
.addText("Frames: ${frame++}\nAngle: $rotate")
......@@ -119,113 +118,4 @@ abstract class Clocks(private val layer: SkiaLayer): SkikoView {
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 -> {}
}
}
}
\ No newline at end of file
}
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.
package org.jetbrains.skiko.sample
import kotlinx.cinterop.*
import platform.UIKit.*
import platform.Foundation.*
import kotlinx.cinterop.autoreleasepool
import kotlinx.cinterop.cstr
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() {
val args = emptyArray<String>()
......@@ -19,7 +33,7 @@ fun main() {
class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta
@ObjCObjectBase.OverrideInit
@OverrideInit
constructor() : super()
private var _window: UIWindow? = null
......@@ -27,11 +41,22 @@ class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
override fun setWindow(window: UIWindow?) {
_window = window
}
override fun application(
application: UIApplication,
didFinishLaunchingWithOptions: Map<Any?, *>?
): Boolean {
val skiaLayer = SkiaLayer()
val clocks = IosClocks(skiaLayer)
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, clocks)
dispatch_async(dispatch_get_main_queue()) {
skiaLayer.needRedraw()
}
override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map<Any?, *>?): Boolean {
window = UIWindow(frame = UIScreen.mainScreen.bounds)
window!!.rootViewController = getSkikoViewContoller()
window!!.makeKeyAndVisible()
window = UIWindow(frame = UIScreen.mainScreen.bounds).also {
it.rootViewController = clocks.viewController
it.makeKeyAndVisible()
}
return true
}
}
......@@ -2,24 +2,21 @@ package org.jetbrains.skiko.sample
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoUIView
import org.jetbrains.skiko.SkikoInput
import platform.UIKit.NSLayoutConstraint
import platform.UIKit.UIViewController
class IosClocks(layer: SkiaLayer) : Clocks(layer) {
override var inputText: String = "Hello, IosClocks"
override fun handleBackspace() {
if (inputText.isNotEmpty()) {
inputText = inputText.dropLast(1)
}
}
override val input: SkikoInput = object : SkikoInput by SkikoInput.Empty {
override fun insertText(text: String) {
inputText += text
}
override fun deleteBackward() {
handleBackspace()
}
class IosClocks(skiaLayer: SkiaLayer) : Clocks(skiaLayer.renderApi) {
val viewController: UIViewController
init {
val view = SkikoUIView(skiaLayer)
view.translatesAutoresizingMaskIntoConstraints = false
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)
))
}
}
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
import kotlinx.browser.document
import kotlinx.browser.window
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerRenderDelegate
import org.jetbrains.skiko.wasm.onWasmReady
import org.w3c.dom.HTMLCanvasElement
......@@ -14,11 +14,10 @@ fun main() {
}
internal fun runApp() {
val skiaLayer = SkiaLayer()
val game = JsClocks(skiaLayer)
skiaLayer.skikoView = GenericSkikoView(skiaLayer, game)
val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement
canvas.setAttribute("tabindex", "0")
skiaLayer.attachTo(canvas)
val skiaLayer = SkiaLayer()
val clocks = WebClocks(skiaLayer, canvas)
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, clocks)
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
import platform.AppKit.*
import org.jetbrains.skiko.*
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerRenderDelegate
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.NSSelectorFromString
import platform.darwin.NSObject
fun makeApp(skiaLayer: SkiaLayer) = MacosClocks(skiaLayer)
fun main() {
val app = NSApplication.sharedApplication()
app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular)
......@@ -30,7 +37,7 @@ fun main() {
NSWindowStyleMaskMiniaturizable or
NSWindowStyleMaskClosable or
NSWindowStyleMaskResizable
val window = object: NSWindow(
val window = object : NSWindow(
contentRect = NSMakeRect(0.0, 0.0, 640.0, 480.0),
styleMask = windowStyle,
backing = NSBackingStoreBuffered,
......@@ -40,8 +47,8 @@ fun main() {
override fun canBecomeMainWindow() = true
}
val skiaLayer = SkiaLayer()
skiaLayer.skikoView = GenericSkikoView(skiaLayer, makeApp(skiaLayer))
skiaLayer.attachTo(window)
val clocks = MacosClocks(skiaLayer, window)
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, clocks)
window.makeKeyAndOrderFront(app)
app.run()
}
package org.jetbrains.skiko.sample
import kotlinx.cinterop.useContents
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) {
override var inputText: String = ""
class MacosClocks(skiaLayer: SkiaLayer, window: NSWindow) : Clocks(skiaLayer.renderApi) {
init {
val nsView = object : NSView(window.frame) {
private var trackingArea : NSTrackingArea? = null
override fun handleBackspace() {
if (inputText.isNotEmpty()) {
inputText = inputText.dropLast(1)
override fun updateTrackingAreas() {
trackingArea?.let { removeTrackingArea(it) }
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) {
inputText += event.input
val contentView = window.contentView!!
nsView.autoresizingMask = NSViewHeightSizable or NSViewWidthSizable
contentView.addSubview(nsView)
skiaLayer.attachTo(nsView)
}
}
package org.jetbrains.skiko.sample
import kotlinx.browser.document
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerRenderDelegate
import org.w3c.dom.HTMLCanvasElement
fun main() {
......@@ -10,11 +10,10 @@ fun main() {
}
internal fun runApp() {
val skiaLayer = SkiaLayer()
val game = JsClocks(skiaLayer)
skiaLayer.skikoView = GenericSkikoView(skiaLayer, game)
val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement
canvas.setAttribute("tabindex", "0")
skiaLayer.attachTo(canvas)
val skiaLayer = SkiaLayer()
val clocks = WebClocks(skiaLayer, canvas)
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, clocks)
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
fun 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 {
......@@ -93,7 +67,7 @@ private class SkikoSurfaceRender(private val layer: SkiaLayer, private val manag
// This method is called from the main thread.
fun update() {
layer.skikoView?.let {
layer.renderDelegate?.let {
val bounds = Rect.makeWH(width.toFloat(), width.toFloat())
val canvas = pictureRecorder.beginRecording(bounds)
try {
......
......@@ -2,34 +2,10 @@ package org.jetbrains.skiko
import android.content.Context
import android.view.*
import android.view.inputmethod.InputMethodManager
import org.jetbrains.skia.Canvas
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 {
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 container: ViewGroup? = null
......@@ -49,7 +25,7 @@ actual open class SkiaLayer {
if (value) throw IllegalArgumentException("transparency unsupported")
}
actual var skikoView: SkikoView? = null
actual var renderDelegate: SkikoRenderDelegate? = null
actual fun attachTo(container: Any) {
when (container) {
......@@ -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) {
initDefaultContext(container.context)
......@@ -89,7 +44,6 @@ actual open class SkiaLayer {
this.container = container
this.glView = view
initGestures()
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 {
fun BufferedImage.toImage(): Image {
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(
actual val component: Any?
get() = backedLayer
actual var skikoView: SkikoView? = null
actual var renderDelegate: SkikoRenderDelegate? = null
actual fun attachTo(container: Any) {
attachTo(container as JComponent)
......@@ -231,69 +231,6 @@ actual open class SkiaLayer internal constructor(
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>()
@Volatile
......@@ -545,7 +482,7 @@ actual open class SkiaLayer internal constructor(
try {
isRendering = true
skikoView?.onRender(canvas, pictureWidth, pictureHeight, nanoTime)
renderDelegate?.onRender(canvas, pictureWidth, pictureHeight, nanoTime)
} finally {
isRendering = false
}
......@@ -670,9 +607,3 @@ internal fun Canvas.clipRectBy(rectangle: ClipRectangle, scale: Float) {
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
internal class Direct3DSwingRedrawer(
swingLayerProperties: SwingLayerProperties,
private val skikoView: SkikoView,
private val renderDelegate: SkikoRenderDelegate,
analytics: SkiaLayerAnalytics
) : SwingRedrawerBase(swingLayerProperties, analytics, GraphicsApi.DIRECT3D) {
companion object {
......@@ -49,7 +49,7 @@ internal class Direct3DSwingRedrawer(
// Calculate aligned width that is needed for performance optimization,
// since DirectX uses aligned bytebuffer.
// 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) {
width + widthSizeAlignment - (width % widthSizeAlignment);
} else {
......@@ -73,7 +73,7 @@ internal class Direct3DSwingRedrawer(
val canvas = surface.canvas
canvas.clear(Color.TRANSPARENT)
skikoView.onRender(canvas, width, height, nanoTime)
renderDelegate.onRender(canvas, width, height, nanoTime)
flush(surface, g)
}
}
......
......@@ -6,7 +6,7 @@ import java.awt.Graphics2D
internal class LinuxOpenGLSwingRedrawer(
swingLayerProperties: SwingLayerProperties,
private val skikoView: SkikoView,
private val renderDelegate: SkikoRenderDelegate,
analytics: SkiaLayerAnalytics
) : SwingRedrawerBase(swingLayerProperties, analytics, GraphicsApi.OPENGL) {
init {
......@@ -76,7 +76,7 @@ internal class LinuxOpenGLSwingRedrawer(
val canvas = surface.canvas
canvas.clear(Color.TRANSPARENT)
skikoView.onRender(canvas, width, height, nanoTime)
renderDelegate.onRender(canvas, width, height, nanoTime)
flush(surface, g)
unbindAndDisposeTexture(texturePtr)
}
......
......@@ -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.
*
* Content to draw is provided by [SkikoView].
* Content to draw is provided by [SkikoRenderDelegate].
*
* For on-screen rendering see [org.jetbrains.skiko.redrawer.MetalRedrawer].
*
......@@ -19,7 +19,7 @@ import java.awt.Graphics2D
*/
internal class MetalSwingRedrawer(
swingLayerProperties: SwingLayerProperties,
private val skikoView: SkikoView,
private val renderDelegate: SkikoRenderDelegate,
analytics: SkiaLayerAnalytics
) : SwingRedrawerBase(swingLayerProperties, analytics, GraphicsApi.METAL) {
companion object {
......@@ -70,7 +70,7 @@ internal class MetalSwingRedrawer(
val canvas = surface.canvas
canvas.clear(Color.TRANSPARENT)
skikoView.onRender(canvas, width, height, nanoTime)
renderDelegate.onRender(canvas, width, height, nanoTime)
flush(surface, g)
}
}
......
......@@ -10,7 +10,7 @@ import javax.swing.JComponent
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].
*
......@@ -22,7 +22,7 @@ import javax.swing.SwingUtilities.isEventDispatchThread
@Suppress("unused") // used in Compose Multiplatform
@ExperimentalSkikoApi
open class SkiaSwingLayer(
skikoView: SkikoView,
renderDelegate: SkikoRenderDelegate,
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
) : JComponent() {
internal companion object {
......@@ -40,14 +40,14 @@ open class SkiaSwingLayer(
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) {
val scale = graphicsConfiguration.defaultTransform.scaleX.toFloat()
// clipping
for (component in clipComponents) {
canvas.clipRectBy(component, scale)
}
skikoView.onRender(canvas, width, height, nanoTime)
renderDelegate.onRender(canvas, width, height, nanoTime)
}
}
......@@ -64,7 +64,7 @@ open class SkiaSwingLayer(
private val redrawerManager = RedrawerManager<SwingRedrawer>(properties.renderApi) { renderApi, oldRedrawer ->
oldRedrawer?.dispose()
createSwingRedrawer(swingLayerProperties, skikoViewWithClipping, renderApi, analytics)
createSwingRedrawer(swingLayerProperties, renderDelegateWithClipping, renderApi, analytics)
}
private val redrawer: SwingRedrawer?
......
......@@ -3,21 +3,21 @@ package org.jetbrains.skiko.swing
import org.jetbrains.skia.*
import org.jetbrains.skiko.GraphicsApi
import org.jetbrains.skiko.SkiaLayerAnalytics
import org.jetbrains.skiko.SkikoView
import org.jetbrains.skiko.SkikoRenderDelegate
import org.jetbrains.skiko.autoCloseScope
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].
*
* Content to draw is provided by [SkikoView].
* Content to draw is provided by [SkikoRenderDelegate].
*
* @see SwingRedrawerBase
* @see SwingOffscreenDrawer
*/
internal class SoftwareSwingRedrawer(
swingLayerProperties: SwingLayerProperties,
private val skikoView: SkikoView,
private val renderDelegate: SkikoRenderDelegate,
analytics: SkiaLayerAnalytics
) : SwingRedrawerBase(
swingLayerProperties,
......@@ -48,7 +48,7 @@ internal class SoftwareSwingRedrawer(
val canvas = Canvas(storage, SurfaceProps(pixelGeometry = PixelGeometry.UNKNOWN)).autoClose()
canvas.clear(Color.TRANSPARENT)
skikoView.onRender(canvas, width, height, nanoTime)
renderDelegate.onRender(canvas, width, height, nanoTime)
flush(g)
}
......
......@@ -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(
swingLayerProperties: SwingLayerProperties,
skikoView: SkikoView,
renderDelegate: SkikoRenderDelegate,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
): SwingRedrawer {
if (renderApi == GraphicsApi.SOFTWARE_COMPAT || renderApi == GraphicsApi.SOFTWARE_FAST) {
return SoftwareSwingRedrawer(
swingLayerProperties,
skikoView,
renderDelegate,
analytics
)
}
return when (hostOs) {
OS.MacOS -> MetalSwingRedrawer(swingLayerProperties, skikoView, analytics)
OS.Windows -> Direct3DSwingRedrawer(swingLayerProperties, skikoView, analytics)
OS.Linux -> LinuxOpenGLSwingRedrawer(swingLayerProperties, skikoView, analytics)
else -> SoftwareSwingRedrawer(swingLayerProperties, skikoView, analytics)
OS.MacOS -> MetalSwingRedrawer(swingLayerProperties, renderDelegate, analytics)
OS.Windows -> Direct3DSwingRedrawer(swingLayerProperties, renderDelegate, analytics)
OS.Linux -> LinuxOpenGLSwingRedrawer(swingLayerProperties, renderDelegate, analytics)
else -> SoftwareSwingRedrawer(swingLayerProperties, renderDelegate, analytics)
}
}
\ No newline at end of file
......@@ -8,12 +8,12 @@ import java.util.concurrent.CancellationException
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:
* 1. For the first request initialize native GPU context using [createDirectContext]
* 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]
*
* All the steps are performed synchronously on EDT.
......
......@@ -52,7 +52,7 @@ class SkiaLayerPerformanceTest {
init {
window.setSize(width, height)
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) {
scene(canvas, width, height, nanoTime)
if (canCollect && frameTimes.size < frameCount) {
......
......@@ -81,7 +81,7 @@ class SkiaLayerTest {
window.setLocation(200, 200)
window.setSize(400, 600)
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) {
val c1 = counter1
val c2 = counter2
......@@ -164,7 +164,7 @@ class SkiaLayerTest {
window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app
window.layer.renderDelegate = app
window.isUndecorated = true
window.isVisible = true
......@@ -188,7 +188,7 @@ class SkiaLayerTest {
window.preferredSize = Dimension(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app
window.layer.renderDelegate = app
window.isUndecorated = true
window.pack()
window.paint(window.graphics)
......@@ -213,7 +213,7 @@ class SkiaLayerTest {
properties = SkiaLayerProperties(renderApi = renderApi)
)
var renderedWidth = -1
layer.skikoView = object : SkikoView {
layer.renderDelegate = object : SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
renderedWidth = width
}
......@@ -268,7 +268,7 @@ class SkiaLayerTest {
window.setLocation(200, 200)
window.setSize(40, 20)
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.isVisible = true
delay(1000)
......@@ -288,7 +288,7 @@ class SkiaLayerTest {
setLocation(200, 200)
setSize(400, 200)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.skikoView = RectRenderer(layer, 200, 100, color)
layer.renderDelegate = RectRenderer(layer, 200, 100, color)
isUndecorated = true
isVisible = true
}
......@@ -355,7 +355,7 @@ class SkiaLayerTest {
window.setLocation(200, 200)
window.setSize(40, 20)
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) {
renderCount++
}
......@@ -386,7 +386,7 @@ class SkiaLayerTest {
setLocation(200, 200)
setSize(40, 20)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.skikoView = if (isAnimated) {
layer.renderDelegate = if (isAnimated) {
AnimatedBoxRenderer(layer, pixelsPerSecond = 20.0, size = 20.0)
} else {
RectRenderer(layer, 20, 10, Color.RED)
......@@ -430,7 +430,7 @@ class SkiaLayerTest {
setLocation(200, 200)
setSize(400, 200)
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
}
......@@ -454,7 +454,7 @@ class SkiaLayerTest {
setSize(400, 200)
preferredSize = Dimension(400, 200)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.skikoView = object : SkikoView {
layer.renderDelegate = object : SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
}
}
......@@ -539,7 +539,7 @@ class SkiaLayerTest {
window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app
window.layer.renderDelegate = app
window.isUndecorated = true
window.isVisible = true
......@@ -591,7 +591,7 @@ class SkiaLayerTest {
window.setLocation(200, 200)
window.setSize(400, 200)
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) {
drawCount++
......@@ -623,7 +623,7 @@ class SkiaLayerTest {
try {
window.setLocation(200, 200)
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) {
window.dispose()
SwingUtilities.invokeLater {
......@@ -646,7 +646,7 @@ class SkiaLayerTest {
window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app
window.layer.renderDelegate = app
window.isUndecorated = true
window.isVisible = true
......@@ -680,7 +680,7 @@ class SkiaLayerTest {
panel.background = Color.GREEN
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)
})
try {
......@@ -699,13 +699,13 @@ class SkiaLayerTest {
excludeRenderApis = listOf(GraphicsApi.SOFTWARE_COMPAT)
) {
val renderTimes = mutableListOf<Long>()
val renderer = object: SkikoView {
val renderer = object: SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
renderTimes.add(System.currentTimeMillis())
}
}
val window = UiTestWindow {
layer.skikoView = renderer
layer.renderDelegate = renderer
contentPane.add(layer, BorderLayout.CENTER)
}
try {
......@@ -834,7 +834,7 @@ class SkiaLayerTest {
window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app
window.layer.renderDelegate = app
window.isUndecorated = true
window.isVisible = true
......@@ -868,7 +868,7 @@ class SkiaLayerTest {
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) {
canvas.clear(Color.WHITE.rgb)
paragraph.layout(Float.POSITIVE_INFINITY)
......@@ -901,7 +901,7 @@ class SkiaLayerTest {
var rectWidth: Int,
var rectHeight: Int,
private val rectColor: Color
) : SkikoView {
) : SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val dpi = layer.contentScale
canvas.drawRect(Rect(0f, 0f, width.toFloat(), height.toFloat()), Paint().apply {
......@@ -917,7 +917,7 @@ class SkiaLayerTest {
private val layer: SkiaLayer,
private val pixelsPerSecond: Double,
private val size: Double
) : SkikoView {
) : SkikoRenderDelegate {
private var oldNanoTime = Long.MAX_VALUE
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 {
/**
* 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.
*/
fun attachTo(container: Any)
/**
* Detach this SkikoView from platform container.
* Detach this [SkikoRenderDelegate] from platform container.
*/
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
import org.jetbrains.skia.Canvas
interface SkikoView {
// Input
fun onKeyboardEvent(event: SkikoKeyboardEvent) = Unit
fun onPointerEvent(event: SkikoPointerEvent) = Unit
@Deprecated("This method will be removed. Use override val input: SkikoInput")
fun onInputEvent(event: SkikoInputEvent) = Unit
val input: SkikoInput get() = SkikoInput.Empty
fun onGestureEvent(event: SkikoGestureEvent) = Unit
// 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)
}
}
@Deprecated(
message = "Replaced with SkikoRenderDelegate",
replaceWith = ReplaceWith("SkikoRenderDelegate")
)
typealias SkikoView = SkikoRenderDelegate
@Deprecated(
message = "Replaced with SkiaLayerRenderDelegate",
replaceWith = ReplaceWith("SkiaLayerRenderDelegate")
)
typealias GenericSkikoView = SkiaLayerRenderDelegate
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 {
// GC bug? fixes leak on iOS
view = null
skikoView = null
renderDelegate = null
}
actual var skikoView: SkikoView? = null
actual var renderDelegate: SkikoRenderDelegate? = null
internal actual fun draw(canvas: Canvas) {
throw UnsupportedOperationException("Don't call it, artifact of wrong abstraction")
}
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
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
@Suppress("CONFLICTING_OVERLOADS")
@ExportObjCClass
class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol {
class SkikoUIView : UIView {
companion object : UIViewMeta() {
override fun layerClass() = CAMetalLayer
}
......@@ -34,25 +34,8 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol {
MTLCreateSystemDefaultDevice() ?: throw IllegalStateException("Metal is not supported on this system")
private val _metalLayer: CAMetalLayer get() = layer as CAMetalLayer
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
/*
* 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 {
multipleTouchEnabled = true
opaque = false // For UIKit interop through a "Hole"
......@@ -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)
constructor(
skiaLayer: SkiaLayer,
frame: CValue<CGRect> = CGRectNull.readValue(),
pointInside: (Point, UIEvent?) -> Boolean = { _, _ -> true },
skikoUITextInputTrains: SkikoUITextInputTraits
frame: CValue<CGRect> = CGRectNull.readValue()
) : super(frame) {
_skiaLayer = skiaLayer
_pointInside = pointInside
_skikoUITextInputTrains = skikoUITextInputTrains
val weakSkiaLayer = WeakReference(skiaLayer)
......@@ -105,60 +74,6 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol {
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() {
_redrawer.dispose()
}
......@@ -186,448 +101,4 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol {
_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
/**
* 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
* using [attachTo] method.
......@@ -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.
*/
actual var skikoView: SkikoView? = null
actual var renderDelegate: SkikoRenderDelegate? = null
/**
* @param container - should be an instance of [HTMLCanvasElement]
......@@ -76,7 +76,7 @@ actual open class SkiaLayer {
/**
* 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) {
this.htmlCanvas = htmlCanvas
......@@ -85,13 +85,13 @@ actual open class SkiaLayer {
override fun drawFrame(currentTimestamp: Double) {
// currentTimestamp is in milliseconds.
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) {
skikoView?.onRender(canvas, state!!.width, state!!.height, currentNanoTime())
renderDelegate?.onRender(canvas, state!!.width, state!!.height, currentNanoTime())
}
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 {
TODO("unimplemented")
}
actual var skikoView: SkikoView? = null
actual var renderDelegate: SkikoRenderDelegate? = null
actual val pixelGeometry: PixelGeometry
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
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
import kotlinx.cinterop.BetaInteropApi
import kotlinx.cinterop.ObjCAction
import kotlinx.cinterop.useContents
import kotlinx.cinterop.CValue
import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.convert
import org.jetbrains.skia.*
import org.jetbrains.skiko.redrawer.MacOsOpenGLRedrawer
import org.jetbrains.skiko.redrawer.Redrawer
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.NSNotificationCenter
import platform.Foundation.NSSelectorFromString
import platform.Foundation.addObserver
import platform.darwin.NSObject
import platform.CoreGraphics.CGRectMake
/**
* SkiaLayer implementation for macOS.
* Supports only [GraphicsApi.METAL]
*/
@OptIn(BetaInteropApi::class)
actual open class SkiaLayer {
fun isShowing(): Boolean {
return true
......@@ -75,6 +62,7 @@ actual open class SkiaLayer {
* Underlying [NSView]
*/
lateinit var nsView: NSView
private set
actual val component: Any?
get() = this.nsView
......@@ -82,7 +70,7 @@ actual open class SkiaLayer {
/**
* Implements rendering logic and events processing.
*/
actual var skikoView: SkikoView? = null
actual var renderDelegate: SkikoRenderDelegate? = null
internal var redrawer: Redrawer? = null
......@@ -93,154 +81,50 @@ actual open class SkiaLayer {
private var picture: PictureHolder? = null
private val pictureRecorder = PictureRecorder()
/**
* @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
private val nsViewObserver = object : NSObject() {
@ObjCAction
fun frameDidChange(notification: NSNotification) {
redrawer?.syncSize()
redrawer?.redrawImmediately()
}
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
fun windowDidChangeBackingProperties(notification: NSNotification) {
redrawer?.syncSize()
redrawer?.redrawImmediately()
}
@ObjCAction
open fun onWindowClose(arg: NSObject?) {
detach()
val center = NSNotificationCenter.defaultCenter()
center.removeObserver(nsView)
}
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,
)
}
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?.redrawImmediately()
}
override fun windowDidChangeBackingProperties(notification: NSNotification) {
redrawer?.syncSize()
redrawer?.redrawImmediately()
}
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 {
syncSize()
needRedraw()
......@@ -248,6 +132,7 @@ actual open class SkiaLayer {
}
actual fun detach() {
nsViewObserver.removeObserver()
redrawer?.dispose()
redrawer = null
}
......@@ -271,7 +156,7 @@ actual open class SkiaLayer {
val bounds = Rect.makeWH(pictureWidth.toFloat(), pictureHeight.toFloat())
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()
this.picture = PictureHolder(picture, pictureWidth.toInt(), pictureHeight.toInt())
......@@ -286,9 +171,3 @@ actual open class SkiaLayer {
actual val pixelGeometry: PixelGeometry
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