diff --git a/Modules/Panels/ControlCenter/Cards/WeatherCard.qml b/Modules/Panels/ControlCenter/Cards/WeatherCard.qml index 737624ba..b9bd0344 100644 --- a/Modules/Panels/ControlCenter/Cards/WeatherCard.qml +++ b/Modules/Panels/ControlCenter/Cards/WeatherCard.qml @@ -134,11 +134,10 @@ NBox { } } - RowLayout { - visible: !weatherReady - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter - NBusyIndicator {} + Loader { + active: !weatherReady + Layout.alignment: Qt.AlignCenter + sourceComponent: NBusyIndicator {} } } } diff --git a/Widgets/NBusyIndicator.qml b/Widgets/NBusyIndicator.qml index bf93abe8..f65da231 100644 --- a/Widgets/NBusyIndicator.qml +++ b/Widgets/NBusyIndicator.qml @@ -14,40 +14,50 @@ Item { implicitWidth: size implicitHeight: size - Canvas { - id: canvas + // GPU-optimized spinner - draw once, rotate with GPU transform + Item { + id: spinner anchors.fill: parent - onPaint: { - var ctx = getContext("2d") - ctx.reset() + // Static canvas - drawn ONCE, then cached + Canvas { + id: canvas + anchors.fill: parent + renderStrategy: Canvas.Cooperative // Better performance than Threaded for simple shapes + renderTarget: Canvas.FramebufferObject // GPU texture - var centerX = width / 2 - var centerY = height / 2 - var radius = Math.min(width, height) / 2 - strokeWidth / 2 + // Enable layer caching - critical for performance! + layer.enabled: true + layer.smooth: true - ctx.strokeStyle = root.color - ctx.lineWidth = Math.max(1, root.strokeWidth) - ctx.lineCap = "round" + Component.onCompleted: { + requestPaint() + } - // Draw arc with gap (270 degrees with 90 degree gap) - ctx.beginPath() - ctx.arc(centerX, centerY, radius, -Math.PI / 2 + rotationAngle, -Math.PI / 2 + rotationAngle + Math.PI * 1.5) - ctx.stroke() + onPaint: { + var ctx = getContext("2d") + ctx.reset() + + var centerX = width / 2 + var centerY = height / 2 + var radius = Math.min(width, height) / 2 - strokeWidth / 2 + + ctx.strokeStyle = root.color + ctx.lineWidth = Math.max(1, root.strokeWidth) + ctx.lineCap = "round" + + // Draw arc with gap (270 degrees = 3/4 of circle) + ctx.beginPath() + ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + Math.PI * 1.5) + ctx.stroke() + } } - property real rotationAngle: 0 - - onRotationAngleChanged: { - requestPaint() - } - - NumberAnimation { - target: canvas - property: "rotationAngle" + // Smooth rotation animation - uses GPU transform, NO canvas repaints! + RotationAnimation on rotation { running: root.running from: 0 - to: 2 * Math.PI + to: 360 duration: root.duration loops: Animation.Infinite } diff --git a/Widgets/NCircleStat.qml b/Widgets/NCircleStat.qml index ad5a6971..169aa445 100644 --- a/Widgets/NCircleStat.qml +++ b/Widgets/NCircleStat.qml @@ -24,8 +24,27 @@ Rectangle { border.color: flat ? Color.transparent : Color.mSurfaceVariant border.width: flat ? 0 : Style.borderS - // Repaint gauge when the bound value changes - onValueChanged: gauge.requestPaint() + // Animated value for smooth transitions - reduces repaint frequency + property real animatedValue: value + + Behavior on animatedValue { + enabled: !Settings.data.general.animationDisabled + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + // Repaint gauge when animated value changes (throttled by animation) + onAnimatedValueChanged: repaintTimer.restart() + + // Debounce timer to limit repaint frequency during rapid value changes + Timer { + id: repaintTimer + interval: 16 // ~60 FPS max + repeat: false + onTriggered: gauge.requestPaint() + } ColumnLayout { id: mainLayout @@ -45,7 +64,18 @@ Rectangle { Canvas { id: gauge anchors.fill: parent - renderStrategy: Canvas.Immediate + + // Optimized Canvas settings for better GPU performance + renderStrategy: Canvas.Cooperative // Better performance than Immediate + renderTarget: Canvas.FramebufferObject // GPU texture rendering + + // Enable layer caching - critical for performance! + layer.enabled: true + layer.smooth: true + + Component.onCompleted: { + requestPaint() + } onPaint: { const ctx = getContext("2d") @@ -68,7 +98,7 @@ Rectangle { ctx.stroke() // Value arc with gradient starting at 25% - const ratio = Math.max(0, Math.min(1, root.value / 100)) + const ratio = Math.max(0, Math.min(1, root.animatedValue / 100)) const end = start + (endBg - start) * ratio // Calculate gradient start point (25% into the arc) @@ -97,7 +127,7 @@ Rectangle { id: valueLabel anchors.centerIn: parent anchors.verticalCenterOffset: -4 * contentScale - text: `${root.value}${root.suffix}` + text: `${Math.round(root.value)}${root.suffix}` pointSize: Style.fontSizeM * contentScale * 0.9 font.weight: Style.fontWeightBold color: Color.mOnSurface diff --git a/Widgets/NSlider.qml b/Widgets/NSlider.qml index 994ced73..82bbf102 100644 --- a/Widgets/NSlider.qml +++ b/Widgets/NSlider.qml @@ -7,6 +7,7 @@ import qs.Services Slider { id: root + property color fillColor: Color.mPrimary property var cutoutColor: Color.mSurface property bool snapAlways: true property real heightRatio: 0.7 @@ -46,7 +47,7 @@ Slider { width: parent.height height: parent.height radius: width / 2 - color: Qt.darker(Color.mPrimary, 1.2) //starting color of gradient + color: Qt.darker(fillColor, 1.2) //starting color of gradient } // The main rectangle @@ -60,35 +61,11 @@ Slider { orientation: Gradient.Horizontal GradientStop { position: 0.0 - color: Qt.darker(Color.mPrimary, 1.2) - Behavior on color { - ColorAnimation { - duration: 300 - } - } - } - GradientStop { - position: 0.5 - color: Color.mPrimary - SequentialAnimation on position { - loops: Animation.Infinite - NumberAnimation { - from: 0.3 - to: 0.7 - duration: 2000 - easing.type: Easing.InOutSine - } - NumberAnimation { - from: 0.7 - to: 0.3 - duration: 2000 - easing.type: Easing.InOutSine - } - } + color: Qt.darker(fillColor, 1.2) } GradientStop { position: 1.0 - color: Qt.lighter(Color.mPrimary, 1.2) + color: fillColor } } } @@ -118,7 +95,7 @@ Slider { implicitHeight: knobDiameter radius: width / 2 color: root.pressed ? Color.mHover : Color.mSurface - border.color: Color.mPrimary + border.color: fillColor border.width: Style.borderL anchors.centerIn: parent