I try to implement an Viewport Element which allow...
# compose-desktop
t
I try to implement an Viewport Element which allows the user to Zoom in/out in element but keep 'element position' under cursor Like the of the zooming behaviour in Google Maps
🧵 1
Because it seems there are no ready to use components, i implemented my own zoom modifier which scales the layout/measured size too, which correctly change the scrollbar size. (Only changes size, no translation)
Copy code
internal class ZoomModifierNode(
	var zoomState: ZoomState,
	var zoomEnabled: Boolean,
) : PointerInputModifierNode, LayoutModifierNode, DelegatingNode() {
	
	override fun MeasureScope.measure(
		measurable: Measurable,
		constraints: Constraints
	): MeasureResult {
		val placeable = measurable.measure(constraints)
		return layout((placeable.width*zoomState.Scale).roundToInt(), (placeable.height*zoomState.Scale).roundToInt()) {
			placeable.placeWithLayer(x = 0, y = 0) {
				this.transformOrigin = TransformOrigin(0f, 0f)
				scaleX = zoomState.Scale
				scaleY = zoomState.Scale
			}
		}
	}
}
Everything else works fine, but how do i get the scroll behavior like in Map Applications? This my current code for the Mouse Scroll Event:
Copy code
// As i understand we have two categories of Coordinates(System):
UnScaled: without applying any scaling. The same pixel at a coordinate, no matter which scaling factor
Scaled: after applying scaling
.onPointerEvent(PointerEventType.Scroll) { pointerEvent ->
	coroutine.launch {
        // it seems the coordinates in pointerEvent uses UnScaled Coords, is this even possible?
		val pointer = pointerEvent.changes[0]
        // the position in scroll state are saved as the Scaled Coordinates
		val oldScrollOffset = Offset(scrollStateHorizontal.value.toFloat(), scrollStateVertical.value.toFloat()) / zoomState.Scale
        // the difference vector between Upper-Left Edge (Scroll position) and Mouse position in UnScaled
		val mouseViewportPos = pointer.position - oldScrollOffset
		zoomState.applyZoomChange(-(pointer.scrollDelta.x).roundToInt())
        // after zoom, the area of the element the viewport can show has changed, the difference vector must be changed too (mouseViewportPos / zoomState.Scale) 
        // with the adapted difference vector calculate the new scroll position and transform to Scaled Coords
		val newScroll = (pointer.position - (mouseViewportPos / zoomState.Scale)) * zoomState.Scale
		scrollStateHorizontal.scrollTo(newScroll.x.roundToInt())
		scrollStateVertical.scrollTo(newScroll.y.roundToInt())
	}
}
f
@Tobias Wohlfarth hey, could you share your code if you managed to make it work?
t
No, I have not found a working solution.