mirror of
https://github.com/Serial-Studio/Serial-Studio.git
synced 2025-01-31 17:42:55 +08:00
403 lines
9.5 KiB
QML
403 lines
9.5 KiB
QML
/*
|
|
* Copyright (c) 2024 Alex Spataru <https://github.com/alex-spataru>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
import QtQuick
|
|
import QtQuick.Effects
|
|
import QtQuick.Layouts
|
|
import QtQuick.Controls
|
|
|
|
import SerialStudio
|
|
|
|
Item {
|
|
id: root
|
|
|
|
//
|
|
// Widget data inputs
|
|
//
|
|
property color color
|
|
property GyroscopeModel model: GyroscopeModel {}
|
|
|
|
//
|
|
// Custom properties
|
|
//
|
|
property real yawAngle: root.model.yaw
|
|
property real rollAngle: root.model.roll
|
|
property real pitchAngle: root.model.pitch
|
|
|
|
//
|
|
// Animations
|
|
//
|
|
Behavior on yawAngle {NumberAnimation{}}
|
|
Behavior on rollAngle {NumberAnimation{}}
|
|
Behavior on pitchAngle {NumberAnimation{}}
|
|
|
|
//
|
|
// Artificial horizon
|
|
//
|
|
Item {
|
|
id: artificialHorizon
|
|
|
|
antialiasing: true
|
|
anchors.fill: parent
|
|
anchors.margins: -root.height / 2
|
|
|
|
Rectangle {
|
|
id: sky
|
|
antialiasing: true
|
|
anchors.fill: parent
|
|
anchors.topMargin: -1 * parent.height
|
|
gradient: Gradient {
|
|
GradientStop {
|
|
position: 0
|
|
color: "#5759A6"
|
|
}
|
|
|
|
GradientStop {
|
|
position: 1
|
|
color: "#7A7BBB"
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
antialiasing: true
|
|
height: parent.height * 1.5
|
|
anchors {
|
|
left: sky.left
|
|
right: sky.right
|
|
bottom: sky.bottom
|
|
bottomMargin: -parent.height
|
|
}
|
|
|
|
gradient: Gradient {
|
|
GradientStop {
|
|
position: 1
|
|
color: "#672122"
|
|
}
|
|
|
|
GradientStop {
|
|
position: 0
|
|
color: "#A63732"
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
height: 2
|
|
color: "#fff"
|
|
antialiasing: true
|
|
anchors {
|
|
top: parent.top
|
|
left: parent.left
|
|
right: parent.right
|
|
}
|
|
}
|
|
}
|
|
|
|
transform: [
|
|
Translate {
|
|
y: root.pitchAngle * root.height / 45
|
|
},
|
|
Rotation {
|
|
angle: -root.rollAngle
|
|
origin.x: artificialHorizon.width / 2
|
|
origin.y: artificialHorizon.height / 2
|
|
}
|
|
]
|
|
}
|
|
|
|
//
|
|
// Pitch reticles
|
|
//
|
|
Item {
|
|
id: pitchIndicator
|
|
|
|
antialiasing: true
|
|
height: root.height - 4 - angles.height
|
|
|
|
anchors {
|
|
left: parent.left
|
|
right: parent.right
|
|
}
|
|
|
|
readonly property var reticleHeight: Math.max(16, pitchIndicator.height / 20)
|
|
|
|
Item {
|
|
antialiasing: true
|
|
width: parent.width
|
|
height: parent.height
|
|
|
|
Column {
|
|
id: column
|
|
spacing: -4
|
|
antialiasing: true
|
|
anchors.centerIn: parent
|
|
|
|
Repeater {
|
|
model: 37
|
|
delegate: Item {
|
|
id: reticle
|
|
antialiasing: true
|
|
width: pitchIndicator.width
|
|
height: pitchIndicator.reticleHeight
|
|
|
|
opacity: {
|
|
var reticleY = reticle.mapToItem(rollDial, 0, 0).y;
|
|
var distance = Math.abs(reticleY - rollDial.height / 2);
|
|
var fadeDistance = rollDial.height / 2;
|
|
return Math.max(0, Math.min(1, (fadeDistance - distance) / fadeDistance));
|
|
}
|
|
|
|
onVisibleChanged: {
|
|
var reticleY = reticle.mapToItem(rollDial, 0, 0).y;
|
|
var distance = Math.abs(reticleY - rollDial.height / 2);
|
|
var fadeDistance = rollDial.height / 2;
|
|
opacity = Math.max(0, Math.min(1, (fadeDistance - distance) / fadeDistance));
|
|
}
|
|
|
|
onYChanged: {
|
|
var reticleY = reticle.mapToItem(rollDial, 0, 0).y;
|
|
var distance = Math.abs(reticleY - rollDial.height / 2);
|
|
var fadeDistance = rollDial.height / 2;
|
|
opacity = Math.max(0, Math.min(1, (fadeDistance - distance) / fadeDistance));
|
|
}
|
|
|
|
visible: Math.abs(pitch - root.pitchAngle) <= 20
|
|
readonly property int pitch: -(index * 5 - 90)
|
|
|
|
Rectangle {
|
|
id: _line
|
|
height: 2
|
|
color: "#fff"
|
|
antialiasing: true
|
|
anchors.centerIn: parent
|
|
opacity: (reticle.pitch % 10) === 0 ? 1 : 0.8
|
|
width: (reticle.pitch % 10) === 0 ? pitchIndicator.width / 4 :
|
|
pitchIndicator.width / 8
|
|
}
|
|
|
|
Label {
|
|
id: leftLabel
|
|
color: "#fff"
|
|
antialiasing: true
|
|
text: reticle.pitch
|
|
anchors.centerIn: parent
|
|
font: Cpp_Misc_CommonFonts.monoFont
|
|
opacity: root.height >= 120 ? 1 : 0
|
|
visible: (reticle.pitch != 0) && (reticle.pitch % 10) === 0
|
|
anchors.horizontalCenterOffset: - 1 * (_line.width + 32) / 2
|
|
}
|
|
|
|
Label {
|
|
color: "#fff"
|
|
antialiasing: true
|
|
text: reticle.pitch
|
|
anchors.centerIn: parent
|
|
font: Cpp_Misc_CommonFonts.monoFont
|
|
opacity: root.height >= 120 ? 1 : 0
|
|
anchors.horizontalCenterOffset: (_line.width + 32) / 2
|
|
visible: (reticle.pitch != 0) && (reticle.pitch % 10) === 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
transform: [Translate {
|
|
y: (root.pitchAngle * pitchIndicator.reticleHeight / 5) + (pitchIndicator.reticleHeight / 2)
|
|
}
|
|
]
|
|
}
|
|
|
|
transform: [
|
|
Rotation {
|
|
angle: -root.rollAngle
|
|
origin.x: root.width / 2
|
|
origin.y: root.height / 2
|
|
}
|
|
]
|
|
}
|
|
|
|
//
|
|
// Angles indicator
|
|
//
|
|
Item {
|
|
id: angles
|
|
visible: root.height >= 120
|
|
|
|
width: parent.width
|
|
height: visible ? 24 : 0
|
|
|
|
anchors {
|
|
bottomMargin: 4
|
|
left: parent.left
|
|
right: parent.right
|
|
bottom: parent.bottom
|
|
}
|
|
|
|
RowLayout {
|
|
spacing: 4
|
|
anchors.fill: parent
|
|
|
|
Item {
|
|
implicitWidth: 4
|
|
}
|
|
|
|
Rectangle {
|
|
color: "#333"
|
|
border.width: 1
|
|
implicitHeight: 24
|
|
border.color: "#fff"
|
|
Layout.fillWidth: true
|
|
|
|
Text {
|
|
color: "#ffffff"
|
|
elide: Qt.ElideLeft
|
|
anchors.fill: parent
|
|
verticalAlignment: Qt.AlignVCenter
|
|
font: Cpp_Misc_CommonFonts.monoFont
|
|
horizontalAlignment: Qt.AlignHCenter
|
|
text: qsTr("Roll: %1").arg(root.rollAngle.toFixed(2) + "°")
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
color: "#333"
|
|
border.width: 1
|
|
implicitHeight: 24
|
|
border.color: "#fff"
|
|
Layout.fillWidth: true
|
|
|
|
Text {
|
|
color: "#ffffff"
|
|
elide: Qt.ElideLeft
|
|
anchors.fill: parent
|
|
verticalAlignment: Qt.AlignVCenter
|
|
font: Cpp_Misc_CommonFonts.monoFont
|
|
horizontalAlignment: Qt.AlignHCenter
|
|
text: qsTr("Yaw: %1").arg(root.yawAngle.toFixed(2) + "°")
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
color: "#333"
|
|
border.width: 1
|
|
implicitHeight: 24
|
|
border.color: "#fff"
|
|
Layout.fillWidth: true
|
|
|
|
Text {
|
|
color: "#ffffff"
|
|
elide: Qt.ElideLeft
|
|
anchors.fill: parent
|
|
verticalAlignment: Qt.AlignVCenter
|
|
font: Cpp_Misc_CommonFonts.monoFont
|
|
horizontalAlignment: Qt.AlignHCenter
|
|
text: qsTr("Pitch: %1").arg(root.pitchAngle.toFixed(2) + "°")
|
|
}
|
|
}
|
|
|
|
Item {
|
|
implicitWidth: 4
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Roll dial visual effects
|
|
//
|
|
MultiEffect {
|
|
blur: 1
|
|
blurMax: 64
|
|
brightness: 0.6
|
|
saturation: 0.1
|
|
blurEnabled: true
|
|
source: rollDial
|
|
anchors.fill: rollDial
|
|
}
|
|
|
|
//
|
|
// Roll pointer visual effects
|
|
//
|
|
MultiEffect {
|
|
blur: 1
|
|
blurMax: 64
|
|
brightness: 0.6
|
|
saturation: 0.1
|
|
blurEnabled: true
|
|
source: rollPointer
|
|
anchors.fill: rollPointer
|
|
}
|
|
|
|
//
|
|
// Cross hair visual effects
|
|
//
|
|
MultiEffect {
|
|
blur: 1
|
|
blurMax: 64
|
|
brightness: 0.6
|
|
saturation: 0.1
|
|
blurEnabled: true
|
|
source: crosshair
|
|
anchors.fill: crosshair
|
|
}
|
|
|
|
|
|
//
|
|
// Roll dial
|
|
//
|
|
Image {
|
|
id: rollDial
|
|
anchors.fill: parent
|
|
sourceSize.height: parent.height
|
|
fillMode: Image.PreserveAspectFit
|
|
source: "qrc:/rcc/instruments/attitude_dial.svg"
|
|
|
|
transform: Rotation {
|
|
angle: -root.rollAngle
|
|
origin.x: root.width / 2
|
|
origin.y: root.height / 2
|
|
}
|
|
}
|
|
|
|
//
|
|
// Roll pointer
|
|
//
|
|
Image {
|
|
id: rollPointer
|
|
anchors.fill: parent
|
|
sourceSize.height: parent.height
|
|
fillMode: Image.PreserveAspectFit
|
|
source: "qrc:/rcc/instruments/attitude_pointer.svg"
|
|
}
|
|
|
|
//
|
|
// Crosshair
|
|
//
|
|
Image {
|
|
id: crosshair
|
|
sourceSize.width: width
|
|
anchors.centerIn: parent
|
|
width: parent.width * 0.75
|
|
fillMode: Image.PreserveAspectFit
|
|
source: "qrc:/rcc/instruments/attitude_crosshair.svg"
|
|
}
|
|
}
|