JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
16.15k stars 1.17k forks source link

click event not raised when release mouse button #1945

Closed Cricin closed 2 years ago

Cricin commented 2 years ago

hi, I am trying to use compose desktop directly with lwjgl, everything is goes well expect mouse event handling. the click event is not raised when i release mouse button until mouse pointer moved. the code i used is this:

private fun initCompose(width: Int, height: Int): ComposeScene {
  scene = ComposeScene(coroutineContext = Dispatchers.App) {
    if(!needRender) {
       needRender = true
       Dispatchers.App.addTask {
         render()
       }
     }
   }
   scene.constraints = Constraints.fixed(width, height)
   scene.setContent {
     ComposeTest()
   }
   return scene
 }

private fun render() {
   if (needRender && scene.hasInvalidations()) {
     needRender = false
     scene.render(canvas, System.nanoTime())
     context.flush()
     GLFW.glfwSwapBuffers(window)
   }
 }

and mouse event is dispatched to ComposeScene:

GLFW.glfwSetMouseButtonCallback(window) { window, button, action, mods ->
        scene.sendPointerEvent(
          eventType = when (action) {
            GLFW.GLFW_PRESS -> PointerEventType.Press
            GLFW.GLFW_RELEASE -> PointerEventType.Release
            else -> PointerEventType.Unknown
          },
          position = offset ?: Offset(0F, 0F),
          buttons = PointerButtons(
            isPrimaryPressed = button == GLFW.GLFW_MOUSE_BUTTON_LEFT,
            isSecondaryPressed = button == GLFW.GLFW_MOUSE_BUTTON_RIGHT,
            isTertiaryPressed = button == GLFW.GLFW_MOUSE_BUTTON_MIDDLE
          )
        )
      }

      GLFW.glfwSetCursorPosCallback(window) { window, xpos, ypos ->
        offset = Offset(xpos.toFloat(), ypos.toFloat())
        scene.sendPointerEvent(
          eventType = PointerEventType.Move,
          position = offset!!,
        )
      }

the content composable function:

@Composable
fun ComposeTest() {
  Box(modifier = Modifier.fillMaxSize().background(Color.White), contentAlignment = Alignment.Center) {
      Button(onClick = {
        println("button clicked")
        RuntimeException().printStacktrace()
      }) {
        Text("Click Me")
      }
  }
}

here is the stack when click raised in a mouse move event

java.lang.RuntimeException
    at compose.lwjgl.AppKt$ComposeTest$1$1.invoke(App.kt:164)
    at compose.lwjgl.AppKt$ComposeTest$1$1.invoke(App.kt:162)
    at androidx.compose.foundation.ClickableKt$clickable$4$gesture$1$2.invoke-k-4lQ0M(Clickable.kt:153)
    at androidx.compose.foundation.ClickableKt$clickable$4$gesture$1$2.invoke(Clickable.kt:142)
    at androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1$1.invokeSuspend(TapGestureDetector.kt:225)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:178)
    at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
    at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
    at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter$PointerEventHandlerCoroutine.offerPointerEvent(SuspendingPointerInputFilter.kt:529)
    at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.dispatchPointerEvent(SuspendingPointerInputFilter.kt:422)
    at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.onPointerEvent-H0pRuoY(SuspendingPointerInputFilter.kt:435)
    at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:285)
    at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:272)
    at androidx.compose.ui.input.pointer.NodeParent.dispatchMainEventPass(HitPathTracker.kt:152)
    at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:89)
    at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:80)
    at androidx.compose.ui.platform.SkiaBasedOwner.processPointerInput-gBdvCQM$ui(SkiaBasedOwner.skiko.kt:321)
    at androidx.compose.ui.platform.SkiaBasedOwner.processPointerInput-gBdvCQM$ui$default(SkiaBasedOwner.skiko.kt:320)
    at androidx.compose.ui.ComposeScene.onMouseMove(ComposeScene.skiko.kt:505)
    at androidx.compose.ui.ComposeScene.processPointerInput(ComposeScene.skiko.kt:465)
    at androidx.compose.ui.ComposeScene.sendPointerEvent-Kr8mkKM(ComposeScene.skiko.kt:451)
    at androidx.compose.ui.ComposeScene.sendPointerEvent-Kr8mkKM$default(ComposeScene.skiko.kt:421)
    at compose.lwjgl.App$Companion.main$lambda-2(App.kt:75)
    at org.lwjgl.glfw.GLFWCursorPosCallbackI.callback(GLFWCursorPosCallbackI.java:37)
    at org.lwjgl.system.JNI.invokeV(Native Method)
    at org.lwjgl.glfw.GLFW.glfwWaitEvents(GLFW.java:3129)
    at compose.lwjgl.App$Companion.main(App.kt:101)
    at compose.lwjgl.App.main(App.kt)

anyone knows reason? help me plz.

Cricin commented 2 years ago

i got the reason, the PointerButtons constructed with pressed state sent to ComposeScene, so Compose think it is pressed regardless of eventType is release, a subsequent move event with no PointerButtons will raise click event, problems solved.

okushnikov commented 3 months ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.