import QtQuick import QtQuick.Controls import Quickshell import qs.Commons import qs.Modules.Bar.Extras import qs.Services.Media import qs.Services.UI import qs.Widgets import qs.Widgets.AudioSpectrum Item { id: root property ShellScreen screen // Widget properties passed from Bar.qml for per-instance settings property string widgetId: "" property string section: "" property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetSettings: { if (section && sectionWidgetIndex >= 0) { var widgets = Settings.data.bar.widgets[section]; if (widgets && sectionWidgetIndex < widgets.length) { return widgets[sectionWidgetIndex]; } } return {}; } // Resolve settings: try user settings or defaults from BarWidgetRegistry readonly property int visualizerWidth: widgetSettings.width !== undefined ? widgetSettings.width : widgetMetadata.width readonly property bool hideWhenIdle: widgetSettings.hideWhenIdle !== undefined ? widgetSettings.hideWhenIdle : widgetMetadata.hideWhenIdle readonly property string colorName: widgetSettings.colorName !== undefined ? widgetSettings.colorName : widgetMetadata.colorName readonly property color fillColor: { switch (colorName) { case "primary": return Color.mPrimary; case "secondary": return Color.mSecondary; case "tertiary": return Color.mTertiary; case "error": return Color.mError; case "onSurface": default: return Color.mOnSurface; } } readonly property bool shouldShow: (currentVisualizerType !== "" && currentVisualizerType !== "none") && (!hideWhenIdle || MediaService.isPlaying) implicitWidth: !shouldShow ? 0 : isVerticalBar ? Style.capsuleHeight : visualizerWidth implicitHeight: !shouldShow ? 0 : isVerticalBar ? visualizerWidth : Style.capsuleHeight visible: shouldShow Behavior on implicitWidth { NumberAnimation { duration: Style.animationNormal easing.type: Easing.InOutCubic } } Behavior on implicitHeight { NumberAnimation { duration: Style.animationNormal easing.type: Easing.InOutCubic } } Rectangle { id: background anchors.fill: parent radius: Style.radiusS color: Style.capsuleColor } // Store visualizer type to force re-evaluation readonly property string currentVisualizerType: Settings.data.audio.visualizerType // When visualizer type or playback changes, shouldShow updates automatically // The Loader dynamically loads the appropriate visualizer based on settings Loader { id: visualizerLoader anchors.fill: parent anchors.margins: Style.marginS active: shouldShow asynchronous: true sourceComponent: { switch (currentVisualizerType) { case "linear": return linearComponent; case "mirrored": return mirroredComponent; case "wave": return waveComponent; default: return null; } } } NPopupContextMenu { id: contextMenu model: [ { "label": I18n.tr("context-menu.cycle-visualizer"), "action": "cycle-visualizer", "icon": "chart-column" }, { "label": I18n.tr("context-menu.widget-settings"), "action": "widget-settings", "icon": "settings" }, ] onTriggered: action => { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.close(); } if (action === "cycle-visualizer") { const types = ["linear", "mirrored", "wave"]; const currentIndex = types.indexOf(currentVisualizerType); const nextIndex = (currentIndex + 1) % types.length; Settings.data.audio.visualizerType = types[nextIndex]; } else if (action === "widget-settings") { BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); } } } // Click to cycle through visualizer types MouseArea { id: mouseArea anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: mouse => { if (mouse.button === Qt.RightButton) { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight); contextMenu.openAtItem(root, pos.x, pos.y); } } else { const types = ["linear", "mirrored", "wave"]; const currentIndex = types.indexOf(currentVisualizerType); const nextIndex = (currentIndex + 1) % types.length; Settings.data.audio.visualizerType = types[nextIndex]; } } } Component { id: linearComponent NLinearSpectrum { anchors.fill: parent values: CavaService.values fillColor: root.fillColor showMinimumSignal: true vertical: root.isVerticalBar } } Component { id: mirroredComponent NMirroredSpectrum { anchors.fill: parent values: CavaService.values fillColor: root.fillColor showMinimumSignal: true vertical: root.isVerticalBar } } Component { id: waveComponent NWaveSpectrum { anchors.fill: parent values: CavaService.values fillColor: root.fillColor showMinimumSignal: true vertical: root.isVerticalBar } } }