Revision control

++ // Copyright (C) 2023 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ import QtQuick
++ import QtLocation as QL
++ import QtPositioning as QP
++ import Qt.labs.animation
++ /*!
++ \qmltype MapView
++ \inqmlmodule QtLocation
++ \brief An interactive map viewer component.
++
++ MapView wraps a Map and adds the typical interactive features:
++ changing the zoom level, panning and tilting the map.
++
++ The implementation is a QML assembly of smaller building blocks that are
++ available separately. In case you want to make changes in your own version
++ of this component, you can copy the QML, which is installed into the
++ \c qml/QtLocation module directory, and modify it as needed.
++
++ \sa Map
++ */
++ Item {
++ /*!
++ \qmlproperty Map MapView::map
++
++ This property provides access to the underlying Map instance.
++ */
++ property alias map: map
++
++ /*!
++ \qmlproperty real minimumZoomLevel
++
++ The minimum zoom level according to the size of the view.
++
++ \sa Map::minimumZoomLevel
++ */
++ property real minimumZoomLevel: map.minimumZoomLevel
++
++ /*!
++ \qmlproperty real maximumZoomLevel
++
++ The maximum valid zoom level for the map.
++
++ \sa Map::maximumZoomLevel
++ */
++ property real maximumZoomLevel: map.maximumZoomLevel
++
++ // --------------------------------
++ // implementation
++ id: root
++ Component.onCompleted: map.resetPinchMinMax()
++
++ QL.Map {
++ id: map
++ width: parent.width
++ height: parent.height
++ tilt: tiltHandler.persistentTranslation.y / -5
++ property bool pinchAdjustingZoom: false
++
++ BoundaryRule on zoomLevel {
++ id: br
++ minimum: map.minimumZoomLevel
++ maximum: map.maximumZoomLevel
++ }
++
++ onZoomLevelChanged: {
++ br.returnToBounds();
++ if (!pinchAdjustingZoom) resetPinchMinMax()
++ }
++
++ function resetPinchMinMax() {
++ pinch.persistentScale = 1
++ pinch.scaleAxis.minimum = Math.pow(2, root.minimumZoomLevel - map.zoomLevel + 1)
++ pinch.scaleAxis.maximum = Math.pow(2, root.maximumZoomLevel - map.zoomLevel - 1)
++ }
++
++ PinchHandler {
++ id: pinch
++ target: null
++ property real rawBearing: 0
++ property QP.geoCoordinate startCentroid
++ onActiveChanged: if (active) {
++ flickAnimation.stop()
++ pinch.startCentroid = map.toCoordinate(pinch.centroid.position, false)
++ } else {
++ flickAnimation.restart(centroid.velocity)
++ map.resetPinchMinMax()
++ }
++ onScaleChanged: (delta) => {
++ map.pinchAdjustingZoom = true
++ map.zoomLevel += Math.log2(delta)
++ map.alignCoordinateToPoint(pinch.startCentroid, pinch.centroid.position)
++ map.pinchAdjustingZoom = false
++ }
++ onRotationChanged: (delta) => {
++ pinch.rawBearing -= delta
++ // snap to 0° if we're close enough
++ map.bearing = (Math.abs(pinch.rawBearing) < 5) ? 0 : pinch.rawBearing
++ map.alignCoordinateToPoint(pinch.startCentroid, pinch.centroid.position)
++ }
++ grabPermissions: PointerHandler.TakeOverForbidden
++ }
++ WheelHandler {
++ id: wheel
++ // workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432:
++ // Magic Mouse pretends to be a trackpad but doesn't work with PinchHandler
++ // and we don't yet distinguish mice and trackpads on Wayland either
++ acceptedDevices: Qt.platform.pluginName === "cocoa" || Qt.platform.pluginName === "wayland"
++ ? PointerDevice.Mouse | PointerDevice.TouchPad
++ : PointerDevice.Mouse
++ onWheel: (event) => {
++ const loc = map.toCoordinate(wheel.point.position)
++ switch (event.modifiers) {
++ case Qt.NoModifier:
++ map.zoomLevel += event.angleDelta.y / 120
++ break
++ case Qt.ShiftModifier:
++ map.bearing += event.angleDelta.y / 15
++ break
++ case Qt.ControlModifier:
++ map.tilt += event.angleDelta.y / 15
++ break
++ }
++ map.alignCoordinateToPoint(loc, wheel.point.position)
++ }
++ }
++ DragHandler {
++ id: drag
++ signal flickStarted // for autotests only
++ signal flickEnded
++ target: null
++ onTranslationChanged: (delta) => map.pan(-delta.x, -delta.y)
++ onActiveChanged: if (active) {
++ flickAnimation.stop()
++ } else {
++ flickAnimation.restart(centroid.velocity)
++ }
++ }
++
++ property vector3d animDest
++ onAnimDestChanged: if (flickAnimation.running) {
++ const delta = Qt.vector2d(animDest.x - flickAnimation.animDestLast.x, animDest.y - flickAnimation.animDestLast.y)
++ map.pan(-delta.x, -delta.y)
++ flickAnimation.animDestLast = animDest
++ }
++
++ Vector3dAnimation on animDest {
++ id: flickAnimation
++ property vector3d animDestLast
++ from: Qt.vector3d(0, 0, 0)
++ duration: 500
++ easing.type: Easing.OutQuad
++ onStarted: drag.flickStarted()
++ onStopped: drag.flickEnded()
++
++ function restart(vel) {
++ stop()
++ map.animDest = Qt.vector3d(0, 0, 0)
++ animDestLast = Qt.vector3d(0, 0, 0)
++ to = Qt.vector3d(vel.x / duration * 100, vel.y / duration * 100, 0)
++ start()
++ }
++ }
++
++ DragHandler {
++ id: tiltHandler
++ minimumPointCount: 2
++ maximumPointCount: 2
++ target: null
++ xAxis.enabled: false
++ grabPermissions: PointerHandler.TakeOverForbidden
++ onActiveChanged: if (active) flickAnimation.stop()
++ }
++ }
++ }