1/****************************************************************************
3 * (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
5 * QGroundControl is licensed according to the terms in the file
6 * COPYING.md in the root of the source code directory.
8 ****************************************************************************/
10pragma ComponentBehavior: Bound
18import QGroundControl.Controls
19import QGroundControl.GeoMap
21/// GeoMap counterpart of QGCMapCircleVisuals: a QGCMapCircle outline with
22/// rotation arrows and, while interactive, center/radius drag handles.
23/// No isValid gating needed: GeoMapItem parks invalid coordinates off-screen.
28 property var surfaceModel
29 property var mapCircle ///< QGCMapCircle object
30 property bool interactive: mapCircle ? mapCircle.interactive : false
31 property color borderColor: QGroundControl.globalPalette.mapMissionTrajectory
32 property real borderWidth: 3
33 property bool centerDragHandleVisible: true
34 property bool radiusLabelVisible: false
35 property int altitudeMode: GeoMapItem.ClampToGround
37 readonly property var _center: mapCircle ? mapCircle.center : QtPositioning.coordinate()
38 readonly property real _radius: mapCircle ? mapCircle.radius.rawValue : 0
39 readonly property var _camera: scene ? scene.camera : null
41 /// Screen-space handle that reports the coordinate it is dragged to, on the
42 /// same surface the handle is drawn on so it stays under the cursor at any tilt
43 component DragHandle: GeoMapItem {
46 property bool labelVisible: false
47 property string labelText
48 property bool showGroundDrop: false ///< Drop line and ground shadow under a floating handle
50 signal dragged(var coordinate)
52 width: ScreenTools.defaultFontPixelHeight * 1.5
54 anchorPoint: Qt.point(width / 2, height / 2)
55 // Handle stays opaque in 3D; its 3D node is only the drop line
57 delegate3D: showGroundDrop ? dropLineComponent : null
64 dropLength: dropShadow.dropLength
65 lineScale: dropShadow.dropLineScale
72 objectName: "geoMapDragHandleDropShadow"
74 indicatorSize: handle.width
75 visible: handle.showGroundDrop && dropLength > 0
86 anchors.bottom: parent.top
87 anchors.bottomMargin: ScreenTools.defaultFontPixelHeight * 0.25
88 anchors.horizontalCenter: parent.horizontalCenter
89 width: handleLabel.width + (ScreenTools.defaultFontPixelHeight * 0.5)
94 visible: handle.labelVisible
99 anchors.bottom: parent.top
100 anchors.bottomMargin: ScreenTools.defaultFontPixelHeight * 0.25
101 anchors.horizontalCenter: parent.horizontalCenter
102 height: parent.height
103 verticalAlignment: Text.AlignVCenter
105 text: handle.labelText
106 visible: handle.labelVisible
111 // The map pan DragHandler would otherwise take over past the drag threshold
112 preventStealing: true
114 // Cursor-to-anchor offset at press, so the handle doesn't jump to the cursor
115 property point _grabOffset
117 function _parentPosition(mouse) {
118 return handle.mapToItem(handle.parent, mouse.x, mouse.y)
121 onPressed: (mouse) => {
122 const pos = _parentPosition(mouse)
123 _grabOffset = Qt.point(handle.x + handle.anchorPoint.x - pos.x, handle.y + handle.anchorPoint.y - pos.y)
126 onPositionChanged: (mouse) => {
127 const pos = _parentPosition(mouse)
128 const target = Qt.point(pos.x + _grabOffset.x, pos.y + _grabOffset.y)
129 const scene = handle.scene
130 // Absolute handles sit on the horizontal plane at their altitude,
131 // ground-clamped ones on the rendered terrain
132 const coordinate = (handle.altitudeMode === GeoMapItem.Absolute)
133 ? scene.camera.coordinateAtScreenPoint(target, handle.scenePosition.z)
134 : handle.surfaceModel.surfaceCoordinateAtScreenPoint(scene.camera, target, scene.verticalScale * scene.terrainScale)
135 if (coordinate.isValid) {
136 handle.dragged(coordinate)
144 surfaceModel: root.surfaceModel
146 radiusMeters: root._radius
147 strokeColor: root.borderColor
148 strokeWidth: root.borderWidth
149 altitudeMode: root.altitudeMode
152 // Rotation direction arrows at the north (index 0) and south of the ring
157 id: rotationIndicator
158 objectName: "geoMapCircleRotationIndicator"
160 required property int index
163 surfaceModel: root.surfaceModel
164 altitudeMode: root.altitudeMode
165 coordinate: root._center.isValid ? root._center.atDistanceAndAzimuth(root._radius, index === 0 ? 0 : 180) : QtPositioning.coordinate()
166 width: ScreenTools.defaultFontPixelHeight
168 anchorPoint: Qt.point(width / 2, height / 2)
169 visible: root.mapCircle ? root.mapCircle.showRotation : false
173 // Camera heading rotates map north on screen; the arrows follow the ring
174 rotation: (root.mapCircle && root.mapCircle.clockwiseRotation ? 0 : 180) + (rotationIndicator.index === 0 ? 180 : 0)
175 + (root._camera ? root._camera.heading : 0)
179 strokeColor: root.borderColor
180 fillColor: root.borderColor
182 startY: rotationIndicator.width / 2
183 PathLine { x: rotationIndicator.width; y: rotationIndicator.width }
184 PathLine { x: rotationIndicator.width; y: 0 }
185 PathLine { x: 0; y: rotationIndicator.width / 2 }
191 enabled: root.interactive
192 onClicked: root.mapCircle.clockwiseRotation = !root.mapCircle.clockwiseRotation
198 objectName: "geoMapCircleCenterHandle"
200 surfaceModel: root.surfaceModel
201 altitudeMode: root.altitudeMode
202 coordinate: root._center
203 visible: root.interactive && root.centerDragHandleVisible
205 // Keep the circle's altitude: the drag only moves it horizontally
206 onDragged: (coordinate) => root.mapCircle.center = QtPositioning.coordinate(coordinate.latitude, coordinate.longitude, root._center.altitude)
210 objectName: "geoMapCircleRadiusHandle"
212 surfaceModel: root.surfaceModel
213 altitudeMode: root.altitudeMode
214 coordinate: root._center.isValid ? root._center.atDistanceAndAzimuth(root._radius, 90) : QtPositioning.coordinate()
215 visible: root.interactive
216 labelVisible: root.radiusLabelVisible
217 labelText: QGroundControl.unitsConversion.metersToAppSettingsHorizontalDistanceUnits(root._radius).toFixed(0) + " "
218 + QGroundControl.unitsConversion.appSettingsHorizontalDistanceUnitsString
219 onDragged: (coordinate) => root.mapCircle.radius.rawValue = root._center.distanceTo(coordinate)