adte
10/03/2022, 9:25 PMMichael Paus
10/04/2022, 9:23 AM.pointerInput(Unit) {
detectTransformGestures { centroid, pan, zoom, rotation ->
...
}
}
instead of fiddling directly with the AWT events?
And in addition to that
.pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
...
}
}
}
orangy
detectTransformGestures
doesn’t work for desktop, because multitouch is not supportedorangy
var scale by mutableStateOf(1f)
val zoomScrollState = rememberScrollableState {
scale = scale + scale * it / 100
it
}
and then Modifier.scrollable(zoomScrollState, Orientation.Vertical)
It has a bit more code, because zooming in/out should retain point under mouse to stay under mouse, but for just scale it’s thisDragos Rachieru
10/04/2022, 9:01 PMIgor Demin
10/04/2022, 10:32 PMWill there be support for multitouch on desktopYes, we plan to support true touch (with multitouch) and trackpad in some point in the future (but we haven't decided yet when we support them). Currently we rely on the platform conversion of touch events into mouse events, and it can lead to some glitches (weird scrolling, false clicks, etc).
adte
10/13/2022, 3:13 PMorangy
.onPointerEvent(PointerEventType.Move) { event ->
val change = event.changes.firstOrNull()
state.mousePosition = change?.position ?: Offset.Zero
}
Something like this. Then you have to do some math to update zoom and pan values to maintain zoom targetorangy
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun Canvas(
modifier: Modifier = Modifier,
state: CanvasState = rememberCanvasState(),
draw: Canvas.(Rect) -> Unit
) {
val panX = rememberScrollableState {
state.offset += Offset(it, 0f)
it
}
val panY = rememberScrollableState {
state.offset += Offset(0f, it)
it
}
val zoomScrollState = rememberScrollableState {
state.zoom(state.scale + state.scale * it / 100, state.mousePosition)
it
}
val scrollMode by derivedStateOf {
when {
!App.metaKeyDown -> Modifier
.scrollable(panX, Orientation.Horizontal)
.scrollable(panY, Orientation.Vertical)
else -> Modifier.scrollable(zoomScrollState, Orientation.Vertical)
}
}
Box(
modifier
.then(scrollMode)
.onSizeChanged { state.size = it }
.onPointerEvent(PointerEventType.Move) { event ->
val change = event.changes.firstOrNull()
state.mousePosition = change?.position ?: Offset.Zero
if (event.buttons.isSecondaryPressed) {
var diff = Offset.Zero
event.changes.forEach {
diff += it.position - it.previousPosition
}
panX.dispatchRawDelta(diff.x)
panY.dispatchRawDelta(diff.y)
}
}
.clip(RectangleShape)
.drawBehind {
drawIntoCanvas { canvas ->
canvas.draw(Rect(Offset.Zero, size))
}
})
}
@Composable
fun rememberCanvasState(): CanvasState {
return remember { CanvasState() }
}
class CanvasState {
var mousePosition by mutableStateOf(Offset.Zero)
var size by mutableStateOf(IntSize.Zero)
var offset by mutableStateOf(Offset.Zero)
var scale by mutableStateOf(1f)
val virtualPosition by derivedStateOf {
val relativePosition = mousePosition - offset
Offset(relativePosition.x / scale, relativePosition.y / scale)
}
fun zoom(value: Float, retainPoint: Offset?) {
val constrained = value.coerceIn(0.1f, 20f)
if (retainPoint != null && retainPoint.x < size.width && retainPoint.y < size.height) {
val oldPosition = retainPoint - offset
val newPosition = oldPosition * constrained / scale
val delta = newPosition - oldPosition
offset -= delta
}
scale = constrained
}
fun zoomTo(x: Int, y: Int, width: Int, height: Int) {
scale = minOf(
(size.width) / width.toFloat(),
(size.height) / height.toFloat()
).coerceIn(0.1f, 10f) * 0.8f
val offsetX = (size.width - width * scale) / 2
val offsetY = (size.height - height * scale) / 2
offset = Offset(offsetX - x * scale, offsetY - y * scale)
}
}