Skip to content

Web: Migrate touch input to unified Pointer Events API#2799

Open
terrakok wants to merge 4 commits intojb-mainfrom
web-pointers
Open

Web: Migrate touch input to unified Pointer Events API#2799
terrakok wants to merge 4 commits intojb-mainfrom
web-pointers

Conversation

@terrakok
Copy link
Member

Fixes https://youtrack.jetbrains.com/issue/CMP-9745

Testing

Migrate touch tests.

Release Notes

N/A

…on web.

- Replace touch events with pointer events for touch input handling.
- Refactor `initEvents` to consolidate pointer event handling into a loop.
- Remove duplicate event processing for touch and mouse inputs.
- Add `setPointerCapture` for better pointer management across elements.
- Disable default browser touch handling via `touch-action: none`.

Fixes https://youtrack.jetbrains.com/issue/CMP-9745
- Add support for `getCoalescedEvents` to process multiple pointer events.
- Refactor `ComposeScenePointer` logic to handle event coalescing.
- Ignore invalid touch events (`Enter` or `Exit`) sent by Firefox.
…cy TouchEvent code.

- Replace `TouchEvent` logic with `PointerEvent` to streamline event handling.
- Remove unused `TouchEvent` classes and related utilities.
- Update tests to use `PointerEvent` for consistency.
- Improve handling of coalesced events and fallback scenarios.
@terrakok terrakok requested review from Schahen and eymar February 26, 2026 15:15
)
if (isTouch) {
if (eventType == PointerEventType.Enter || eventType == PointerEventType.Exit) {
//Firefox sends such touch events, so we need to ignore them
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you observe some bad behaviour on firefox before you added this early return?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. the problem is I remove an active pointer from the map on an up event. And then I get one more exit event

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Safari? Quite often Safari has a 3rd way of sending events :D

private val activeTouchPointers = mutableMapOf<Int, TouchEventWithContainerOffset>()

private fun onPointerEvent(event: PointerEvent) {
val isTouch = event.pointerType == "touch"
Copy link
Member

@eymar eymar Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make the touch handler a bit cheaper we can try to call some js interop helper function which would return integers instead of strings (strings are more expensive when transferred from js to wasm).

And then map Integeres to Compose values.

Since the handler is called quite often, it might be beneficial.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this is a problem? I see a lot the same strings manipulations already

Copy link
Member

@eymar eymar Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://pl.kotl.in/A3ahEyDdk

There is a difference:

String time = 1.4ms
Int time = 0s

Are you sure this is a problem?

If we want to make the touch handlers cheaper, then it's worth it.

I see a lot the same strings manipulations already

IMO, sometimes it's okay to prefer the simplicity. In frequent calls such as (touch, key, mouse) event handlers it's worth to reduce the costs.

}

keyboardModeState = KeyboardModeState.Hardware
private val activeTouchPointers = mutableMapOf<Int, TouchEventWithContainerOffset>()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mutableIntObjectMapOf ??

val composePointers: List<ComposeScenePointer> by lazy {
val coalesced = getCoalescedEvents(event).toList()
val list = coalesced.takeIf { it.isNotEmpty() } ?: listOf(event)
list.map { e ->

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fastMap

"pointerup",
"pointerleave",
"pointercancel"
).forEach { name ->

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fastForEach

"pointerleave",
"pointercancel"
).forEach { name ->
addTypedEvent<PointerEvent>(name, passive = false) { onPointerEvent(it) }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
addTypedEvent<PointerEvent>(name, passive = false) { onPointerEvent(it) }
addTypedEvent<PointerEvent>(name, passive = false, handler = ::onPointerEvent)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants