diff --git a/skiko/build.gradle.kts b/skiko/build.gradle.kts index 52c08e315..fc1a6ad53 100644 --- a/skiko/build.gradle.kts +++ b/skiko/build.gradle.kts @@ -273,9 +273,6 @@ kotlin { val awtMain by getting { dependsOn(jvmMain) - dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:$coroutinesVersion") - } } if (supportAndroid) { @@ -298,9 +295,6 @@ kotlin { val awtTest by getting { dependsOn(jvmTest) - dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:$coroutinesVersion") - } } if (supportAndroid) { diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/HardwareLayer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/HardwareLayer.kt index 1476fc1f2..8e4cb38cf 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/HardwareLayer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/HardwareLayer.kt @@ -2,7 +2,6 @@ package org.jetbrains.skiko import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.swing.Swing import java.awt.* import java.awt.event.FocusEvent import java.awt.event.InputMethodEvent @@ -110,7 +109,7 @@ internal open class HardwareLayer( // and its accessibility context. This timeout is used to deal with concurrency // TODO Find more reliable procedure resetFocusAccessibleJob?.cancel() - resetFocusAccessibleJob = GlobalScope.launch(Dispatchers.Swing) { + resetFocusAccessibleJob = GlobalScope.launch(MainUIDispatcher) { delay(100) _focusedAccessible = null } diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/MainUIDispatcher.awt.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/MainUIDispatcher.awt.kt new file mode 100644 index 000000000..b40fe8c6f --- /dev/null +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/MainUIDispatcher.awt.kt @@ -0,0 +1,57 @@ +package org.jetbrains.skiko + +import kotlinx.coroutines.* +import java.awt.event.ActionListener +import java.lang.Runnable +import java.util.concurrent.TimeUnit +import javax.swing.SwingUtilities +import javax.swing.Timer +import kotlin.coroutines.CoroutineContext + +/** + * Dispatcher for UI thread, which is used in the current implementation of native UI integration. + * Currently, it uses Swing event dispatching thread. + */ +val MainUIDispatcher: CoroutineDispatcher + get() = SwingDispatcher + +/** + * Dispatcher for Swing event dispatching thread. + * + * Copy of Dispatchers.Swing from kotlinx-coroutines-swing. + * + * We don't depend on kotlinx-coroutines-swing, because it will override Dispatchers.Main, and + * application can require a different Dispatchers.Main. + * + * Note, that we use internal API `Delay` and experimental `resumeUndispatched`. + * That means it can be changed in the future. When it happens, we need + * to release a new version of Skiko. + */ +@OptIn(InternalCoroutinesApi::class, ExperimentalCoroutinesApi::class) +private object SwingDispatcher : CoroutineDispatcher(), Delay { + override fun dispatch(context: CoroutineContext, block: Runnable): Unit = SwingUtilities.invokeLater(block) + + override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { + val timer = schedule(timeMillis, TimeUnit.MILLISECONDS) { + with(continuation) { resumeUndispatched(Unit) } + } + continuation.invokeOnCancellation { timer.stop() } + } + + override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { + val timer = schedule(timeMillis, TimeUnit.MILLISECONDS) { + block.run() + } + return object : DisposableHandle { + override fun dispose() { + timer.stop() + } + } + } + + private fun schedule(time: Long, unit: TimeUnit, action: ActionListener): Timer = + Timer(unit.toMillis(time).coerceAtMost(Int.MAX_VALUE.toLong()).toInt(), action).apply { + isRepeats = false + start() + } +} \ No newline at end of file diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AbstractDirectSoftwareRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AbstractDirectSoftwareRedrawer.kt index 1ffd44552..23b9a5bb4 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AbstractDirectSoftwareRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AbstractDirectSoftwareRedrawer.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.* import org.jetbrains.skiko.FrameDispatcher import org.jetbrains.skiko.FrameLimiter import org.jetbrains.skiko.RenderException +import org.jetbrains.skiko.MainUIDispatcher import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayerProperties import org.jetbrains.skiko.context.DirectSoftwareContextHandler diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AngleRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AngleRedrawer.kt index e3e973cf4..7aa0d9843 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AngleRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AngleRedrawer.kt @@ -3,6 +3,7 @@ package org.jetbrains.skiko.redrawer import org.jetbrains.skia.BackendRenderTarget import org.jetbrains.skia.DirectContext import org.jetbrains.skiko.FrameDispatcher +import org.jetbrains.skiko.MainUIDispatcher import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayerProperties import org.jetbrains.skiko.context.AngleContextHandler diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/MainUIDispatcher.awt.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/MainUIDispatcher.awt.kt deleted file mode 100644 index b99de1424..000000000 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/MainUIDispatcher.awt.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.jetbrains.skiko.redrawer - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainCoroutineDispatcher -import kotlinx.coroutines.swing.Swing - -val MainUIDispatcher: MainCoroutineDispatcher - get() = Dispatchers.Swing diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/SoftwareRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/SoftwareRedrawer.kt index 130003274..0e67fad0e 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/SoftwareRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/SoftwareRedrawer.kt @@ -3,6 +3,7 @@ package org.jetbrains.skiko.redrawer import kotlinx.coroutines.* import org.jetbrains.skiko.FrameDispatcher import org.jetbrains.skiko.FrameLimiter +import org.jetbrains.skiko.MainUIDispatcher import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayerProperties import org.jetbrains.skiko.context.SoftwareContextHandler diff --git a/skiko/src/jvmTest/kotlin/org/jetbrains/skiko/util/UiTest.kt b/skiko/src/jvmTest/kotlin/org/jetbrains/skiko/util/UiTest.kt index 7995421ed..027c308e0 100644 --- a/skiko/src/jvmTest/kotlin/org/jetbrains/skiko/util/UiTest.kt +++ b/skiko/src/jvmTest/kotlin/org/jetbrains/skiko/util/UiTest.kt @@ -1,9 +1,7 @@ package org.jetbrains.skiko.util import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.swing.Swing import org.jetbrains.skiko.* import org.junit.Assume.assumeFalse import org.junit.Assume.assumeTrue @@ -16,7 +14,7 @@ internal fun uiTest(block: suspend UiTestScope.() -> Unit) { val renderApi = System.getProperty("skiko.test.ui.renderApi", "all") - runBlocking(Dispatchers.Swing) { + runBlocking(MainUIDispatcher) { if (renderApi == "all") { SkikoProperties.fallbackRenderApiQueue(SkikoProperties.renderApi).forEach { println("Testing $it renderApi")