diff --git a/src/FlightMap/CMakeLists.txt b/src/FlightMap/CMakeLists.txt index 1e1d2c3be5c..0fb2dea4f57 100644 --- a/src/FlightMap/CMakeLists.txt +++ b/src/FlightMap/CMakeLists.txt @@ -15,6 +15,7 @@ qt_add_qml_module(FlightMapModule MapItems/MapLineArrow.qml MapItems/MissionItemIndicator.qml MapItems/MissionItemIndicatorDrag.qml + MapItems/MissionItemIndicatorGroup.qml MapItems/MissionLineView.qml MapItems/PlanMapItems.qml MapItems/ProximityRadarMapView.qml diff --git a/src/FlightMap/MapItems/MapLineArrow.qml b/src/FlightMap/MapItems/MapLineArrow.qml index 3f095742970..87fa262110b 100644 --- a/src/FlightMap/MapItems/MapLineArrow.qml +++ b/src/FlightMap/MapItems/MapLineArrow.qml @@ -8,14 +8,18 @@ import QGroundControl.Controls import QGroundControl.FlightMap MapQuickItem { + id: root + + required property FlightMap mapControl + property color arrowColor: "white" property var fromCoord: QtPositioning.coordinate() property var toCoord: QtPositioning.coordinate() property int arrowPosition: 1 ///< 1: first quarter, 2: halfway, 3: last quarter - property var _map: parent property real _arrowSize: 15 property real _arrowHeading: 0 + property real _screenLegLength: 0 function _updateArrowDetails() { if (fromCoord && fromCoord.isValid && toCoord && toCoord.isValid) { @@ -26,16 +30,38 @@ MapQuickItem { coordinate = QtPositioning.coordinate() _arrowHeading = 0 } + _updateScreenLegLength() + } + + function _updateScreenLegLength() { + if (mapControl && fromCoord && fromCoord.isValid && toCoord && toCoord.isValid) { + const fromPoint = mapControl.fromCoordinate(fromCoord, false) + const toPoint = mapControl.fromCoordinate(toCoord, false) + _screenLegLength = Math.hypot(toPoint.x - fromPoint.x, toPoint.y - fromPoint.y) + } else { + _screenLegLength = 0 + } } onFromCoordChanged: _updateArrowDetails() onToCoordChanged: _updateArrowDetails() + onMapControlChanged: _updateScreenLegLength() + + Component.onCompleted: _updateArrowDetails() + + Connections { + target: root.mapControl + + function onCenterChanged() { root._updateScreenLegLength() } + function onZoomLevelChanged() { root._updateScreenLegLength() } + } sourceItem: Canvas { x: -_arrowSize y: 0 width: _arrowSize * 2 height: _arrowSize + visible: root._screenLegLength >= width onPaint: { var ctx = getContext("2d"); diff --git a/src/FlightMap/MapItems/MissionItemIndicator.qml b/src/FlightMap/MapItems/MissionItemIndicator.qml index 76e53fa1220..44758dd0e4c 100644 --- a/src/FlightMap/MapItems/MissionItemIndicator.qml +++ b/src/FlightMap/MapItems/MissionItemIndicator.qml @@ -11,25 +11,51 @@ MapQuickItem { property var missionItem property int sequenceNumber + property MissionItemIndicatorGroup indicatorGroup + property bool indicatorVisible: true + property bool interactive: true + + readonly property bool _isCurrentItem: missionItem ? missionItem.isCurrentItem || missionItem.hasCurrentChildItem : false + readonly property bool _usesAbbreviation: missionItem && missionItem.abbreviation.charAt(0) > 'A' && missionItem.abbreviation.charAt(0) < 'z' + readonly property var _group: indicatorGroup ? indicatorGroup.groupForItem(missionItem) : null + readonly property bool _isGrouped: _group && _group.items.length > 1 + readonly property bool _isGroupRepresentative: !_group || _group.representative === missionItem signal clicked + function activate() { + if (_isGrouped) { + const topLeft = _label.mapToItem(globals.parent, Qt.point(0, 0)) + const bottomRight = _label.mapToItem(globals.parent, Qt.point(_label.width, _label.height)) + const clickRect = Qt.rect(topLeft.x, topLeft.y, + bottomRight.x - topLeft.x, bottomRight.y - topLeft.y) + indicatorGroup.showGroup(missionItem, clickRect) + } else { + clicked() + } + } + anchorPoint.x: sourceItem.anchorPointX anchorPoint.y: sourceItem.anchorPointY + autoFadeIn: false + z: QGroundControl.zOrderMapItems + (_isCurrentItem ? 1 : 0) + visible: indicatorVisible && _isGroupRepresentative sourceItem: MissionItemIndexLabel { id: _label - checked: _isCurrentItem - label: missionItem.abbreviation - index: missionItem.abbreviation.charAt(0) > 'A' && missionItem.abbreviation.charAt(0) < 'z' ? -1 : missionItem.sequenceNumber + checked: _item._isCurrentItem + label: _item.missionItem.abbreviation + index: _item._usesAbbreviation ? -1 : _item.missionItem.sequenceNumber + indicatorSubText: _item._isGrouped ? "…" : "" + small: !_item._isGrouped && !_item._isCurrentItem + medium: _item._isGrouped && !_item._isCurrentItem gimbalYaw: missionItem.missionGimbalYaw vehicleYaw: missionItem.missionVehicleYaw - showGimbalYaw: !isNaN(missionItem.missionGimbalYaw) + showGimbalYaw: !_item._isGrouped && !isNaN(_item.missionItem.missionGimbalYaw) highlightSelected: true - onClicked: _item.clicked() + enabled: _item.interactive + onClicked: _item.activate() opacity: _item.opacity - - property bool _isCurrentItem: missionItem ? missionItem.isCurrentItem || missionItem.hasCurrentChildItem : false } } diff --git a/src/FlightMap/MapItems/MissionItemIndicatorGroup.qml b/src/FlightMap/MapItems/MissionItemIndicatorGroup.qml new file mode 100644 index 00000000000..5762181e93d --- /dev/null +++ b/src/FlightMap/MapItems/MissionItemIndicatorGroup.qml @@ -0,0 +1,321 @@ +pragma ComponentBehavior: Bound + +import QtQuick + +import QGroundControl +import QGroundControl.Controls +import QGroundControl.FlightMap +import QGroundControl.PlanView + +/// Groups mission item indicators which are too close to select individually. +Item { + id: root + + required property FlightMap map + required property QmlObjectListModel missionItems + + readonly property real groupingDistance: _mediumIndicatorRadius + + readonly property real _smallIndicatorRadius: _oddCeil((ScreenTools.defaultFontPixelHeight * ScreenTools.smallFontPointRatio) / 2) + readonly property real _largeIndicatorRadius: _oddCeil(ScreenTools.defaultFontPixelHeight * 0.66) + readonly property real _mediumIndicatorRadius: _oddCeil(((_smallIndicatorRadius * 2) + _largeIndicatorRadius) / 3) + readonly property var _groupingState: { + const state = [] + const count = missionItems ? missionItems.count : 0 + + for (let i = 0; i < count; i++) { + const item = missionItems.get(i) + if (!item || !item.isSimpleItem || (!item.specifiesCoordinate && !item.isTakeoffItem)) { + continue + } + + const coordinate = _coordinateForItem(item) + state.push({ + sequenceNumber: item.sequenceNumber, + coordinateValid: coordinate && coordinate.isValid, + latitude: coordinate && coordinate.isValid ? coordinate.latitude : NaN, + longitude: coordinate && coordinate.isValid ? coordinate.longitude : NaN, + current: item.isCurrentItem || item.hasCurrentChildItem + }) + } + + return state + } + + property var _groupsBySequenceNumber: ({}) + property var _selectionPanel + + signal itemSelected(int sequenceNumber) + + function groupForItem(item) { + return item ? _groupsBySequenceNumber[item.sequenceNumber] : null + } + + function showGroup(item, clickRect) { + const group = groupForItem(item) + if (!group || group.items.length < 2) { + itemSelected(item.sequenceNumber) + return + } + + _closeSelectionPanel() + const panel = selectionPanelComponent.createObject(mainWindow, { + clickRect: clickRect, + groupItems: group.items + }) + if (!panel) { + return + } + + _selectionPanel = panel + panel.open() + } + + function _oddCeil(value) { + const rounded = Math.ceil(value) + return rounded + (rounded % 2 === 0 ? 1 : 0) + } + + function _scheduleRegroup() { + Qt.callLater(_regroup) + } + + function _coordinateForItem(item) { + return item.isTakeoffItem && !item.specifiesCoordinate ? item.launchCoordinate : item.coordinate + } + + function _closeSelectionPanel() { + if (_selectionPanel) { + _selectionPanel.close() + _selectionPanel = null + } + } + + function _regroup() { + _closeSelectionPanel() + + if (!map || !map.mapReady) { + _groupsBySequenceNumber = {} + return + } + + const entries = [] + const count = missionItems ? missionItems.count : 0 + for (let i = 0; i < count; i++) { + const item = missionItems.get(i) + if (!item || !item.isSimpleItem || (!item.specifiesCoordinate && !item.isTakeoffItem)) { + continue + } + + const coordinate = _coordinateForItem(item) + if (!coordinate || !coordinate.isValid) { + continue + } + + const point = map.fromCoordinate(coordinate, false /* clipToViewPort */) + if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) { + continue + } + + entries.push({ + item: item, + point: point, + current: item.isCurrentItem || item.hasCurrentChildItem + }) + } + + entries.sort((first, second) => { + if (first.current !== second.current) { + return first.current ? -1 : 1 + } + return first.item.sequenceNumber - second.item.sequenceNumber + }) + + const cells = {} + const groups = [] + for (const entry of entries) { + const cellX = Math.floor(entry.point.x / groupingDistance) + const cellY = Math.floor(entry.point.y / groupingDistance) + let closestGroup = null + let closestDistanceSquared = Infinity + + for (let x = cellX - 1; x <= cellX + 1; x++) { + for (let y = cellY - 1; y <= cellY + 1; y++) { + const nearbyGroups = cells[`${x},${y}`] || [] + for (const group of nearbyGroups) { + const deltaX = entry.point.x - group.point.x + const deltaY = entry.point.y - group.point.y + const distanceSquared = (deltaX * deltaX) + (deltaY * deltaY) + if (distanceSquared <= groupingDistance * groupingDistance + && (distanceSquared < closestDistanceSquared + || (distanceSquared === closestDistanceSquared + && group.representative.sequenceNumber < closestGroup.representative.sequenceNumber))) { + closestGroup = group + closestDistanceSquared = distanceSquared + } + } + } + } + + if (closestGroup) { + closestGroup.items.push(entry.item) + continue + } + + const group = { + items: [entry.item], + point: entry.point, + representative: entry.item + } + groups.push(group) + + const cellKey = `${cellX},${cellY}` + if (!cells[cellKey]) { + cells[cellKey] = [] + } + cells[cellKey].push(group) + } + + const groupsBySequenceNumber = {} + for (const group of groups) { + group.items.sort((first, second) => first.sequenceNumber - second.sequenceNumber) + for (const item of group.items) { + groupsBySequenceNumber[item.sequenceNumber] = group + } + } + _groupsBySequenceNumber = groupsBySequenceNumber + } + + Component { + id: selectionPanelComponent + + DropPanel { + id: selectionPanel + + modal: false + + required property var groupItems + + sourceComponent: Component { + Item { + implicitWidth: itemListView.width + implicitHeight: itemListView.height + + QGCListView { + id: itemListView + + width: Math.min(Math.max(contentItem.childrenRect.width, _itemExtent), _maxWidth) + height: _itemExtent + orientation: ListView.Horizontal + model: selectionPanel.groupItems + cacheBuffer: width * 2 + reuseItems: true + currentIndex: -1 + + readonly property real _itemExtent: Math.max(ScreenTools.minTouchPixels, ScreenTools.defaultFontPixelHeight * 2.5) + readonly property real _maxWidth: selectionPanel.dropViewPort.width * 0.4 + + function _scrollBy(delta) { + const currentTarget = wheelScrollAnimation.running ? wheelScrollAnimation.to : contentX + const target = Math.max(0, Math.min(currentTarget - delta, Math.max(0, contentWidth - width))) + wheelScrollAnimation.stop() + wheelScrollAnimation.from = contentX + wheelScrollAnimation.to = target + wheelScrollAnimation.start() + } + + delegate: Item { + id: itemDelegate + + width: Math.max(itemListView._itemExtent, itemLabel.width + ScreenTools.defaultFontPixelWidth) + height: itemListView._itemExtent + + required property var modelData + + readonly property bool _usesAbbreviation: modelData.abbreviation.charAt(0) > 'A' && modelData.abbreviation.charAt(0) < 'z' + readonly property string _supplementaryLabel: !_usesAbbreviation ? "" : `${modelData.abbreviation} (${modelData.sequenceNumber})` + + MissionItemIndexLabel { + id: itemLabel + anchors.centerIn: parent + checked: itemDelegate.modelData.isCurrentItem || itemDelegate.modelData.hasCurrentChildItem + label: itemDelegate.modelData.abbreviation + index: itemDelegate._usesAbbreviation ? -1 : itemDelegate.modelData.sequenceNumber + small: false + supplementaryLabel: itemDelegate._supplementaryLabel + } + + MouseArea { + anchors.fill: parent + onClicked: { + selectionPanel.close() + root.itemSelected(itemDelegate.modelData.sequenceNumber) + } + } + } + + NumberAnimation { + id: wheelScrollAnimation + + target: itemListView + property: "contentX" + duration: 120 + easing.type: Easing.OutCubic + } + + WheelHandler { + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + orientation: Qt.Vertical + target: null + onWheel: event => { + itemListView._scrollBy(event.pixelDelta.y || event.angleDelta.y) + event.accepted = true + } + } + + WheelHandler { + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + orientation: Qt.Horizontal + target: null + onWheel: event => { + itemListView._scrollBy(event.pixelDelta.x || event.angleDelta.x) + event.accepted = true + } + } + + onMovementStarted: wheelScrollAnimation.stop() + } + } + } + + onClosed: { + if (root._selectionPanel === selectionPanel) { + root._selectionPanel = null + } + destroy() + } + } + } + + Connections { + target: root.map + + function onMapPanStop() { root._scheduleRegroup() } + function onMapReadyChanged() { root._scheduleRegroup() } + function onZoomLevelChanged() { root._scheduleRegroup() } + } + + Connections { + target: root.missionItems + + function onDataChanged() { root._scheduleRegroup() } + } + + on_GroupingStateChanged: _scheduleRegroup() + onGroupingDistanceChanged: _scheduleRegroup() + onMapChanged: _scheduleRegroup() + onMissionItemsChanged: _scheduleRegroup() + + Component.onCompleted: _scheduleRegroup() + Component.onDestruction: _closeSelectionPanel() +} diff --git a/src/FlightMap/MapItems/PlanMapItems.qml b/src/FlightMap/MapItems/PlanMapItems.qml index 8a37f7030ca..2eea5fa9b94 100644 --- a/src/FlightMap/MapItems/PlanMapItems.qml +++ b/src/FlightMap/MapItems/PlanMapItems.qml @@ -27,14 +27,25 @@ Item { property string fmode: vehicle.flightMode + MissionItemIndicatorGroup { + id: _missionItemIndicatorGroup + + map: _root._map + missionItems: _root.largeMapView ? _root._missionController.visualItems : null + onItemSelected: (sequenceNumber) => { + _root._guidedController.confirmAction(_root._guidedController.actionSetWaypoint, Math.max(sequenceNumber, 1)) + } + } + // Add the mission item visuals to the map Repeater { model: largeMapView ? _missionController.visualItems : 0 delegate: MissionItemMapVisual { - map: _map - vehicle: _vehicle - onClicked: _guidedController.confirmAction(_guidedController.actionSetWaypoint, Math.max(object.sequenceNumber, 1)) + map: _map + vehicle: _vehicle + indicatorGroup: _missionItemIndicatorGroup + onClicked: _guidedController.confirmAction(_guidedController.actionSetWaypoint, Math.max(object.sequenceNumber, 1)) } } @@ -68,6 +79,7 @@ Item { fromCoord: object ? object.coordinate1 : undefined toCoord: object ? object.coordinate2 : undefined arrowPosition: 3 + mapControl: _root.map z: QGroundControl.zOrderWaypointLines + 1 } } diff --git a/src/PlanView/MissionItemIndexLabel.qml b/src/PlanView/MissionItemIndexLabel.qml index 332838b9b11..8b24f4e384d 100644 --- a/src/PlanView/MissionItemIndexLabel.qml +++ b/src/PlanView/MissionItemIndexLabel.qml @@ -16,6 +16,7 @@ Canvas { property int index: 0 ///< Index to show in the indicator, 0 will show single char label instead, -1 first char of label in indicator full label to the side property bool checked: false property bool small: !checked + property bool medium: false property bool child: false property bool highlightSelected: false property var color: checked ? "green" : (child ? qgcPal.mapIndicatorChild : qgcPal.mapIndicator) @@ -26,20 +27,21 @@ Canvas { property real vehicleYaw property bool showGimbalYaw: false property bool showSequenceNumbers: true + property string indicatorSubText + property string supplementaryLabel property real _width: showGimbalYaw ? Math.max(_gimbalYawWidth, labelControl.visible ? labelControl.width : indicator.width) : (labelControl.visible ? labelControl.width : indicator.width) property real _height: showGimbalYaw ? _gimbalYawWidth : (labelControl.visible ? labelControl.height : indicator.height) property real _gimbalYawRadius: ScreenTools.defaultFontPixelHeight property real _gimbalYawWidth: _gimbalYawRadius * 2 - property real _smallRadiusRaw: Math.ceil((ScreenTools.defaultFontPixelHeight * ScreenTools.smallFontPointRatio) / 2) - property real _smallRadius: _smallRadiusRaw + ((_smallRadiusRaw % 2 == 0) ? 1 : 0) // odd number for better centering - property real _normalRadiusRaw: Math.ceil(ScreenTools.defaultFontPixelHeight * 0.66) - property real _normalRadius: _normalRadiusRaw + ((_normalRadiusRaw % 2 == 0) ? 1 : 0) - property real _indicatorRadius: small ? _smallRadius : _normalRadius + property real _smallRadius: _oddCeil((ScreenTools.defaultFontPixelHeight * ScreenTools.smallFontPointRatio) / 2) + property real _largeRadius: _oddCeil(ScreenTools.defaultFontPixelHeight * 0.66) + property real _mediumRadius: _oddCeil(((_smallRadius * 2) + _largeRadius) / 3) + property real _indicatorRadius: small ? _smallRadius : (medium ? _mediumRadius : _largeRadius) property real _gimbalRadians: degreesToRadians(vehicleYaw + gimbalYaw - 90) property real _labelMargin: 2 property real _labelRadius: _indicatorRadius + _labelMargin - property string _label: label.length > 1 ? label : "" + property string _label: supplementaryLabel !== "" ? supplementaryLabel : (label.length > 1 ? label : "") property string _index: index === 0 || index === -1 ? label.charAt(0) : (showSequenceNumbers ? index : "") onColorChanged: requestPaint() @@ -49,6 +51,11 @@ Canvas { QGCPalette { id: qgcPal } + function _oddCeil(value) { + const rounded = Math.ceil(value) + return rounded + (rounded % 2 === 0 ? 1 : 0) + } + function degreesToRadians(degrees) { return (Math.PI/180)*degrees } @@ -110,6 +117,7 @@ Canvas { radius: _indicatorRadius QGCLabel { + id: indexLabel anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -118,6 +126,23 @@ Canvas { fontSizeMode: Text.Fit text: _index } + + QGCLabel { + anchors { + horizontalCenter: indexLabel.horizontalCenter + baseline: indexLabel.baseline + baselineOffset: indexLabel.contentHeight / 5 + } + width: indicator.width + height: indicator.height * 0.4 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: "white" + font.pointSize: ScreenTools.smallFontPointSize + fontSizeMode: Text.Fit + text: root.indicatorSubText + visible: text !== "" + } } // Extra circle to indicate selection @@ -132,10 +157,10 @@ Canvas { anchors.centerIn: indicator } - // The mouse click area is always the size of a normal indicator + // The mouse click area is always the size of a large indicator Item { id: mouseAreaFill - anchors.margins: small ? -(_normalRadius - _smallRadius) : 0 + anchors.margins: -(root._largeRadius - root._indicatorRadius) anchors.fill: indicator } diff --git a/src/PlanView/MissionItemMapVisual.qml b/src/PlanView/MissionItemMapVisual.qml index b71a2d95036..7fe04aa331d 100644 --- a/src/PlanView/MissionItemMapVisual.qml +++ b/src/PlanView/MissionItemMapVisual.qml @@ -5,6 +5,7 @@ import QtPositioning import QGroundControl import QGroundControl.Controls +import QGroundControl.FlightMap /// Mission item map visual Item { @@ -13,6 +14,7 @@ Item { property var map ///< Map control to place item in property var vehicle ///< Vehicle associated with this item property bool interactive: true ///< Vehicle associated with this item + property MissionItemIndicatorGroup indicatorGroup signal clicked(int sequenceNumber) @@ -22,12 +24,16 @@ Item { asynchronous: true Component.onCompleted: { - mapVisualLoader.setSource(object.mapVisualQML, { + const properties = { map: _root.map, vehicle: _root.vehicle, opacity: Qt.binding(() => _root.opacity), interactive: Qt.binding(() => _root.interactive) - }) + } + if (object.isSimpleItem) { + properties.indicatorGroup = _root.indicatorGroup + } + mapVisualLoader.setSource(object.mapVisualQML, properties) } onLoaded: { diff --git a/src/PlanView/MissionItemMapVisualBase.qml b/src/PlanView/MissionItemMapVisualBase.qml index 8bdc65769de..14d044f4391 100644 --- a/src/PlanView/MissionItemMapVisualBase.qml +++ b/src/PlanView/MissionItemMapVisualBase.qml @@ -15,6 +15,7 @@ Item { property var map ///< Map control to place item in property var vehicle ///< Vehicle associated with this item property bool interactive: true + property MissionItemIndicatorGroup indicatorGroup /// Subclasses must set this to their indicator Component property Component indicatorComponent @@ -48,7 +49,7 @@ Item { } function updateDragArea() { - if (_missionItem.isCurrentItem && map.planView && _missionItem.specifiesCoordinate) { + if (_missionItem.isCurrentItem && map.planView && _missionItem.specifiesCoordinate && itemVisualLoader.item) { showDragArea() } else { hideDragArea() @@ -108,6 +109,7 @@ Item { if (item) { item.parent = map map.addMapItem(item) + updateDragArea() } } } @@ -121,6 +123,14 @@ Item { itemIndicator: itemVisualLoader.item itemCoordinate: _missionItem.coordinate visible: control.interactive + onClicked: { + const indicator = itemVisualLoader.item + if (indicator && indicator.activate) { + indicator.activate() + } else { + control.clicked(control._missionItem.sequenceNumber) + } + } onItemCoordinateChanged: _missionItem.coordinate = itemCoordinate } } diff --git a/src/PlanView/PlanView.qml b/src/PlanView/PlanView.qml index a8cf0fb6b57..f0949e7538e 100644 --- a/src/PlanView/PlanView.qml +++ b/src/PlanView/PlanView.qml @@ -309,6 +309,16 @@ Item { } } + MissionItemIndicatorGroup { + id: _missionItemIndicatorGroup + + map: editorMap + missionItems: _root._missionController.visualItems + onItemSelected: (sequenceNumber) => { + _root._missionController.setCurrentPlanViewSeqNum(sequenceNumber, false) + } + } + // Add the mission item visuals to the map Repeater { model: _missionController.visualItems @@ -317,7 +327,10 @@ Item { opacity: _editingLayer == _layerMission ? 1 : editorMap._nonInteractiveOpacity interactive: _editingLayer == _layerMission vehicle: _planMasterController.controllerVehicle - onClicked: (sequenceNumber) => { _missionController.setCurrentPlanViewSeqNum(sequenceNumber, false) } + indicatorGroup: _missionItemIndicatorGroup + onClicked: (sequenceNumber) => { + _root._missionController.setCurrentPlanViewSeqNum(sequenceNumber, false) + } } } @@ -336,6 +349,7 @@ Item { fromCoord: object ? object.coordinate1 : undefined toCoord: object ? object.coordinate2 : undefined arrowPosition: 3 + mapControl: editorMap z: QGroundControl.zOrderWaypointLines + 1 } } @@ -343,10 +357,14 @@ Item { // UI for splitting the current segment MapQuickItem { id: splitSegmentItem + + property real _screenLegLength: 0 + anchorPoint.x: sourceItem.width / 2 anchorPoint.y: sourceItem.height / 2 z: QGroundControl.zOrderWaypointLines + 1 visible: _editingLayer == _layerMission + && _screenLegLength > _missionItemIndicatorGroup.groupingDistance * 2 sourceItem: SplitIndicator { onClicked: _missionController.insertSimpleMissionItem(splitSegmentItem.coordinate, @@ -354,6 +372,17 @@ Item { true /* makeCurrentItem */) } + function _updateScreenLegLength() { + const segment = _root._missionController.splitSegment + if (segment && segment.coordinate1.isValid && segment.coordinate2.isValid) { + const fromPoint = editorMap.fromCoordinate(segment.coordinate1, false /* clipToViewPort */) + const toPoint = editorMap.fromCoordinate(segment.coordinate2, false /* clipToViewPort */) + _screenLegLength = Math.hypot(toPoint.x - fromPoint.x, toPoint.y - fromPoint.y) + } else { + _screenLegLength = 0 + } + } + function _updateSplitCoord() { if (_missionController.splitSegment) { var distance = _missionController.splitSegment.coordinate1.distanceTo(_missionController.splitSegment.coordinate2) @@ -362,6 +391,7 @@ Item { } else { coordinate = QtPositioning.coordinate() } + _updateScreenLegLength() } Connections { @@ -374,6 +404,12 @@ Item { function onCoordinate1Changed() { splitSegmentItem._updateSplitCoord() } function onCoordinate2Changed() { splitSegmentItem._updateSplitCoord() } } + + Connections { + target: editorMap + function onCenterChanged() { splitSegmentItem._updateScreenLegLength() } + function onZoomLevelChanged() { splitSegmentItem._updateScreenLegLength() } + } } // Add the vehicles to the map diff --git a/src/PlanView/SimpleItemMapVisual.qml b/src/PlanView/SimpleItemMapVisual.qml index c441e75cb18..e8aaa8a12de 100644 --- a/src/PlanView/SimpleItemMapVisual.qml +++ b/src/PlanView/SimpleItemMapVisual.qml @@ -63,13 +63,14 @@ MissionItemMapVisualBase { id: indicatorComponent MissionItemIndicator { - coordinate: _missionItem.coordinate - visible: _missionItem.specifiesCoordinate - z: QGroundControl.zOrderMapItems - missionItem: _missionItem - sequenceNumber: _missionItem.sequenceNumber - onClicked: if(_root.interactive) _root.clicked(_missionItem.sequenceNumber) - opacity: _root.opacity + coordinate: _missionItem.coordinate + indicatorGroup: _root.indicatorGroup + indicatorVisible: _missionItem.specifiesCoordinate + interactive: _root.interactive + missionItem: _missionItem + sequenceNumber: _missionItem.sequenceNumber + onClicked: _root.clicked(_missionItem.sequenceNumber) + opacity: _root.opacity } } diff --git a/src/PlanView/TakeoffItemMapVisual.qml b/src/PlanView/TakeoffItemMapVisual.qml index 20985a3758a..1bc99f58fc6 100644 --- a/src/PlanView/TakeoffItemMapVisual.qml +++ b/src/PlanView/TakeoffItemMapVisual.qml @@ -14,6 +14,7 @@ Item { property var map ///< Map control to place item in property var vehicle ///< Vehicle associated with this item property bool interactive: true + property MissionItemIndicatorGroup indicatorGroup property var _missionItem: object property var _takeoffIndicatorItem @@ -98,7 +99,8 @@ Item { MissionItemIndicator { coordinate: _missionItem.specifiesCoordinate ? _missionItem.coordinate : _missionItem.launchCoordinate - z: QGroundControl.zOrderMapItems + indicatorGroup: _root.indicatorGroup + interactive: _root.interactive missionItem: _missionItem sequenceNumber: _missionItem.sequenceNumber onClicked: _root.clicked(_missionItem.sequenceNumber) diff --git a/src/PlanView/TransectStyleMapVisuals.qml b/src/PlanView/TransectStyleMapVisuals.qml index ce6ddf17da5..6bbf3ffb3f2 100644 --- a/src/PlanView/TransectStyleMapVisuals.qml +++ b/src/PlanView/TransectStyleMapVisuals.qml @@ -136,6 +136,7 @@ Item { fromCoord: _transectPoints[_firstTrueTransectIndex] toCoord: _transectPoints[_firstTrueTransectIndex + 1] arrowPosition: 1 + mapControl: _root.map visible: _currentItem && !_vertexDrag opacity: _root.opacity } @@ -148,6 +149,7 @@ Item { fromCoord: _transectPoints[nextTrueTransectIndex] toCoord: _transectPoints[nextTrueTransectIndex + 1] arrowPosition: 1 + mapControl: _root.map visible: _currentItem && _transectCount > 3 && !_vertexDrag opacity: _root.opacity @@ -162,6 +164,7 @@ Item { fromCoord: _transectPoints[_lastTrueTransectIndex - 1] toCoord: _transectPoints[_lastTrueTransectIndex] arrowPosition: 3 + mapControl: _root.map visible: _currentItem && !_vertexDrag opacity: _root.opacity } @@ -174,6 +177,7 @@ Item { fromCoord: _transectPoints[prevTrueTransectIndex - 1] toCoord: _transectPoints[prevTrueTransectIndex] arrowPosition: 13 + mapControl: _root.map visible: _currentItem && _transectCount > 3 && !_vertexDrag opacity: _root.opacity