Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hover doesn't always disappear after exit #1324

Closed
JetpackDuba opened this issue Oct 29, 2021 · 15 comments
Closed

Hover doesn't always disappear after exit #1324

JetpackDuba opened this issue Oct 29, 2021 · 15 comments
Assignees
Labels
bug Something isn't working desktop p:critical Critical priority
Milestone

Comments

@JetpackDuba
Copy link
Contributor

JetpackDuba commented Oct 29, 2021

Seems like sometimes the hover exit is not working as expected, as the item is still marked as if it was still being hovered.

Code to reproduce it:

fun main() = singleWindowApplication {
    LazyColumn {
        items(count = 10) { i ->
            Text(
                text = "Item number $i",
                modifier = Modifier.clickable {}
            )
        }
    }
}

GIF showing the problem where I hover the list's items and sometimes some of them keep being hovered when the mouse is not on top them:

hover_issue

OS: Ubuntu 20.04 & Manjaro with KDE
Compose Desktop Version: 1.0.0-beta5 (started happening since the first version that included the hovering)

@akurasov akurasov self-assigned this Oct 29, 2021
@akurasov akurasov added desktop bug Something isn't working Saved labels Oct 29, 2021
@akurasov akurasov assigned prepor and unassigned akurasov Nov 1, 2021
@akurasov
Copy link
Contributor

akurasov commented Nov 1, 2021

Managed to reproduce on Mac, but it is really-really difficult

@JetpackDuba
Copy link
Contributor Author

I have noticed that it happens much more in my Manjaro KDE installation than on Ubuntu, not sure why tho. Not sure if it's the desktop environment or there is something different in the build process (like different JDK). In any case, still happens in Ubuntu even if it's less.

@hrach
Copy link

hrach commented Nov 13, 2021

I have the same problem on Windows. Reproducibility is easy:

fun main() = singleWindowApplication {
    val interactionSource = MutableInteractionSource()
    val isHovered by interactionSource.collectIsHoveredAsState()
    Box(Modifier.size(100.dp).background(Color.Gray).hoverable(interactionSource)) {
        if (isHovered) {
            Text("Hovered")
        }
    }
}
2021-11-13-21-29-30.mp4

edit: id("org.jetbrains.compose") version "1.0.0-beta6-dev455"

@akurasov akurasov removed the Saved label Nov 15, 2021
@akurasov akurasov added this to the 1.0 milestone Nov 15, 2021
@igordmn
Copy link
Collaborator

igordmn commented Nov 25, 2021

In 1.0.0-rc4 reproduced with a scrollbar and a heavy lazy list:

2021-11-26-69.mp4

With a light lazy list it is hard to reproduce.

@igordmn igordmn self-assigned this Nov 25, 2021
@igordmn igordmn added p:high High priority p:critical Critical priority and removed p:high High priority labels Nov 25, 2021
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Nov 26, 2021
@JetpackDuba
Copy link
Contributor Author

With rc5 the issue seems to be solved when simply hovering with the cursor, however, when hovering an item in a lazy list and then scrolling, it still sometimes ends up with a hovered line that shouldn't be.

igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Nov 27, 2021
Fixes JetBrains/compose-multiplatform#1324 (comment)

Compose doesn't work well if we send an event with different coordinates without sending Move event before it:
```
Column {
  Box(size=10)
  Box(size=10)
}
```
If we send these events:
Move(5,5) -> Scroll(5,15) -> Move(5,15)

Then during the scroll event, HitPathTracker forgets about the first Box, and never send Exit to it. Instead it sends the Scroll event to it.

We should send events in this order:
Move(5,5) -> Move(5,15) -> Scroll(5,15) -> Move(5,15)

With synthetic events things more complicated, as we always send events with the same position. I suppose the proper fix should be in Compose core itself, but it would a very huge fix.

Reproducer:
```
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.ImageComposeScene
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.use
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@OptIn(ExperimentalComposeUiApi::class)
@RunWith(JUnit4::class)
class MouseHoverFilterTest {
    // bug: JetBrains/compose-multiplatform#1324 (comment)
    @test
    fun `move from one component to another, between another non-move event`() = ImageComposeScene(
        width = 100,
        height = 100,
        density = Density(1f)
    ).use { scene ->
        var enterCount1 = 0
        var exitCount1 = 0
        var enterCount2 = 0
        var exitCount2 = 0

        scene.setContent {
            Column {
                Box(
                    modifier = Modifier
                        .pointerMove(
                            onEnter = { enterCount1++ },
                            onExit = { exitCount1++ }
                        )
                        .size(10.dp, 10.dp)
                )
                Box(
                    modifier = Modifier
                        .pointerMove(
                            onEnter = { enterCount2++ },
                            onExit = { exitCount2++ }
                        )
                        .size(10.dp, 10.dp)
                )
            }
        }

        scene.sendPointerEvent(PointerEventType.Enter, Offset(5f, 5f))
        assertThat(enterCount1).isEqualTo(1)
        assertThat(exitCount1).isEqualTo(0)
        assertThat(enterCount2).isEqualTo(0)
        assertThat(exitCount2).isEqualTo(0)

        // Compose doesn't work well if we send an event with different type and different coordinates, without sending move event before it
        scene.sendPointerEvent(PointerEventType.Scroll, Offset(5f, 15f))
        scene.sendPointerEvent(PointerEventType.Move, Offset(5f, 15f))
        assertThat(enterCount1).isEqualTo(1)
        assertThat(exitCount1).isEqualTo(1)
        assertThat(enterCount2).isEqualTo(1)
        assertThat(exitCount2).isEqualTo(0)
    }
}
```

