Unverified Commit fe6bf4af authored by Roman Sedaikin's avatar Roman Sedaikin Committed by GitHub

Prototype for common skiko events. (#344)

* Prototype for common skiko events.
parent fa2f2af4
......@@ -7,4 +7,5 @@ local.properties
.vscode
deploy.sh
deploy.bat
*.xcodeproj
skiko/src/jvmMain/java
......@@ -7,19 +7,30 @@ import org.jetbrains.skia.paragraph.ParagraphStyle
import org.jetbrains.skia.paragraph.TextStyle
import org.jetbrains.skiko.SkikoView
import org.jetbrains.skiko.SkikoPointerEvent
import org.jetbrains.skiko.SkikoGestureEvent
import org.jetbrains.skiko.SkikoGestureEventKind
import org.jetbrains.skiko.SkikoGestureEventState
import org.jetbrains.skiko.isLeftClick
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.PI
import kotlin.math.pow
class Clocks: SkikoView {
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
private val fontCollection = FontCollection()
.setDefaultFontManager(FontMgr.default)
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
canvas.translate(xOffset.toFloat(), yOffset.toFloat())
canvas.scale(scale.toFloat(), scale.toFloat())
val watchFill = Paint().apply { color = 0xFFFFFFFF.toInt() }
val watchStroke = Paint().apply {
color = 0xFF000000.toInt()
......@@ -32,9 +43,9 @@ class Clocks: SkikoView {
strokeWidth = 1f
}
val watchFillHover = Paint().apply { color = 0xFFE4FF01.toInt() }
for (x in 0 .. (width - 50) step 50) {
for (y in 20 .. (height - 50) step 50) {
val hover = xpos > x + 0 && xpos < x + 50 && ypos > y + 0 && ypos < y + 50
for (x in 0 .. width - 50 step 50) {
for (y in 20 .. height - 50 step 50) {
val hover = xpos / scale > x && xpos / scale < x + 50 && ypos / scale > y && ypos / scale < y + 50
val fill = if (hover) watchFillHover else watchFill
val stroke = if (x > width / 2) watchStrokeAA else watchStroke
canvas.drawOval(Rect.makeXYWH(x + 5f, y + 5f, 40f, 40f), fill)
......@@ -55,13 +66,17 @@ class Clocks: SkikoView {
(y.toFloat() / width * 5000).toLong()
val angle1 = (time.toFloat() / 5000 * 2f * PI).toFloat()
canvas.drawLine(x + 25f, y + 25f,
canvas.drawLine(
x + 25f,
y + 25f,
x + 25f - 15f * sin(angle1),
y + 25f + 15 * cos(angle1),
stroke)
val angle2 = (time / 60000 * 2f * PI).toFloat()
canvas.drawLine(x + 25f, y + 25f,
canvas.drawLine(
x + 25f,
y + 25f,
x + 25f - 10f * sin(angle2),
y + 25f + 10f * cos(angle2),
stroke)
......@@ -70,12 +85,12 @@ class Clocks: SkikoView {
val style = ParagraphStyle()
val frames = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFFFFAA0A.toInt()).setFontSize(25f))
.addText("Frames: ${frame++}")
.pushStyle(TextStyle().setColor(0xff9BC730L.toInt()).setFontSize(20f))
.addText("Frames: ${frame++}\nAngle: $rotate")
.popStyle()
.build()
frames.layout(Float.POSITIVE_INFINITY)
frames.paint(canvas, xpos.toFloat(), ypos.toFloat())
frames.paint(canvas, (xpos / scale).toFloat(), (ypos / scale).toFloat())
}
override fun onPointerEvent(event: SkikoPointerEvent) {
......@@ -84,4 +99,28 @@ class Clocks: SkikoView {
ypos = event.y
}
}
override fun onGestureEvent(event: SkikoGestureEvent) {
when (event.kind) {
SkikoGestureEventKind.PRESS,
SkikoGestureEventKind.TAP -> {
xpos = event.x - xOffset
ypos = event.y - yOffset
}
SkikoGestureEventKind.PINCH -> {
if (event.state == SkikoGestureEventState.STARTED) {
k = scale
}
scale = k * event.scale
}
SkikoGestureEventKind.PAN -> {
xOffset = event.x - xpos
yOffset = event.y - ypos
}
SkikoGestureEventKind.ROTATION -> {
rotate = (event.rotation * PI)
}
else -> {}
}
}
}
\ No newline at end of file
......@@ -45,4 +45,4 @@ class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
window!!.makeKeyAndVisible()
return true
}
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ import javax.swing.*
fun main() {
val skiaLayer = SkiaLayer()
skiaLayer.skikoView = GenericSkikoView(skiaLayer, BouncingBalls())
skiaLayer.addView( GenericSkikoView(skiaLayer, Clocks()))
SwingUtilities.invokeLater {
val window = JFrame("Skiko example").apply {
defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
......
......@@ -8,6 +8,28 @@ object SkikoMouseButtons {
const val MIDDLE = 1 shl 2
}
enum class SkikoGestureEventKind {
PRESS, TAP, PAN, PINCH, ROTATION, LONGPRESS, SWIPE, UNKNOWN
}
enum class SkikoGestureEventDirection {
UP, DOWN, LEFT, RIGHT, UNKNOWN
}
enum class SkikoGestureEventState {
PRESSED, STARTED, CHANGED, ENDED, UNKNOWN
}
expect class SkikoGesturePlatformEvent
data class SkikoGestureEvent(
val x: Double, val y: Double,
val velocity: Double = 0.0,
val direction: SkikoGestureEventDirection = SkikoGestureEventDirection.UNKNOWN,
val interval: Long = 0,
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,
......@@ -15,7 +37,7 @@ data class SkikoInputEvent(
)
enum class SkikoKeyboardEventKind {
UP, DOWN
UP, DOWN, TYPE, UNKNOWN
}
expect class SkikoPlatformKeyboardEvent
data class SkikoKeyboardEvent(
......@@ -25,7 +47,7 @@ data class SkikoKeyboardEvent(
)
enum class SkikoPointerEventKind {
UP, DOWN, MOVE
UP, DOWN, MOVE, DRAG, SCROLL, ENTER, EXIT, UNKNOWN
}
expect class SkikoPlatformPointerEvent
data class SkikoPointerEvent(
......
......@@ -7,6 +7,7 @@ interface SkikoView {
fun onKeyboardEvent(event: SkikoKeyboardEvent) = Unit
fun onPointerEvent(event: SkikoPointerEvent) = Unit
fun onInputEvent(event: SkikoInputEvent) = Unit
fun onGestureEvent(event: SkikoGestureEvent) = Unit
// Rendering
fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long)
......@@ -36,4 +37,8 @@ open class GenericSkikoView(
override fun onPointerEvent(event: SkikoPointerEvent) {
app.onPointerEvent(event)
}
override fun onGestureEvent(event: SkikoGestureEvent) {
app.onGestureEvent(event)
}
}
package org.jetbrains.skiko
import platform.UIKit.*
fun toSkikoGestureDirection(direction: UISwipeGestureRecognizerDirection) : SkikoGestureEventDirection {
return when(direction) {
UISwipeGestureRecognizerDirectionUp -> SkikoGestureEventDirection.UP
UISwipeGestureRecognizerDirectionDown -> SkikoGestureEventDirection.DOWN
UISwipeGestureRecognizerDirectionLeft -> SkikoGestureEventDirection.LEFT
UISwipeGestureRecognizerDirectionRight -> SkikoGestureEventDirection.RIGHT
else -> SkikoGestureEventDirection.UNKNOWN
}
}
fun toSkikoGestureState(state: UIGestureRecognizerState ) : SkikoGestureEventState {
return when(state) {
UIGestureRecognizerStatePossible -> SkikoGestureEventState.PRESSED
UIGestureRecognizerStateBegan -> SkikoGestureEventState.STARTED
UIGestureRecognizerStateChanged -> SkikoGestureEventState.CHANGED
UIGestureRecognizerStateEnded -> SkikoGestureEventState.ENDED
else -> SkikoGestureEventState.UNKNOWN
}
}
......@@ -62,23 +62,95 @@ actual open class SkiaLayer {
controller = object : NSObject() {
@ObjCAction
fun onTap(sender: UITapGestureRecognizer) {
val (x, y) = sender.locationInView(view).useContents {
x to y
}
// TODO: rework events using https://developer.apple.com/documentation/uikit/uiresponder/1621142-touchesbegan?language=objc
skikoView?.onPointerEvent(
SkikoPointerEvent(x, y,
SkikoMouseButtons.LEFT, SkikoPointerEventKind.DOWN,
null))
skikoView?.onPointerEvent(
SkikoPointerEvent(x, y,
SkikoMouseButtons.LEFT, SkikoPointerEventKind.UP,
null))
val (x, y) = sender.locationInView(view).useContents { x to y }
skikoView?.onGestureEvent(
SkikoGestureEvent(
x = x,
y = y,
kind = SkikoGestureEventKind.TAP,
state = toSkikoGestureState(sender.state)
)
)
}
@ObjCAction
fun onLongPress(sender: UILongPressGestureRecognizer) {
val (x, y) = sender.locationInView(view).useContents { x to y }
skikoView?.onGestureEvent(
SkikoGestureEvent(
x = x,
y = y,
kind = SkikoGestureEventKind.LONGPRESS,
state = toSkikoGestureState(sender.state)
)
)
}
@ObjCAction
fun onPinch(sender: UIPinchGestureRecognizer) {
val (x, y) = sender.locationInView(view).useContents { x to y }
skikoView?.onGestureEvent(
SkikoGestureEvent(
x = x,
y = y,
kind = SkikoGestureEventKind.PINCH,
scale = sender.scale,
velocity = sender.velocity,
state = toSkikoGestureState(sender.state)
)
)
}
@ObjCAction
fun onRotation(sender: UIRotationGestureRecognizer) {
val (x, y) = sender.locationInView(view).useContents { x to y }
skikoView?.onGestureEvent(
SkikoGestureEvent(
x = x,
y = y,
kind = SkikoGestureEventKind.ROTATION,
rotation = sender.rotation,
velocity = sender.velocity,
state = toSkikoGestureState(sender.state)
)
)
}
@ObjCAction
fun onSwipe(sender: UISwipeGestureRecognizer) {
val (x, y) = sender.locationInView(view).useContents { x to y }
skikoView?.onGestureEvent(
SkikoGestureEvent(
x = x,
y = y,
kind = SkikoGestureEventKind.SWIPE,
direction = toSkikoGestureDirection(sender.direction),
state = toSkikoGestureState(sender.state)
)
)
}
@ObjCAction
fun onPan(sender: UIPanGestureRecognizer) {
val (x, y) = sender.locationInView(view).useContents { x to y }
skikoView?.onGestureEvent(
SkikoGestureEvent(
x = x,
y = y,
kind = SkikoGestureEventKind.PAN,
state = toSkikoGestureState(sender.state)
)
)
}
}
// We have ':' in selector to take care of function argument.
view.addGestureRecognizer(UITapGestureRecognizer(controller, NSSelectorFromString("onTap:")))
view.addGestureRecognizer(UILongPressGestureRecognizer(controller, NSSelectorFromString("onLongPress:")))
view.addGestureRecognizer(UIPinchGestureRecognizer(controller, NSSelectorFromString("onPinch:")))
view.addGestureRecognizer(UIRotationGestureRecognizer(controller, NSSelectorFromString("onRotation:")))
view.addGestureRecognizer(UISwipeGestureRecognizer(controller, NSSelectorFromString("onSwipe:")))
view.addGestureRecognizer(UIPanGestureRecognizer(controller, NSSelectorFromString("onPan:")))
// TODO: maybe add observer for view.viewDidDisappear() to detach us?
redrawer = MetalRedrawer(this).apply {
needRedraw()
......@@ -129,6 +201,7 @@ actual open class SkiaLayer {
}
// TODO: do properly
actual typealias SkikoGesturePlatformEvent = UIEvent
actual typealias SkikoPlatformInputEvent = UIEvent
actual typealias SkikoPlatformKeyboardEvent = UIEvent
actual typealias SkikoPlatformPointerEvent = UIEvent
\ No newline at end of file
......@@ -5,6 +5,7 @@ import kotlinx.cinterop.useContents
import platform.CoreGraphics.CGRectMake
import platform.Foundation.NSCoder
import platform.UIKit.UIEvent
import platform.UIKit.UITouch
import platform.UIKit.UIScreen
import platform.UIKit.UIViewController
import platform.UIKit.setFrame
......@@ -19,12 +20,20 @@ class SkikoViewController : UIViewController {
constructor(coder: NSCoder) : super(coder)
override fun touchesBegan(touches: Set<*>, withEvent: UIEvent?) {
println("touchesBegan: $withEvent")
super.touchesBegan(touches, withEvent)
val sender = (touches.elementAt(0) as UITouch)
val (x, y) = sender.locationInView(null).useContents { x to y }
skikoLayer.skikoView?.onGestureEvent(
SkikoGestureEvent(
x = x,
y = y,
kind = SkikoGestureEventKind.PRESS,
state = SkikoGestureEventState.PRESSED
)
)
}
override fun touchesEnded(touches: Set<*>, withEvent: UIEvent?) {
println("touchesEnded $withEvent")
super.touchesEnded(touches, withEvent)
}
......
......@@ -100,6 +100,7 @@ actual open class SkiaLayer {
}
}
actual typealias SkikoGesturePlatformEvent = Any
actual typealias SkikoPlatformInputEvent = InputEvent
actual typealias SkikoPlatformKeyboardEvent = KeyboardEvent
// MouseEvent is base class of PointerEvent
......
......@@ -9,6 +9,7 @@ import org.jetbrains.skia.impl.BufferUtil
import java.awt.Transparency
import java.awt.color.ColorSpace
import java.awt.image.*
import java.awt.event.*
import java.nio.ByteBuffer
private class DirectDataBuffer(val backing: ByteBuffer): DataBuffer(TYPE_BYTE, backing.limit()) {
......@@ -76,3 +77,59 @@ fun BufferedImage.toBitmap(): Bitmap {
fun BufferedImage.toImage(): Image {
return Image.makeFromBitmap(toBitmap())
}
fun toSkikoEvent(e: MouseEvent): SkikoPointerEvent {
return SkikoPointerEvent(
e.x.toDouble(),
e.y.toDouble(),
when(e.button) {
MouseEvent.BUTTON1 -> SkikoMouseButtons.LEFT
MouseEvent.BUTTON2 -> SkikoMouseButtons.RIGHT
MouseEvent.BUTTON3 -> SkikoMouseButtons.MIDDLE
else -> SkikoMouseButtons.NONE
},
when(e.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
},
e
)
}
fun toSkikoEvent(e: MouseWheelEvent): SkikoPointerEvent {
return SkikoPointerEvent(
e.x.toDouble(),
e.y.toDouble(),
SkikoMouseButtons.NONE,
when(e.id) {
MouseEvent.MOUSE_WHEEL-> SkikoPointerEventKind.SCROLL
else -> SkikoPointerEventKind.UNKNOWN
},
e
)
}
fun toSkikoEvent(e: KeyEvent): SkikoKeyboardEvent {
return SkikoKeyboardEvent(
e.keyCode,
when(e.id) {
KeyEvent.KEY_PRESSED -> SkikoKeyboardEventKind.DOWN
KeyEvent.KEY_RELEASED -> SkikoKeyboardEventKind.UP
KeyEvent.KEY_TYPED -> SkikoKeyboardEventKind.TYPE
else -> SkikoKeyboardEventKind.UNKNOWN
},
e
)
}
fun toSkikoEvent(e: InputMethodEvent): SkikoInputEvent {
return SkikoInputEvent(
"",
e
)
}
......@@ -148,59 +148,58 @@ actual open class SkiaLayer internal constructor(
fun attachTo(jComponent: JComponent) {
jComponent.add(this)
backedLayer.addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
e!!
skikoView?.onPointerEvent(
SkikoPointerEvent(e.x.toDouble(), e.y.toDouble(),
if (e.button == 1) SkikoMouseButtons.LEFT else SkikoMouseButtons.RIGHT,
SkikoPointerEventKind.DOWN,
e
)
)
}
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 mouseReleased(e: MouseEvent?) {
e!!
skikoView?.onPointerEvent(
SkikoPointerEvent(e.x.toDouble(), e.y.toDouble(),
if (e.button == 1) SkikoMouseButtons.LEFT else SkikoMouseButtons.RIGHT,
SkikoPointerEventKind.UP,
e
)
)
override fun mouseExited(e: MouseEvent) {
skikoView?.onPointerEvent(toSkikoEvent(e))
}
})
backedLayer.addMouseMotionListener(object : MouseMotionAdapter() {
override fun mouseMoved(e: MouseEvent?) {
e!!
skikoView?.onPointerEvent(
SkikoPointerEvent(e.x.toDouble(), e.y.toDouble(),
SkikoMouseButtons.NONE,
SkikoPointerEventKind.MOVE,
e
)
)
addMouseMotionListener(object : MouseMotionAdapter() {
override fun mouseDragged(e: MouseEvent) {
skikoView?.onPointerEvent(toSkikoEvent(e))
}
override fun mouseMoved(e: MouseEvent) {
skikoView?.onPointerEvent(toSkikoEvent(e))
}
})
backedLayer.addKeyListener(object : KeyAdapter() {
override fun keyPressed(e: KeyEvent?) {
e!!
skikoView?.onKeyboardEvent(
SkikoKeyboardEvent(e.keyCode,
SkikoKeyboardEventKind.DOWN,
e
)
)
addMouseWheelListener(object : MouseWheelListener {
override fun mouseWheelMoved(e: MouseWheelEvent) {
skikoView?.onPointerEvent(toSkikoEvent(e))
}
})
addKeyListener(object : KeyAdapter() {
override fun keyPressed(e: KeyEvent) {
skikoView?.onKeyboardEvent(toSkikoEvent(e))
}
override fun keyReleased(e: KeyEvent) {
skikoView?.onKeyboardEvent(toSkikoEvent(e))
}
override fun keyTyped(e: KeyEvent) {
skikoView?.onKeyboardEvent(toSkikoEvent(e))
}
})
addInputMethodListener(object : InputMethodListener {
override fun caretPositionChanged(e: InputMethodEvent) {
skikoView?.onInputEvent(toSkikoEvent(e))
}
override fun keyReleased(e: KeyEvent?) {
e!!
skikoView?.onKeyboardEvent(
SkikoKeyboardEvent(
e.keyCode,
SkikoKeyboardEventKind.UP,
e
)
)
override fun inputMethodTextChanged(e: InputMethodEvent) {
skikoView?.onInputEvent(toSkikoEvent(e))
}
})
}
......@@ -540,6 +539,7 @@ internal fun defaultFPSCounter(
}
// InputEvent is abstract, so we wrap to match modality.
actual class SkikoPlatformInputEvent(val wrapped: InputEvent)
actual typealias SkikoGesturePlatformEvent = Any
actual typealias SkikoPlatformInputEvent = InputMethodEvent
actual typealias SkikoPlatformKeyboardEvent = KeyEvent
actual typealias SkikoPlatformPointerEvent = MouseEvent
\ No newline at end of file
......@@ -25,6 +25,7 @@ actual open class SkiaLayer {
}
// TODO: do properly
actual typealias SkikoGesturePlatformEvent = Any
actual typealias SkikoPlatformInputEvent = Any
actual typealias SkikoPlatformKeyboardEvent = Any
actual typealias SkikoPlatformPointerEvent = Any
\ No newline at end of file
......@@ -175,6 +175,7 @@ actual open class SkiaLayer {
}
// TODO: do properly
actual typealias SkikoGesturePlatformEvent = NSEvent
actual typealias SkikoPlatformInputEvent = NSEvent
actual typealias SkikoPlatformKeyboardEvent = NSEvent
actual typealias SkikoPlatformPointerEvent = NSEvent
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