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