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
15.96k stars 1.16k forks source link

Resizing window on desktop sometimes triggers onClick handlers of Composables #2850

Closed SebastianAigner closed 9 months ago

SebastianAigner commented 1 year ago

Reproducable with experimental ImageViewer project. Resize the window in the gallery view while having the cursor at the height of the preview image, and sometimes, it'll trigger the transition to the Memory View.

https://user-images.githubusercontent.com/2178959/223841903-55c74b1c-4c7c-4829-b187-53e2256aee72.mov

AlexeyTsvetkov commented 1 year ago

Reproduced with a simple button example:

import androidx.compose.material.MaterialTheme
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application

@Composable
@Preview
fun App() {
    val colors = listOf(Color.Red, Color.Green, Color.Blue)
    val numberOfColors = colors.size
    var index by remember { mutableStateOf(0) }
    Row(Modifier.background(colors[index % numberOfColors]).fillMaxSize()) {
        Button(onClick = { index++ }) {
            Text("ClickMe")
        }
    }
}

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        MaterialTheme {
            App()
        }
    }
}

To reproduce:

  1. Start resizing outside of a button (press mouse/touchpad down).
  2. Move cursor inside of the button.
  3. Move cursor outside of the button & stop resizing. Click on the button is often registered (but not always).

Kotlin 1.8.0 Compose 1.3.0 JDK 18

m-sasha commented 1 year ago

This sounds like an unintended side-effect of the synthetic events + somehow receiving/processing the mouse event before the measure/layout cycle.

hgourvest commented 11 months ago

On Linux Fedora 38 Gnome Desktop, I have a similar problem, but slightly different. After resizing the window, I release the mouse button and immediately move the mouse pointer into the window area, at which point the Click event is triggered.

m-sasha commented 9 months ago

Ok, I did some digging and what happens is that during the resizing AWT sends us MOUSE_ENTER and MOUSE_EXIT events (no press or release events though) with extModifiers=Button1. However PointerInputScope.detectTapAndPress doesn't look at the event type - it only cares about whether any button is pressed or released. So when it receives an event which says a button is pressed, and then another one which says it's released, it detects a tap.

@igordmn Where do you think it's best to fix/workaround this? I'm thinking we should completely filter out these MOUSE_ENTER and MOUSE_EXIT events because that's the root cause of the problem. AWT should not be sending us mouse events while resizing the window.

m-sasha commented 9 months ago

These MOUSE_ENTER and MOUSE_EXIT events have other undesirable effects too - a widget placed close to the edge receives them and becomes "hovered", for example.

igordmn commented 9 months ago

we should completely filter out these MOUSE_ENTER and MOUSE_EXIT

I agree, this will be a good solution if we manage to do that (ideally, AWT should consume hover on resize in the first place)

But how we can know that we should filter the received event? Maybe we should filter enter's with pressed buttons?

m-sasha commented 9 months ago

Yes, filter enter/exit with pressed button(s) if we didn't receive the press event for those buttons.

okushnikov commented 2 months ago

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