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 } } } }