Unverified Commit bbd1c31e authored by Nikolay Igotti's avatar Nikolay Igotti Committed by GitHub

Universal and mostly automated dispose logic (#292)

parent 784e398a
......@@ -19,7 +19,7 @@ class Clocks: SkikoView {
private val fontCollection = FontCollection()
.setDefaultFontManager(FontMgr.default)
override fun onRender(canvas: Canvas, width: Int, height: Int, currentTimestamp: Long) {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val watchFill = Paint().apply { color = 0xFFFFFFFF.toInt() }
val watchStroke = Paint().apply {
color = 0xFF000000.toInt()
......@@ -50,7 +50,7 @@ class Clocks: SkikoView {
)
angle += (2.0 * PI / 12.0).toFloat()
}
val time = (currentTimestamp / 1E6) % 60000 +
val time = (nanoTime / 1E6) % 60000 +
(x.toFloat() / width * 5000).toLong() +
(y.toFloat() / width * 5000).toLong()
......
......@@ -12,6 +12,8 @@ expect open class SkiaLayer {
// Actual type of attach container is platform-specific.
fun attachTo(container: Any)
fun detach()
fun needRedraw()
}
......
......@@ -6,6 +6,7 @@ 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
......@@ -22,7 +23,7 @@ actual open class SkiaLayer(
set(value) { throw UnsupportedOperationException() }
actual val contentScale: Float
get() = view.contentScaleFactor.toFloat()
get() = view!!.contentScaleFactor?.toFloat()
actual var fullscreen: Boolean
get() = true
......@@ -37,16 +38,16 @@ actual open class SkiaLayer(
}
val width: Float
get() = view.frame.useContents {
get() = view!!.frame.useContents {
return@useContents size.width.toFloat()
}
val height: Float
get() = view.frame.useContents {
get() = view!!.frame.useContents {
return@useContents size.height.toFloat()
}
lateinit var view: UIView
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
actual fun attachTo(container: Any) {
......@@ -54,7 +55,8 @@ actual open class SkiaLayer(
}
fun attachTo(view: UIView) {
this.view = view
contextHandler = MetalContextHandler(this)
pictureRecorder = PictureRecorder()
// 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
......@@ -76,33 +78,48 @@ actual open class SkiaLayer(
}
// We have ':' in selector to take care of function argument.
view.addGestureRecognizer(UITapGestureRecognizer(controller, NSSelectorFromString("onTap:")))
// TODO: maybe add observer for view.viewDidDisappear() to detach us?
redrawer = MetalRedrawer(this, properties)
redrawer?.redrawImmediately()
}
private var isDisposed = false
actual fun detach() {
if (!isDisposed) {
redrawer?.dispose()
redrawer = null
contextHandler?.dispose()
contextHandler = null
picture?.instance?.close()
picture = null
pictureRecorder?.close()
pictureRecorder = null
isDisposed = true
}
}
actual var skikoView: SkikoView? = null
internal var redrawer: MetalRedrawer? = null
private var picture: PictureHolder? = null
private val pictureRecorder = PictureRecorder()
private val contextHandler = MetalContextHandler(this)
private var pictureRecorder: PictureRecorder? = null
private var contextHandler: MetalContextHandler? = null
fun update(nanoTime: Long) {
val (w, h) = view.frame.useContents {
val (w, h) = view!!.frame.useContents {
size.width to size.height
}
val pictureWidth = (w.toFloat() * contentScale).coerceAtLeast(0.0F)
val pictureHeight = (h.toFloat() * contentScale).coerceAtLeast(0.0F)
val bounds = Rect.makeWH(pictureWidth, pictureHeight)
val canvas = pictureRecorder.beginRecording(bounds)
val canvas = pictureRecorder!!.beginRecording(bounds)
skikoView?.onRender(canvas, pictureWidth.toInt(), pictureHeight.toInt(), nanoTime)
val picture = pictureRecorder.finishRecordingAsPicture()
val picture = pictureRecorder!!.finishRecordingAsPicture()
this.picture = PictureHolder(picture, pictureWidth.toInt(), pictureHeight.toInt())
}
fun draw() {
contextHandler.apply {
contextHandler?.apply {
if (!initContext()) {
error("initContext() failure")
}
......
......@@ -33,17 +33,23 @@ class SkikoViewController : UIViewController {
this.appFactory = appFactory
}
private lateinit var skikoLayer: SkiaLayer
override fun viewDidLoad() {
super.viewDidLoad()
val (width, height) = UIScreen.mainScreen.bounds.useContents {
this.size.width to this.size.height
}
val layer = SkiaLayer().apply {
skikoLayer = SkiaLayer().apply {
skikoView = appFactory(this)
}
view.contentScaleFactor = UIScreen.mainScreen.scale
view.setFrame(CGRectMake(0.0, 0.0, width, height))
layer.attachTo(this.view)
skikoLayer.attachTo(this.view)
}
// viewDidUnload() is deprecated and not called.
override fun viewDidDisappear(animated: Boolean) {
skikoLayer.detach()
}
}
......@@ -38,7 +38,7 @@ internal class MetalContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
override fun initCanvas() {
disposeCanvas()
val scale = layer.contentScale
val (w, h) = layer.view.frame.useContents {
val (w, h) = layer.view!!.frame.useContents {
(size.width * scale).toInt().coerceAtLeast(0) to (size.height * scale).toInt().coerceAtLeast(0)
}
......
......@@ -55,10 +55,11 @@ internal class MetalRedrawer(
override fun syncSize() {
metalLayer.contentsScale = layer.contentScale.toDouble()
val (w, h) = layer.view.frame.useContents {
val osView = layer.view!!
val (w, h) = osView.frame.useContents {
size.width to size.height
}
metalLayer.frame = layer.view.frame
metalLayer.frame = osView.frame
metalLayer.init(layer, device)
metalLayer.drawableSize = CGSizeMake(w * metalLayer.contentsScale, h * metalLayer.contentsScale)
}
......@@ -120,8 +121,10 @@ class MetalLayer : CAMetalLayer {
CGColorCreate(CGColorSpaceCreateDeviceRGB(), it.addressOf(0))
}
this.opaque = true
this.frame = skiaLayer.view.frame
skiaLayer.view.layer.addSublayer(this)
skiaLayer.view?.let {
this.frame = it.frame
it.layer.addSublayer(this)
}
}
fun draw() {
......
......@@ -48,10 +48,14 @@ actual open class SkiaLayer(properties: SkiaLayerProperties = makeDefaultSkiaLay
}
actual fun attachTo(container: Any) {
attachTo(container as HTMLCanvasElement)
attachTo(container as HTMLCanvasElement, false)
}
fun attachTo(htmlCanvas: HTMLCanvasElement) {
actual fun detach() {
// TODO: when switch to the frame dispatcher - stop it here.
}
fun attachTo(htmlCanvas: HTMLCanvasElement, autoDetach: Boolean = true) {
state = object: CanvasRenderer(htmlCanvas) {
override fun drawFrame(currentTimestamp: Double) {
// currentTimestamp is in milliseconds.
......
......@@ -84,6 +84,15 @@ actual open class SkiaLayer internal constructor(
}
}
override fun removeNotify() {
dispose()
super.removeNotify()
}
actual fun detach() {
dispose()
}
private var isInited = false
private var isRendering = false
......@@ -256,9 +265,8 @@ actual open class SkiaLayer internal constructor(
open fun dispose() {
check(isEventDispatchThread()) { "Method should be called from AWT event dispatch thread" }
check(!isDisposed) { "SkiaLayer is disposed" }
if (isInited) {
if (isInited && !isDisposed) {
redrawer?.dispose() // we should dispose redrawer first (to cancel `draw` in rendering thread)
contextHandler?.dispose()
picture?.instance?.close()
......@@ -508,6 +516,16 @@ actual open class SkiaLayer internal constructor(
}
}
fun SkiaLayer.disableTitleBar() {
backedLayer.useDrawingSurfacePlatformInfo {
platformOperations.disableTitleBar(it)
}
}
fun orderEmojiAndSymbolsPopup() {
platformOperations.orderEmojiAndSymbolsPopup()
}
// InputEvent is abstract, so we wrap to match modality.
actual class SkikoPlatformInputEvent(val wrapped: InputEvent)
actual typealias SkikoPlatformKeyboardEvent = KeyEvent
......
package org.jetbrains.skiko
import javax.swing.JFrame
@Deprecated("Will be removed soon")
open class SkiaWindow(
properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties(),
layerFactory: () -> SkiaLayer = { SkiaLayer(properties) }
) : JFrame() {
val layer = layerFactory()
init {
contentPane.add(layer)
}
override fun dispose() {
layer.dispose()
super.dispose()
}
fun disableTitleBar() {
layer.backedLayer.useDrawingSurfacePlatformInfo {
platformOperations.disableTitleBar(it)
}
}
}
fun SkiaLayer.disableTitleBar() {
backedLayer.useDrawingSurfacePlatformInfo {
platformOperations.disableTitleBar(it)
}
}
fun orderEmojiAndSymbolsPopup() {
platformOperations.orderEmojiAndSymbolsPopup()
}
\ No newline at end of file
......@@ -27,6 +27,22 @@ import javax.swing.WindowConstants
import kotlin.random.Random
import kotlin.test.assertTrue
internal open class SkiaWindow(
properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties(),
layerFactory: () -> SkiaLayer = { SkiaLayer(properties) }
) : JFrame() {
val layer = layerFactory()
init {
contentPane.add(layer)
}
override fun dispose() {
layer.dispose()
super.dispose()
}
}
@Suppress("BlockingMethodInNonBlockingContext", "SameParameterValue")
class SkiaWindowTest {
private val fontCollection = FontCollection()
......
......@@ -18,6 +18,9 @@ actual open class SkiaLayer(properties: SkiaLayerProperties) {
actual fun attachTo(container: Any) {
TODO("unimplemented")
}
actual fun detach() {
TODO("unimplemented")
}
actual var skikoView: SkikoView? = null
}
......
package org.jetbrains.skiko
import kotlinx.cinterop.ObjCAction
import kotlinx.cinterop.useContents
import org.jetbrains.skiko.context.*
import org.jetbrains.skia.*
import org.jetbrains.skiko.redrawer.Redrawer
import platform.AppKit.*
import platform.Foundation.NSMakeRect
import platform.Foundation.NSNotificationCenter
import platform.Foundation.NSSelectorFromString
import platform.Foundation.addObserver
import platform.darwin.NSObject
actual open class SkiaLayer(
private val properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties()
......@@ -76,12 +81,9 @@ actual open class SkiaLayer(
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,
options = NSMouseMoved or NSTrackingActiveInActiveApp,
owner = nsView, userInfo = null)
nsView.addTrackingArea(trackingArea!!)
}
......@@ -107,13 +109,23 @@ actual open class SkiaLayer(
override fun keyUp(event: NSEvent) {
skikoView?.onKeyboardEvent(eventToKeyboard(event, SkikoKeyboardEventKind.UP))
}
@ObjCAction
open fun onWindowClose(arg: NSObject?) {
detach()
val center = NSNotificationCenter.defaultCenter()
center.removeObserver(nsView)
}
}
val center = NSNotificationCenter.defaultCenter()
center.addObserver(nsView, NSSelectorFromString("onWindowClose:"),
NSWindowWillCloseNotification!!, window)
window.contentView!!.addSubview(nsView)
redrawer = createNativeRedrawer(this, GraphicsApi.OPENGL, properties)
redrawer?.redrawImmediately()
}
fun disposeLayer() {
actual fun detach() {
redrawer?.dispose()
redrawer = null
initedCanvas = false
......
......@@ -18,10 +18,6 @@ internal class MacOsOpenGLRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
init {
println("create MacOsOpenGLRedrawer")
}
private val drawLayer = MacosGLLayer(layer, setNeedsDisplayOnBoundsChange = true)
override fun dispose() {
......
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