Files
noctalia-shell/Modules/Bar/Widgets/AudioVisualizer.qml
T

223 lines
7.1 KiB
QML

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
// Explicit screenName property ensures reactive binding when screen changes
readonly property string screenName: screen ? screen.name : ""
readonly property string barPosition: Settings.getBarPositionForScreen(screenName)
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName)
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] ?? {}
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0 && screenName) {
var widgets = Settings.getBarWidgetsForScreen(screenName)[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: Color.resolveColorKey(colorName)
readonly property bool shouldShow: (currentVisualizerType !== "" && currentVisualizerType !== "none") && (!hideWhenIdle || MediaService.isPlaying)
readonly property bool needsSpectrum: root.shouldShow && MediaService.isPlaying
// Register/unregister with SpectrumService based on visibility (use screenName — screen can be null after DPMS/output changes)
readonly property string spectrumComponentId: "bar:audiovisualizer:" + screenName + ":" + root.section + ":" + root.sectionWidgetIndex
onNeedsSpectrumChanged: {
if (root.needsSpectrum) {
SpectrumService.registerComponent(root.spectrumComponentId);
} else {
SpectrumService.unregisterComponent(root.spectrumComponentId);
}
}
Component.onCompleted: {
if (root.needsSpectrum) {
SpectrumService.registerComponent(root.spectrumComponentId);
}
}
Component.onDestruction: {
SpectrumService.unregisterComponent(root.spectrumComponentId);
}
// Content dimensions for implicit sizing
readonly property real contentWidth: !shouldShow ? 0 : isVerticalBar ? capsuleHeight : visualizerWidth
readonly property real contentHeight: !shouldShow ? 0 : isVerticalBar ? visualizerWidth : capsuleHeight
implicitWidth: contentWidth
implicitHeight: contentHeight
visible: shouldShow
opacity: shouldShow ? 1.0 : 0.0
Behavior on implicitWidth {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.InOutCubic
}
}
Behavior on implicitHeight {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.InOutCubic
}
}
// Store visualizer type to force re-evaluation
readonly property string currentVisualizerType: Settings.data.audio.visualizerType
// Visual capsule centered in parent
Rectangle {
id: background
width: root.contentWidth
height: root.contentHeight
anchors.centerIn: parent
radius: Style.radiusS
color: Style.capsuleColor
border.color: Style.capsuleBorderColor
border.width: Style.capsuleBorderWidth
// 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: root.needsSpectrum
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("actions.cycle-visualizer"),
"action": "cycle-visualizer",
"icon": "chart-column"
},
{
"label": I18n.tr("actions.widget-settings"),
"action": "widget-settings",
"icon": "settings"
},
]
onTriggered: action => {
contextMenu.close();
if (screen) {
PanelService.closeContextMenu(screen);
}
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" && screen) {
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) {
if (screen) {
PanelService.showContextMenu(contextMenu, root, screen);
}
} 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: SpectrumService.values
fillColor: root.fillColor
showMinimumSignal: true
vertical: root.isVerticalBar
barPosition: root.barPosition
mirrored: Settings.data.audio.spectrumMirrored
}
}
Component {
id: mirroredComponent
NMirroredSpectrum {
anchors.fill: parent
values: SpectrumService.values
fillColor: root.fillColor
showMinimumSignal: true
vertical: root.isVerticalBar
mirrored: Settings.data.audio.spectrumMirrored
}
}
Component {
id: waveComponent
NWaveSpectrum {
anchors.fill: parent
values: SpectrumService.values
fillColor: root.fillColor
showMinimumSignal: true
vertical: root.isVerticalBar
mirrored: Settings.data.audio.spectrumMirrored
}
}
}