diff --git a/commons/build.gradle.kts b/commons/build.gradle.kts index 6cbd6071499..9345a95816c 100644 --- a/commons/build.gradle.kts +++ b/commons/build.gradle.kts @@ -10,6 +10,7 @@ plugins { val mockkVersion = extra["mockk_version"] as String val kotlinLoggingVersion = extra["kotlinLogging_version"] as String +val kotlinxCoroutinesVersion = extra["kotlinx_coroutines_version"] as String val hamcrestVersion = extra["hamcrest_version"] as String val mockitoVersion = extra["mockito_version"] as String val assertjVersion = extra["assertj_version"] as String @@ -24,6 +25,14 @@ kotlin { } sourceSets { + commonMain { + dependencies { + // Can't use compileOnly + // > Task :commons:compileTestDevelopmentExecutableKotlinJs FAILED + //e: Could not find "org.jetbrains.kotlinx:kotlinx-coroutines-core" in [/home/me/.local/share/kotlin/daemon] + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion") + } + } commonTest { dependencies { implementation(kotlin("test")) diff --git a/commons/src/commonMain/kotlin/org/jetbrains/letsPlot/commons/Debounce.kt b/commons/src/commonMain/kotlin/org/jetbrains/letsPlot/commons/Debounce.kt new file mode 100644 index 00000000000..772a68b6829 --- /dev/null +++ b/commons/src/commonMain/kotlin/org/jetbrains/letsPlot/commons/Debounce.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package org.jetbrains.letsPlot.commons + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + + +// Not thread-safe +fun debounce( + delayMs: Long, + scope: CoroutineScope, + action: (T) -> Unit +): (T) -> Unit { + var activeJob: Job? = null + + return { v: T -> + activeJob?.cancel() + activeJob = scope.launch { + delay(delayMs) + action(v) + } + } +} diff --git a/demo/plot/build.gradle.kts b/demo/plot/build.gradle.kts index 6c97286308d..0867ac4fdb0 100644 --- a/demo/plot/build.gradle.kts +++ b/demo/plot/build.gradle.kts @@ -28,6 +28,7 @@ kotlin { val batikVersion = extra["batik_version"] as String val kotlinLoggingVersion = extra["kotlinLogging_version"] as String val kotlinxHtmlVersion = extra["kotlinx_html_version"] as String + val kotlinxCoroutinesVersion = extra["kotlinx_coroutines_version"] as String val ktorVersion = extra["ktor_version"] as String val jfxPlatform = extra["jfxPlatformResolved"] as String val jfxVersion = extra["jfx_version"] as String @@ -39,6 +40,7 @@ kotlin { commonMain { dependencies { implementation(kotlin("stdlib-common")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion") implementation(project(":commons")) implementation(project(":datamodel")) diff --git a/gradle.properties b/gradle.properties index f3298ba4ab1..a496f99f1c3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,7 @@ kotlin_version=1.9.23 kotlinLogging_version=2.0.5 kotlinx_html_version=0.7.3 +kotlinx_coroutines_version=1.7.1 slf4j_version=1.7.29 diff --git a/plot-builder/build.gradle.kts b/plot-builder/build.gradle.kts index 9e62134a4fb..1d41be4a0a8 100644 --- a/plot-builder/build.gradle.kts +++ b/plot-builder/build.gradle.kts @@ -9,6 +9,7 @@ plugins { val mockkVersion = extra["mockk_version"] as String val kotlinLoggingVersion = extra["kotlinLogging_version"] as String +val kotlinxCoroutinesVersion = extra["kotlinx_coroutines_version"] as String val hamcrestVersion = extra["hamcrest_version"] as String val mockitoVersion = extra["mockito_version"] as String val assertjVersion = extra["assertj_version"] as String @@ -22,6 +23,11 @@ kotlin { sourceSets { commonMain { dependencies { + // Can't use compileOnly + // > Task :commons:compileTestDevelopmentExecutableKotlinJs FAILED + //e: Could not find "org.jetbrains.kotlinx:kotlinx-coroutines-core" in [/home/me/.local/share/kotlin/daemon] + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion") + compileOnly(project(":commons")) compileOnly(project(":datamodel")) compileOnly(project(":plot-base")) diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/interact/PlotToolEventDispatcher.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/interact/PlotToolEventDispatcher.kt index 8afd08b575d..4642ac56ba8 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/interact/PlotToolEventDispatcher.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/interact/PlotToolEventDispatcher.kt @@ -5,6 +5,9 @@ package org.jetbrains.letsPlot.core.plot.builder.interact +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import org.jetbrains.letsPlot.commons.debounce import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle import org.jetbrains.letsPlot.commons.registration.Registration import org.jetbrains.letsPlot.core.interact.DrawRectFeedback @@ -47,6 +50,11 @@ internal class PlotToolEventDispatcher( deactivateOverlappingInteractions(origin, interactionSpec) val interactionName = interactionSpec.getValue(ToolInteractionSpec.NAME) as String + val completeInteractionDebounced = + debounce(DEBOUNCE_DELAY_MS, CoroutineScope(Dispatchers.Default)) { dataBounds -> + println("Debounced interaction: $interactionName, dataBounds: $dataBounds") + completeInteraction(origin, interactionName, dataBounds) + } // ToDo: sent "completed" event in "onCompleted" val feedback = when (interactionName) { @@ -66,7 +74,7 @@ internal class PlotToolEventDispatcher( ToolInteractionSpec.WHEEL_ZOOM -> WheelZoomFeedback( onCompleted = { dataBounds -> - completeInteraction(origin, interactionName, dataBounds) + completeInteractionDebounced(dataBounds) } ) @@ -151,4 +159,8 @@ internal class PlotToolEventDispatcher( ) { val interactionName = interactionSpec.getValue(ToolInteractionSpec.NAME) as String } + + companion object { + private const val DEBOUNCE_DELAY_MS = 30L + } } \ No newline at end of file