1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-02 19:48:27 -04:00

qs: numerous performance optimizations

- ClippingRectangle usages
- Asynchronous loader usages
- Replace cava visualizers with shaders
This commit is contained in:
bbedward
2026-07-07 16:11:11 -04:00
parent 71b1901ab0
commit 0511cd19df
26 changed files with 558 additions and 366 deletions
@@ -7,15 +7,24 @@ Item {
id: root
readonly property MprisPlayer activePlayer: MprisController.activePlayer
readonly property bool hasActiveMedia: activePlayer !== null
readonly property bool isPlaying: hasActiveMedia && activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing
readonly property bool isPlaying: activePlayer !== null && activePlayer.playbackState === MprisPlaybackState.Playing
readonly property bool live: visible && isPlaying
width: 20
height: Theme.iconSize
Loader {
active: isPlaying
readonly property real maxBarHeight: Theme.iconSize - 2
readonly property real minBarHeight: 3
onLiveChanged: {
if (!live) {
bars.bandsA = Qt.vector4d(0, 0, 0, 0);
bars.bandsB = Qt.vector2d(0, 0);
}
}
Loader {
active: root.live
sourceComponent: Component {
Ref {
service: CavaService
@@ -23,15 +32,8 @@ Item {
}
}
readonly property real maxBarHeight: Theme.iconSize - 2
readonly property real minBarHeight: 3
readonly property real heightRange: maxBarHeight - minBarHeight
property var barHeights: [minBarHeight, minBarHeight, minBarHeight, minBarHeight, minBarHeight, minBarHeight]
Timer {
id: fallbackTimer
running: !CavaService.cavaAvailable && isPlaying
running: !CavaService.cavaAvailable && root.live
interval: 500
repeat: true
onTriggered: {
@@ -41,54 +43,32 @@ Item {
Connections {
target: CavaService
enabled: root.live
function onValuesChanged() {
if (!root.isPlaying) {
root.barHeights = [root.minBarHeight, root.minBarHeight, root.minBarHeight, root.minBarHeight, root.minBarHeight, root.minBarHeight];
const v = CavaService.values;
if (v.length < 6)
return;
}
const newHeights = [];
for (let i = 0; i < 6; i++) {
if (CavaService.values.length <= i) {
newHeights.push(root.minBarHeight);
continue;
}
const rawLevel = CavaService.values[i];
if (rawLevel <= 0) {
newHeights.push(root.minBarHeight);
} else if (rawLevel >= 100) {
newHeights.push(root.maxBarHeight);
} else {
newHeights.push(root.minBarHeight + Math.sqrt(rawLevel * 0.01) * root.heightRange);
}
}
root.barHeights = newHeights;
const n = i => {
const x = v[i];
return x <= 0 ? 0 : x >= 100 ? 1 : Math.sqrt(x * 0.01);
};
bars.bandsA = Qt.vector4d(n(0), n(1), n(2), n(3));
bars.bandsB = Qt.vector2d(n(4), n(5));
}
}
Row {
anchors.centerIn: parent
spacing: 1.5
ShaderEffect {
id: bars
anchors.fill: parent
Repeater {
model: 6
property real widthPx: width
property real heightPx: height
property real minH: root.minBarHeight
property real maxH: root.maxBarHeight
property vector4d bandsA: Qt.vector4d(0, 0, 0, 0)
property vector2d bandsB: Qt.vector2d(0, 0)
property vector4d fillColor: Qt.vector4d(Theme.primary.r, Theme.primary.g, Theme.primary.b, Theme.primary.a)
Rectangle {
width: 2
height: root.barHeights[index]
radius: 1.5
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
Behavior on height {
enabled: root.isPlaying && !CavaService.cavaAvailable
NumberAnimation {
duration: 100
easing.type: Easing.Linear
}
}
}
}
fragmentShader: Qt.resolvedUrl("../../../Shaders/qsb/viz_bars.frag.qsb")
}
}
@@ -1,6 +1,5 @@
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
@@ -234,16 +233,19 @@ BasePill {
color: Theme.widgetTextColor
}
RowLayout {
Row {
id: contentRow
anchors.centerIn: parent
spacing: Theme.spacingS
visible: !root.isVerticalOrientation
readonly property real iconSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
IconImage {
id: horizontalAppIcon
Layout.preferredWidth: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
Layout.preferredHeight: Layout.preferredWidth
width: contentRow.iconSize
height: contentRow.iconSize
anchors.verticalCenter: parent.verticalCenter
visible: root.showIcon && activeWindow && status === Image.Ready
source: {
if (!activeWindow || !activeWindow.appId)
@@ -264,8 +266,10 @@ BasePill {
}
DankIcon {
Layout.preferredWidth: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
size: Layout.preferredWidth
id: horizontalSteamIcon
width: contentRow.iconSize
size: contentRow.iconSize
anchors.verticalCenter: parent.verticalCenter
name: "sports_esports"
color: Theme.widgetTextColor
visible: root.showIcon && activeWindow && activeWindow.appId && horizontalAppIcon.status !== Image.Ready && Paths.isSteamApp(activeWindow.appId)
@@ -280,9 +284,11 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.NoWrap
elide: Text.ElideRight
maximumLineCount: 1
Layout.maximumWidth: compactMode ? 80 : 180
width: Math.min(implicitWidth, compactMode ? 80 : 180)
visible: text.length > 0
}
@@ -291,6 +297,7 @@ BasePill {
text: compactMode ? "" : "•"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: !compactMode && appText.text && titleText.text
}
@@ -318,9 +325,24 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.NoWrap
elide: Text.ElideRight
maximumLineCount: 1
Layout.maximumWidth: maxWidth - appText.implicitWidth - appSeparator.implicitWidth
width: {
const sp = contentRow.spacing;
let used = 0;
if (horizontalAppIcon.visible)
used += horizontalAppIcon.width + sp;
else if (horizontalSteamIcon.visible)
used += horizontalSteamIcon.width + sp;
if (appText.visible)
used += appText.width + sp;
if (appSeparator.visible)
used += appSeparator.width + sp;
const budget = root.maxWidth - root.horizontalPadding * 2 - used;
return Math.min(implicitWidth, Math.max(0, budget));
}
visible: text.length > 0
}
}
@@ -411,6 +411,7 @@ DankPopout {
anchors.fill: parent
active: root.currentTabId === "media"
visible: active
asynchronous: true
sourceComponent: Component {
MediaPlayerTab {
targetScreen: root.screen
@@ -462,7 +463,7 @@ DankPopout {
DankSpinner {
anchors.centerIn: parent
size: 40
visible: wallpaperLoader.active && wallpaperLoader.status === Loader.Loading
visible: (wallpaperLoader.active && wallpaperLoader.status === Loader.Loading) || (mediaLoader.active && mediaLoader.status === Loader.Loading) || (weatherLoader.active && weatherLoader.status === Loader.Loading)
}
Loader {
@@ -470,6 +471,7 @@ DankPopout {
anchors.fill: parent
active: root.currentTabId === "weather"
visible: active
asynchronous: true
sourceComponent: Component {
WeatherTab {}
}
@@ -44,6 +44,8 @@ Item {
property int __panelHoverCount: 0
readonly property bool overlayBlurActive: dropdownBlur.active
onDropdownTypeChanged: {
if (dropdownType === 0) {
__panelHoverCount = 0;
@@ -75,8 +77,10 @@ Item {
}
WindowBlur {
id: dropdownBlur
targetWindow: root.targetWindow
readonly property bool active: root.__activePanel !== null && root.__activePanel.visible && root.__activePanel.opacity > 0
blurEnabled: active && Theme.connectedSurfaceBlurEnabled
readonly property real s: root.__activePanel ? Math.min(1, root.__activePanel.scale) : 1
blurX: root.__activePanel ? root.__activePanel.x + root.__activePanel.width * (1 - s) * 0.5 : 0
blurY: root.__activePanel ? root.__activePanel.y + root.__activePanel.height * (1 - s) * 0.5 : 0
+78 -45
View File
@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell.Services.Mpris
import Quickshell.Widgets
import qs.Common
import qs.Services
import qs.Widgets
@@ -70,7 +71,7 @@ Item {
property bool _switchHold: false
Timer {
id: _switchHoldTimer
interval: 650
interval: 1500
repeat: false
onTriggered: _switchHold = false
}
@@ -78,7 +79,8 @@ Item {
onActivePlayerChanged: {
if (!activePlayer) {
isSwitching = false;
_switchHold = false;
_switchHold = true;
_switchHoldTimer.restart();
return;
}
isSwitching = true;
@@ -117,14 +119,10 @@ Item {
Connections {
target: MprisController
function onAvailablePlayersChanged() {
const count = (MprisController.availablePlayers?.length || 0);
if (count === 0) {
if ((MprisController.availablePlayers?.length || 0) === 0)
isSwitching = false;
_switchHold = false;
} else {
_switchHold = true;
_switchHoldTimer.restart();
}
_switchHold = true;
_switchHoldTimer.restart();
}
}
@@ -290,34 +288,64 @@ Item {
Item {
id: bgContainer
anchors.fill: parent
visible: TrackArtService.resolvedArtUrl !== ""
Image {
id: bgImage
anchors.centerIn: parent
width: Math.max(parent.width, parent.height) * 1.1
height: width
source: TrackArtService.resolvedArtUrl
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: true
visible: false
onStatusChanged: {
if (status === Image.Ready)
maybeFinishSwitch();
}
readonly property string curArt: TrackArtService.resolvedArtUrl
// Two layers crossfade: new art loads into the hidden one and fades in once decoded.
property bool _showA: true
visible: layerA.ready || layerB.ready
onCurArtChanged: syncArt()
Component.onCompleted: syncArt()
function syncArt() {
if (curArt === "" || layerA.art == curArt || layerB.art == curArt)
return;
if (_showA)
layerB.art = curArt;
else
layerA.art = curArt;
}
Item {
id: blurredBg
component BgBlurLayer: ClippingRectangle {
id: layer
property alias art: layerImg.source
readonly property bool ready: layerImg.status === Image.Ready && layerImg.source != ""
property bool front: false
signal loaded
anchors.fill: parent
visible: false
radius: Theme.cornerRadius
color: "transparent"
antialiasing: true
opacity: front ? 0.7 : 0
Behavior on opacity {
NumberAnimation {
duration: 350
easing.type: Easing.InOutQuad
}
}
Image {
id: layerImg
anchors.centerIn: parent
width: Math.max(parent.width, parent.height) * 1.1
height: width
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: true
visible: false
onStatusChanged: {
if (status === Image.Ready && source != "")
layer.loaded();
}
}
MultiEffect {
anchors.centerIn: parent
width: bgImage.width
height: bgImage.height
source: bgImage
width: layerImg.width
height: layerImg.height
source: layerImg
blurEnabled: true
blurMax: 64
blur: 0.8
@@ -326,22 +354,26 @@ Item {
}
}
Rectangle {
id: bgMask
anchors.fill: parent
radius: Theme.cornerRadius
visible: false
layer.enabled: true
BgBlurLayer {
id: layerA
front: bgContainer._showA
onLoaded: {
if (!bgContainer._showA) {
bgContainer._showA = true;
root.maybeFinishSwitch();
}
}
}
MultiEffect {
anchors.fill: parent
source: blurredBg
maskEnabled: true
maskSource: bgMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1.0
opacity: 0.7
BgBlurLayer {
id: layerB
front: !bgContainer._showA
onLoaded: {
if (bgContainer._showA) {
bgContainer._showA = false;
root.maybeFinishSwitch();
}
}
}
Rectangle {
@@ -442,7 +474,8 @@ Item {
elide: Text.ElideRight
wrapMode: Text.WordWrap
maximumLineCount: 1
visible: text.length > 0
// Reserve the line so late album metadata doesn't shift the seekbar.
height: Math.max(implicitHeight, Theme.fontSizeSmall * 1.4)
}
}
+17 -6
View File
@@ -1,6 +1,7 @@
import QtQuick
import qs.Common
import qs.Modules.DankDash.Overview
import qs.Widgets
Item {
id: root
@@ -17,7 +18,7 @@ Item {
signal navFocusRequested
function handleKeyEvent(event) {
return calendarCard.handleKeyEvent(event);
return calendarLoader.item ? calendarLoader.item.handleKeyEvent(event) : false;
}
Item {
@@ -57,16 +58,26 @@ Item {
height: 220
}
// Calendar - bottom middle (wider and taller)
CalendarOverviewCard {
id: calendarCard
// Calendar - bottom middle; deferred so the grid stays off the emerge frame.
Loader {
id: calendarLoader
x: parent.width * 0.2 - Theme.spacingM
y: 100 + Theme.spacingM
width: parent.width * 0.6
height: 300
asynchronous: true
sourceComponent: Component {
CalendarOverviewCard {
onCloseDash: root.closeDash()
onNavFocusRequested: root.navFocusRequested()
}
}
onCloseDash: root.closeDash()
onNavFocusRequested: root.navFocusRequested()
DankSpinner {
anchors.centerIn: parent
size: 32
visible: calendarLoader.status === Loader.Loading
}
}
// Media - bottom right (narrow and taller)
+2 -32
View File
@@ -1,5 +1,4 @@
import QtQuick
import QtQuick.Effects
import QtQuick.Shapes
import qs.Common
import qs.Services
@@ -245,17 +244,6 @@ Item {
size: Theme.iconSize * 2
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
layer.enabled: Theme.elevationEnabled
layer.effect: MultiEffect {
shadowEnabled: Theme.elevationEnabled
shadowHorizontalOffset: Theme.elevationOffsetX(Theme.elevationLevel1)
shadowVerticalOffset: Theme.elevationOffsetY(Theme.elevationLevel1, 1)
shadowBlur: Theme.elevationEnabled ? Math.max(0, Math.min(1, (Theme.elevationLevel1 && Theme.elevationLevel1.blurPx !== undefined ? Theme.elevationLevel1.blurPx : 4) / Theme.elevationBlurMax)) : 0
blurMax: Theme.elevationBlurMax
shadowColor: Theme.elevationShadowColor(Theme.elevationLevel1)
shadowOpacity: Theme.elevationLevel1 && Theme.elevationLevel1.alpha !== undefined ? Theme.elevationLevel1.alpha : 0.2
}
}
Column {
@@ -820,16 +808,6 @@ Item {
property var pos: WeatherService.getSkyArcPosition(skyBox.currentDate, false)
x: (pos?.h ?? 0) * skyBox.effectiveWidth - (moonPhase.width / 2) + skyBox.hMargin
y: (pos?.v ?? 0) * -(skyBox.effectiveHeight / 2) + skyBox.effectiveHeight / 2 - (moonPhase.height / 2) + skyBox.vMargin
layer.enabled: Theme.elevationEnabled
layer.effect: MultiEffect {
shadowEnabled: Theme.elevationEnabled
shadowHorizontalOffset: Theme.elevationOffsetX(Theme.elevationLevel2)
shadowVerticalOffset: Theme.elevationOffsetY(Theme.elevationLevel2, 4)
shadowBlur: Theme.elevationEnabled ? Math.max(0, Math.min(1, (Theme.elevationLevel2 && Theme.elevationLevel2.blurPx !== undefined ? Theme.elevationLevel2.blurPx : 8) / Theme.elevationBlurMax)) : 0
blurMax: Theme.elevationBlurMax
shadowColor: Theme.elevationShadowColor(Theme.elevationLevel2)
}
}
DankIcon {
@@ -842,16 +820,6 @@ Item {
property var pos: WeatherService.getSkyArcPosition(skyBox.currentDate, true)
x: (pos?.h ?? 0) * skyBox.effectiveWidth - (sun.width / 2) + skyBox.hMargin
y: (pos?.v ?? 0) * -(skyBox.effectiveHeight / 2) + skyBox.effectiveHeight / 2 - (sun.height / 2) + skyBox.vMargin
layer.enabled: Theme.elevationEnabled
layer.effect: MultiEffect {
shadowEnabled: Theme.elevationEnabled
shadowHorizontalOffset: Theme.elevationOffsetX(Theme.elevationLevel2)
shadowVerticalOffset: Theme.elevationOffsetY(Theme.elevationLevel2, 4)
shadowBlur: Theme.elevationEnabled ? Math.max(0, Math.min(1, (Theme.elevationLevel2 && Theme.elevationLevel2.blurPx !== undefined ? Theme.elevationLevel2.blurPx : 8) / Theme.elevationBlurMax)) : 0
blurMax: Theme.elevationBlurMax
shadowColor: Theme.elevationShadowColor(Theme.elevationLevel2)
}
}
}
}
@@ -993,6 +961,7 @@ Item {
ListView {
id: hourlyList
anchors.fill: parent
reuseItems: true
orientation: ListView.Horizontal
spacing: Theme.spacingS
clip: true
@@ -1079,6 +1048,7 @@ Item {
ListView {
id: dailyList
anchors.fill: parent
reuseItems: true
orientation: ListView.Horizontal
spacing: Theme.spacingS
clip: true