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

Added skiko events for android. (#463)

* Added skiko events for android. Added DOUBLETAP gesture.

* More flexible build processs
Co-authored-by: 's avatarNikolay Igotti <igotti@gmail.com>
parent f9444dab
......@@ -112,6 +112,13 @@ 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,
......@@ -152,20 +159,23 @@ class Clocks(private val layer: SkiaLayer): SkikoView {
when (event.key) {
SkikoKey.KEY_NUMPAD_ADD -> scale *= 1.1
SkikoKey.KEY_I -> {
if (event.modifiers == SkikoInputModifiers.CONTROL) {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
scale *= 1.1
}
}
SkikoKey.KEY_NUMPAD_SUBTRACT -> scale *= 0.9
SkikoKey.KEY_O -> {
if (event.modifiers == SkikoInputModifiers.CONTROL) {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
scale *= 0.9
}
}
SkikoKey.KEY_R -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
rotate -= 5.0
} else if (event.modifiers == SkikoInputModifiers.CONTROL) {
}
}
SkikoKey.KEY_L -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
rotate += 5.0
}
}
......@@ -177,12 +187,7 @@ class Clocks(private val layer: SkiaLayer): SkikoView {
SkikoKey.KEY_RIGHT -> xOffset += 5.0
SkikoKey.KEY_NUMPAD_2,
SkikoKey.KEY_DOWN -> yOffset += 5.0
SkikoKey.KEY_SPACE -> {
xOffset = 0.0
yOffset = 0.0
rotate = 0.0
scale = 1.0
}
SkikoKey.KEY_SPACE -> { reset() }
SkikoKey.KEY_BACKSPACE -> {
if (inputText.isNotEmpty()) {
inputText = inputText.dropLast(1)
......@@ -207,6 +212,7 @@ class Clocks(private val layer: SkiaLayer): SkikoView {
xpos = event.x
ypos = event.y
}
SkikoGestureEventKind.DOUBLETAP -> { reset() }
SkikoGestureEventKind.PINCH -> {
if (event.state == SkikoGestureEventState.STARTED) {
k = scale
......
......@@ -2,9 +2,11 @@ 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
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
......@@ -19,7 +21,9 @@ class MainActivity : Activity() {
//layoutParams = ViewGroup.LayoutParams(1000, 1200)
}
val skiaLayer = SkiaLayer()
val skiaLayer = SkiaLayer().apply {
gesturesToListen = SkikoGestureEventKind.values()
}
skiaLayer.skikoView = GenericSkikoView(skiaLayer, Clocks(skiaLayer))
skiaLayer.attachTo(holder)
layout.addView(holder)
......
......@@ -113,6 +113,13 @@ 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,
......@@ -153,20 +160,23 @@ class Clocks(private val layer: SkiaLayer): SkikoView {
when (event.key) {
SkikoKey.KEY_NUMPAD_ADD -> scale *= 1.1
SkikoKey.KEY_I -> {
if (event.modifiers == SkikoInputModifiers.CONTROL) {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
scale *= 1.1
}
}
SkikoKey.KEY_NUMPAD_SUBTRACT -> scale *= 0.9
SkikoKey.KEY_O -> {
if (event.modifiers == SkikoInputModifiers.CONTROL) {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
scale *= 0.9
}
}
SkikoKey.KEY_R -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
rotate -= 5.0
} else if (event.modifiers == SkikoInputModifiers.CONTROL) {
}
}
SkikoKey.KEY_L -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
rotate += 5.0
}
}
......@@ -178,12 +188,7 @@ class Clocks(private val layer: SkiaLayer): SkikoView {
SkikoKey.KEY_RIGHT -> xOffset += 5.0
SkikoKey.KEY_NUMPAD_2,
SkikoKey.KEY_DOWN -> yOffset += 5.0
SkikoKey.KEY_SPACE -> {
xOffset = 0.0
yOffset = 0.0
rotate = 0.0
scale = 1.0
}
SkikoKey.KEY_SPACE -> { reset() }
SkikoKey.KEY_BACKSPACE -> {
if (inputText.isNotEmpty()) {
inputText = inputText.dropLast(1)
......@@ -208,6 +213,7 @@ class Clocks(private val layer: SkiaLayer): SkikoView {
xpos = event.x
ypos = event.y
}
SkikoGestureEventKind.DOUBLETAP -> { reset() }
SkikoGestureEventKind.PINCH -> {
if (event.state == SkikoGestureEventState.STARTED) {
k = scale
......
......@@ -40,7 +40,8 @@ class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
window = UIWindow(frame = UIScreen.mainScreen.bounds)
window!!.rootViewController = SkikoViewController(
SkikoUIView(
SkiaLayer(SkikoGestureEventKind.values()).apply {
SkiaLayer().apply {
gesturesToListen = SkikoGestureEventKind.values()
skikoView = GenericSkikoView(this, makeApp(this))
}
)
......
......@@ -713,8 +713,10 @@ fun androidClangFor(targetArch: Arch, version: String = "30"): String {
}
val ndkHome = if (System.getenv("ANDROID_NDK_HOME").isNullOrEmpty()) {
val androidHome = androidHome()
val ndkDir1 = file("$androidHome/ndk")
val candidates1 = if (ndkDir1.exists()) ndkDir1.list() else emptyArray()
val ndkVersion =
arrayOf(*(file("$androidHome/ndk").list().map { "ndk/$it" }.sortedDescending()).toTypedArray(), "ndk-bundle").find {
arrayOf(*(candidates1.map { "ndk/$it" }.sortedDescending()).toTypedArray(), "ndk-bundle").find {
androidHome.resolve(it).exists()
} ?: throw GradleException("Cannot find NDK, is it installed (Tools/SDK Manager)?")
"$androidHome/$ndkVersion"
......@@ -729,9 +731,14 @@ fun androidClangFor(targetArch: Arch, version: String = "30"): String {
return clang
}
fun androidJar(version: String = "30"): String {
fun androidJar(askedVersion: String = ""): String {
val androidHome = androidHome()
return androidHome.resolve("platforms/android-$version/android.jar").absolutePath
val version = if (askedVersion.isEmpty()) {
File(androidHome, "platforms").list().sortedArrayDescending().first()
} else {
"android-$askedVersion"
}
return androidHome.resolve("platforms/$version/android.jar").absolutePath
}
fun createCompileJvmBindingsTask(
......@@ -918,7 +925,9 @@ fun createLinkJvmBindings(
"-shared",
"-static-libstdc++",
"-lGLESv3",
"-lEGL"
"-lEGL",
"-llog",
"-landroid"
)
linker.set(androidClangFor(targetArch))
}
......
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.getEventTime(),
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 toSkikoTouchEvent(event: MotionEvent, index: Int, density: Float): SkikoTouchEvent {
return SkikoTouchEvent(
x = (event.getX(index) / density).toDouble(),
y = (event.getY(index) / density).toDouble(),
timestamp = event.getEventTime(),
kind = toSkikoTouchEventKind(event),
platform = event
)
}
fun toSkikoGestureEvent(event: MotionEvent, density: Float): SkikoGestureEvent {
return SkikoGestureEvent(
x = (event.x / density).toDouble(),
y = (event.y / density).toDouble(),
kind = toSkikoGestureEventKind(event),
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 {
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
}
private fun toSkikoTouchEventKind(event: MotionEvent): SkikoTouchEventKind {
return when (event.action) {
MotionEvent.ACTION_POINTER_DOWN,
MotionEvent.ACTION_DOWN -> SkikoTouchEventKind.STARTED
MotionEvent.ACTION_POINTER_UP,
MotionEvent.ACTION_UP -> SkikoTouchEventKind.ENDED
MotionEvent.ACTION_MOVE -> SkikoTouchEventKind.MOVED
else -> SkikoTouchEventKind.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
......@@ -4,13 +4,15 @@ import android.content.Context
import android.opengl.GLES30
import android.opengl.GLSurfaceView
import android.widget.LinearLayout
import android.view.MotionEvent
import android.view.KeyEvent
import kotlinx.coroutines.Dispatchers
import org.jetbrains.skia.*
import java.nio.IntBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
class SkikoSurfaceView(context: Context, layer: SkiaLayer) : GLSurfaceView(context) {
class SkikoSurfaceView(context: Context, val layer: SkiaLayer) : GLSurfaceView(context) {
private val renderer = SkikoSurfaceRender(layer)
init {
layoutParams = LinearLayout.LayoutParams(
......@@ -29,6 +31,37 @@ class SkikoSurfaceView(context: Context, layer: SkiaLayer) : GLSurfaceView(conte
fun scheduleFrame() {
frameDispatcher.scheduleFrame()
}
internal val gesturesDetector = SkikoGesturesDetector(context, layer)
override fun onTouchEvent(event: MotionEvent): Boolean {
val events: MutableList<SkikoTouchEvent> = mutableListOf()
val count = event.pointerCount
for (index in 0 .. count - 1) {
events.add(toSkikoTouchEvent(event, index, layer.contentScale))
}
layer.skikoView?.onTouchEvent(events.toTypedArray())
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) : GLSurfaceView.Renderer {
......@@ -86,6 +119,7 @@ private class SkikoSurfaceRender(private val layer: SkiaLayer) : GLSurfaceView.R
// This method is called from GL rendering thread, it shall render Skia picture.
override fun onDrawFrame(gl: GL10?) {
lockPicture {
canvas?.clear(-1)
canvas?.drawPicture(it.instance)
Unit
}
......
package org.jetbrains.skiko
import android.content.Context
import android.view.*
import android.view.inputmethod.InputMethodManager
import org.jetbrains.skia.Canvas
actual typealias SkikoGesturePlatformEvent = MotionEvent
actual typealias SkikoPlatformPointerEvent = MotionEvent
// TODO: most likely wrong.
actual typealias SkikoPlatformInputEvent = Any
actual typealias SkikoTouchPlatformEvent = Any
actual typealias SkikoTouchPlatformEvent = 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
......@@ -41,6 +60,27 @@ 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)
......@@ -49,6 +89,9 @@ actual open class SkiaLayer {
this.container = container
this.glView = view
initGestures()
view.setFocusableInTouchMode(true)
needRedraw()
}
......
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: SimpleOnGestureListener() {
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 onScroll(
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 onFling(
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()
}
}
}
......@@ -67,10 +67,10 @@ actual enum class SkikoKey(val value: Int) {
KEY_RIGHT_ALT(KEYCODE_ALT_RIGHT),
KEY_RIGHT_CONTROL(KEYCODE_CTRL_RIGHT),
KEY_MENU(KEYCODE_UNKNOWN),
KEY_UP(KEYCODE_SYSTEM_NAVIGATION_UP),
KEY_DOWN(KEYCODE_SYSTEM_NAVIGATION_DOWN),
KEY_LEFT(KEYCODE_SYSTEM_NAVIGATION_LEFT),
KEY_RIGHT(KEYCODE_SYSTEM_NAVIGATION_RIGHT),
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),
......@@ -109,4 +109,11 @@ actual enum class SkikoKey(val value: Int) {
KEY_NUMPAD_MULTIPLY(KEYCODE_NUMPAD_MULTIPLY),
KEY_NUMPAD_DIVIDE(KEYCODE_NUMPAD_DIVIDE),
KEY_NUMPAD_DECIMAL(KEYCODE_UNKNOWN);
companion object {
fun valueOf(value: Int): SkikoKey {
val key = SkikoKey.values().firstOrNull { it.value == value }
return key ?: SkikoKey.KEY_UNKNOWN
}
}
}
\ No newline at end of file
......@@ -203,7 +203,7 @@ expect enum class SkikoKey {
}
enum class SkikoGestureEventKind {
TAP, PAN, PINCH, ROTATION, LONGPRESS, SWIPE, UNKNOWN
TAP, DOUBLETAP, LONGPRESS, PAN, PINCH, ROTATION, SWIPE, UNKNOWN
}
enum class SkikoGestureEventDirection {
UP, DOWN, LEFT, RIGHT, UNKNOWN
......
package org.jetbrains.skiko
import kotlinx.cinterop.ObjCAction
import kotlinx.cinterop.useContents
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.PictureRecorder
import org.jetbrains.skia.Rect
import org.jetbrains.skiko.context.MetalContextHandler
import org.jetbrains.skiko.redrawer.MetalRedrawer
import platform.Foundation.NSNotificationCenter
import platform.Foundation.NSSelectorFromString
import platform.UIKit.*
import platform.darwin.NSObject
import kotlin.system.getTimeNanos
actual open class SkiaLayer {
private val gestures: Array<SkikoGestureEventKind>?
constructor(gestures: Array<SkikoGestureEventKind>? = null) {
this.gestures = gestures
}
fun isShowing(): Boolean {
return true
......@@ -64,8 +53,17 @@ actual open class SkiaLayer {
}
internal var view: UIView? = null
// We need to keep reference to controller as Objective-C will only keep weak reference here.
lateinit private var controller: NSObject
// We need to keep reference to gesturesDetector as Objective-C will only keep weak reference here.
internal var gesturesDetector = SkikoGesturesDetector(this)
var gesturesToListen: Array<SkikoGestureEventKind>? = null
set(value) {
field = value
initGestures()
}
internal fun initGestures() {
gesturesDetector.setGesturesToListen(gesturesToListen)
}
actual fun attachTo(container: Any) {
attachTo(container as UIView)
......@@ -74,113 +72,6 @@ actual open class SkiaLayer {
fun attachTo(view: UIView) {
this.view = view
contextHandler = MetalContextHandler(this)
// See https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events?language=objc
controller = object : NSObject() {
@ObjCAction
fun onTap(sender: UITapGestureRecognizer) {
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)
)
)
}
}
if (!gestures.isNullOrEmpty()) {
// We have ':' in selector to take care of function argument.
if (gestures.contains(SkikoGestureEventKind.TAP)) {
view.addGestureRecognizer(UITapGestureRecognizer(controller, NSSelectorFromString("onTap:")))
}
if (gestures.contains(SkikoGestureEventKind.LONGPRESS)) {
view.addGestureRecognizer(UILongPressGestureRecognizer(controller, NSSelectorFromString("onLongPress:")))
}
if (gestures.contains(SkikoGestureEventKind.PINCH)) {
view.addGestureRecognizer(UIPinchGestureRecognizer(controller, NSSelectorFromString("onPinch:")))
}
if (gestures.contains(SkikoGestureEventKind.ROTATION)) {
view.addGestureRecognizer(UIRotationGestureRecognizer(controller, NSSelectorFromString("onRotation:")))
}
if (gestures.contains(SkikoGestureEventKind.SWIPE)) {
view.addGestureRecognizer(UISwipeGestureRecognizer(controller, NSSelectorFromString("onSwipe:")))
}
if (gestures.contains(SkikoGestureEventKind.PAN)) {
view.addGestureRecognizer(UIPanGestureRecognizer(controller, NSSelectorFromString("onPan:")))
}
}
// TODO: maybe add observer for view.viewDidDisappear() to detach us?
redrawer = MetalRedrawer(this).apply {
needRedraw()
......
package org.jetbrains.skiko
import kotlinx.cinterop.ObjCAction
import kotlinx.cinterop.useContents
import platform.darwin.NSObject
import platform.Foundation.NSSelectorFromString
import platform.UIKit.*
// See https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events?language=objc
internal class SkikoGesturesDetector(
private val layer: SkiaLayer
) : NSObject() {
val view: UIView?
get() = layer.view
@ObjCAction
fun onTap(sender: UITapGestureRecognizer) {
val (x, y) = sender.locationInView(view).useContents { x to y }
layer.skikoView?.onGestureEvent(
SkikoGestureEvent(
x = x,
y = y,
kind = SkikoGestureEventKind.TAP,
state = toSkikoGestureState(sender.state)
)
)
}
@ObjCAction
fun onDoubleTap(sender: UITapGestureRecognizer) {
val (x, y) = sender.locationInView(view).useContents { x to y }
layer.skikoView?.onGestureEvent(
SkikoGestureEvent(
x = x,
y = y,
kind = SkikoGestureEventKind.DOUBLETAP,
state = toSkikoGestureState(sender.state)
)
)
}
@ObjCAction
fun onLongPress(sender: UILongPressGestureRecognizer) {
val (x, y) = sender.locationInView(view).useContents { x to y }
layer.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 }
layer.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 }
layer.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 }
layer.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 }
layer.skikoView?.onGestureEvent(
SkikoGestureEvent(
x = x,
y = y,
kind = SkikoGestureEventKind.PAN,
state = toSkikoGestureState(sender.state)
)
)
}
// We have ':' in selector to take care of function argument.
private val gestureRecognizers = hashMapOf(
SkikoGestureEventKind.TAP to UITapGestureRecognizer(this, NSSelectorFromString("onTap:")),
SkikoGestureEventKind.DOUBLETAP to UITapGestureRecognizer(this, NSSelectorFromString("onDoubleTap:")).apply {
numberOfTapsRequired = 2.toULong()
},
SkikoGestureEventKind.LONGPRESS to UILongPressGestureRecognizer(this, NSSelectorFromString("onLongPress:")),
SkikoGestureEventKind.PINCH to UIPinchGestureRecognizer(this, NSSelectorFromString("onPinch:")),
SkikoGestureEventKind.ROTATION to UIRotationGestureRecognizer(this, NSSelectorFromString("onRotation:")),
SkikoGestureEventKind.SWIPE to UISwipeGestureRecognizer(this, NSSelectorFromString("onSwipe:")),
SkikoGestureEventKind.PAN to UIPanGestureRecognizer(this, NSSelectorFromString("onPan:"))
)
fun setGesturesToListen(gestures: Array<SkikoGestureEventKind>?) {
clearGesturesToListen()
if (!gestures.isNullOrEmpty()) {
for (gesture in gestures) {
if (gestureRecognizers.containsKey(gesture)) {
view?.addGestureRecognizer(gestureRecognizers.get(gesture)!!)
}
}
}
}
private fun clearGesturesToListen() {
for ((key, value) in gestureRecognizers) {
view?.removeGestureRecognizer(value)
}
}
}
......@@ -65,7 +65,10 @@ class SkikoUIView : UIView, UIKeyInputProtocol {
}
setFrame(CGRectMake(0.0, 0.0, width, height))
contentScaleFactor = UIScreen.mainScreen.scale
skiaLayer?.attachTo(this)
skiaLayer?.let { layer ->
layer.attachTo(this)
layer.initGestures()
}
return this
}
......
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