Files
volvodisplay/TemperatureGauge.qml

722 lines
24 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Shapes
Item {
id: root
width: 480
height: 480
property real currentTemp: 0 // Bound in main.qml
onCurrentTempChanged: console.log("QML Left Temp Updated: " + currentTemp)
property real currentRightTemp: 0 // Bound in main.qml
property bool startupComplete: true
// Sweep Property for Startup - REMOVED
// property real sweepTemp: -15
// Startup Sweep Animation - REMOVED
// SequentialAnimation { ... }
Component.onCompleted: {
forceActiveFocus()
root.startupComplete = true
}
Rectangle {
id: background
anchors.fill: parent
// color: "#050505" // Old solid color
// Gradient for depth (Vertical Linear)
gradient: Gradient {
GradientStop { position: 0.0; color: "black" }
GradientStop { position: 0.5; color: "#1a1a1a" } // Lighter center strip
GradientStop { position: 1.0; color: "black" }
}
}
// --- Left Gauge (Temperature) ---
Item {
id: leftGauge
anchors.fill: parent
// Track Outline
Shape {
anchors.fill: parent
// Anti-aliasing for smoother lines
// layer.enabled: true
// layer.samples: 4
// layer.smooth: true
ShapePath {
strokeColor: "white"
strokeWidth: 2
fillColor: "transparent"
capStyle: ShapePath.FlatCap
// Outer Arc (145 to 215 degrees)
// 145 deg = 0.8055 PI
// 215 deg = 1.1944 PI
startX: 240 + 210 * Math.cos(Math.PI * (145/180))
startY: 240 + 210 * Math.sin(Math.PI * (145/180))
PathArc {
x: 240 + 210 * Math.cos(Math.PI * (215/180))
y: 240 + 210 * Math.sin(Math.PI * (215/180))
radiusX: 210; radiusY: 210
useLargeArc: false
}
// Bottom Line connecting to inner
PathLine {
x: 240 + 160 * Math.cos(Math.PI * (215/180))
y: 240 + 160 * Math.sin(Math.PI * (215/180))
}
// Inner Arc
PathArc {
x: 240 + 160 * Math.cos(Math.PI * (145/180))
y: 240 + 160 * Math.sin(Math.PI * (145/180))
radiusX: 160; radiusY: 160
useLargeArc: false
direction: PathArc.Counterclockwise
}
// Top Line connecting back to start
PathLine {
x: 240 + 210 * Math.cos(Math.PI * (145/180))
y: 240 + 210 * Math.sin(Math.PI * (145/180))
}
}
}
// Indicator (Blue Bar)
Shape {
anchors.fill: parent
// layer.enabled: true
// layer.samples: 4
ShapePath {
strokeColor: "transparent"
fillColor: "#2196F3" // Blue
// Range -15 to 0.
// -15 = 145 deg
// 0 = 171.25 deg
startX: 240 + 205 * Math.cos(Math.PI * (145/180))
startY: 240 + 205 * Math.sin(Math.PI * (145/180))
PathArc {
x: 240 + 205 * Math.cos(Math.PI * (171.25/180))
y: 240 + 205 * Math.sin(Math.PI * (171.25/180))
radiusX: 205; radiusY: 205
}
PathLine {
x: 240 + 165 * Math.cos(Math.PI * (171.25/180))
y: 240 + 165 * Math.sin(Math.PI * (171.25/180))
}
PathArc {
x: 240 + 165 * Math.cos(Math.PI * (145/180))
y: 240 + 165 * Math.sin(Math.PI * (145/180))
radiusX: 165; radiusY: 165
direction: PathArc.Counterclockwise
}
PathLine {
x: 240 + 205 * Math.cos(Math.PI * (145/180))
y: 240 + 205 * Math.sin(Math.PI * (145/180))
}
}
}
// Temperature Markers & Ticks
Shape {
anchors.fill: parent
// layer.enabled: true
// layer.samples: 4
ShapePath {
strokeColor: "white"
strokeWidth: 2
fillColor: "transparent"
// -15 @ 145 deg
startX: 240 + 210 * Math.cos(Math.PI * (145/180))
startY: 240 + 210 * Math.sin(Math.PI * (145/180))
PathLine { x: 240 + 215 * Math.cos(Math.PI * (145/180)); y: 240 + 215 * Math.sin(Math.PI * (145/180)) }
// -10 @ 153.75 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (153.75/180)); y: 240 + 210 * Math.sin(Math.PI * (153.75/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (153.75/180)); y: 240 + 215 * Math.sin(Math.PI * (153.75/180)) }
// -5 @ 162.5 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (162.5/180)); y: 240 + 210 * Math.sin(Math.PI * (162.5/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (162.5/180)); y: 240 + 215 * Math.sin(Math.PI * (162.5/180)) }
// 0 @ 171.25 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (171.25/180)); y: 240 + 210 * Math.sin(Math.PI * (171.25/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (171.25/180)); y: 240 + 215 * Math.sin(Math.PI * (171.25/180)) }
// 5 @ 180 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * 1.0); y: 240 + 210 * Math.sin(Math.PI * 1.0) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * 1.0); y: 240 + 215 * Math.sin(Math.PI * 1.0) }
// 10 @ 188.75 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (188.75/180)); y: 240 + 210 * Math.sin(Math.PI * (188.75/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (188.75/180)); y: 240 + 215 * Math.sin(Math.PI * (188.75/180)) }
// 15 @ 197.5 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (197.5/180)); y: 240 + 210 * Math.sin(Math.PI * (197.5/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (197.5/180)); y: 240 + 215 * Math.sin(Math.PI * (197.5/180)) }
// 20 @ 206.25 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (206.25/180)); y: 240 + 210 * Math.sin(Math.PI * (206.25/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (206.25/180)); y: 240 + 215 * Math.sin(Math.PI * (206.25/180)) }
// 25 @ 215 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (215/180)); y: 240 + 210 * Math.sin(Math.PI * (215/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (215/180)); y: 240 + 215 * Math.sin(Math.PI * (215/180)) }
}
}
// -15 @ 145 deg
Text {
text: "-15"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (145/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (145/180)) - height/2
}
// -10 @ 153.75 deg
Text {
text: "-10"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (153.75/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (153.75/180)) - height/2
}
// -5 @ 162.5 deg
Text {
text: "-5"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (162.5/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (162.5/180)) - height/2
}
// 0 @ 171.25 deg
Text {
text: "0"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (171.25/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (171.25/180)) - height/2
}
// 5 @ 180 deg
Text {
text: "5"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * 1.0) - width/2
y: 240 + 230 * Math.sin(Math.PI * 1.0) - height/2
}
// 10 @ 188.75 deg
Text {
text: "10"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (188.75/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (188.75/180)) - height/2
}
// 15 @ 197.5 deg
Text {
text: "15"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (197.5/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (197.5/180)) - height/2
}
// 20 @ 206.25 deg
Text {
text: "20"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (206.25/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (206.25/180)) - height/2
}
// 25 @ 215 deg
Text {
text: "25"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (215/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (215/180)) - height/2
}
}
// --- Right Gauge (Mirrored) ---
Item {
id: rightGauge
anchors.fill: parent
// Track Outline
Shape {
anchors.fill: parent
// layer.enabled: true
// layer.samples: 4
// layer.smooth: true
ShapePath {
strokeColor: "white"
strokeWidth: 2
fillColor: "transparent"
capStyle: ShapePath.FlatCap
// Outer Arc (35 to -35 degrees)
// 35 deg = 0.1944 PI
// -35 deg = 325 deg = 1.8055 PI
startX: 240 + 210 * Math.cos(Math.PI * (325/180))
startY: 240 + 210 * Math.sin(Math.PI * (325/180))
PathArc {
x: 240 + 210 * Math.cos(Math.PI * (35/180))
y: 240 + 210 * Math.sin(Math.PI * (35/180))
radiusX: 210; radiusY: 210
useLargeArc: false
}
// Bottom Line connecting to inner
PathLine {
x: 240 + 160 * Math.cos(Math.PI * (35/180))
y: 240 + 160 * Math.sin(Math.PI * (35/180))
}
// Inner Arc
PathArc {
x: 240 + 160 * Math.cos(Math.PI * (325/180))
y: 240 + 160 * Math.sin(Math.PI * (325/180))
radiusX: 160; radiusY: 160
useLargeArc: false
direction: PathArc.Counterclockwise
}
// Top Line connecting back to start
PathLine {
x: 240 + 210 * Math.cos(Math.PI * (325/180))
y: 240 + 210 * Math.sin(Math.PI * (325/180))
}
}
}
// Indicator (Blue Bar)
Shape {
anchors.fill: parent
// layer.enabled: true
// layer.samples: 4
ShapePath {
strokeColor: "transparent"
fillColor: "#2196F3" // Blue
// Range -15 to 0.
// -15 = 35 deg
// 0 = 8.75 deg
startX: 240 + 205 * Math.cos(Math.PI * (35/180))
startY: 240 + 205 * Math.sin(Math.PI * (35/180))
PathArc {
x: 240 + 205 * Math.cos(Math.PI * (8.75/180))
y: 240 + 205 * Math.sin(Math.PI * (8.75/180))
radiusX: 205; radiusY: 205
direction: PathArc.Counterclockwise
}
PathLine {
x: 240 + 165 * Math.cos(Math.PI * (8.75/180))
y: 240 + 165 * Math.sin(Math.PI * (8.75/180))
}
PathArc {
x: 240 + 165 * Math.cos(Math.PI * (35/180))
y: 240 + 165 * Math.sin(Math.PI * (35/180))
radiusX: 165; radiusY: 165
}
PathLine {
x: 240 + 205 * Math.cos(Math.PI * (35/180))
y: 240 + 205 * Math.sin(Math.PI * (35/180))
}
}
}
// Markers & Ticks
Shape {
anchors.fill: parent
// layer.enabled: true
// layer.samples: 4
ShapePath {
strokeColor: "white"
strokeWidth: 2
fillColor: "transparent"
// -15 @ 35 deg
startX: 240 + 210 * Math.cos(Math.PI * (35/180))
startY: 240 + 210 * Math.sin(Math.PI * (35/180))
PathLine { x: 240 + 215 * Math.cos(Math.PI * (35/180)); y: 240 + 215 * Math.sin(Math.PI * (35/180)) }
// -10 @ 26.25 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (26.25/180)); y: 240 + 210 * Math.sin(Math.PI * (26.25/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (26.25/180)); y: 240 + 215 * Math.sin(Math.PI * (26.25/180)) }
// -5 @ 17.5 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (17.5/180)); y: 240 + 210 * Math.sin(Math.PI * (17.5/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (17.5/180)); y: 240 + 215 * Math.sin(Math.PI * (17.5/180)) }
// 0 @ 8.75 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (8.75/180)); y: 240 + 210 * Math.sin(Math.PI * (8.75/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (8.75/180)); y: 240 + 215 * Math.sin(Math.PI * (8.75/180)) }
// 5 @ 0 deg
PathMove { x: 240 + 210 * Math.cos(0); y: 240 + 210 * Math.sin(0) }
PathLine { x: 240 + 215 * Math.cos(0); y: 240 + 215 * Math.sin(0) }
// 10 @ -8.75 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (-8.75/180)); y: 240 + 210 * Math.sin(Math.PI * (-8.75/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (-8.75/180)); y: 240 + 215 * Math.sin(Math.PI * (-8.75/180)) }
// 15 @ -17.5 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (-17.5/180)); y: 240 + 210 * Math.sin(Math.PI * (-17.5/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (-17.5/180)); y: 240 + 215 * Math.sin(Math.PI * (-17.5/180)) }
// 20 @ -26.25 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (-26.25/180)); y: 240 + 210 * Math.sin(Math.PI * (-26.25/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (-26.25/180)); y: 240 + 215 * Math.sin(Math.PI * (-26.25/180)) }
// 25 @ -35 deg
PathMove { x: 240 + 210 * Math.cos(Math.PI * (-35/180)); y: 240 + 210 * Math.sin(Math.PI * (-35/180)) }
PathLine { x: 240 + 215 * Math.cos(Math.PI * (-35/180)); y: 240 + 215 * Math.sin(Math.PI * (-35/180)) }
}
}
// -15 @ 35 deg
Text {
text: "-15"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (35/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (35/180)) - height/2
}
// -10 @ 26.25 deg
Text {
text: "-10"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (26.25/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (26.25/180)) - height/2
}
// -5 @ 17.5 deg
Text {
text: "-5"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (17.5/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (17.5/180)) - height/2
}
// 0 @ 8.75 deg
Text {
text: "0"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (8.75/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (8.75/180)) - height/2
}
// 5 @ 0 deg
Text {
text: "5"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(0) - width/2
y: 240 + 230 * Math.sin(0) - height/2
}
// 10 @ -8.75 deg
Text {
text: "10"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (-8.75/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (-8.75/180)) - height/2
}
// 15 @ -17.5 deg
Text {
text: "15"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (-17.5/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (-17.5/180)) - height/2
}
// 20 @ -26.25 deg
Text {
text: "20"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (-26.25/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (-26.25/180)) - height/2
}
// 25 @ -35 deg
Text {
text: "25"
color: "white"
font.pixelSize: 20
font.family: "Arial"
font.bold: true
x: 240 + 230 * Math.cos(Math.PI * (-35/180)) - width/2
y: 240 + 230 * Math.sin(Math.PI * (-35/180)) - height/2
}
}
// --- Center Cover Shape ---
Shape {
anchors.fill: parent
// layer.enabled: true
// layer.samples: 4
z: 1 // Above background, below text/needles
ShapePath {
strokeColor: "#333333" // Subtle outline
strokeWidth: 1
fillColor: "#111111" // Slightly lighter than black
// Top Arc (225 to 315 degrees)
startX: 240 + 210 * Math.cos(Math.PI * 1.25) // 225 deg
startY: 240 + 210 * Math.sin(Math.PI * 1.25)
PathArc {
x: 240 + 210 * Math.cos(Math.PI * 1.75) // 315 deg
y: 240 + 210 * Math.sin(Math.PI * 1.75)
radiusX: 210; radiusY: 210
}
// Right Side (Straight lines)
// Line to Top-Right Waist
PathLine {
x: 240 + 90
y: 240 - 80
}
// Vertical Line down
PathLine {
x: 240 + 90
y: 240 + 80
}
// Line to Bottom Arc Start (45 deg)
PathLine {
x: 240 + 210 * Math.cos(Math.PI * 0.25)
y: 240 + 210 * Math.sin(Math.PI * 0.25)
}
// Bottom Arc (45 to 135 degrees)
PathArc {
x: 240 + 210 * Math.cos(Math.PI * 0.75) // 135 deg
y: 240 + 210 * Math.sin(Math.PI * 0.75)
radiusX: 210; radiusY: 210
}
// Left Side (Straight lines)
// Line to Bottom-Left Waist
PathLine {
x: 240 - 90
y: 240 + 80
}
// Vertical Line up
PathLine {
x: 240 - 90
y: 240 - 80
}
// Line back to Top Arc Start (225 deg)
PathLine {
x: 240 + 210 * Math.cos(Math.PI * 1.25)
y: 240 + 210 * Math.sin(Math.PI * 1.25)
}
}
// Inner Glow / Highlight on the cover
ShapePath {
strokeColor: "transparent"
fillColor: "transparent" // Placeholder for an effect if needed
}
}
// --- Center Details ---
Text {
text: "VOLVO"
font.pixelSize: 28
font.bold: true
font.family: "Serif"
color: "white"
anchors.bottom: parent.bottom
anchors.bottomMargin: 100
anchors.horizontalCenter: parent.horizontalCenter
z: 2 // Ensure on top of cover
}
// Unit Symbol (Moved to root to be above center cover)
Text {
text: "°C"
color: "orange"
font.pixelSize: 28
font.family: "Arial"
font.bold: true
z: 10 // Above center cover
anchors.centerIn: parent
anchors.verticalCenterOffset: -40 // Above center
}
// --- Needles ---
component GaugeNeedle : Item {
id: needleComponent
property real angle: 0
property color needleColor: "#FF6600"
x: 240
y: 240
width: 1
height: 1
// Glow/Shadow effect behind needle
Rectangle {
width: 180
height: 12
color: needleComponent.needleColor
opacity: 0.3
radius: 6
y: -height / 2
x: 0
transform: Rotation {
origin.x: 0
origin.y: 6
angle: needleComponent.angle
}
}
Rectangle {
id: needleRect
width: 180
height: 4 // Thinner for sharper look
color: needleComponent.needleColor
radius: 2
antialiasing: true
y: -height / 2
x: 0 // Starts at center and extends outwards
transform: Rotation {
origin.x: 0
origin.y: needleRect.height / 2
angle: needleComponent.angle
}
}
}
function getAngleFromTemp(temp) {
// Range -15 to 25 (40 deg range)
// Span 70 degrees (145 to 215)
// 70 / 40 = 1.75 degrees per unit
return 145 + (temp + 15) * 1.75
}
function getRightAngleFromTemp(temp) {
// Range -15 to 25
// Span 70 degrees (35 to -35)
return 35 - (temp + 15) * 1.75
}
// Keyboard Control
focus: true
Keys.onUpPressed: {
if (root.startupComplete) {
root.currentTemp = Math.min(root.currentTemp + 2, 25)
}
}
Keys.onDownPressed: {
if (root.startupComplete) {
root.currentTemp = Math.max(root.currentTemp - 2, -15)
}
}
Keys.onPressed: (event) => {
if (!root.startupComplete) return;
if (event.key === Qt.Key_PageUp) {
root.currentRightTemp = Math.min(root.currentRightTemp + 2, 25)
event.accepted = true
} else if (event.key === Qt.Key_PageDown) {
root.currentRightTemp = Math.max(root.currentRightTemp - 2, -15)
event.accepted = true
}
}
GaugeNeedle {
id: leftNeedle
// If startup complete, use real temp. Else use sweep temp.
angle: root.getAngleFromTemp(root.startupComplete ? root.currentTemp : -15)
Behavior on angle {
// Slower, smoother damping for a "heavy" gauge feel
SpringAnimation { spring: 2; damping: 0.2; epsilon: 0.1 }
}
}
GaugeNeedle {
id: rightNeedle
angle: root.getRightAngleFromTemp(root.startupComplete ? root.currentRightTemp : -15)
Behavior on angle {
SpringAnimation { spring: 2; damping: 0.2; epsilon: 0.1 }
}
}
}