1+ /*
2+ * Copyright 2025 The Android Open Source Project
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+ package androidx.compose.mpp.demo.bugs
18+
19+ import androidx.compose.foundation.Canvas
20+ import androidx.compose.foundation.layout.Box
21+ import androidx.compose.foundation.layout.BoxScope
22+ import androidx.compose.foundation.layout.fillMaxSize
23+ import androidx.compose.material.Text
24+ import androidx.compose.runtime.Composable
25+ import androidx.compose.runtime.mutableStateOf
26+ import androidx.compose.runtime.remember
27+ import androidx.compose.ui.Modifier
28+ import androidx.compose.ui.geometry.Offset
29+ import androidx.compose.ui.graphics.Color
30+ import androidx.compose.ui.input.pointer.pointerInput
31+ import androidx.compose.ui.unit.Dp
32+ import androidx.compose.ui.unit.dp
33+
34+ // https://youtrack.jetbrains.com/issue/CMP-9030/wasm-canvas-incorrect-handling-of-multi-touch-input
35+ @Composable
36+ fun PointerInputDebugOverlay (
37+ modifier : Modifier = Modifier .fillMaxSize(),
38+ circleRadius : Dp = 50.dp,
39+ circleColor : Color = Color .Red ,
40+ ) {
41+ // Keep a map of active pointer positions (keyed by pointer id value)
42+ val pointers = remember { mutableStateOf<Map <Long , Offset >>(emptyMap()) }
43+ val log = remember { mutableStateOf(" " ) }
44+
45+ fun logString (s : String ) {
46+ log.value = s + " \n " + log.value
47+ if (log.value.length > 500 ) {
48+ log.value = log.value.take(500 )
49+ }
50+ }
51+
52+ Box (modifier = modifier) {
53+ Text (log.value)
54+ // overlay that listens to pointer events and draws circles
55+ Box (
56+ modifier = Modifier
57+ .matchParentSize()
58+ .pointerInput(Unit ) {
59+ // Use awaitPointerEventScope to get raw pointer events and positions
60+ awaitPointerEventScope {
61+ while (true ) {
62+ val event = awaitPointerEvent()
63+ logString(" >" )
64+
65+ // update positions for each pointer change
66+ for (change in event.changes) {
67+ val idValue = change.id.value
68+ if (change.pressed) {
69+ // pointer is down or moved
70+ pointers.value + = (idValue to change.position)
71+ logString(" + $idValue " )
72+ } else {
73+ // pointer was released
74+ logString(" - $idValue " )
75+ pointers.value - = idValue
76+ }
77+ }
78+ }
79+ }
80+ }
81+ ) {
82+ // Draw the active pointer locations
83+ Canvas (modifier = Modifier .matchParentSize()) {
84+ val radiusPx = circleRadius.toPx()
85+ for ((_, pos) in pointers.value) {
86+ // draw a small red circle at each pointer position
87+ drawCircle(
88+ color = circleColor,
89+ radius = radiusPx,
90+ center = pos
91+ )
92+ }
93+ }
94+ }
95+ }
96+ }
0 commit comments