Warning, /utilities/kirogi/src/ui/FlightControls.qml is written in an unsupported language. File is not indexed.

0001 /*
0002  * Copyright 2019 Eike Hein <hein@kde.org>
0003  *
0004  * This program is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU General Public License as
0006  * published by the Free Software Foundation; either version 2 of
0007  * the License or (at your option) version 3 or any later version
0008  * accepted by the membership of KDE e.V. (or its successor approved
0009  * by the membership of KDE e.V.), which shall act as a proxy
0010  * defined in Section 14 of version 3 of the license.
0011  *
0012  * This program is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015  * GNU General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU General Public License
0018  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0019  */
0020 
0021 import QtQuick 2.12
0022 import QtQuick.Controls 2.12 as QQC2
0023 
0024 import org.kde.kirigami 2.6 as Kirigami
0025 
0026 import org.kde.kirogi 0.1 as Kirogi
0027 import org.kde.kirogi.video 0.1 as KirogiVideo
0028 
0029 Kirigami.Page {
0030     id: page
0031 
0032     LayoutMirroring.enabled: false
0033     LayoutMirroring.childrenInherit: true
0034 
0035     readonly property int yardstick: Math.min(parent.width, parent.height)
0036     readonly property bool touched: leftTouchPoint.active || rightTouchPoint.active
0037 
0038     property alias gamepad: gamepadLoader.item
0039 
0040     leftPadding: 0
0041     rightPadding: 0
0042     topPadding: 0
0043     bottomPadding: 0
0044 
0045     Connections {
0046         target: kirogi
0047 
0048         onCurrentPageChanged: updatePilotingState()
0049         onCurrentVehicleChanged: updatePilotingState()
0050         onReadyChanged: updatePilotingState()
0051     }
0052 
0053     function updatePilotingState() {
0054         var vehicle = kirogi.currentVehicle;
0055 
0056         if (!vehicle || !vehicle.ready) {
0057             return;
0058         }
0059 
0060         vehicle.setPiloting(kirogi.currentPage == page);
0061 
0062         if (kirogi.currentPage == page && !kirogi.currentVehicle.videoStreamEnabled
0063             && vehicle.isActionSupported(Kirogi.AbstractVehicle.ToggleVideoStream)) {
0064             vehicle.requestEnableVideoStream(true);
0065         }
0066     }
0067 
0068     function setNewStickPosition() {
0069         if (kirogi.currentVehicle) {
0070             kirogi.currentVehicle.pilot(rightDPad.axisX, rightDPad.axisY, leftDPad.axisX, leftDPad.axisY);
0071         }
0072     }
0073 
0074     VideoElement {
0075         anchors.fill: parent
0076     }
0077 
0078     // FIXME TODO: This is a workaround around the org.kde.desktop+Breeze style engine
0079     // hijacking drag on the window.
0080     TapHandler {
0081         enabled: !Kirigami.Settings.isMobile
0082     }
0083 
0084     Item {
0085         anchors.left: parent.left
0086 
0087         width: parent.width / 2
0088         height: parent.height
0089 
0090 
0091         PointHandler {
0092             id: leftTouchPoint
0093 
0094             enabled: inputMode.selectedMode == 0 || kirogiSettings.alwaysShowDPads
0095 
0096             grabPermissions: PointerHandler.ApprovesTakeOverByAnything | PointerHandler.ApprovesCancellation
0097         }
0098     }
0099 
0100     Item {
0101         anchors.right: parent.right
0102 
0103         width: parent.width / 2
0104         height: parent.height
0105 
0106         PointHandler {
0107             id: rightTouchPoint
0108 
0109             enabled: inputMode.selectedMode == 0 || kirogiSettings.alwaysShowDPads
0110         }
0111     }
0112 
0113     TouchButton {
0114         id: leftButton
0115 
0116         anchors.top: leftPillBox.bottom
0117         anchors.topMargin: Math.round(leftPillBox.y * 1.5)
0118         anchors.left: parent.left
0119         anchors.leftMargin: leftPillBox.y
0120 
0121         icon: kirogi.currentVehicle ? kirogi.currentVehicle.iconName : "uav"
0122         toolTipText: i18nc("%1 = Keyboard shortcut", "Drone (%1)", vehiclePageAction.shortcut)
0123 
0124         onTapped: switchApplicationPage(vehiclePage)
0125     }
0126 
0127     PillBox {
0128         id: launchButton
0129 
0130         anchors.horizontalCenter: parent.horizontalCenter
0131         anchors.top: parent.top
0132         anchors.topMargin: Kirigami.Units.smallSpacing
0133 
0134         width: Math.min(launchButtonLabel.implicitWidth + Kirigami.Units.smallSpacing * 4,
0135             rightPillBox.x - leftPillBox.x - leftPillBox.width - (leftPillBox.x * 2))
0136         height: 2 * Math.round((leftPillBox.height * 1.12) / 2);
0137 
0138         readonly property var __color: {
0139             if (launchButtonMouseArea.containsMouse) {
0140                 return Kirigami.Theme.hoverColor;
0141             }
0142 
0143             if (kirogi.connected) {
0144                 if (kirogi.flying) {
0145                     return "red";
0146                 } else if (kirogi.ready) {
0147                     return "green";
0148                 } else {
0149                     return "yellow";
0150                 }
0151             }
0152 
0153             return "red";
0154         }
0155 
0156         backgroundColor: "dark" + __color
0157         backgroundOpacity: 0.4
0158 
0159         borderWidth: 2
0160         borderRadius: height / 4
0161         borderColor: launchButtonLabel.color
0162 
0163         Text {
0164             id: launchButtonLabel
0165 
0166             anchors.fill: parent
0167 
0168             font.pixelSize: parent.height * 0.7
0169             font.bold: true
0170             color: launchButton.__color
0171 
0172             horizontalAlignment: Text.AlignHCenter
0173             verticalAlignment: Text.AlignVCenter
0174 
0175             elide: Text.ElideRight
0176 
0177             text: {
0178                 if (kirogi.connected) {
0179                     if (kirogi.flying) {
0180                         return i18n("LAND");
0181                     } else if (kirogi.ready) {
0182                         return i18n("TAKE OFF");
0183                     } else {
0184                         return i18n("PREPARING")
0185                     }
0186                 }
0187 
0188                 return i18n("DISCONNECTED");
0189             }
0190 
0191             Behavior on color {
0192                 enabled: !launchButtonMouseArea.pressed
0193 
0194                 ColorAnimation { duration: Kirigami.Units.shortDuration }
0195             }
0196         }
0197 
0198         MouseArea {
0199             id: launchButtonMouseArea
0200 
0201             anchors.fill: parent
0202 
0203             enabled: kirogi.ready
0204             hoverEnabled: enabled
0205 
0206             onClicked: {
0207                 if (kirogi.flying) {
0208                     kirogi.currentVehicle.requestLand();
0209                 } else {
0210                     kirogi.currentVehicle.requestTakeOff();
0211                 }
0212             }
0213         }
0214     }
0215 
0216     TouchButton {
0217         id: rightButton
0218 
0219         anchors.top: rightPillBox.bottom
0220         anchors.topMargin: leftButton.anchors.topMargin
0221         anchors.right: parent.right
0222         anchors.rightMargin: leftButton.anchors.leftMargin
0223 
0224         icon: "map-flat"
0225         toolTipText: i18nc("%1 = Keyboard shortcut", "Navigation Map (%1)", navigationMapPageAction.shortcut)
0226 
0227         onTapped: switchApplicationPage(navigationMapPage)
0228     }
0229 
0230     ModeRocker {
0231         id: flightMode
0232 
0233         enabled: kirogi.ready
0234 
0235         anchors.left: parent.left
0236         anchors.verticalCenter: shotButton.verticalCenter
0237 
0238         selectedMode: {
0239             if (kirogi.ready) {
0240                 if (kirogi.currentVehicle.performanceMode == Kirogi.AbstractVehicle.FilmPerformance) {
0241                     return 0;
0242                 } else if (kirogi.currentVehicle.performanceMode == Kirogi.AbstractVehicle.SportPerformance) {
0243                     return 1;
0244                 }
0245             }
0246 
0247             return -1;
0248         }
0249 
0250         firstLabelText: i18n("FILM")
0251         firstToolTipText: i18n("Fly Slow")
0252 
0253         secondLabelText: i18n("SPORT")
0254         secondToolTipText: i18n("Fly Fast")
0255 
0256         onModeTapped: {
0257             if (selectedMode == mode) {
0258                 return;
0259             }
0260 
0261             if (mode == 0) {
0262                 kirogi.currentVehicle.requestPerformanceMode(Kirogi.AbstractVehicle.FilmPerformance);
0263             } else if (mode == 1) {
0264                 kirogi.currentVehicle.requestPerformanceMode(Kirogi.AbstractVehicle.SportPerformance);
0265             }
0266         }
0267 
0268         Component.onCompleted: {
0269             var handleWidth = kirogi.LayoutMirroring.enabled ? parent.width - globalDrawer.handle.x
0270                 : globalDrawer.handle.x + globalDrawer.handle.width;
0271             anchors.leftMargin = globalDrawer.modal ? handleWidth + leftPillBox.y : leftPillBox.y;
0272         }
0273     }
0274 
0275     ModeRocker {
0276         id: inputMode
0277 
0278         visible: gamepad && gamepad.connected && !kirogiSettings.alwaysShowDPads
0279 
0280         anchors.verticalCenter: shotButton.verticalCenter
0281         anchors.left: flightMode.right
0282         anchors.leftMargin: leftPillBox.y
0283 
0284         firstLabelText: i18n("SCREEN")
0285         firstIconSource: Kirigami.Settings.isMobile ? "phone-symbolic" : "computer-symbolic"
0286         firstToolTipText: i18n("Use Virtual D-Pads")
0287 
0288         secondLabelText: i18n("CONTROLLER")
0289         secondIconSource: "folder-games-symbolic"
0290         secondToolTipText: i18n("Use Gamepad Controller")
0291 
0292         showLabels: false
0293         showIcons: true
0294 
0295         // If there is no gamepad connected, the user will not be able to change back to virtual joystick mode.
0296         // It's necessary to force the virtual joystick as default if no joystick is connected.
0297         selectedMode: gamepad.connected ? kirogiSettings.lastInputMode : 0
0298 
0299         onModeTapped: {
0300             selectedMode = mode;
0301             kirogiSettings.lastInputMode = selectedMode;
0302         }
0303 
0304     }
0305 
0306     ModeRocker {
0307         id: shotMode
0308 
0309         enabled: kirogi.ready
0310 
0311         anchors.right: shotButton.right
0312         anchors.rightMargin: shotButton.width / 2
0313         anchors.verticalCenter: shotButton.verticalCenter
0314 
0315         width: (implicitWidth + shotButton.width / 2) - Kirigami.Units.largeSpacing
0316 
0317         property int requestedMode: 0
0318 
0319         firstModeEnabled: enabled && kirogi.currentVehicle.isActionSupported(Kirogi.AbstractVehicle.RecordVideo)
0320         secondModeEnabled: enabled && kirogi.currentVehicle.isActionSupported(Kirogi.AbstractVehicle.TakePicture)
0321 
0322         firstLabelText: i18n("VIDEO")
0323         firstIconSource: "emblem-videos-symbolic"
0324         firstToolTipText: i18n("Record Videos")
0325 
0326         secondLabelText: i18n("PHOTO")
0327         secondIconSource: "emblem-photos-symbolic"
0328         secondToolTipText: i18n("Take Photos")
0329 
0330         showLabels: false
0331         showIcons: true
0332 
0333         selectedMode: kirogi.ready ? requestedMode : -1
0334 
0335         onModeTapped: requestedMode = mode
0336     }
0337 
0338     TouchButton {
0339         id: shotButton
0340 
0341         enabled: {
0342             if (!kirogi.ready) {
0343                 return false;
0344             }
0345 
0346             if ((shotMode.selectedMode == 0 && !kirogi.currentVehicle.isActionSupported(Kirogi.AbstractVehicle.RecordVideo))
0347                 || (shotMode.selectedMode == 1 && (!kirogi.currentVehicle.isActionSupported(Kirogi.AbstractVehicle.TakePicture)
0348                 || !kirogi.currentVehicle.canTakePicture))) {
0349                 return false;
0350             }
0351 
0352             return true;
0353         }
0354 
0355         anchors.right: parent.right
0356         anchors.rightMargin: flightMode.anchors.leftMargin
0357         anchors.bottom: parent.bottom
0358         anchors.bottomMargin: launchButton.anchors.topMargin
0359 
0360         icon: "media-record-symbolic"
0361         iconColor: shotMode.selectedMode == 0 && (kirogi.currentVehicle && kirogi.currentVehicle.isRecordingVideo) ? "red" : "white"
0362         toolTipText: {
0363             if (shotMode.selectedMode) {
0364                 return i18n("Take Photo");
0365             } else if (kirogi.currentVehicle && kirogi.currentVehicle.isRecordingVideo) {
0366                 return i18n("Stop Recording Video");
0367             }
0368 
0369             return i18n("Record Video");
0370         }
0371 
0372         onTapped: {
0373             if (!kirogi.ready) {
0374                 return;
0375             }
0376 
0377             if (shotMode.selectedMode == 0) {
0378                 kirogi.currentVehicle.requestAction(Kirogi.AbstractVehicle.RecordVideo);
0379             } else if (shotMode.selectedMode == 1) {
0380                 kirogi.currentVehicle.requestAction(Kirogi.AbstractVehicle.TakePicture);
0381             }
0382         }
0383     }
0384 
0385     TouchDPad {
0386         id: leftDPad
0387 
0388         visible: inputMode.selectedMode == 0 || kirogiSettings.alwaysShowDPads || (gamepad && !gamepad.connected)
0389 
0390         anchors.left: parent.left
0391         anchors.leftMargin: yardstick * 0.18
0392         anchors.bottom: parent.bottom
0393         anchors.bottomMargin: yardstick * 0.20
0394 
0395         width: Math.min(yardstick * 0.45, parent.width / 4)
0396         height: width
0397 
0398         leftIcon: "edit-undo"
0399         leftToolTipText: i18n("Turn Left")
0400         rightIcon: "edit-redo"
0401         rightToolTipText: i18n("Turn Right")
0402         topIcon: "arrow-up"
0403         topToolTipText: i18n("Move Up")
0404         bottomIcon: "arrow-down"
0405         bottomToolTipText: i18n("Move Down")
0406 
0407         onXChanged: moved = aboutToMove
0408 
0409         onAxisXChanged: setNewStickPosition()
0410         onAxisYChanged: setNewStickPosition()
0411 
0412         touchPos: {
0413             if (moved && leftTouchPoint) {
0414                 var xDifference = 0;
0415 
0416                 if (leftTouchPoint.point.scenePosition.x > leftTouchPoint.point.scenePressPosition.x) {
0417                     xDifference = xDifference + Math.abs(leftTouchPoint.point.scenePressPosition.x - leftTouchPoint.point.scenePosition.x);
0418                 } else {
0419                     xDifference = xDifference - Math.abs(leftTouchPoint.point.scenePressPosition.x - leftTouchPoint.point.scenePosition.x)
0420                 }
0421 
0422                 var x = leftDPad.x + leftDPad.width / 2 + xDifference;
0423 
0424                 var yDifference = 0;
0425 
0426                 if (leftTouchPoint.point.scenePosition.y > leftTouchPoint.point.scenePressPosition.y) {
0427                     yDifference = yDifference + Math.abs(leftTouchPoint.point.scenePressPosition.y - leftTouchPoint.point.scenePosition.y);
0428                 } else {
0429                     yDifference = yDifference - Math.abs(leftTouchPoint.point.scenePressPosition.y - leftTouchPoint.point.scenePosition.y)
0430                 }
0431 
0432                 var y = leftDPad.y + leftDPad.height / 2 + yDifference;
0433 
0434                 return parent.mapToItem(leftDPad, x, y);
0435             }
0436 
0437             return null;
0438         }
0439 
0440         states: [
0441             State {
0442                 name: "inactive"
0443 
0444                 AnchorChanges {
0445                     target: leftDPad
0446 
0447                     anchors.left: parent.left
0448                     anchors.bottom: parent.bottom
0449                 }
0450 
0451                 PropertyChanges {
0452                     target: leftDPad
0453 
0454                     aboutToMove: false
0455                     moved: false
0456                 }
0457             },
0458             State {
0459                 name: "active"
0460 
0461                 when: leftTouchPoint.active
0462 
0463                 AnchorChanges {
0464                     target: leftDPad
0465 
0466                     anchors.left: undefined
0467                     anchors.bottom: undefined
0468                 }
0469 
0470                 PropertyChanges {
0471                     target: leftDPad
0472 
0473                     aboutToMove: true
0474                     x: Math.min((parent.width / 2) - width, Math.max(0, leftTouchPoint.point.scenePressPosition.x - width / 2))
0475                     y: Math.min(parent.height - height, Math.max(0, leftTouchPoint.point.scenePressPosition.y - height / 2))
0476                 }
0477             }
0478         ]
0479     }
0480 
0481     TouchDPad {
0482         id: rightDPad
0483 
0484         visible: leftDPad.visible
0485 
0486         width: leftDPad.height
0487         height: width
0488 
0489         anchors.right: parent.right
0490         anchors.rightMargin: leftDPad.anchors.leftMargin
0491         anchors.bottom: parent.bottom
0492         anchors.bottomMargin: leftDPad.anchors.bottomMargin
0493 
0494         leftIcon: "go-previous"
0495         leftToolTipText: i18n("Move Left")
0496         rightIcon: "go-next"
0497         rightToolTipText: i18n("Move Right")
0498         topIcon: "go-up"
0499         topToolTipText: i18n("Move Forward")
0500         bottomIcon: "go-down"
0501         bottomToolTipText: i18n("Move Backward")
0502 
0503         onXChanged: moved = aboutToMove
0504 
0505         onAxisXChanged: setNewStickPosition()
0506         onAxisYChanged: setNewStickPosition()
0507 
0508         touchPos: {
0509             if (moved && rightTouchPoint.active) {
0510                 var xDifference = 0;
0511 
0512                 if (rightTouchPoint.point.scenePosition.x > rightTouchPoint.point.scenePressPosition.x) {
0513                     xDifference = xDifference + Math.abs(rightTouchPoint.point.scenePressPosition.x - rightTouchPoint.point.scenePosition.x);
0514                 } else {
0515                     xDifference = xDifference - Math.abs(rightTouchPoint.point.scenePressPosition.x - rightTouchPoint.point.scenePosition.x)
0516                 }
0517 
0518                 var x = rightDPad.x + rightDPad.width / 2 + xDifference;
0519 
0520                 var yDifference = 0;
0521 
0522                 if (rightTouchPoint.point.scenePosition.y > rightTouchPoint.point.scenePressPosition.y) {
0523                     yDifference = yDifference + Math.abs(rightTouchPoint.point.scenePressPosition.y - rightTouchPoint.point.scenePosition.y);
0524                 } else {
0525                     yDifference = yDifference - Math.abs(rightTouchPoint.point.scenePressPosition.y - rightTouchPoint.point.scenePosition.y)
0526                 }
0527 
0528                 var y = rightDPad.y + rightDPad.height / 2 + yDifference;
0529 
0530                 return parent.mapToItem(rightDPad, x, y);
0531             }
0532 
0533             return null;
0534         }
0535 
0536         states: [
0537             State {
0538                 name: "inactive"
0539 
0540                 AnchorChanges {
0541                     target: rightDPad
0542 
0543                     anchors.left: parent.right
0544                     anchors.bottom: parent.bottom
0545                 }
0546 
0547                 PropertyChanges {
0548                     target: rightDPad
0549 
0550                     aboutToMove: false
0551                     moved: false
0552                 }
0553             },
0554             State {
0555                 name: "active"
0556 
0557                 when: rightTouchPoint.active
0558                 AnchorChanges {
0559                     target: rightDPad
0560 
0561                     anchors.right: undefined
0562                     anchors.bottom: undefined
0563                 }
0564 
0565                 PropertyChanges {
0566                     target: rightDPad
0567 
0568                     aboutToMove: true
0569                     x: Math.max(parent.width / 2, Math.min(parent.width - width, rightTouchPoint.point.scenePressPosition.x - width / 2))
0570                     y: Math.min(parent.height - height, Math.max(0, rightTouchPoint.point.scenePressPosition.y - height / 2))
0571                 }
0572             }
0573         ]
0574     }
0575 
0576     PillBox {
0577         id: leftPillBox
0578 
0579         anchors.verticalCenter: launchButton.verticalCenter
0580         anchors.left: parent.left
0581         anchors.leftMargin: y
0582 
0583         width: leftPillBoxContents.implicitWidth + Kirigami.Units.largeSpacing * 4
0584         height: 2 * Math.round((Math.max(Kirigami.Units.iconSizes.small, fontMetrics.height) + Kirigami.Units.smallSpacing * 3) / 2);
0585 
0586         Row {
0587             id: leftPillBoxContents
0588 
0589             anchors.horizontalCenter: parent.horizontalCenter
0590 
0591             height: parent.height
0592 
0593             spacing: Kirigami.Units.largeSpacing
0594 
0595             Kirigami.Icon {
0596                 anchors.verticalCenter: parent.verticalCenter
0597 
0598                 width: Kirigami.Units.iconSizes.small
0599                 height: width
0600 
0601                 color: "white"
0602                 smooth: true
0603                 isMask: true
0604 
0605                 source: "speedometer"
0606             }
0607 
0608             QQC2.Label {
0609                 anchors.verticalCenter: parent.verticalCenter
0610 
0611                 width: kirogi.currentVehicle ? Math.round(Math.max(implicitWidth, fontMetrics.tightBoundingRect(i18n("%1 m/s", "0.0")).width))
0612                     : Math.round(implicitWidth)
0613 
0614                 color: "white"
0615 
0616                 horizontalAlignment: Text.AlignRight
0617 
0618                 text: {
0619                     if(kirogi.currentVehicle) {
0620                         return i18n("%1 m/s", kirogi.flying ? kirogi.currentVehicle.speed : "0");
0621                     }
0622 
0623                     return i18n("– m/s");
0624                 }
0625             }
0626 
0627             PillBoxSeparator {}
0628 
0629             Kirigami.Icon {
0630                 anchors.verticalCenter: parent.verticalCenter
0631 
0632                 width: Kirigami.Units.iconSizes.small
0633                 height: width
0634 
0635                 color: "white"
0636                 smooth: true
0637                 isMask: true
0638 
0639                 source: "kruler-west"
0640             }
0641 
0642             QQC2.Label {
0643                 anchors.verticalCenter: parent.verticalCenter
0644 
0645                 width: kirogi.ready ? Math.round(Math.max(implicitWidth, fontMetrics.tightBoundingRect(i18n("%1 m", "0.0")).width))
0646                     : Math.round(implicitWidth)
0647 
0648                 color: "white"
0649 
0650                 horizontalAlignment: Text.AlignRight
0651 
0652                 text: {
0653                     if (kirogi.currentVehicle) {
0654                         return i18n("%1 m", kirogi.currentVehicle.altitude.toFixed(2))
0655                     }
0656 
0657                     return i18n("– m");
0658                 }
0659             }
0660 
0661             PillBoxSeparator {}
0662 
0663             Kirigami.Icon {
0664                 anchors.verticalCenter: parent.verticalCenter
0665 
0666                 width: Kirigami.Units.iconSizes.small
0667                 height: width
0668 
0669                 color: "white"
0670                 smooth: true
0671                 isMask: true
0672 
0673                 source: "kruler-south"
0674             }
0675 
0676             QQC2.Label {
0677                 anchors.verticalCenter: parent.verticalCenter
0678 
0679                 width: kirogi.currentVehicle ? Math.round(Math.max(implicitWidth, fontMetrics.tightBoundingRect(i18n("%1 m", "0.0")).width))
0680                     : Math.round(implicitWidth)
0681 
0682                 color: "white"
0683 
0684                 horizontalAlignment: Text.AlignRight
0685 
0686                 text: {
0687                     if (kirogi.currentVehicle && kirogi.currentVehicle.distance >= 0) {
0688                         return i18n("%1 m", kirogi.currentVehicle.distance.toFixed(1));
0689                     }
0690 
0691                     if (kirogi.distance !== 0) {
0692                         return i18n("%1 m", kirogi.distance.toFixed(1));
0693                     }
0694 
0695                     return i18n("– m");
0696                 }
0697             }
0698         }
0699     }
0700 
0701     PillBox {
0702         id: rightPillBox
0703 
0704         anchors.verticalCenter: launchButton.verticalCenter
0705         anchors.right: parent.right
0706         anchors.rightMargin: leftPillBox.anchors.leftMargin
0707 
0708         width: rightPillBoxContents.implicitWidth + Kirigami.Units.largeSpacing * 4
0709         height: leftPillBox.height
0710 
0711        Row {
0712            id: rightPillBoxContents
0713 
0714             x: Kirigami.Units.largeSpacing
0715 
0716             anchors.horizontalCenter: parent.horizontalCenter
0717 
0718             height: parent.height
0719 
0720             spacing: Kirigami.Units.largeSpacing
0721 
0722             Kirigami.Icon {
0723                 anchors.verticalCenter: parent.verticalCenter
0724 
0725                 width: Kirigami.Units.iconSizes.small
0726                 height: width
0727 
0728                 color: "white"
0729                 smooth: true
0730                 isMask: true
0731 
0732                 source: "clock"
0733             }
0734 
0735             QQC2.Label {
0736                 anchors.verticalCenter: parent.verticalCenter
0737 
0738                 width: kirogi.currentVehicle ? Math.round(Math.max(implicitWidth, fontMetrics.tightBoundingRect(i18n("%1 m", "0:00")).width))
0739                     : Math.round(implicitWidth)
0740 
0741                 color: "white"
0742 
0743                 horizontalAlignment: Text.AlignRight
0744 
0745                 text: {
0746                     if (kirogi.ready) {
0747                         var time = kirogi.flying ? kirogi.currentVehicle.flightTime : 0;
0748                         return i18n("%1 min", (time - (time %= 60)) / 60 + (9 < time ?':':':0') + time);
0749                     }
0750 
0751                     return i18n("– min");
0752                 }
0753             }
0754 
0755             PillBoxSeparator {}
0756 
0757             Kirigami.Icon {
0758                 anchors.verticalCenter: parent.verticalCenter
0759 
0760                 width: Kirigami.Units.iconSizes.small
0761                 height: width
0762 
0763                 color: "white"
0764                 smooth: true
0765                 isMask: kirogi.currentVehicle
0766 
0767                 source: {
0768                     if (kirogi.currentVehicle) {
0769                         if (kirogi.currentVehicle.signalStrength === 0) {
0770                             return "network-wireless-connected-00";
0771                         } else if (kirogi.currentVehicle.signalStrength < 25) {
0772                             return "network-wireless-connected-25";
0773                         } else if (kirogi.currentVehicle.signalStrength < 50) {
0774                             return "network-wireless-connected-50";
0775                         } else if (kirogi.currentVehicle.signalStrength < 75) {
0776                             return "network-wireless-connected-75";
0777                         } else if (kirogi.currentVehicle.signalStrength <= 100) {
0778                             return "network-wireless-connected-100";
0779                         }
0780                     }
0781 
0782                     if (kirogi.connected) {
0783                         return "network-wireless-acquiring";
0784                     }
0785 
0786                     return "network-wireless-disconnected";
0787                 }
0788             }
0789 
0790             QQC2.Label {
0791                 anchors.verticalCenter: parent.verticalCenter
0792 
0793                 width: kirogi.currentVehicle ? Math.round(Math.max(implicitWidth, fontMetrics.tightBoundingRect(i18n("%1%", 00)).width))
0794                     : Math.round(implicitWidth)
0795 
0796                 color: "white"
0797 
0798                 horizontalAlignment: Text.AlignRight
0799 
0800                 text: kirogi.currentVehicle ? i18n("%1%", kirogi.currentVehicle.signalStrength) : i18n("N/A")
0801             }
0802 
0803             PillBoxSeparator {}
0804 
0805             Kirigami.Icon {
0806                 anchors.verticalCenter: parent.verticalCenter
0807 
0808                 width: Kirigami.Units.iconSizes.small
0809                 height: width
0810 
0811                 color: "white"
0812                 smooth: true
0813                 isMask: kirogi.currentVehicle
0814 
0815                 source: {
0816                     if (kirogi.currentVehicle) {
0817                         var roundedBatteryLevel = Math.round(kirogi.currentVehicle.batteryLevel / 10);
0818                         return "battery-" + roundedBatteryLevel.toString().padStart(2, "0") + "0";
0819                     }
0820 
0821                     return "battery-missing";
0822                 }
0823             }
0824 
0825             QQC2.Label {
0826                 anchors.verticalCenter: parent.verticalCenter
0827 
0828                 width: kirogi.currentVehicle ? Math.round(Math.max(implicitWidth, fontMetrics.tightBoundingRect(i18n("%1%", 00)).width))
0829                     : Math.round(implicitWidth)
0830 
0831                 color: "white"
0832 
0833                 horizontalAlignment: Text.AlignRight
0834 
0835                 text: kirogi.currentVehicle ?  i18n("%1%", kirogi.currentVehicle.batteryLevel) : i18n("N/A")
0836             }
0837         }
0838     }
0839 
0840     PitchBar {
0841         id: pitchBar
0842 
0843         anchors.centerIn: parent
0844 
0845         width: 25
0846         height: parent.height * 0.6
0847 
0848         pitch: kirogi.currentVehicle ? kirogi.currentVehicle.pitch * (180/Math.PI) : 0.0
0849     }
0850 
0851     VirtualHorizon {
0852         id: virtualHorizon
0853 
0854         anchors.centerIn: pitchBar
0855 
0856         width: 110
0857 
0858         roll: kirogi.currentVehicle ? kirogi.currentVehicle.roll * (180/Math.PI) : 0
0859     }
0860 
0861     YawBar {
0862         id: yawBar
0863 
0864         anchors.bottom: inputMode.visible ? inputMode.top : parent.bottom
0865         anchors.horizontalCenter: parent.horizontalCenter
0866         width: parent.width / 2
0867 
0868         tickWidth: 10
0869 
0870         yaw: kirogi.currentVehicle ? kirogi.currentVehicle.yaw * (180 / Math.PI) : 0
0871     }
0872 
0873     VehicleActionsDrawer {
0874         enabled: kirogi.currentPage == page // FIXME TODO: Why doesn't come down from page.enabled?
0875 
0876         width: Kirigami.Units.gridUnit * 19
0877 
0878         edge: kirogi.LayoutMirroring.enabled ? Qt.LeftEdge : Qt.RightEdge
0879 
0880         handleClosedIcon.source: "configure"
0881     }
0882 
0883     Loader {
0884         id: gamepadLoader
0885 
0886         source: "Gamepad.qml"
0887         asynchronous: true
0888 
0889         // FIXME TODO: QtGamepad currently causes performance problems on
0890         // Android (blocking multi-tasking) that need to be investigated.
0891         active: !Kirigami.Settings.isMobile
0892     }
0893 }