Closed ryanmitchener closed 11 months ago
Could you provide a snippet, where this issue reproduces?
@igordmn Here is a full snippet of what I have in the video:
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.mouse.MouseScrollUnit
import androidx.compose.ui.input.mouse.mouseScrollFilter
import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.*
import java.awt.Dimension
fun main() = singleWindowApplication {
val density = LocalDensity.current
val minWindowSize = remember(density) { Dimension(1280, 768) }
if (window.minimumSize != minWindowSize) window.minimumSize = minWindowSize
CanvasScreen()
}
@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
@Composable
fun CanvasScreen() {
val density = LocalDensity.current
val scale = remember { mutableStateOf(1f) }
val panX = remember { mutableStateOf(0f) }
val panY = remember { mutableStateOf(0f) }
val scaleAnim by animateFloatAsState(scale.value, animationSpec = tween(250))
Column(modifier = Modifier.background(Color(0xFFFFFFFF))) {
Box(modifier = Modifier.clipToBounds().size(10_000.dp).pointerInput(Unit) {
while (true) {
awaitPointerEventScope {
awaitFirstDown()
while (true) {
val event = awaitPointerEvent().changes.first()
if (event.changedToUp()) {
break
} else if (event.positionChanged()) {
val posChange = event.positionChange().toDpOffset(density)
panX.value += posChange.x.value
panY.value += posChange.y.value
}
}
}
}
}.mouseScrollFilter { event, _ ->
if (event.delta !is MouseScrollUnit.Line) return@mouseScrollFilter false
val deltaValue = (event.delta as MouseScrollUnit.Line).value
scale.value = (scale.value - (deltaValue * .025f)).coerceIn(.1f, 1f)
false
}) {
Test(DpOffset(panX.value.dp, panY.value.dp), scale = scaleAnim, itemOffset = DpOffset(0.dp, 20.dp))
Test(DpOffset(panX.value.dp, panY.value.dp), itemOffset = DpOffset(400.dp, 20.dp), scale = scaleAnim)
Test(DpOffset(panX.value.dp, panY.value.dp), itemOffset = DpOffset(0.dp, 400.dp), scale = scaleAnim)
Text("Offset: ${panX.value}:${panY.value}, Scale: $scaleAnim")
}
}
}
@Composable
fun Test(pan: DpOffset, itemOffset: DpOffset = DpOffset.Zero, scale: Float) {
val dragOffset = remember(Unit) { mutableStateOf(DpOffset(0.dp, 0.dp)) }
val density = LocalDensity.current
Box(
modifier = Modifier
.offset(pan)
.scale(scale)
.offset(dragOffset.value + itemOffset)
.pointerInput(Unit) {
while (true) {
awaitPointerEventScope {
awaitFirstDown()
while (true) {
val event = awaitPointerEvent().changes.first()
if (event.changedToUp()) {
break
} else if (event.positionChanged()) {
val posChange = event.positionChange().toDpOffset(density)
dragOffset.value += posChange
event.consumeAllChanges()
}
}
}
}
}
.size(200.dp)
.background(Color(0xFFBBBBBB)),
contentAlignment = Alignment.Center
) {
Text("Item Offset:\n${itemOffset}\n\nWidth: 200.dp")
}
}
A small reproducer:
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
fun main() = singleWindowApplication {
Box(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.offset(-100.dp, 0.dp)
.scale(0.95f)
.offset(200.dp, 20.dp)
.size(100.dp)
.background(Color.LightGray)
)
Box(
modifier = Modifier
.offset(0.dp, 0.dp)
.scale(0.95f)
.offset(200.dp, 20.dp)
.size(100.dp)
.background(Color.LightGray)
)
}
}
(Isn't reproducible on Android)
The issue is the same as in https://github.com/JetBrains/compose-multiplatform/issues/2807. Let's keep 2807, as the core issue is more visible there.
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.
I am creating surface that's able to be panned around and scaled in and out and I ran across this bug. If I keep the scale at 1f, then everything works perfectly fine. However, if I change the scale slightly and pan passed the width of the composables, they disappear. See the attached video for an example
https://user-images.githubusercontent.com/6433145/145070662-83af05cb-5247-454a-8e24-7b6332fdee57.mov