Another issue:
JetBrains/compose-multiplatform#1480
@hrach
Copy link

hrach commented Nov 28, 2021

My usecase #1324 (comment) behave exactly the same in latest RC5. Windows 11.

@igordmn
Copy link
Collaborator

igordmn commented Nov 28, 2021

My usecase #1324 (comment) behave exactly the same in latest RC5. Windows 11.

Can you check on 0.0.0-feature-test28112021-dev491? It will be rc6 tomorrow

@igordmn
Copy link
Collaborator

igordmn commented Nov 28, 2021

My usecase #1324 (comment) behave exactly the same in latest RC5. Windows 11.

Actually, I reproduced it. The issue is in your snippet. It should be:

val interactionSource = remember { MutableInteractionSource() }

@hrach
Copy link

hrach commented Nov 28, 2021

I really sorry, lame mistake. My non-simiplified usecase works correctly with RC5. THX.

@JetpackDuba
Copy link
Contributor Author

Can you check on 0.0.0-feature-test28112021-dev491? It will be rc6 tomorrow

Does this version include JetBrains/compose-multiplatform-core#135?

@igordmn
Copy link
Collaborator

igordmn commented Nov 28, 2021

Does this version include JetBrains/compose-multiplatform-core#135?

Yes, it includes this fix

@JetpackDuba
Copy link
Contributor Author

The hover issue when scrolling still persists but it's less common.
list_hover

@igordmn
Copy link
Collaborator

igordmn commented Dec 1, 2021

Can you check if this works in 1.0.0-rc12?

There was one more change regarding this issue.

On my PC it works in all tested cases.

@JetpackDuba
Copy link
Contributor Author

In 1.0.0-rc12 seems to work nicely 🥳 Good job and thanks @igordmn !

igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Jan 14, 2022
Call enter/exit events if we hover another popup

Fixes JetBrains/compose-multiplatform#841

Consume key events

Don't send event to focusable popup if there is no hovered popup

Don't scroll outside of focusable popup

Fixes JetBrains/compose-multiplatform#1346

Fix crash when we press right mouse button during dragging with left button

Fixes JetBrains/compose-multiplatform#1426
Fixes JetBrains/compose-multiplatform#1176

