diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc index 99607527ab4f1a2dd86a75a3fb3ee9ac1db74efc..9fe698a3fc06e95b985471a41fd3c0f7956a369a 100644 --- a/qgroundcontrol.qrc +++ b/qgroundcontrol.qrc @@ -94,6 +94,8 @@ <file alias="QGroundControl/Controls/SubMenuButton.qml">src/QmlControls/SubMenuButton.qml</file> <file alias="QGroundControl/Controls/VehicleRotationCal.qml">src/QmlControls/VehicleRotationCal.qml</file> <file alias="QGroundControl/Controls/VehicleSummaryRow.qml">src/QmlControls/VehicleSummaryRow.qml</file> + <file alias="QGroundControl/Controls/ToolStrip.qml">src/QmlControls/ToolStrip.qml</file> + <file alias="QGroundControl/Controls/DropPanel.qml">src/QmlControls/DropPanel.qml</file> <file alias="QGroundControl/Controls/ViewWidget.qml">src/ViewWidgets/ViewWidget.qml</file> <file alias="QGroundControl/FactControls/FactBitmask.qml">src/FactSystem/FactControls/FactBitmask.qml</file> <file alias="QGroundControl/FactControls/FactCheckBox.qml">src/FactSystem/FactControls/FactCheckBox.qml</file> @@ -111,6 +113,8 @@ <file alias="QGroundControl/FlightDisplay/MultiVehicleList.qml">src/FlightDisplay/MultiVehicleList.qml</file> <file alias="QGroundControl/FlightDisplay/qmldir">src/FlightDisplay/qmldir</file> <file alias="QGroundControl/FlightMap/CenterMapDropButton.qml">src/FlightMap/Widgets/CenterMapDropButton.qml</file> + <file alias="QGroundControl/FlightMap/CenterMapDropPanel.qml">src/FlightMap/Widgets/CenterMapDropPanel.qml</file> + <file alias="QGroundControl/FlightMap/MapFitFunctions.qml">src/FlightMap/Widgets/MapFitFunctions.qml</file> <file alias="QGroundControl/FlightMap/FlightMap.qml">src/FlightMap/FlightMap.qml</file> <file alias="QGroundControl/FlightMap/InstrumentSwipeView.qml">src/FlightMap/Widgets/InstrumentSwipeView.qml</file> <file alias="QGroundControl/FlightMap/MapScale.qml">src/FlightMap/MapScale.qml</file> diff --git a/src/FlightMap/Widgets/CenterMapDropPanel.qml b/src/FlightMap/Widgets/CenterMapDropPanel.qml new file mode 100644 index 0000000000000000000000000000000000000000..fe3a43b6d050442a279a4e2c24fa9a93df4b1b17 --- /dev/null +++ b/src/FlightMap/Widgets/CenterMapDropPanel.qml @@ -0,0 +1,103 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +import QtQuick 2.4 +import QtQuick.Controls 1.3 +import QtQuick.Layouts 1.2 +import QtPositioning 5.3 + +import QGroundControl 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.Palette 1.0 + +ColumnLayout { + id: root + spacing: ScreenTools.defaultFontPixelWidth * 0.5 + + property var map + property var fitFunctions + property bool showMission: true + property bool showAllItems: true + property bool showFollowVehicle: false + property bool followVehicle: false + + property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle + + QGCLabel { text: qsTr("Center map on:") } + + QGCButton { + text: qsTr("Mission") + Layout.fillWidth: true + visible: showMission + enabled: !followVehicleCheckBox.checked + + onClicked: { + dropPanel.hide() + fitFunctions.fitMapViewportToMissionItems() + } + } + + QGCButton { + text: qsTr("All items") + Layout.fillWidth: true + visible: showAllItems + enabled: !followVehicleCheckBox.checked + + onClicked: { + dropPanel.hide() + fitFunctions.fitMapViewportToAllItems() + } + } + + QGCButton { + text: qsTr("Home") + Layout.fillWidth: true + enabled: !followVehicleCheckBox.checked + + onClicked: { + dropPanel.hide() + map.center = fitFunctions.fitHomePosition() + } + } + + QGCButton { + text: qsTr("Current Location") + Layout.fillWidth: true + enabled: mainWindow.gcsPosition.isValid && !followVehicleCheckBox.checked + + onClicked: { + dropPanel.hide() + map.center = mainWindow.gcsPosition + } + } + + QGCButton { + text: qsTr("Vehicle") + Layout.fillWidth: true + enabled: _activeVehicle && _activeVehicle.latitude != 0 && _activeVehicle.longitude != 0 && !followVehicleCheckBox.checked + + onClicked: { + dropPanel.hide() + map.center = activeVehicle.coordinate + } + } + + QGCCheckBox { + id: followVehicleCheckBox + text: qsTr("Follow Vehicle") + checked: followVehicle + visible: showFollowVehicle + + onClicked: { + dropPanel.hide() + _root.followVehicle = checked + } + } +} // Column diff --git a/src/FlightMap/Widgets/MapFitFunctions.qml b/src/FlightMap/Widgets/MapFitFunctions.qml new file mode 100644 index 0000000000000000000000000000000000000000..dc76a54fe0672d6da042774b93f868d470297972 --- /dev/null +++ b/src/FlightMap/Widgets/MapFitFunctions.qml @@ -0,0 +1,144 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +import QtQuick 2.4 +import QtPositioning 5.3 + +import QGroundControl 1.0 + +/// Set of functions for fitting the map viewpoer to a specific constraint +Item { + property var map + property rect mapFitViewport + property bool usePlannedHomePosition ///< true: planned home position used for calculations, false: vehicle home position use for calculations + property var mapGeoFenceController + property var mapMissionController + property var mapRallyPointController + + property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle + + function fitHomePosition() { + var homePosition = QtPositioning.coordinate() + var activeVehicle = QGroundControl.multiVehicleManager.activeVehicle + if (usePlannedHomePosition) { + homePosition = mapMissionController.visualItems.get(0).coordinate + } else if (activeVehicle) { + homePosition = activeVehicle.homePosition + } + return homePosition + } + + /// Normalize latitude to range: 0 to 180, S to N + function normalizeLat(lat) { + return lat + 90.0 + } + + /// Normalize longitude to range: 0 to 360, W to E + function normalizeLon(lon) { + return lon + 180.0 + } + + /// Fits the visible region of the map to inclues all of the specified coordinates. If no coordinates + /// are specified the map will center to fitHomePosition() + function fitMapViewportToAllCoordinates(coordList) { + if (coordList.length == 0) { + map.center = fitHomePosition() + return + } + + // Create the normalized lat/lon corners for the coordinate bounding rect from the list of coordinates + var north = normalizeLat(coordList[0].latitude) + var south = north + var east = normalizeLon(coordList[0].longitude) + var west = east + for (var i=1; i<coordList.length; i++) { + var lat = normalizeLat(coordList[i].latitude) + var lon = normalizeLon(coordList[i].longitude) + + north = Math.max(north, lat) + south = Math.min(south, lat) + east = Math.max(east, lon) + west = Math.min(west, lon) + } + + // Expand the coordinate bounding rect to make room for the tools around the edge of the map + var latDegreesPerPixel = (north - south) / mapFitViewport.width + var lonDegreesPerPixel = (east - west) / mapFitViewport.height + north = Math.min(north + (mapFitViewport.y * latDegreesPerPixel), 180) + south = Math.max(south - ((map.height - mapFitViewport.bottom) * latDegreesPerPixel), 0) + west = Math.max(west - (mapFitViewport.x * lonDegreesPerPixel), 0) + east = Math.min(east + ((map.width - mapFitViewport.right) * lonDegreesPerPixel), 360) + + // Fix the map region to the new bounding rect + var topLeftCoord = QtPositioning.coordinate(north - 90.0, west - 180.0) + var bottomRightCoord = QtPositioning.coordinate(south - 90.0, east - 180.0) + map.setVisibleRegion(QtPositioning.rectangle(topLeftCoord, bottomRightCoord)) + } + + function addMissionItemCoordsForFit(coordList) { + var homePosition = fitHomePosition() + if (homePosition.isValid) { + coordList.push(homePosition) + } + for (var i=1; i<mapMissionController.visualItems.count; i++) { + var missionItem = mapMissionController.visualItems.get(i) + if (missionItem.specifiesCoordinate && !missionItem.isStandaloneCoordinate) { + coordList.push(missionItem.coordinate) + } + } + } + + function fitMapViewportToMissionItems() { + var coordList = [ ] + addMissionItemCoordsForFit(coordList) + fitMapViewportToAllCoordinates(coordList) + } + + function addFenceItemCoordsForFit(coordList) { + var homePosition = fitHomePosition() + if (homePosition.isValid && mapGeoFenceController.circleEnabled) { + var azimuthList = [ 0, 180, 90, 270 ] + for (var i=0; i<azimuthList.length; i++) { + var edgeCoordinate = homePosition.atDistanceAndAzimuth(mapGeoFenceController.circleRadius, azimuthList[i]) + coordList.push(edgeCoordinate) + } + } + if (mapGeoFenceController.polygonEnabled && mapGeoFenceController.polygon.count() > 2) { + for (var i=0; i<mapGeoFenceController.polygon.count(); i++) { + coordList.push(mapGeoFenceController.polygon.path[i]) + } + } + } + + function fitMapViewportToFenceItems() { + var coordList = [ ] + addFenceItemCoordsForFit(coordList) + fitMapViewportToAllCoordinates(coordList) + } + + function addRallyItemCoordsForFit(coordList) { + for (var i=0; i<mapRallyPointController.points.count; i++) { + coordList.push(mapRallyPointController.points.get(i).coordinate) + } + } + + function fitMapViewportToRallyItems() { + var coordList = [ ] + addRallyItemCoordsForFit(coordList) + fitMapViewportToAllCoordinates(coordList) + } + + function fitMapViewportToAllItems() { + var coordList = [ ] + addMissionItemCoordsForFit(coordList) + addFenceItemCoordsForFit(coordList) + addRallyItemCoordsForFit(coordList) + fitMapViewportToAllCoordinates(coordList) + } +} // Item diff --git a/src/FlightMap/qmldir b/src/FlightMap/qmldir index f11975a0f353ccc8093fd775027a7fd04e51f32c..c6d02e2cf02b458c0500ec20cb0920839b820513 100644 --- a/src/FlightMap/qmldir +++ b/src/FlightMap/qmldir @@ -1,12 +1,14 @@ Module QGroundControl.FlightMap # Main view controls -FlightMap 1.0 FlightMap.qml -QGCVideoBackground 1.0 QGCVideoBackground.qml +FlightMap 1.0 FlightMap.qml +QGCVideoBackground 1.0 QGCVideoBackground.qml # Widgets CenterMapDropButton 1.0 CenterMapDropButton.qml +CenterMapDropPanel 1.0 CenterMapDropPanel.qml InstrumentSwipeView 1.0 InstrumentSwipeView.qml +MapFitFunctions 1.0 MapFitFunctions.qml MapScale 1.0 MapScale.qml QGCArtificialHorizon 1.0 QGCArtificialHorizon.qml QGCAttitudeHUD 1.0 QGCAttitudeHUD.qml diff --git a/src/MissionEditor/MissionEditor.qml b/src/MissionEditor/MissionEditor.qml index 889f8296888af8b2f9be999e515f622dc67376a7..1389766e2bbf81082a7e4e6ec57a7522b6b863d3 100644 --- a/src/MissionEditor/MissionEditor.qml +++ b/src/MissionEditor/MissionEditor.qml @@ -48,6 +48,7 @@ QGCView { property bool _firstVehiclePosition: true property var activeVehiclePosition: _activeVehicle ? _activeVehicle.coordinate : QtPositioning.coordinate() property bool _lightWidgetBorders: editorMap.isSatelliteMap + property bool _addWaypointOnClick: false /// The controller which should be called for load/save, send to/from vehicle calls property var _syncDropDownController: missionController @@ -84,10 +85,33 @@ QGCView { function checkFirstLoadComplete() { if (!_firstLoadComplete && _firstMissionLoadComplete && _firstRallyLoadComplete && _firstFenceLoadComplete) { _firstLoadComplete = true - centerMapButton.fitMapViewportToAllItems() + mapFitFunctions.fitMapViewportToAllItems() } } + function addSurveyItem() { + var coordinate = editorMap.center + coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces) + coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces) + coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces) + var sequenceNumber = missionController.insertComplexMissionItem(coordinate, missionController.visualItems.count) + setCurrentItem(sequenceNumber) + } + + MapFitFunctions { + id: mapFitFunctions + map: editorMap + mapFitViewport: Qt.rect(leftToolWidth, toolbarHeight, editorMap.width - leftToolWidth - rightPanelWidth, editorMap.height - toolbarHeight) + usePlannedHomePosition: true + mapGeoFenceController: geoFenceController + mapMissionController: missionController + mapRallyPointController: rallyPointController + + property real toolbarHeight: qgcView.height - ScreenTools.availableHeight + property real rightPanelWidth: _rightPanelWidth + property real leftToolWidth: mapFitFunctions.x + mapFitFunctions.width + } + MissionController { id: missionController @@ -101,7 +125,7 @@ QGCView { qgcView.showDialog(mobileFilePicker, qsTr("Select Mission File"), qgcView.showDialogDefaultWidth, StandardButton.Cancel) } else { missionController.loadFromFilePicker() - centerMapButton.fitMapViewportToMissionItems() + mapFitFunctions.fitMapViewportToMissionItems() _currentMissionItem = _visualItems.get(0) } } @@ -115,7 +139,7 @@ QGCView { } function fitViewportToItems() { - centerMapButton.fitMapViewportToMissionItems() + mapFitFunctions.fitMapViewportToMissionItems() } onVisualItemsChanged: { @@ -123,7 +147,7 @@ QGCView { } onNewItemsFromVehicle: { - centerMapButton.fitMapViewportToMissionItems() + mapFitFunctions.fitMapViewportToMissionItems() setCurrentItem(0) _firstMissionLoadComplete = true checkFirstLoadComplete() @@ -148,7 +172,7 @@ QGCView { qgcView.showDialog(mobileFilePicker, qsTr("Select Fence File"), qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel) } else { geoFenceController.loadFromFilePicker() - centerMapButton.fitMapViewportToFenceItems() + mapFitFunctions.fitMapViewportToFenceItems() } } @@ -164,14 +188,14 @@ QGCView { } function fitViewportToItems() { - centerMapButton.fitMapViewportToFenceItems() + mapFitFunctions.fitMapViewportToFenceItems() } onLoadComplete: { _firstFenceLoadComplete = true switch (_syncDropDownController) { case geoFenceController: - centerMapButton.fitMapViewportToFenceItems() + mapFitFunctions.fitMapViewportToFenceItems() break case missionController: checkFirstLoadComplete() @@ -206,19 +230,19 @@ QGCView { qgcView.showDialog(mobileFilePicker, qsTr("Select Rally Point File"), qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel) } else { rallyPointController.loadFromFilePicker() - centerMapButton.fitMapViewportToRallyItems() + mapFitFunctions.fitMapViewportToRallyItems() } } function fitViewportToItems() { - centerMapButton.fitMapViewportToRallyItems() + mapFitFunctions.fitMapViewportToRallyItems() } onLoadComplete: { _firstRallyLoadComplete = true switch (_syncDropDownController) { case rallyPointController: - centerMapButton.fitMapViewportToRallyItems() + mapFitFunctions.fitMapViewportToRallyItems() break case missionController: checkFirstLoadComplete() @@ -360,7 +384,7 @@ QGCView { switch (_editingLayer) { case _layerMission: - if (addMissionItemsButton.checked) { + if (_addWaypointOnClick) { var sequenceNumber = missionController.insertSimpleMissionItem(coordinate, missionController.visualItems.count) setCurrentItem(sequenceNumber) } @@ -795,122 +819,9 @@ QGCView { } } - QGCMapLabel { - id: planLabel - map: editorMap - text: qsTr("Plan") - visible: !ScreenTools.isShortScreen - anchors.topMargin: _toolButtonTopMargin - anchors.horizontalCenter: addMissionItemsButton.horizontalCenter - anchors.top: parent.top - } - - // IMPORTANT NOTE: Drop Buttons must be parented directly to the map. If they are placed in a Column for example the drop control positioning - // will not work correctly. - - //-- Vertical Tool Buttons - - RoundButton { - id: addMissionItemsButton - anchors.topMargin: planLabel.visible ? ScreenTools.defaultFontPixelHeight / 2 : _toolButtonTopMargin - anchors.leftMargin: ScreenTools.defaultFontPixelHeight - anchors.left: parent.left - anchors.top: planLabel.visible ? planLabel.bottom : parent.top - buttonImage: "/qmlimages/MapAddMission.svg" - lightBorders: _lightWidgetBorders - visible: _editingLayer == _layerMission - } - - RoundButton { - id: addShapeButton - anchors.topMargin: ScreenTools.defaultFontPixelHeight - anchors.top: addMissionItemsButton.bottom - anchors.left: addMissionItemsButton.left - buttonImage: "/qmlimages/MapDrawShape.svg" - lightBorders: _lightWidgetBorders - visible: _editingLayer == _layerMission + /* - onClicked: { - var coordinate = editorMap.center - coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces) - coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces) - coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces) - var sequenceNumber = missionController.insertComplexMissionItem(coordinate, missionController.visualItems.count) - setCurrentItem(sequenceNumber) - checked = false - addMissionItemsButton.checked = false - } - } - - DropButton { - id: syncButton - anchors.topMargin: ScreenTools.defaultFontPixelHeight - anchors.top: addShapeButton.bottom - anchors.left: addShapeButton.left - dropDirection: dropRight - buttonImage: _syncDropDownController.dirty ? "/qmlimages/MapSyncChanged.svg" : "/qmlimages/MapSync.svg" - viewportMargins: ScreenTools.defaultFontPixelWidth / 2 - exclusiveGroup: _dropButtonsExclusiveGroup - dropDownComponent: syncDropDownComponent - enabled: !_syncDropDownController.syncInProgress - rotateImage: _syncDropDownController.syncInProgress - lightBorders: _lightWidgetBorders - } - - CenterMapDropButton { - id: centerMapButton - anchors.topMargin: ScreenTools.defaultFontPixelHeight - anchors.top: syncButton.bottom - anchors.left: syncButton.left - exclusiveGroup: _dropButtonsExclusiveGroup - map: editorMap - mapFitViewport: Qt.rect(leftToolWidth, toolbarHeight, editorMap.width - leftToolWidth - rightPanelWidth, editorMap.height - toolbarHeight) - usePlannedHomePosition: true - geoFenceController: geoFenceController - missionController: missionController - rallyPointController: rallyPointController - - property real toolbarHeight: qgcView.height - ScreenTools.availableHeight - property real rightPanelWidth: _rightPanelWidth - property real leftToolWidth: centerMapButton.x + centerMapButton.width - } - - DropButton { - id: mapTypeButton - anchors.topMargin: ScreenTools.defaultFontPixelHeight - anchors.top: centerMapButton.bottom - anchors.left: centerMapButton.left - dropDirection: dropRight - buttonImage: "/qmlimages/MapType.svg" - viewportMargins: ScreenTools.defaultFontPixelWidth / 2 - exclusiveGroup: _dropButtonsExclusiveGroup - lightBorders: _lightWidgetBorders - - dropDownComponent: Component { - Column { - spacing: _margin - QGCLabel { text: qsTr("Map type:") } - Row { - spacing: ScreenTools.defaultFontPixelWidth - Repeater { - model: QGroundControl.flightMapSettings.mapTypes - - QGCButton { - checkable: true - checked: QGroundControl.flightMapSettings.mapType === text - text: modelData - exclusiveGroup: _mapTypeButtonsExclusiveGroup - onClicked: { - QGroundControl.flightMapSettings.mapType = text - checked = true - mapTypeButton.hideDropDown() - } - } - } - } - } - } - } + FIXME: Need to put these back into ToolStrip //-- Zoom Map In RoundButton { @@ -945,6 +856,60 @@ QGCView { } } + */ + + ToolStrip { + anchors.leftMargin: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.topMargin: _toolButtonTopMargin + anchors.top: parent.top + color: qgcPal.window + title: qsTr("Plan") + z: QGroundControl.zOrderWidgets + showAlternateIcon: [ false, false, _syncDropDownController.dirty, false, false ] + rotateImage: [ false, false, _syncDropDownController.syncInProgress, false, false ] + buttonEnabled: [ true, true, !_syncDropDownController.syncInProgress, true, true ] + + model: [ + { + name: "Waypoint", + iconSource: "/qmlimages/MapAddMission.svg", + toggle: true + }, + { + name: "Pattern", + iconSource: "/qmlimages/MapDrawShape.svg" + }, + { + name: "Sync", + iconSource: "/qmlimages/MapSync.svg", + alternateIconSource: "/qmlimages/MapSyncChanged.svg", + dropPanelComponent: syncDropPanel + }, + { + name: "Center", + iconSource: "/qmlimages/MapCenter.svg", + dropPanelComponent: centerMapDropPanel + }, + { + name: "Map", + iconSource: "/qmlimages/MapType.svg", + dropPanelComponent: mapTypeDropPanel + } + ] + + onClicked: { + switch (index) { + case 0: + _addWaypointOnClick = checked + break + case 1: + addSurveyItem() + break + } + } + } + MapScale { anchors.margins: ScreenTools.defaultFontPixelHeight * (0.66) anchors.bottom: waypointValuesDisplay.visible ? waypointValuesDisplay.top : parent.bottom @@ -1007,8 +972,10 @@ QGCView { } } + //- ToolStrip DropPanel Components + Component { - id: syncDropDownComponent + id: syncDropPanel Column { id: columnHolder @@ -1036,7 +1003,7 @@ QGCView { Layout.fillWidth: true enabled: _activeVehicle && !_syncDropDownController.syncInProgress onClicked: { - syncButton.hideDropDown() + dropPanel.hide() _syncDropDownController.sendToVehicle() } } @@ -1046,7 +1013,7 @@ QGCView { Layout.fillWidth: true enabled: _activeVehicle && !_syncDropDownController.syncInProgress onClicked: { - syncButton.hideDropDown() + dropPanel.hide() if (_syncDropDownController.dirty) { qgcView.showDialog(syncLoadFromVehicleOverwrite, columnHolder._overwriteText, qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel) } else { @@ -1060,7 +1027,7 @@ QGCView { Layout.fillWidth: true enabled: !_syncDropDownController.syncInProgress onClicked: { - syncButton.hideDropDown() + dropPanel.hide() _syncDropDownController.saveToSelectedFile() } } @@ -1070,7 +1037,7 @@ QGCView { Layout.fillWidth: true enabled: !_syncDropDownController.syncInProgress onClicked: { - syncButton.hideDropDown() + dropPanel.hide() if (_syncDropDownController.dirty) { qgcView.showDialog(syncLoadFromFileOverwrite, columnHolder._overwriteText, qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel) } else { @@ -1083,11 +1050,47 @@ QGCView { text: qsTr("Remove All") Layout.fillWidth: true onClicked: { - syncButton.hideDropDown() + dropPanel.hide() qgcView.showDialog(removeAllPromptDialog, qsTr("Remove all"), qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.No) } } } } } + + Component { + id: centerMapDropPanel + + CenterMapDropPanel { + map: editorMap + fitFunctions: mapFitFunctions + } + } + + Component { + id: mapTypeDropPanel + + Column { + spacing: _margin + + QGCLabel { text: qsTr("Map type:") } + Row { + spacing: ScreenTools.defaultFontPixelWidth + Repeater { + model: QGroundControl.flightMapSettings.mapTypes + + QGCButton { + checkable: true + checked: QGroundControl.flightMapSettings.mapType === text + text: modelData + exclusiveGroup: _mapTypeButtonsExclusiveGroup + onClicked: { + QGroundControl.flightMapSettings.mapType = text + dropPanel.hide() + } + } + } + } + } + } } // QGCVIew diff --git a/src/QmlControls/DropPanel.qml b/src/QmlControls/DropPanel.qml new file mode 100644 index 0000000000000000000000000000000000000000..2dc61a3a0dc51e941e291949d2797b752b569440 --- /dev/null +++ b/src/QmlControls/DropPanel.qml @@ -0,0 +1,162 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +import QtQuick 2.4 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 + +import QGroundControl 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Palette 1.0 + +Item { + id: _root + z: QGroundControl.zOrderWidgets + visible: false + + signal clicked() + property real radius: ScreenTools.isMobile ? ScreenTools.defaultFontPixelHeight * 1.75 : ScreenTools.defaultFontPixelHeight * 1.25 + property real viewportMargins: 0 + property real topMargin: parent.height - ScreenTools.availableHeight + property var toolStrip + + + width: radius * 2 + height: radius * 2 + + // Should be an enum but that get's into the whole problem of creating a singleton which isn't worth the effort + readonly property int dropLeft: 1 + readonly property int dropRight: 2 + readonly property int dropUp: 3 + readonly property int dropDown: 4 + + readonly property real _arrowBaseWidth: radius // Width of long side of arrow + readonly property real _arrowPointHeight: radius * 0.666 // Height is long side to point + readonly property real _dropCornerRadius: ScreenTools.defaultFontPixelWidth * 0.5 + readonly property real _dropCornerRadiusX2: _dropCornerRadius * 2 + readonly property real _dropMargin: _dropCornerRadius + readonly property real _dropMarginX2: _dropMargin * 2 + + property var _dropEdgeTopPoint + property real _dropEdgeHeight + property alias _dropDownComponent: dropDownLoader.sourceComponent + property real _viewportMaxLeft: -x + viewportMargins + property real _viewportMaxRight: parent.width - (viewportMargins * 2) - x + property real _viewportMaxTop: -y + viewportMargins + topMargin + property real _viewportMaxBottom: parent.height - (viewportMargins * 2) - y + + function show(panelEdgeTopPoint, panelEdgeHeight, panelComponent) { + _dropEdgeTopPoint = panelEdgeTopPoint + _dropEdgeHeight = panelEdgeHeight + _dropDownComponent = panelComponent + _calcPositions() + visible = true + } + + function hide() { + if (visible) { + visible = false + _dropDownComponent = undefined + toolStrip.uncheckAll() + } + } + + function _calcPositions() { + var dropComponentWidth = dropDownLoader.item.width + var dropComponentHeight = dropDownLoader.item.height + var dropRectWidth = dropComponentWidth + _dropMarginX2 + var dropRectHeight = dropComponentHeight + _dropMarginX2 + + dropItemHolderRect.width = dropRectWidth + dropItemHolderRect.height = dropRectHeight + + dropDownItem.width = dropComponentWidth + _dropMarginX2 + dropDownItem.height = dropComponentHeight + _dropMarginX2 + + dropDownItem.width += _arrowPointHeight + + dropDownItem.y = _dropEdgeTopPoint.y -(dropDownItem.height / 2) + radius + + dropItemHolderRect.y = 0 + + dropDownItem.x = _dropEdgeTopPoint.x + _dropMargin + dropItemHolderRect.x = _arrowPointHeight + + // Validate that dropdown is within viewport + dropDownItem.y = Math.max(dropDownItem.y, _viewportMaxTop) + dropDownItem.y = Math.min(dropDownItem.y + dropDownItem.height, _viewportMaxBottom) - dropDownItem.height + + // Arrow points + arrowCanvas.arrowPoint.y = (_dropEdgeTopPoint.y + radius) - dropDownItem.y + arrowCanvas.arrowPoint.x = 0 + arrowCanvas.arrowBase1.x = _arrowPointHeight + arrowCanvas.arrowBase1.y = arrowCanvas.arrowPoint.y - (_arrowBaseWidth / 2) + arrowCanvas.arrowBase2.x = arrowCanvas.arrowBase1.x + arrowCanvas.arrowBase2.y = arrowCanvas.arrowBase1.y + _arrowBaseWidth + arrowCanvas.requestPaint() + } // function - _calcPositions + + QGCPalette { id: qgcPal } + + /* + MouseArea { + x: _viewportMaxLeft + y: _viewportMaxTop + width: _viewportMaxRight -_viewportMaxLeft + height: _viewportMaxBottom - _viewportMaxTop + visible: checked + onClicked: { + checked = false + _root.clicked() + } + }*/ + + Item { + id: dropDownItem + + Canvas { + id: arrowCanvas + anchors.fill: parent + + property var arrowPoint: Qt.point(0, 0) + property var arrowBase1: Qt.point(0, 0) + property var arrowBase2: Qt.point(0, 0) + + onPaint: { + var context = getContext("2d") + context.reset() + context.beginPath() + + context.moveTo(dropItemHolderRect.x, dropItemHolderRect.y) + context.lineTo(dropItemHolderRect.x + dropItemHolderRect.width, dropItemHolderRect.y) + context.lineTo(dropItemHolderRect.x + dropItemHolderRect.width, dropItemHolderRect.y + dropItemHolderRect.height) + context.lineTo(dropItemHolderRect.x, dropItemHolderRect.y + dropItemHolderRect.height) + context.lineTo(arrowBase2.x, arrowBase2.y) + context.lineTo(arrowPoint.x, arrowPoint.y) + context.lineTo(arrowBase1.x, arrowBase1.y) + context.lineTo(dropItemHolderRect.x, dropItemHolderRect.y) + context.closePath() + context.fillStyle = qgcPal.windowShade + context.fill() + } + } // Canvas - arrowCanvas + + Item { + id: dropItemHolderRect + + Loader { + id: dropDownLoader + x: _dropMargin + y: _dropMargin + + property var dropPanel: _root + } + } + } // Item - dropDownItem +} diff --git a/src/QmlControls/QGroundControl.Controls.qmldir b/src/QmlControls/QGroundControl.Controls.qmldir index c33e30fea26a5662d53bfb26b5d225ceb9165072..eed1fdc475d98f89546f56c0af12483f07a7bb38 100644 --- a/src/QmlControls/QGroundControl.Controls.qmldir +++ b/src/QmlControls/QGroundControl.Controls.qmldir @@ -4,6 +4,7 @@ AnalyzePage 1.0 AnalyzePage.qml AppMessages 1.0 AppMessages.qml ClickableColor 1.0 ClickableColor.qml DropButton 1.0 DropButton.qml +DropPanel 1.0 DropPanel.qml ExclusiveGroupItem 1.0 ExclusiveGroupItem.qml FactSliderPanel 1.0 FactSliderPanel.qml FlightModeDropdown 1.0 FlightModeDropdown.qml @@ -49,6 +50,7 @@ SetupPage 1.0 SetupPage.qml SignalStrength 1.0 SignalStrength.qml SliderSwitch 1.0 SliderSwitch.qml SubMenuButton 1.0 SubMenuButton.qml +ToolStrip 1.0 ToolStrip.qml VehicleRotationCal 1.0 VehicleRotationCal.qml VehicleSummaryRow 1.0 VehicleSummaryRow.qml ViewWidget 1.0 ViewWidget.qml diff --git a/src/QmlControls/ToolStrip.qml b/src/QmlControls/ToolStrip.qml new file mode 100644 index 0000000000000000000000000000000000000000..9c2e7c3ec88de0951c123d50abe702d9bc70b8b7 --- /dev/null +++ b/src/QmlControls/ToolStrip.qml @@ -0,0 +1,183 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +import QtQuick 2.4 +import QtQuick.Controls 1.2 + +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Palette 1.0 + +Rectangle { + id: _root + color: qgcPal.window + width: ScreenTools.defaultFontPixelWidth * 6 + height: buttonStripColumn.height + (buttonStripColumn.anchors.margins * 2) + radius: _radius + + property string title: "Title" + property alias model: repeater.model + property var showAlternateIcon + property var rotateImage + property var buttonEnabled + + signal clicked(int index, bool checked) + + readonly property real _radius: ScreenTools.defaultFontPixelWidth / 2 + readonly property real _margin: ScreenTools.defaultFontPixelWidth / 2 + readonly property real _buttonSpacing: ScreenTools.defaultFontPixelWidth + + ExclusiveGroup { + id: dropButtonsExclusiveGroup + } + + function uncheckAll() { + dropButtonsExclusiveGroup.current = null + // Signal all toggles as off + for (var i=0; i<model.length; i++) { + if (model[i].toggleButton === true) { + clicked(index, false) + } + } + } + + MouseArea { + x: -_root.x + y: -_root.y + width: _root.parent.width + height: _root.parent.height + visible: dropPanel.visible + onClicked: dropPanel.hide() + } + + Column { + id: buttonStripColumn + anchors.margins: ScreenTools.defaultFontPixelWidth / 2 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + QGCLabel { + anchors.horizontalCenter: parent.horizontalCenter + text: title + } + + Item { width: 1; height: _buttonSpacing } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: qgcPal.text + } + + Repeater { + id: repeater + + delegate: Column { + id: buttonColumn + width: buttonStripColumn.width + + property bool checked: false + property ExclusiveGroup exclusiveGroup: dropButtonsExclusiveGroup + + property var _iconSource: modelData.iconSource + property var _alternateIconSource: modelData.alternateIconSource + property var _source: _root.showAlternateIcon[index] ? _alternateIconSource : _iconSource + property bool rotateImage: _root.rotateImage[index] + + onExclusiveGroupChanged: { + if (exclusiveGroup) { + exclusiveGroup.bindCheckable(buttonColumn) + } + } + + onRotateImageChanged: { + if (rotateImage) { + imageRotation.running = true + } else { + imageRotation.running = false + button.rotation = 0 + } + } + + Item { width: 1; height: _buttonSpacing } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: width + color: checked ? qgcPal.buttonHighlight : qgcPal.button + + QGCColoredImage { + id: button + anchors.fill: parent + source: _source + sourceSize.height: parent.height + fillMode: Image.PreserveAspectFit + mipmap: true + smooth: true + color: checked ? qgcPal.buttonHighlightText : qgcPal.buttonText + + RotationAnimation on rotation { + id: imageRotation + loops: Animation.Infinite + from: 0 + to: 360 + duration: 500 + running: false + } + + } + + MouseArea { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: parent.height + buttonLabel.height + buttonColumn.spacing + visible: _root.buttonEnabled[index] + + onClicked: { + if (modelData.dropPanelComponent === undefined) { + dropPanel.hide() + if (modelData.toggle === true) { + checked = !checked + } else { + // dropPanel.hide above will close panel, but we need to do this to clear toggles + uncheckAll() + } + _root.clicked(index, checked) + } else { + if (checked) { + dropPanel.hide() // hide affects checked, so this needs to be duplicated inside not outside if + } else { + dropPanel.hide() // hide affects checked, so this needs to be duplicated inside not outside if + checked = true + var panelEdgeTopPoint = mapToItem(_root, width, 0) + dropPanel.show(panelEdgeTopPoint, height, modelData.dropPanelComponent) + } + } + } + } + } + + QGCLabel { + id: buttonLabel + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: ScreenTools.smallFontPointSize + text: modelData.name + } + } + } + } + + DropPanel { + id: dropPanel + toolStrip: _root + } +}