Unverified Commit 9383b5d9 authored by Nikolay Igotti's avatar Nikolay Igotti Committed by GitHub

Input handling (#276)

parent 7d15187d
......@@ -2,10 +2,9 @@ package org.jetbrains.skiko.sample.js
import kotlinx.browser.document
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Color4f
import org.jetbrains.skia.Paint
import org.jetbrains.skia.Rect
import org.jetbrains.skiko.GenericRenderer
import org.jetbrains.skiko.GenericSkikoApp
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaRenderer
import org.jetbrains.skiko.wasm.onWasmReady
......@@ -34,7 +33,7 @@ fun main() {
} else {
BouncingBalls()
}
skiaLayer.renderer = GenericRenderer(skiaLayer, app)
skiaLayer.renderer = GenericSkikoApp(skiaLayer, app)
skiaLayer.setCanvas(canvas)
skiaLayer.draw()
}
......
package org.jetbrains.skiko.sample
import org.jetbrains.skia.*
import org.jetbrains.skiko.SkiaRenderer
import org.jetbrains.skiko.*
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
class BouncingBalls: SkiaRenderer {
class BouncingBalls: SkikoView {
private data class Circle(var x: Float, var y: Float, var r: Float)
companion object {
......@@ -82,7 +82,7 @@ class BouncingBalls: SkiaRenderer {
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, currentTimestamp: Long) {
val dtime = (currentTimestamp - prevTimestamp)
......@@ -93,4 +93,26 @@ class BouncingBalls: SkiaRenderer {
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 != SkikoMouseEventKind.MOVE)
println("onMouse: $event")
}
}
\ No newline at end of file
package org.jetbrains.skiko.sample
import org.jetbrains.skia.*
import org.jetbrains.skiko.SkiaRenderer
import org.jetbrains.skiko.*
class RotatingSquare : SkiaRenderer {
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 }
......@@ -15,4 +15,16 @@ class RotatingSquare : SkiaRenderer {
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
......@@ -2,11 +2,13 @@
package org.jetbrains.skiko.sample
import kotlinx.cinterop.*
import org.jetbrains.skiko.SkiaRenderer
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkikoView
import org.jetbrains.skiko.SkikoViewController
import platform.UIKit.*
import platform.Foundation.*
fun makeApp(): SkiaRenderer = BouncingBalls()
fun makeApp(): SkikoView = BouncingBalls()
fun main() {
val args = emptyArray<String>()
......@@ -19,3 +21,28 @@ fun main() {
}
}
class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta
@ObjCObjectBase.OverrideInit
constructor() : super()
private var _window: UIWindow? = null
override fun window() = _window
override fun setWindow(window: UIWindow?) {
_window = window
}
override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map<Any?, *>?): Boolean {
window = UIWindow(frame = UIScreen.mainScreen.bounds)
window!!.rootViewController = SkikoViewController().apply {
setAppFactory { layer ->
GenericSkikoView(layer, makeApp()).also {
layer.skikoView = it
}
}
}
window!!.makeKeyAndVisible()
return true
}
}
package org.jetbrains.skiko.sample
import kotlinx.cinterop.*
import platform.UIKit.*
class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta
@ObjCObjectBase.OverrideInit
constructor() : super()
private var _window: UIWindow? = null
override fun window() = _window
override fun setWindow(window: UIWindow?) {
_window = window
}
override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map<Any?, *>?): Boolean {
window = UIWindow(frame = UIScreen.mainScreen.bounds)
window!!.rootViewController = SkikoViewController()
window!!.makeKeyAndVisible()
return true
}
}
\ No newline at end of file
package org.jetbrains.skiko.sample
import kotlinx.browser.document
import org.jetbrains.skiko.GenericRenderer
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.wasm.onWasmReady
import org.w3c.dom.HTMLCanvasElement
......@@ -9,9 +9,11 @@ import org.w3c.dom.HTMLCanvasElement
fun main() {
onWasmReady {
val skiaLayer = SkiaLayer()
skiaLayer.renderer = GenericRenderer(skiaLayer, RotatingSquare())
val game = BouncingBalls()
skiaLayer.skikoView = GenericSkikoView(skiaLayer, game)
val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement
canvas.setAttribute("tabindex", "0")
skiaLayer.setCanvas(canvas)
skiaLayer.draw()
skiaLayer.needRedraw()
}
}
......@@ -5,14 +5,24 @@ import platform.AppKit.*
import org.jetbrains.skia.*
import org.jetbrains.skiko.*
import kotlinx.cinterop.*
import platform.Foundation.NSMakeRect
fun makeApp() = BouncingBalls()
fun main() {
NSApplication.sharedApplication()
val layer = SkiaLayer()
val window = SkiaWindow(layer)
layer.renderer = GenericRenderer(layer, makeApp())
window.nsWindow.orderFrontRegardless()
val windowStyle = NSWindowStyleMaskTitled or
NSWindowStyleMaskMiniaturizable or
NSWindowStyleMaskClosable or
NSWindowStyleMaskResizable
val window = NSWindow(
contentRect = NSMakeRect(0.0, 0.0, 640.0, 480.0),
styleMask = windowStyle,
backing = NSBackingStoreBuffered,
defer = true)
val skiaLayer = SkiaLayer()
skiaLayer.skikoView = GenericSkikoView(skiaLayer, makeApp())
skiaLayer.initLayer(window)
window.orderFrontRegardless()
NSApp?.run()
}
package org.jetbrains.skiko
// Clicked mouse buttons bitmask.
object SkikoMouseButtons {
const val NONE = 0
const val LEFT = 1 shl 0
const val RIGHT = 1 shl 1
const val MIDDLE = 1 shl 2
}
expect class SkikoPlatformInputEvent
data class SkikoInputEvent(
val input: String,
val platform: SkikoPlatformInputEvent?
)
enum class SkikoKeyboardEventKind {
UP, DOWN
}
expect class SkikoPlatformKeyboardEvent
data class SkikoKeyboardEvent(
val code: Int,
val kind: SkikoKeyboardEventKind,
val platform: SkikoPlatformKeyboardEvent?
)
enum class SkikoPointerEventKind {
UP, DOWN, MOVE
}
expect class SkikoPlatformPointerEvent
data class SkikoPointerEvent(
val x: Double, val y: Double,
val buttonMask: Int,
val kind: SkikoPointerEventKind,
val platform: SkikoPlatformPointerEvent?
)
val SkikoPointerEvent.isLeftClick: Boolean
get() = (buttonMask and SkikoMouseButtons.LEFT) != 0 && (kind == SkikoPointerEventKind.UP)
val SkikoPointerEvent.isRightClick: Boolean
get() = (buttonMask and SkikoMouseButtons.RIGHT) != 0 && (kind == SkikoPointerEventKind.UP)
package org.jetbrains.skiko
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Picture
expect open class SkiaLayer {
......@@ -9,25 +8,9 @@ expect open class SkiaLayer {
var fullscreen: Boolean
var transparency: Boolean
var renderer: SkiaRenderer?
var skikoView: SkikoView?
fun needRedraw()
}
interface SkiaRenderer {
fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long)
}
open class GenericRenderer(
val layer: SkiaLayer,
val app: SkiaRenderer
): SkiaRenderer {
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)
layer.needRedraw()
}
}
internal class PictureHolder(val instance: Picture, val width: Int, val height: Int)
package org.jetbrains.skiko
import org.jetbrains.skia.Canvas
interface SkikoView {
// Input
fun onKeyboardEvent(event: SkikoKeyboardEvent)
fun onPointerEvent(event: SkikoPointerEvent)
fun onInputEvent(event: SkikoInputEvent)
// 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 fun onKeyboardEvent(event: SkikoKeyboardEvent) {
app.onKeyboardEvent(event)
}
override fun onPointerEvent(event: SkikoPointerEvent) {
app.onPointerEvent(event)
}
}
abstract class NoInputSkikoView: SkikoView {
override fun onInputEvent(event: SkikoInputEvent) {
}
override fun onKeyboardEvent(event: SkikoKeyboardEvent) {
}
override fun onPointerEvent(event: SkikoPointerEvent) {
}
}
package org.jetbrains.skiko.sample
package org.jetbrains.skiko
import kotlinx.cinterop.*
import kotlinx.coroutines.runBlocking
import org.jetbrains.skiko.GenericRenderer
import org.jetbrains.skiko.SkiaLayer
import platform.CoreGraphics.CGRect
import kotlinx.cinterop.ExportObjCClass
import kotlinx.cinterop.useContents
import platform.CoreGraphics.CGRectMake
import platform.Foundation.*
import platform.UIKit.*
import platform.Foundation.NSCoder
import platform.UIKit.UIEvent
import platform.UIKit.UIScreen
import platform.UIKit.UIViewController
import platform.UIKit.setFrame
@ExportObjCClass
class SkikoViewController : UIViewController {
@OverrideInit
constructor() : super(nibName = null, bundle = null)
@OverrideInit
constructor(coder: NSCoder) : super(coder)
override fun touchesBegan(touches: Set<*>, withEvent: UIEvent?) {
println("touchesBegan: $withEvent")
super.touchesBegan(touches, withEvent)
}
override fun touchesEnded(touches: Set<*>, withEvent: UIEvent?) {
println("touchesEnded $withEvent")
super.touchesEnded(touches, withEvent)
}
internal lateinit var appFactory: (SkiaLayer) -> SkikoView
fun setAppFactory(appFactory: (SkiaLayer) -> SkikoView) {
this.appFactory = appFactory
}
override fun viewDidLoad() {
super.viewDidLoad()
val (width, height) = UIScreen.mainScreen.bounds.useContents {
this.size.width to this.size.height
}
val layer = SkiaLayer().apply {
skikoView = appFactory(this)
}
view.setFrame(CGRectMake(0.0, 0.0, width, height))
val layer = SkiaLayer(width.toFloat(), height.toFloat())
layer.renderer = GenericRenderer(layer, makeApp())
layer.initLayer(view)
layer.initLayer(this)
}
}
package org.jetbrains.skiko
import kotlinx.cinterop.ObjCAction
import kotlinx.cinterop.useContents
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.NSSelectorFromString
import platform.UIKit.*
import platform.darwin.NSObject
actual open class SkiaLayer(
var width: Float, var height: Float,
val properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties()
)
{
) {
fun isShowing(): Boolean {
return true
}
......@@ -34,16 +36,50 @@ actual open class SkiaLayer(
redrawer?.needRedraw()
}
val width: Float
get() = view.frame.useContents {
return@useContents size.width.toFloat()
}
val height: Float
get() = view.frame.useContents {
return@useContents size.height.toFloat()
}
lateinit var view: UIView
// We need to keep reference to controller as Objective-C will only keep weak reference here.
lateinit private var controller: NSObject
fun initLayer(viewController: SkikoViewController) {
this.view = viewController.view
fun initLayer(view: UIView) {
println("SkiaLayer.initLayer")
this.view = view
// 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
}
// 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))
}
}
// We have ':' in selector to take care of function argument.
view.addGestureRecognizer(UITapGestureRecognizer(controller, NSSelectorFromString("onTap:")))
redrawer = MetalRedrawer(this, properties)
redrawer?.redrawImmediately()
}
actual var renderer: SkiaRenderer? = null
actual var skikoView: SkikoView? = null
internal var redrawer: MetalRedrawer? = null
private var picture: PictureHolder? = null
private val pictureRecorder = PictureRecorder()
......@@ -55,7 +91,7 @@ actual open class SkiaLayer(
val bounds = Rect.makeWH(pictureWidth, pictureHeight)
val canvas = pictureRecorder.beginRecording(bounds)
renderer?.onRender(canvas, pictureWidth.toInt(), pictureHeight.toInt(), nanoTime)
skikoView?.onRender(canvas, pictureWidth.toInt(), pictureHeight.toInt(), nanoTime)
val picture = pictureRecorder.finishRecordingAsPicture()
this.picture = PictureHolder(picture, pictureWidth.toInt(), pictureHeight.toInt())
}
......@@ -75,3 +111,8 @@ actual open class SkiaLayer(
}
}
}
// TODO: do properly
actual typealias SkikoPlatformInputEvent = UIEvent
actual typealias SkikoPlatformKeyboardEvent = UIEvent
actual typealias SkikoPlatformPointerEvent = UIEvent
\ No newline at end of file
......@@ -2,6 +2,9 @@ package org.jetbrains.skiko
import org.jetbrains.skiko.wasm.api.CanvasRenderer
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.events.InputEvent
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.MouseEvent
actual open class SkiaLayer(properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties()
) {
......@@ -12,28 +15,89 @@ actual open class SkiaLayer(properties: SkiaLayerProperties = makeDefaultSkiaLay
get() = 1.0f
actual var fullscreen: Boolean
get() = false
set(value) = throw Exception("Fullscreen is not supported!")
set(value) {
if (value) throw Exception("Fullscreen is not supported!")
}
actual var transparency: Boolean
get() = false
set(value) = throw Exception("Transparency is not supported!")
set(value) {
if (value) throw Exception("Transparency is not supported!")
}
actual fun needRedraw() {
draw()
}
actual var renderer: SkiaRenderer? = null
actual var skikoView: SkikoView? = null
fun translatePointerEvent(
event: MouseEvent,
buttons: Boolean,
kind: SkikoPointerEventKind
): SkikoPointerEvent {
var mask = SkikoMouseButtons.NONE
if (buttons && event.button.toInt() == 0)
mask = mask or SkikoMouseButtons.LEFT
// https://www.w3schools.com/jsref/event_button.asp
if (buttons && event.button.toInt() == 2)
mask = mask or SkikoMouseButtons.RIGHT
return SkikoPointerEvent(
event.offsetX, event.offsetY,
mask, kind, event
)
}
fun setCanvas(htmlCanvas: HTMLCanvasElement) {
state = object: CanvasRenderer(htmlCanvas) {
override fun drawFrame(currentTimestamp: Double) {
// currentTimestamp is milliseconds.
val currentNanos = currentTimestamp * 1000000
renderer?.onRender(canvas, width, height, currentNanos.toLong())
// currentTimestamp is in milliseconds.
val currentNanos = currentTimestamp * 1_000_000
skikoView?.onRender(canvas, width, height, currentNanos.toLong())
}
}
// See https://www.w3schools.com/jsref/dom_obj_event.asp
// https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events
htmlCanvas.addEventListener("pointerdown", { event ->
event as MouseEvent
skikoView?.onPointerEvent(translatePointerEvent(
event, true, SkikoPointerEventKind.DOWN))
})
htmlCanvas.addEventListener("pointerup", { event ->
event as MouseEvent
var mask = SkikoMouseButtons.NONE
skikoView?.onPointerEvent(translatePointerEvent(
event, true, SkikoPointerEventKind.UP))
})
htmlCanvas.addEventListener("pointermove", { event ->
event as MouseEvent
skikoView?.onPointerEvent(translatePointerEvent(
event, false, SkikoPointerEventKind.MOVE))
})
htmlCanvas.addEventListener("contextmenu", { event ->
event.preventDefault()
})
htmlCanvas.addEventListener("keydown", { event ->
event as KeyboardEvent
skikoView?.onKeyboardEvent(
SkikoKeyboardEvent(
event.keyCode, SkikoKeyboardEventKind.DOWN, event
)
)
})
htmlCanvas.addEventListener("keyup", { event ->
event as KeyboardEvent
skikoView?.onKeyboardEvent(SkikoKeyboardEvent(
event.keyCode, SkikoKeyboardEventKind.UP, event
))
})
}
fun draw() {
state?.draw()
}
}
actual typealias SkikoPlatformInputEvent = InputEvent
actual typealias SkikoPlatformKeyboardEvent = KeyboardEvent
// MouseEvent is base class of PointerEvent
actual typealias SkikoPlatformPointerEvent = MouseEvent
......@@ -12,7 +12,6 @@ import org.jetbrains.skiko.wasm.CreateWebGLContext
import org.jetbrains.skiko.wasm.GL
import org.w3c.dom.HTMLCanvasElement
fun Surface.Companion.createFromGL(width: Int, height: Int) = makeFromBackendRenderTarget(
DirectContext.makeGL(),
BackendRenderTarget.makeGL(
......@@ -31,7 +30,7 @@ abstract class CanvasRenderer constructor(htmlCanvas: HTMLCanvasElement, val wid
GL.makeContextCurrent(contextPointer)
}
val surface = Surface.createFromGL(width, height)
private val surface = Surface.createFromGL(width, height)
val canvas: Canvas
get() = surface.canvas
......
......@@ -8,7 +8,6 @@ import org.jetbrains.skia.ColorType
import org.jetbrains.skia.ColorSpace
import org.jetbrains.skia.ClipMode
import org.jetbrains.skia.ImageInfo
import org.jetbrains.skia.Picture
import org.jetbrains.skia.PictureRecorder
import org.jetbrains.skia.Rect
import org.jetbrains.skiko.context.ContextHandler
......@@ -123,7 +122,8 @@ actual open class SkiaLayer internal constructor(
backedLayer.fullscreen = value
}
actual var renderer: SkiaRenderer? = null
actual var skikoView: SkikoView? = null
val clipComponents = mutableListOf<ClipRectangle>()
@Volatile
......@@ -340,7 +340,7 @@ actual open class SkiaLayer internal constructor(
try {
isRendering = true
renderer?.onRender(canvas, pictureWidth, pictureHeight, nanoTime)
skikoView?.onRender(canvas, pictureWidth, pictureHeight, nanoTime)
} finally {
isRendering = false
}
......@@ -443,3 +443,8 @@ actual open class SkiaLayer internal constructor(
return rounded.toInt()
}
}
// InputEvent is abstract, so we wrap to match modality.
actual class SkikoPlatformInputEvent(val wrapped: InputEvent)
actual typealias SkikoPlatformKeyboardEvent = KeyEvent
actual typealias SkikoPlatformPointerEvent = MouseEvent
\ No newline at end of file
......@@ -71,7 +71,7 @@ native crash in SkiaWindowTest "render single window"
init {
setSize(width, height)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.renderer = object : SkiaRenderer {
layer.skikoView = object : NoInputSkikoView() {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
if (canCollect && frameTimes.size < frameCount) {
frameTimes.add(System.nanoTime()) // we check the real time, not the time provided by the argument
......@@ -181,7 +181,7 @@ j org.jetbrains.skiko.redrawer.MacOsRedrawer$drawLayer$1.draw()V+7
val smoothFPSCounter = FPSCounter(count = frameCheckCount)
val onComplete = CompletableDeferred<Unit>()
fun renderer(window: SkiaWindow) = object : SkiaRenderer {
fun renderer(window: SkiaWindow) = object : NoInputSkikoView() {
var t1 = Long.MAX_VALUE
val refreshRate = window.graphicsConfiguration.device.displayMode.refreshRate
......@@ -223,7 +223,7 @@ j org.jetbrains.skiko.redrawer.MacOsRedrawer$drawLayer$1.draw()V+7
window.isVisible = true
delay(1000)
window.layer.renderer = renderer(window)
window.layer.skikoView = renderer(window)
window.layer.needRedraw()
onComplete.await()
......
......@@ -54,15 +54,15 @@ class SkiaWindowTest {
window.setLocation(200, 200)
window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val renderer = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.renderer = renderer
val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app
window.isUndecorated = true
window.isVisible = true
delay(1000)
screenshots.assert(window.bounds, "frame1")
renderer.rectWidth = 100
app.rectWidth = 100
window.layer.needRedraw()
delay(1000)
screenshots.assert(window.bounds, "frame2")
......@@ -78,8 +78,8 @@ class SkiaWindowTest {
window.setLocation(200, 200)
window.preferredSize = Dimension(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val renderer = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.renderer = renderer
val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app
window.isUndecorated = true
window.pack()
window.paint(window.graphics)
......@@ -88,7 +88,7 @@ class SkiaWindowTest {
delay(1000)
screenshots.assert(window.bounds, "frame1")
renderer.rectWidth = 100
app.rectWidth = 100
window.layer.needRedraw()
delay(1000)
screenshots.assert(window.bounds, "frame2")
......@@ -104,7 +104,7 @@ class SkiaWindowTest {
window.setLocation(200, 200)
window.setSize(40, 20)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
window.layer.renderer = RectRenderer(window.layer, 20, 10, Color.RED)
window.layer.skikoView = RectRenderer(window.layer, 20, 10, Color.RED)
window.isUndecorated = true
window.isVisible = true
delay(1000)
......@@ -124,7 +124,7 @@ class SkiaWindowTest {
setLocation(200,200)
setSize(400, 200)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.renderer = RectRenderer(layer, 200, 100, color)
layer.skikoView = RectRenderer(layer, 200, 100, color)
isUndecorated = true
isVisible = true
}
......@@ -163,7 +163,7 @@ class SkiaWindowTest {
window.setLocation(200, 200)
window.setSize(40, 20)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
window.layer.renderer = object : SkiaRenderer {
window.layer.skikoView = object : NoInputSkikoView() {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
renderCount++
}
......@@ -194,7 +194,7 @@ class SkiaWindowTest {
setLocation(200,200)
setSize(40, 20)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.renderer = if (isAnimated) {
layer.skikoView = if (isAnimated) {
AnimatedBoxRenderer(layer, pixelsPerSecond = 20.0, size = 20.0)
} else {
RectRenderer(layer, 20, 10, Color.RED)
......@@ -238,7 +238,7 @@ class SkiaWindowTest {
setLocation(200,200)
setSize(400, 200)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.renderer = AnimatedBoxRenderer(layer, pixelsPerSecond = 20.0, size = 20.0)
layer.skikoView = AnimatedBoxRenderer(layer, pixelsPerSecond = 20.0, size = 20.0)
isVisible = true
}
......@@ -262,7 +262,7 @@ class SkiaWindowTest {
setSize(400, 200)
preferredSize = Dimension(400, 200)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.renderer = object : SkiaRenderer {
layer.skikoView = object : NoInputSkikoView() {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
}
}
......@@ -364,15 +364,15 @@ class SkiaWindowTest {
window.setLocation(200, 200)
window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val renderer = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.renderer = renderer
val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app
window.isUndecorated = true
window.isVisible = true
delay(1000)
screenshots.assert(window.bounds, "frame1", "testFallbackToSoftware")
renderer.rectWidth = 100
app.rectWidth = 100
window.layer.needRedraw()
delay(1000)
screenshots.assert(window.bounds, "frame2", "testFallbackToSoftware")
......@@ -424,7 +424,7 @@ class SkiaWindowTest {
window.setLocation(200, 200)
window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
window.layer.renderer = object : SkiaRenderer {
window.layer.skikoView = object : NoInputSkikoView() {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
drawCount++
......@@ -470,7 +470,7 @@ class SkiaWindowTest {
val paragraph by lazy { paragraph(window.layer.contentScale * 40, "=-+Нп") }
window.layer.renderer = object : SkiaRenderer {
window.layer.skikoView = object : NoInputSkikoView() {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
paragraph.layout(Float.POSITIVE_INFINITY)
paragraph.paint(canvas, 0f, 0f)
......@@ -502,7 +502,7 @@ class SkiaWindowTest {
var rectWidth: Int,
var rectHeight: Int,
private val rectColor: Color
) : SkiaRenderer {
) : NoInputSkikoView() {
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 {
......@@ -518,7 +518,7 @@ class SkiaWindowTest {
private val layer: SkiaLayer,
private val pixelsPerSecond: Double,
private val size: Double
) : SkiaRenderer {
) : NoInputSkikoView() {
private var oldNanoTime = Long.MAX_VALUE
private var x = 0.0
......
......@@ -7,7 +7,6 @@ internal actual fun createNativeContextHandler(layer: SkiaLayer, renderApi: Grap
TODO()
}
internal actual fun createNativeRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
......
......@@ -15,5 +15,10 @@ actual open class SkiaLayer(properties: SkiaLayerProperties) {
actual fun needRedraw() {
TODO("unimplemented")
}
actual var renderer: SkiaRenderer? = null
actual var skikoView: SkikoView? = null
}
// TODO: do properly
actual typealias SkikoPlatformInputEvent = Any
actual typealias SkikoPlatformKeyboardEvent = Any
actual typealias SkikoPlatformPointerEvent = Any
\ No newline at end of file
......@@ -4,7 +4,7 @@ import kotlinx.cinterop.useContents
import org.jetbrains.skiko.context.*
import org.jetbrains.skia.*
import org.jetbrains.skiko.redrawer.Redrawer
import platform.AppKit.NSView
import platform.AppKit.*
import platform.Foundation.NSMakeRect
actual open class SkiaLayer(
......@@ -26,10 +26,10 @@ actual open class SkiaLayer(
if (value) throw IllegalArgumentException("transparency unsupported")
}
val nsView = NSView(NSMakeRect(0.0, 0.0, 640.0, 480.0))
lateinit var nsView: NSView
var _contentScale: Float = 1.0f
actual var renderer: SkiaRenderer? = null
actual var skikoView: SkikoView? = null
private var contextHandler = MacOSOpenGLContextHandler(this)
......@@ -38,8 +38,75 @@ actual open class SkiaLayer(
private var picture: PictureHolder? = null
private val pictureRecorder = PictureRecorder()
fun initLayer() {
println("SkiaLayer.initLayer")
private fun eventToMouse(event: NSEvent, kind: SkikoPointerEventKind): SkikoPointerEvent {
var buttons = SkikoMouseButtons.NONE
val mask = event.buttonMask.toInt()
if ((mask and 1) != 0 || event.buttonNumber == 0L) {
buttons = buttons or SkikoMouseButtons.LEFT
}
if ((mask and 2) != 0 || event.buttonNumber == 1L) {
buttons = buttons or SkikoMouseButtons.RIGHT
}
var (x, y) = event.locationInWindow.useContents {
this.x to this.y
}
// Translate.
nsView.frame.useContents {
y = size.height - y
}
return SkikoPointerEvent(x, y, buttons, kind, event)
}
private fun eventToKeyboard(event: NSEvent, kind: SkikoKeyboardEventKind): SkikoKeyboardEvent {
return SkikoKeyboardEvent(event.keyCode.toInt(), kind, event)
}
fun initLayer(window: NSWindow) {
val (width, height) = window.contentLayoutRect.useContents {
this.size.width to this.size.height
}
nsView = object : NSView(NSMakeRect(0.0, 0.0, width, height)) {
private var trackingArea : NSTrackingArea? = null
override fun acceptsFirstResponder(): Boolean {
return true
}
override fun viewWillMoveToWindow(newWindow: NSWindow?) {
updateTrackingAreas()
}
override fun updateTrackingAreas() {
bounds.useContents {
println("update tracking areas to ${this.size.width} ${this.size.height}")
}
trackingArea?.let { removeTrackingArea(it) }
trackingArea = NSTrackingArea(rect = bounds,
options = NSMouseMoved or NSTrackingActiveAlways, // NSTrackingActiveInActiveApp,
owner = nsView, userInfo = null)
nsView.addTrackingArea(trackingArea!!)
}
override fun rightMouseDown(event: NSEvent) {
skikoView?.onPointerEvent(eventToMouse(event, SkikoPointerEventKind.DOWN))
}
override fun rightMouseUp(event: NSEvent) {
skikoView?.onPointerEvent(eventToMouse(event, SkikoPointerEventKind.UP))
}
override fun mouseDown(event: NSEvent) {
skikoView?.onPointerEvent(eventToMouse(event, SkikoPointerEventKind.DOWN))
}
override fun mouseUp(event: NSEvent) {
skikoView?.onPointerEvent(eventToMouse(event, SkikoPointerEventKind.UP))
}
override fun mouseMoved(event: NSEvent) {
skikoView?.onPointerEvent(eventToMouse(event, SkikoPointerEventKind.MOVE))
}
override fun keyDown(event: NSEvent) {
skikoView?.onKeyboardEvent(eventToKeyboard(event, SkikoKeyboardEventKind.DOWN))
}
override fun keyUp(event: NSEvent) {
skikoView?.onKeyboardEvent(eventToKeyboard(event, SkikoKeyboardEventKind.UP))
}
}
window.contentView!!.addSubview(nsView)
redrawer = createNativeRedrawer(this, GraphicsApi.OPENGL, properties)
redrawer?.redrawImmediately()
}
......@@ -55,8 +122,6 @@ actual open class SkiaLayer(
}
fun update(nanoTime: Long) {
println("SkiaLayer.update")
val width = nsView.frame.useContents { size.width }
val height = nsView.frame.useContents { size.height }
......@@ -65,7 +130,7 @@ actual open class SkiaLayer(
val bounds = Rect.makeWH(pictureWidth.toFloat(), pictureHeight.toFloat())
val canvas = pictureRecorder.beginRecording(bounds)
renderer?.onRender(canvas, pictureWidth.toInt(), pictureHeight.toInt(), nanoTime)
skikoView?.onRender(canvas, pictureWidth.toInt(), pictureHeight.toInt(), nanoTime)
val picture = pictureRecorder.finishRecordingAsPicture()
this.picture = PictureHolder(picture, pictureWidth.toInt(), pictureHeight.toInt())
......@@ -74,7 +139,6 @@ actual open class SkiaLayer(
private var initedCanvas = false
fun draw() {
println("SkiaLayer.draw")
contextHandler.apply {
if (!initedCanvas) {
if (!initContext()) {
......@@ -86,7 +150,6 @@ actual open class SkiaLayer(
}
clearCanvas()
val picture = picture
println("SkiaLayer.draw: picture=$picture")
if (picture != null) {
drawOnCanvas(picture.instance)
}
......@@ -95,3 +158,7 @@ actual open class SkiaLayer(
}
}
// TODO: do properly
actual typealias SkikoPlatformInputEvent = NSEvent
actual typealias SkikoPlatformKeyboardEvent = NSEvent
actual typealias SkikoPlatformPointerEvent = NSEvent
package org.jetbrains.skiko
import platform.AppKit.*
import platform.Foundation.*
class SkiaWindow(layer: SkiaLayer) {
private val windowStyle =
NSWindowStyleMaskTitled or
NSWindowStyleMaskMiniaturizable or
NSWindowStyleMaskClosable or
NSWindowStyleMaskResizable
// TODO: what's the proper way to create window here?
// Should we subclass NSWindow?
// Provide multiple constructors for SkiaWindow?
// Behave similar to Skiko/JVM?
val nsWindow = NSWindow(
contentRect = NSMakeRect(0.0, 0.0, 640.0, 480.0),
styleMask = windowStyle,
backing = NSBackingStoreBuffered,
defer = true)
init {
nsWindow.contentView!!.addSubview(layer.nsView)
layer.initLayer()
}
}
......@@ -9,7 +9,6 @@ import platform.OpenGLCommon.GLenum
internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
override fun initContext(): Boolean {
println("MacOSOpenGLContextHandler.initContext")
try {
if (context == null) {
context = DirectContext.makeGL()
......@@ -33,7 +32,6 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye
}
override fun initCanvas() {
println("MacOSOpenGLContextHandler.initCanvas")
val scale = layer.contentScale
val w = (layer.nsView.frame.useContents { size.width } * scale).toInt().coerceAtLeast(0)
val h = (layer.nsView.frame.useContents { size.height } * scale).toInt().coerceAtLeast(0)
......
......@@ -103,8 +103,6 @@ class MacosGLLayer(val layer: SkiaLayer, setNeedsDisplayOnBoundsChange: Boolean)
displayTime: CPointer<CVTimeStamp>?
) {
CGLSetCurrentContext(ctx);
println("MacosGLLayer::drawInCGLContext")
performDraw()
//context.flush() // TODO: I thought the below should call context.flush().
......
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