The weird behaviour doesn't reproduce anymore after cherry-picks
(this fix was reverted a few days ago here: #89)

Fix lazy scrollbar

Fixes JetBrains/compose-multiplatform#1430

Fix ScrollbarTest

Make real synthetic move events

I encountered code, where users doesn't use Compose events at all. They just listen to awtEvent, and check its type.

The feature "send synthetic move event on relayout", which we recently implemented, will break such use case, as synthetic event type would not reflect reality. For example, we would resend the latest native event, which was MousePressed. The application checks it and determines, that that cursor is over some button and presses it.

To fix that, the platform should provide a factory-method to create synthetic move events.

Fix build

Desktop. Fix problems with Enter/Exit events

Resend events similar to Android:
https://android-review.googlesource.com/c/platform/frameworks/support/+/1856856

On desktop we don't change the native event, as we don't know the nature of it, it can be not only MouseEvent in the future.
We change only PointerEvent.
This can lead to some confusion, if user reads the native event type instead of Compose event type.

Fixes JetBrains/compose-multiplatform#523
Fixes JetBrains/compose-multiplatform#594

Manual test:
```
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
import kotlinx.coroutines.delay

@composable
private fun App() {
    val state = rememberScrollState()
    Column(Modifier.size(300.dp).verticalScroll(state)) {
        repeat(20) {
            Item(it)
        }
    }
}

@composable
private fun Item(num: Int) {
    var isHovered by remember { mutableStateOf(false) }
    Box(
        Modifier
            .fillMaxWidth()
            .height(50.dp)
            .padding(8.dp)
            .background(if (isHovered) Color.Red else Color.Green)
            .pointerInput(Unit) {
                while (true) {
                    awaitPointerEventScope<Unit> {
                        val event = awaitPointerEvent()
                        when (event.type) {
                            PointerEventType.Enter -> {
                                isHovered = true
                            }
                            PointerEventType.Exit -> {
                                isHovered = false
                            }
                        }
                    }
                }
            }
    ) {
        Text(num.toString())
    }
}

fun main() {
    singleWindowApplication {
        App()
    }
}
```

Change-Id: I667c206bd08568fa0cb78208037c797bb8298702
Test: manual and ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true

# Conflicts:
#	compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt
#	compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt

Get rid of mousePressed from ComposeScene

mousePressed is unreliable on Windows (we can miss the release event), and doesn't work well with multiple buttons.

After this fix, mouseClickable only reacts to the first pressed button. Right button click doesn't trigger callback, if there is already left mouse button is pressed.

`Clickable` shouldn't be able to handle these cases. If users would want simultaneously handle multiple buttons, they have to use low-level api:
```
Modifier.pointerInput(Unit) {
    while (true) {
        val event = awaitPointerEventScope { awaitPointerEvent() }

        if (event.type == PointerEventType.Press && event.buttons.isPrimaryPressed) {
            // do something
        } else if (event.type == PointerEventType.Press && event.buttons.isSecondaryPressed) {
            // do something
        }
    }
}
```
(it is verbose, there is a field for improvement)

Also pass PointerButtons and PointerKeyboardModifiers to ComposeScene, instead of reading them from AWT event.

Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
Test: the snippet from JetBrains/compose-multiplatform#1149, because changed the code for that fix

Revert "Remove pointerId from ComposeScene"

Remove pointerId from ComposeScene

pointerId was indroduced in https://android-review.googlesource.com/c/platform/frameworks/support/+/1402607, because double click didn't work (see https://jetbrains.slack.com/archives/GT449QBCK/p1597328095373000)

But it messes with hover and clicking multiple mouse buttons at the same time.

Double clicking still works after removing it:
```
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication

@OptIn(ExperimentalFoundationApi::class)
fun main() = singleWindowApplication {
    Box(
        Modifier
        .size(300.dp)
        .background(Color.Red)
            .combinedClickable(onDoubleClick = {
                println("onDoubleClick")
            }, onClick = {
                println("onClick")
            })
    ) {
    }
}
```

Fixes JetBrains/compose-multiplatform#1176

Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
Test: manual (see the snippet)

Events

Fix sending events from AWT

Fix disposing window in event callback

Fixes JetBrains/compose-multiplatform#1448

The sequence of calls:
mouseReleased
  window.dispose
    scene.dispose
  cancelRippleEffect
    scene.invalidate
      layer.needRedraw

Fix losing Exit event on scroll

Fixes JetBrains/compose-multiplatform#1324 (comment)

Compose doesn't work well if we send an event with different coordinates without sending Move event before it:
```
Column {
  Box(size=10)
  Box(size=10)
}
```
If we send these events:
Move(5,5) -> Scroll(5,15) -> Move(5,15)

Then during the scroll event, HitPathTracker forgets about the first Box, and never send Exit to it. Instead it sends the Scroll event to it.

We should send events in this order:
Move(5,5) -> Move(5,15) -> Scroll(5,15) -> Move(5,15)

With synthetic events things more complicated, as we always send events with the same position. I suppose the proper fix should be in Compose core itself, but it would a very huge fix.

Reproducer:
```
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.ImageComposeScene
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.use
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@OptIn(ExperimentalComposeUiApi::class)
@RunWith(JUnit4::class)
class MouseHoverFilterTest {
    // bug: JetBrains/compose-multiplatform#1324 (comment)
    @test
    fun `move from one component to another, between another non-move event`() = ImageComposeScene(
        width = 100,
        height = 100,
        density = Density(1f)
    ).use { scene ->
        var enterCount1 = 0
        var exitCount1 = 0
        var enterCount2 = 0
        var exitCount2 = 0

        scene.setContent {
            Column {
                Box(
                    modifier = Modifier
                        .pointerMove(
                            onEnter = { enterCount1++ },
                            onExit = { exitCount1++ }
                        )
                        .size(10.dp, 10.dp)
                )
                Box(
                    modifier = Modifier
                        .pointerMove(
                            onEnter = { enterCount2++ },
                            onExit = { exitCount2++ }
                        )
                        .size(10.dp, 10.dp)
                )
            }
        }

        scene.sendPointerEvent(PointerEventType.Enter, Offset(5f, 5f))
        assertThat(enterCount1).isEqualTo(1)
        assertThat(exitCount1).isEqualTo(0)
        assertThat(enterCount2).isEqualTo(0)
        assertThat(exitCount2).isEqualTo(0)

        // Compose doesn't work well if we send an event with different type and different coordinates, without sending move event before it
        scene.sendPointerEvent(PointerEventType.Scroll, Offset(5f, 15f))
        scene.sendPointerEvent(PointerEventType.Move, Offset(5f, 15f))
        assertThat(enterCount1).isEqualTo(1)
        assertThat(exitCount1).isEqualTo(1)
        assertThat(enterCount2).isEqualTo(1)
        assertThat(exitCount2).isEqualTo(0)
    }
}
```

Another issue:
JetBrains/compose-multiplatform#1480

Fix losing hover events, speed up scroll

Fixes JetBrains/compose-multiplatform#1324

Fix crash when AWT event is sent after the window is disposed

Fixes JetBrains/compose-multiplatform#1448 (comment)

AWT can send event even after calling `window.dipose`

I checked, and it nothing to do with scheduleSyntheticMoveEvent - the crash still reproducible without it.

Remove async events

Async events is cause why sometimes interaction is so clunky:
- when we resize a heavy undecorated window from #124, it continue resizing, even if we release the mouse
- when we scroll CodeViewer, it continues to scroll for 1-2 seconds

Async events removed freezes for us for scrolling heavy lazy lists, but instead it adds unresponsive UI that lives its own live.

Also, i checked heavy lazy list (codeviewer), and it doesn't freeze anymore during scrolling

Partially fixes JetBrains/compose-multiplatform#1345

Without that we can't merge #124

1
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Jan 18, 2022
Make real synthetic move events

I encountered code, where users doesn't use Compose events at all. They just listen to awtEvent, and check its type.

The feature "send synthetic move event on relayout", which we recently implemented, will break such use case, as synthetic event type would not reflect reality. For example, we would resend the latest native event, which was MousePressed. The application checks it and determines, that that cursor is over some button and presses it.

To fix that, the platform should provide a factory-method to create synthetic move events.

Desktop. Fix problems with Enter/Exit events

Resend events similar to Android:
https://android-review.googlesource.com/c/platform/frameworks/support/+/1856856

On desktop we don't change the native event, as we don't know the nature of it, it can be not only MouseEvent in the future.
We change only PointerEvent.
This can lead to some confusion, if user reads the native event type instead of Compose event type.

Fixes JetBrains/compose-multiplatform#523
Fixes JetBrains/compose-multiplatform#594

Manual test:
```
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
import kotlinx.coroutines.delay

@composable
private fun App() {
    val state = rememberScrollState()
    Column(Modifier.size(300.dp).verticalScroll(state)) {
        repeat(20) {
            Item(it)
        }
    }
}

@composable
private fun Item(num: Int) {
    var isHovered by remember { mutableStateOf(false) }
    Box(
        Modifier
            .fillMaxWidth()
            .height(50.dp)
            .padding(8.dp)
            .background(if (isHovered) Color.Red else Color.Green)
            .pointerInput(Unit) {
                while (true) {
                    awaitPointerEventScope<Unit> {
                        val event = awaitPointerEvent()
                        when (event.type) {
                            PointerEventType.Enter -> {
                                isHovered = true
                            }
                            PointerEventType.Exit -> {
                                isHovered = false
                            }
                        }
                    }
                }
            }
    ) {
        Text(num.toString())
    }
}

fun main() {
    singleWindowApplication {
        App()
    }
}
```

Change-Id: I667c206bd08568fa0cb78208037c797bb8298702
Test: manual and ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true

# Conflicts:
#	compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt
#	compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt

Fix losing Exit event on scroll

Fixes JetBrains/compose-multiplatform#1324 (comment)

Compose doesn't work well if we send an event with different coordinates without sending Move event before it:
```
Column {
  Box(size=10)
  Box(size=10)
}
```
If we send these events:
Move(5,5) -> Scroll(5,15) -> Move(5,15)

Then during the scroll event, HitPathTracker forgets about the first Box, and never send Exit to it. Instead it sends the Scroll event to it.

We should send events in this order:
Move(5,5) -> Move(5,15) -> Scroll(5,15) -> Move(5,15)

With synthetic events things more complicated, as we always send events with the same position. I suppose the proper fix should be in Compose core itself, but it would a very huge fix.

Reproducer:
```
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.ImageComposeScene
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.use
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@OptIn(ExperimentalComposeUiApi::class)
@RunWith(JUnit4::class)
class MouseHoverFilterTest {
    // bug: JetBrains/compose-multiplatform#1324 (comment)
    @test
    fun `move from one component to another, between another non-move event`() = ImageComposeScene(
        width = 100,
        height = 100,
        density = Density(1f)
    ).use { scene ->
        var enterCount1 = 0
        var exitCount1 = 0
        var enterCount2 = 0
        var exitCount2 = 0

        scene.setContent {
            Column {
                Box(
                    modifier = Modifier
                        .pointerMove(
                            onEnter = { enterCount1++ },
                            onExit = { exitCount1++ }
                        )
                        .size(10.dp, 10.dp)
                )
                Box(
                    modifier = Modifier
                        .pointerMove(
                            onEnter = { enterCount2++ },
                            onExit = { exitCount2++ }
                        )
                        .size(10.dp, 10.dp)
                )
            }
        }

        scene.sendPointerEvent(PointerEventType.Enter, Offset(5f, 5f))
        assertThat(enterCount1).isEqualTo(1)
        assertThat(exitCount1).isEqualTo(0)
        assertThat(enterCount2).isEqualTo(0)
        assertThat(exitCount2).isEqualTo(0)

        // Compose doesn't work well if we send an event with different type and different coordinates, without sending move event before it
        scene.sendPointerEvent(PointerEventType.Scroll, Offset(5f, 15f))
        scene.sendPointerEvent(PointerEventType.Move, Offset(5f, 15f))
        assertThat(enterCount1).isEqualTo(1)
        assertThat(exitCount1).isEqualTo(1)
        assertThat(enterCount2).isEqualTo(1)
        assertThat(exitCount2).isEqualTo(0)
    }
}
```

Another issue:
JetBrains/compose-multiplatform#1480

JetBrains/compose-multiplatform#1324
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Jan 20, 2022
Make real synthetic move events

I encountered code, where users doesn't use Compose events at all. They just listen to awtEvent, and check its type.

The feature "send synthetic move event on relayout", which we recently implemented, will break such use case, as synthetic event type would not reflect reality. For example, we would resend the latest native event, which was MousePressed. The application checks it and determines, that that cursor is over some button and presses it.

To fix that, the platform should provide a factory-method to create synthetic move events.

Desktop. Fix problems with Enter/Exit events

Resend events similar to Android:
https://android-review.googlesource.com/c/platform/frameworks/support/+/1856856

On desktop we don't change the native event, as we don't know the nature of it, it can be not only MouseEvent in the future.
We change only PointerEvent.
This can lead to some confusion, if user reads the native event type instead of Compose event type.

Fixes JetBrains/compose-multiplatform#523
Fixes JetBrains/compose-multiplatform#594

Manual test:
```
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
import kotlinx.coroutines.delay

@composable
private fun App() {
    val state = rememberScrollState()
    Column(Modifier.size(300.dp).verticalScroll(state)) {
        repeat(20) {
            Item(it)
        }
    }
}

@composable
private fun Item(num: Int) {
    var isHovered by remember { mutableStateOf(false) }
    Box(
        Modifier
            .fillMaxWidth()
            .height(50.dp)
            .padding(8.dp)
            .background(if (isHovered) Color.Red else Color.Green)
            .pointerInput(Unit) {
                while (true) {
                    awaitPointerEventScope<Unit> {
                        val event = awaitPointerEvent()
                        when (event.type) {
                            PointerEventType.Enter -> {
                                isHovered = true
                            }
                            PointerEventType.Exit -> {
                                isHovered = false
                            }
                        }
                    }
                }
            }
    ) {
        Text(num.toString())
    }
}

fun main() {
    singleWindowApplication {
        App()
    }
}
```

Change-Id: I667c206bd08568fa0cb78208037c797bb8298702
Test: manual and ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true

# Conflicts:
#	compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt
#	compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt

Fix losing Exit event on scroll

Fixes JetBrains/compose-multiplatform#1324 (comment)

Compose doesn't work well if we send an event with different coordinates without sending Move event before it:
```
Column {
  Box(size=10)
  Box(size=10)
}
```
If we send these events:
Move(5,5) -> Scroll(5,15) -> Move(5,15)

Then during the scroll event, HitPathTracker forgets about the first Box, and never send Exit to it. Instead it sends the Scroll event to it.

We should send events in this order:
Move(5,5) -> Move(5,15) -> Scroll(5,15) -> Move(5,15)

With synthetic events things more complicated, as we always send events with the same position. I suppose the proper fix should be in Compose core itself, but it would a very huge fix.

Reproducer:
```
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.ImageComposeScene
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.use
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@OptIn(ExperimentalComposeUiApi::class)
@RunWith(JUnit4::class)
class MouseHoverFilterTest {
    // bug: JetBrains/compose-multiplatform#1324 (comment)
    @test
    fun `move from one component to another, between another non-move event`() = ImageComposeScene(
        width = 100,
        height = 100,
        density = Density(1f)
    ).use { scene ->
        var enterCount1 = 0
        var exitCount1 = 0
        var enterCount2 = 0
        var exitCount2 = 0

        scene.setContent {
            Column {
                Box(
                    modifier = Modifier
                        .pointerMove(
                            onEnter = { enterCount1++ },
                            onExit = { exitCount1++ }
                        )
                        .size(10.dp, 10.dp)
                )
                Box(
                    modifier = Modifier
                        .pointerMove(
                            onEnter = { enterCount2++ },
                            onExit = { exitCount2++ }
                        )
                        .size(10.dp, 10.dp)
                )
            }
        }

        scene.sendPointerEvent(PointerEventType.Enter, Offset(5f, 5f))
        assertThat(enterCount1).isEqualTo(1)
        assertThat(exitCount1).isEqualTo(0)
        assertThat(enterCount2).isEqualTo(0)
        assertThat(exitCount2).isEqualTo(0)

        // Compose doesn't work well if we send an event with different type and different coordinates, without sending move event before it
        scene.sendPointerEvent(PointerEventType.Scroll, Offset(5f, 15f))
        scene.sendPointerEvent(PointerEventType.Move, Offset(5f, 15f))
        assertThat(enterCount1).isEqualTo(1)
        assertThat(exitCount1).isEqualTo(1)
        assertThat(enterCount2).isEqualTo(1)
        assertThat(exitCount2).isEqualTo(0)
    }
}
```

Another issue:
JetBrains/compose-multiplatform#1480

JetBrains/compose-multiplatform#1324
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Jan 20, 2022
Make real synthetic move events

I encountered code, where users doesn't use Compose events at all. They just listen to awtEvent, and check its type.

The feature "send synthetic move event on relayout", which we recently implemented, will break such use case, as synthetic event type would not reflect reality. For example, we would resend the latest native event, which was MousePressed. The application checks it and determines, that that cursor is over some button and presses it.

To fix that, the platform should provide a factory-method to create synthetic move events.

Desktop. Fix problems with Enter/Exit events

Resend events similar to Android:
https://android-review.googlesource.com/c/platform/frameworks/support/+/1856856

On desktop we don't change the native event, as we don't know the nature of it, it can be not only MouseEvent in the future.
We change only PointerEvent.
This can lead to some confusion, if user reads the native event type instead of Compose event type.

Fixes JetBrains/compose-multiplatform#523
Fixes JetBrains/compose-multiplatform#594

Manual test:
```
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
import kotlinx.coroutines.delay

@composable
private fun App() {
    val state = rememberScrollState()
    Column(Modifier.size(300.dp).verticalScroll(state)) {
        repeat(20) {
            Item(it)
        }
    }
}

@composable
private fun Item(num: Int) {
    var isHovered by remember { mutableStateOf(false) }
    Box(
        Modifier
            .fillMaxWidth()
            .height(50.dp)
            .padding(8.dp)
            .background(if (isHovered) Color.Red else Color.Green)
            .pointerInput(Unit) {
                while (true) {
                    awaitPointerEventScope<Unit> {
                        val event = awaitPointerEvent()
                        when (event.type) {
                            PointerEventType.Enter -> {
                                isHovered = true
                            }
                            PointerEventType.Exit -> {
                                isHovered = false
                            }
                        }
                    }
                }
            }
    ) {
        Text(num.toString())
    }
}

fun main() {
    singleWindowApplication {
        App()
    }
}
```

Change-Id: I667c206bd08568fa0cb78208037c797bb8298702
Test: manual and ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true

# Conflicts:
#	compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt
#	compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt

Fix losing Exit event on scroll

Fixes JetBrains/compose-multiplatform#1324 (comment)

Compose doesn't work well if we send an event with different coordinates without sending Move event before it:
```
Column {
  Box(size=10)
  Box(size=10)
}
```
If we send these events:
Move(5,5) -> Scroll(5,15) -> Move(5,15)

Then during the scroll event, HitPathTracker forgets about the first Box, and never send Exit to it. Instead it sends the Scroll event to it.

We should send events in this order:
Move(5,5) -> Move(5,15) -> Scroll(5,15) -> Move(5,15)

With synthetic events things more complicated, as we always send events with the same position. I suppose the proper fix should be in Compose core itself, but it would a very huge fix.

Reproducer:
```
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.ImageComposeScene
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.use
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@OptIn(ExperimentalComposeUiApi::class)
@RunWith(JUnit4::class)
class MouseHoverFilterTest {
    // bug: JetBrains/compose-multiplatform#1324 (comment)
    @test
    fun `move from one component to another, between another non-move event`() = ImageComposeScene(
        width = 100,
        height = 100,
        density = Density(1f)
    ).use { scene ->
        var enterCount1 = 0
        var exitCount1 = 0
        var enterCount2 = 0
        var exitCount2 = 0

        scene.setContent {
            Column {
                Box(
                    modifier = Modifier
                        .pointerMove(
                            onEnter = { enterCount1++ },
                            onExit = { exitCount1++ }
                        )
                        .size(10.dp, 10.dp)
                )
                Box(
                    modifier = Modifier
                        .pointerMove(
                            onEnter = { enterCount2++ },
                            onExit = { exitCount2++ }
                        )
                        .size(10.dp, 10.dp)
                )
            }
        }

        scene.sendPointerEvent(PointerEventType.Enter, Offset(5f, 5f))
        assertThat(enterCount1).isEqualTo(1)
        assertThat(exitCount1).isEqualTo(0)
        assertThat(enterCount2).isEqualTo(0)
        assertThat(exitCount2).isEqualTo(0)

        // Compose doesn't work well if we send an event with different type and different coordinates, without sending move event before it
        scene.sendPointerEvent(PointerEventType.Scroll, Offset(5f, 15f))
        scene.sendPointerEvent(PointerEventType.Move, Offset(5f, 15f))
        assertThat(enterCount1).isEqualTo(1)
        assertThat(exitCount1).isEqualTo(1)
        assertThat(enterCount2).isEqualTo(1)
        assertThat(exitCount2).isEqualTo(0)
    }
}
```

Another issue:
JetBrains/compose-multiplatform#1480

JetBrains/compose-multiplatform#1324
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Feb 18, 2022
Make real synthetic move events

I encountered code, where users doesn't use Compose events at all. They just listen to awtEvent, and check its type.

The feature "send synthetic move event on relayout", which we recently implemented, will break such use case, as synthetic event type would not reflect reality. For example, we would resend the latest native event, which was MousePressed. The application checks it and determines, that that cursor is over some button and presses it.

To fix that, the platform should provide a factory-method to create synthetic move events.

Desktop. Fix problems with Enter/Exit events

Resend events similar to Android:
https://android-review.googlesource.com/c/platform/frameworks/support/+/1856856

On desktop we don't change the native event, as we don't know the nature of it, it can be not only MouseEvent in the future.
We change only PointerEvent.
This can lead to some confusion, if user reads the native event type instead of Compose event type.

Fixes JetBrains/compose-multiplatform#523
Fixes JetBrains/compose-multiplatform#594

Manual test:
```
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
import kotlinx.coroutines.delay

@composable
private fun App() {
    val state = rememberScrollState()
    Column(Modifier.size(300.dp).verticalScroll(state)) {
        repeat(20) {
            Item(it)
        }
    }
}

@composable
private fun Item(num: Int) {
    var isHovered by remember { mutableStateOf(false) }
    Box(
        Modifier
            .fillMaxWidth()
            .height(50.dp)
            .padding(8.dp)
            .background(if (isHovered) Color.Red else Color.Green)
            .pointerInput(Unit) {
                while (true) {
                    awaitPointerEventScope<Unit> {
                        val event = awaitPointerEvent()
                        when (event.type) {
                            PointerEventType.Enter -> {
                                isHovered = true
                            }
                            PointerEventType.Exit -> {
                                isHovered = false
                            }
                        }
                    }
                }
            }
    ) {
        Text(num.toString())
    }
}

fun main() {
    singleWindowApplication {
        App()
    }
}
```

Change-Id: I667c206bd08568fa0cb78208037c797bb8298702
Test: manual and ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true

# Conflicts:
#	compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt
#	compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt

Fix losing Exit event on scroll

Fixes JetBrains/compose-multiplatform#1324 (comment)

Compose doesn't work well if we send an event with different coordinates without sending Move event before it:
```
Column {
  Box(size=10)
  Box(size=10)
}
```
If we send these events:
Move(5,5) -> Scroll(5,15) -> Move(5,15)

Then during the scroll event, HitPathTracker forgets about the first Box, and never send Exit to it. Instead it sends the Scroll event to it.

We should send events in this order:
Move(5,5) -> Move(5,15) -> Scroll(5,15) -> Move(5,15)

With synthetic events things more complicated, as we always send events with the same position. I suppose the proper fix should be in Compose core itself, but it would a very huge fix.

Reproducer:
```
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.ImageComposeScene
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.use
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@OptIn(ExperimentalComposeUiApi::class)
@RunWith(JUnit4::class)
class MouseHoverFilterTest {
    // bug: JetBrains/compose-multiplatform#1324 (comment)
    @test
    fun `move from one component to another, between another non-move event`() = ImageComposeScene(
        width = 100,
        height = 100,
        density = Density(1f)
    ).use { scene ->
        var enterCount1 = 0
        var exitCount1 = 0
        var enterCount2 = 0
        var exitCount2 = 0

        scene.setContent {
            Column {
                Box(
                    modifier = Modifier
                        .pointerMove(
                            onEnter = { enterCount1++ },
                            onExit = { exitCount1++ }
                        )
                        .size(10.dp, 10.dp)
                )
                Box(
                    modifier = Modifier
                        .pointerMove(
                            onEnter = { enterCount2++ },
                            onExit = { exitCount2++ }
                        )
                        .size(10.dp, 10.dp)
                )
            }
        }

        scene.sendPointerEvent(PointerEventType.Enter, Offset(5f, 5f))
        assertThat(enterCount1).isEqualTo(1)
        assertThat(exitCount1).isEqualTo(0)
        assertThat(enterCount2).isEqualTo(0)
        assertThat(exitCount2).isEqualTo(0)

        // Compose doesn't work well if we send an event with different type and different coordinates, without sending move event before it
        scene.sendPointerEvent(PointerEventType.Scroll, Offset(5f, 15f))
        scene.sendPointerEvent(PointerEventType.Move, Offset(5f, 15f))
        assertThat(enterCount1).isEqualTo(1)
        assertThat(exitCount1).isEqualTo(1)
        assertThat(enterCount2).isEqualTo(1)
        assertThat(exitCount2).isEqualTo(0)
    }
}
```

Another issue:
JetBrains/compose-multiplatform#1480

https: //github.com/JetBrains/compose-multiplatform/issues/1324
Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
@igordmn
Copy link
Collaborator

igordmn commented Feb 25, 2022

It appears, it isn't completely fixed in 1.0.0. The new found cases should be fixed in 1.1.0

igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Feb 28, 2022
1. Don't send native event when we send synthetic events

2. Fix losing Exit event on scroll (JetBrains/compose-multiplatform#1324)

The current HitPathTracker algorithm doesn't work well if we miss Move event before non-Move event with a different position (see example in PointerPositionUpdater)

Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Mar 28, 2022
1. Don't send native event when we send synthetic events

2. Fix losing Exit event on scroll (JetBrains/compose-multiplatform#1324)

The current HitPathTracker algorithm doesn't work well if we miss Move event before non-Move event with a different position (see example in PointerPositionUpdater)

Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Mar 30, 2022
1. Don't send native event when we send synthetic events

2. Fix losing Exit event on scroll (JetBrains/compose-multiplatform#1324)

The current HitPathTracker algorithm doesn't work well if we miss Move event before non-Move event with a different position (see example in PointerPositionUpdater)

Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
eymar pushed a commit to JetBrains/compose-multiplatform-core that referenced this issue Apr 18, 2022
1. Don't send native event when we send synthetic events

2. Fix losing Exit event on scroll (JetBrains/compose-multiplatform#1324)

The current HitPathTracker algorithm doesn't work well if we miss Move event before non-Move event with a different position (see example in PointerPositionUpdater)

Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
eymar pushed a commit to JetBrains/compose-multiplatform-core that referenced this issue Jun 2, 2022
1. Don't send native event when we send synthetic events

2. Fix losing Exit event on scroll (JetBrains/compose-multiplatform#1324)

The current HitPathTracker algorithm doesn't work well if we miss Move event before non-Move event with a different position (see example in PointerPositionUpdater)

Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
eymar pushed a commit to JetBrains/compose-multiplatform-core that referenced this issue Jun 27, 2022
1. Don't send native event when we send synthetic events

2. Fix losing Exit event on scroll (JetBrains/compose-multiplatform#1324)

The current HitPathTracker algorithm doesn't work well if we miss Move event before non-Move event with a different position (see example in PointerPositionUpdater)

Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Aug 18, 2022
1. Don't send native event when we send synthetic events

2. Fix losing Exit event on scroll (JetBrains/compose-multiplatform#1324)

The current HitPathTracker algorithm doesn't work well if we miss Move event before non-Move event with a different position (see example in PointerPositionUpdater)

Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Aug 18, 2022
1. Don't send native event when we send synthetic events

2. Fix losing Exit event on scroll (JetBrains/compose-multiplatform#1324)

The current HitPathTracker algorithm doesn't work well if we miss Move event before non-Move event with a different position (see example in PointerPositionUpdater)

Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
eymar pushed a commit to JetBrains/compose-multiplatform-core that referenced this issue Oct 26, 2022
1. Don't send native event when we send synthetic events

2. Fix losing Exit event on scroll (JetBrains/compose-multiplatform#1324)

The current HitPathTracker algorithm doesn't work well if we miss Move event before non-Move event with a different position (see example in PointerPositionUpdater)

Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
eymar pushed a commit to JetBrains/compose-multiplatform-core that referenced this issue Nov 16, 2022
1. Don't send native event when we send synthetic events

2. Fix losing Exit event on scroll (JetBrains/compose-multiplatform#1324)

The current HitPathTracker algorithm doesn't work well if we miss Move event before non-Move event with a different position (see example in PointerPositionUpdater)

Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
eymar pushed a commit to JetBrains/compose-multiplatform-core that referenced this issue Jan 13, 2023
1. Don't send native event when we send synthetic events

2. Fix losing Exit event on scroll (JetBrains/compose-multiplatform#1324)

The current HitPathTracker algorithm doesn't work well if we miss Move event before non-Move event with a different position (see example in PointerPositionUpdater)

Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
MatkovIvan pushed a commit to MatkovIvan/compose-multiplatform that referenced this issue May 10, 2023
1. Don't send native event when we send synthetic events

2. Fix losing Exit event on scroll (JetBrains#1324)

The current HitPathTracker algorithm doesn't work well if we miss Move event before non-Move event with a different position (see example in PointerPositionUpdater)

Change-Id: I3bba1a9878626a3a8bd52d86671e3260229425fa
Test: ./gradlew jvmTest desktopTest -Pandroidx.compose.multiplatformEnabled=true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working desktop p:critical Critical priority
Projects
None yet
Development

No branches or pull requests

5 participants