From afb736102d7d40bfe25fcc5c42ba3d9f1dc5467a Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Thu, 26 Feb 2026 16:27:05 +0100 Subject: [PATCH] cava + nix --- Components/MarqueeText.qml | 28 ++++- Config/BarConfig.qml | 4 + Modules/AudioWidget.qml | 126 +++++++++-------------- Modules/Bar/BarLoader.qml | 17 +-- Modules/Dashboard/Dash/Media.qml | 35 ++++++- Modules/Lock/Media.qml | 6 +- Modules/MediaWidget.qml | 73 +++++++++++++ Modules/Resources.qml | 101 +++++++++--------- Modules/UpdatesWidget.qml | 10 +- Plugins/ZShell/Services/cavaprovider.cpp | 41 +++----- nix/default.nix | 107 +++++++++---------- 11 files changed, 311 insertions(+), 237 deletions(-) create mode 100644 Modules/MediaWidget.qml diff --git a/Components/MarqueeText.qml b/Components/MarqueeText.qml index 3f8b69a..3788844 100644 --- a/Components/MarqueeText.qml +++ b/Components/MarqueeText.qml @@ -4,6 +4,7 @@ import qs.Config Item { id: root + property bool animate: false property color color: DynamicColors.palette.m3onSurface property int fadeStrengthAnimMs: 180 property real fadeStrengthIdle: 0.0 @@ -27,6 +28,19 @@ Item { return Math.max(1, Math.round(Math.abs(px) / root.pixelsPerSecond * 1000)); } + function resetMarquee() { + // stop + reset all state immediately + marqueeAnim.stop(); + strip.x = 0; + root.sliding = false; + root.leftFadeEnabled = false; + + // restart after bindings/layout settle + if (root.marqueeEnabled && root.overflowing && root.visible) { + marqueeAnim.restart(); + } + } + clip: true implicitHeight: elideText.implicitHeight @@ -39,10 +53,10 @@ Item { } } - onTextChanged: strip.x = 0 + onTextChanged: resetMarquee() onVisibleChanged: if (!visible) - strip.x = 0 - onWidthChanged: strip.x = 0 + resetMarquee() + onWidthChanged: resetMarquee() TextMetrics { id: metrics @@ -55,8 +69,10 @@ Item { id: elideText anchors.verticalCenter: parent.verticalCenter + animate: root.animate + animateProp: "scale,opacity" color: root.color - elide: Text.ElideRight + elide: Text.ElideNone visible: !root.overflowing width: root.width } @@ -84,6 +100,8 @@ Item { CustomText { id: t1 + animate: root.animate + animateProp: "opacity" color: root.color text: elideText.text } @@ -91,6 +109,8 @@ Item { CustomText { id: t2 + animate: root.animate + animateProp: "opacity" color: root.color text: t1.text x: t1.width + root.gap diff --git a/Config/BarConfig.qml b/Config/BarConfig.qml index 32292ee..4ec7578 100644 --- a/Config/BarConfig.qml +++ b/Config/BarConfig.qml @@ -11,6 +11,10 @@ JsonObject { id: "audio", enabled: true }, + { + id: "media", + enabled: true + }, { id: "resources", enabled: true diff --git a/Modules/AudioWidget.qml b/Modules/AudioWidget.qml index 4e3fa41..6b56153 100644 --- a/Modules/AudioWidget.qml +++ b/Modules/AudioWidget.qml @@ -24,101 +24,75 @@ Item { } } - Rectangle { + CustomRect { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter color: DynamicColors.tPalette.m3surfaceContainer height: 22 radius: height / 2 + } - Behavior on color { - CAnim { - } + RowLayout { + id: layout + + anchors.fill: parent + anchors.leftMargin: Appearance.padding.small + anchors.rightMargin: Appearance.padding.small * 2 + anchors.verticalCenter: parent.verticalCenter + + MaterialIcon { + Layout.alignment: Qt.AlignVCenter + color: Audio.muted ? DynamicColors.palette.m3error : root.textColor + font.pointSize: 14 + text: Audio.muted ? "volume_off" : "volume_up" } - Rectangle { - anchors.centerIn: parent - border.color: "#30ffffff" - border.width: 0 - color: "transparent" - height: parent.height - radius: width / 2 - width: parent.width - } + CustomRect { + Layout.fillWidth: true + color: "#50ffffff" + implicitHeight: 4 + radius: 20 - RowLayout { - id: layout + CustomRect { + id: sinkVolumeBar - anchors.left: parent.left - anchors.leftMargin: Appearance.padding.small - anchors.right: parent.right - anchors.rightMargin: Appearance.padding.small * 2 - anchors.verticalCenter: parent.verticalCenter + color: Audio.muted ? DynamicColors.palette.m3error : root.barColor + implicitWidth: parent.width * (Audio.volume ?? 0) + radius: parent.radius - MaterialIcon { - Layout.alignment: Qt.AlignVCenter - color: Audio.muted ? DynamicColors.palette.m3error : root.textColor - font.pointSize: 14 - text: Audio.muted ? "volume_off" : "volume_up" - } - - Rectangle { - Layout.fillWidth: true - color: "#50ffffff" - implicitHeight: 4 - radius: 20 - - Rectangle { - id: sinkVolumeBar - - color: Audio.muted ? DynamicColors.palette.m3error : root.barColor - implicitWidth: parent.width * (Audio.volume ?? 0) - radius: parent.radius - - Behavior on color { - CAnim { - } - } - - anchors { - bottom: parent.bottom - left: parent.left - top: parent.top - } + anchors { + bottom: parent.bottom + left: parent.left + top: parent.top } } + } - MaterialIcon { - Layout.alignment: Qt.AlignVCenter - color: (Audio.sourceMuted ?? false) ? DynamicColors.palette.m3error : root.textColor - font.pointSize: 14 - text: Audio.sourceMuted ? "mic_off" : "mic" - } + MaterialIcon { + Layout.alignment: Qt.AlignVCenter + color: (Audio.sourceMuted ?? false) ? DynamicColors.palette.m3error : root.textColor + font.pointSize: 14 + text: Audio.sourceMuted ? "mic_off" : "mic" + } - Rectangle { - Layout.fillWidth: true - color: "#50ffffff" - implicitHeight: 4 - radius: 20 + CustomRect { + Layout.fillWidth: true + color: "#50ffffff" + implicitHeight: 4 + radius: 20 - Rectangle { - id: sourceVolumeBar + CustomRect { + id: sourceVolumeBar - color: (Audio.sourceMuted ?? false) ? DynamicColors.palette.m3error : root.barColor - implicitWidth: parent.width * (Audio.sourceVolume ?? 0) - radius: parent.radius + color: (Audio.sourceMuted ?? false) ? DynamicColors.palette.m3error : root.barColor + implicitWidth: parent.width * (Audio.sourceVolume ?? 0) + radius: parent.radius - Behavior on color { - CAnim { - } - } - - anchors { - bottom: parent.bottom - left: parent.left - top: parent.top - } + anchors { + bottom: parent.bottom + left: parent.left + top: parent.top } } } diff --git a/Modules/Bar/BarLoader.qml b/Modules/Bar/BarLoader.qml index 24d9071..85522ac 100644 --- a/Modules/Bar/BarLoader.qml +++ b/Modules/Bar/BarLoader.qml @@ -206,14 +206,15 @@ RowLayout { } } } - // DelegateChoice { - // roleValue: "dash" - // delegate: WrappedLoader { - // sourceComponent: DashWidget { - // visibilities: root.visibilities - // } - // } - // } + + DelegateChoice { + roleValue: "media" + + delegate: WrappedLoader { + sourceComponent: MediaWidget { + } + } + } } } diff --git a/Modules/Dashboard/Dash/Media.qml b/Modules/Dashboard/Dash/Media.qml index 11774c1..4b47b81 100644 --- a/Modules/Dashboard/Dash/Media.qml +++ b/Modules/Dashboard/Dash/Media.qml @@ -217,7 +217,7 @@ Item { width: parent.width - Appearance.padding.large * 4 } - Row { + RowLayout { id: controls anchors.horizontalCenter: parent.horizontalCenter @@ -260,20 +260,48 @@ Item { required property bool canUse required property string icon + property int level: 1 + property string set_color: "Secondary" function onClicked(): void { } + Layout.preferredWidth: implicitWidth + (controlState.pressed ? Appearance.padding.normal * 2 : 0) + color: canUse ? DynamicColors.palette[`m3${set_color.toLowerCase()}`] : DynamicColors.palette[`m3${set_color.toLowerCase()}Container`] implicitHeight: implicitWidth implicitWidth: Math.max(icon.implicitHeight, icon.implicitHeight) + Appearance.padding.small + radius: Appearance.rounding.full + + Behavior on Layout.preferredWidth { + Anim { + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial + } + } + Behavior on radius { + Anim { + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial + } + } + + Elevation { + anchors.fill: parent + level: controlState.containsMouse && !controlState.pressed ? control.level + 1 : control.level + radius: parent.radius + z: -1 + } StateLayer { + id: controlState + function onClicked(): void { control.onClicked(); } + color: control.canUse ? DynamicColors.palette[`m3on${control.set_color}`] : DynamicColors.palette[`m3on${control.set_color}Container`] disabled: !control.canUse - radius: Appearance.rounding.full + // radius: Appearance.rounding.full } MaterialIcon { @@ -282,7 +310,8 @@ Item { anchors.centerIn: parent anchors.verticalCenterOffset: font.pointSize * 0.05 animate: true - color: control.canUse ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline + color: control.canUse ? DynamicColors.palette[`m3on${control.set_color}`] : DynamicColors.palette[`m3on${control.set_color}Container`] + fill: control.canUse ? 1 : 0 font.pointSize: Appearance.font.size.large text: control.icon } diff --git a/Modules/Lock/Media.qml b/Modules/Lock/Media.qml index 1ec301c..a241e4b 100644 --- a/Modules/Lock/Media.qml +++ b/Modules/Lock/Media.qml @@ -121,9 +121,9 @@ Item { active: Players.active?.isPlaying ?? false animate: true - colour: "Primary" icon: active ? "pause" : "play_arrow" level: active ? 2 : 1 + set_color: "Primary" } PlayerControl { @@ -142,15 +142,15 @@ Item { property bool active property alias animate: controlIcon.animate - property string colour: "Secondary" property alias icon: controlIcon.text property int level: 1 + property string set_color: "Secondary" function onClicked(): void { } Layout.preferredWidth: implicitWidth + (controlState.pressed ? Appearance.padding.normal * 2 : active ? Appearance.padding.small * 2 : 0) - color: active ? DynamicColors.palette[`m3${colour.toLowerCase()}`] : DynamicColors.palette[`m3${colour.toLowerCase()}Container`] + color: active ? DynamicColors.palette[`m3${set_color.toLowerCase()}`] : DynamicColors.palette[`m3${set_color.toLowerCase()}Container`] implicitHeight: controlIcon.implicitHeight + Appearance.padding.normal * 2 implicitWidth: controlIcon.implicitWidth + Appearance.padding.large * 2 radius: active || controlState.pressed ? Appearance.rounding.small : Appearance.rounding.normal diff --git a/Modules/MediaWidget.qml b/Modules/MediaWidget.qml new file mode 100644 index 0000000..73b4056 --- /dev/null +++ b/Modules/MediaWidget.qml @@ -0,0 +1,73 @@ +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Daemons +import qs.Config +import qs.Helpers + +Item { + id: root + + readonly property string currentMedia: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title") + readonly property int textWidth: Math.min(metrics.width, 200) + + anchors.bottom: parent.bottom + anchors.top: parent.top + implicitWidth: layout.implicitWidth + Appearance.padding.normal * 2 + + Behavior on implicitWidth { + Anim { + } + } + + CustomRect { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: 22 + radius: Appearance.rounding.full + } + + TextMetrics { + id: metrics + + font: mediatext.font + text: mediatext.text + } + + RowLayout { + id: layout + + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.left: parent.left + anchors.leftMargin: Appearance.padding.normal + anchors.top: parent.top + + Behavior on implicitWidth { + Anim { + } + } + + MaterialIcon { + animate: true + color: Players.active?.isPlaying ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface + font.pointSize: 14 + text: Players.active?.isPlaying ? "music_note" : "music_off" + } + + MarqueeText { + id: mediatext + + Layout.preferredWidth: root.textWidth + animate: true + color: Players.active?.isPlaying ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface + font.pointSize: Appearance.font.size.normal + horizontalAlignment: Text.AlignHCenter + pauseMs: 4000 + text: root.currentMedia + width: root.textWidth + } + } +} diff --git a/Modules/Resources.qml b/Modules/Resources.qml index 829251f..ba0babf 100644 --- a/Modules/Resources.qml +++ b/Modules/Resources.qml @@ -16,80 +16,75 @@ Item { implicitHeight: 34 implicitWidth: rowLayout.implicitWidth + Appearance.padding.small * 2 - Rectangle { + CustomRect { id: backgroundRect color: DynamicColors.tPalette.m3surfaceContainer implicitHeight: 22 radius: height / 2 - Behavior on color { - CAnim { - } - } - anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter } + } - RowLayout { - id: rowLayout + RowLayout { + id: rowLayout - anchors.centerIn: parent - spacing: 6 + anchors.centerIn: parent + spacing: 6 - MaterialIcon { - Layout.alignment: Qt.AlignVCenter - color: DynamicColors.palette.m3onSurface - font.pointSize: 14 - text: "memory_alt" - } + MaterialIcon { + Layout.alignment: Qt.AlignVCenter + color: DynamicColors.palette.m3onSurface + font.pointSize: 14 + text: "memory_alt" + } - Resource { - Layout.alignment: Qt.AlignVCenter - mainColor: DynamicColors.palette.m3primary - percentage: ResourceUsage.memoryUsedPercentage - warningThreshold: 95 - } + Resource { + Layout.alignment: Qt.AlignVCenter + mainColor: DynamicColors.palette.m3primary + percentage: ResourceUsage.memoryUsedPercentage + warningThreshold: 95 + } - MaterialIcon { - Layout.alignment: Qt.AlignVCenter - color: DynamicColors.palette.m3onSurface - font.pointSize: 14 - text: "memory" - } + MaterialIcon { + Layout.alignment: Qt.AlignVCenter + color: DynamicColors.palette.m3onSurface + font.pointSize: 14 + text: "memory" + } - Resource { - mainColor: DynamicColors.palette.m3secondary - percentage: ResourceUsage.cpuUsage - warningThreshold: 80 - } + Resource { + mainColor: DynamicColors.palette.m3secondary + percentage: ResourceUsage.cpuUsage + warningThreshold: 80 + } - MaterialIcon { - Layout.alignment: Qt.AlignVCenter - color: DynamicColors.palette.m3onSurface - font.pointSize: 14 - text: "gamepad" - } + MaterialIcon { + Layout.alignment: Qt.AlignVCenter + color: DynamicColors.palette.m3onSurface + font.pointSize: 14 + text: "gamepad" + } - Resource { - mainColor: DynamicColors.palette.m3tertiary - percentage: ResourceUsage.gpuUsage - } + Resource { + mainColor: DynamicColors.palette.m3tertiary + percentage: ResourceUsage.gpuUsage + } - MaterialIcon { - Layout.alignment: Qt.AlignVCenter - color: DynamicColors.palette.m3onSurface - font.pointSize: 14 - text: "developer_board" - } + MaterialIcon { + Layout.alignment: Qt.AlignVCenter + color: DynamicColors.palette.m3onSurface + font.pointSize: 14 + text: "developer_board" + } - Resource { - mainColor: DynamicColors.palette.m3primary - percentage: ResourceUsage.gpuMemUsage - } + Resource { + mainColor: DynamicColors.palette.m3primary + percentage: ResourceUsage.gpuMemUsage } } } diff --git a/Modules/UpdatesWidget.qml b/Modules/UpdatesWidget.qml index 7bb4b51..c0141a8 100644 --- a/Modules/UpdatesWidget.qml +++ b/Modules/UpdatesWidget.qml @@ -27,25 +27,17 @@ Item { id: contentRow anchors.centerIn: parent - implicitHeight: 22 spacing: Appearance.spacing.small MaterialIcon { - Layout.alignment: Qt.AlignVCenter font.pointSize: 14 text: "package_2" } - TextMetrics { - id: textMetrics - - text: root.countUpdates - } - CustomText { color: root.textColor font.pointSize: 12 - text: textMetrics.text + text: root.countUpdates } } } diff --git a/Plugins/ZShell/Services/cavaprovider.cpp b/Plugins/ZShell/Services/cavaprovider.cpp index 85a456f..8715f39 100644 --- a/Plugins/ZShell/Services/cavaprovider.cpp +++ b/Plugins/ZShell/Services/cavaprovider.cpp @@ -4,7 +4,6 @@ #include "audioprovider.hpp" #include #include -#include #include #include @@ -40,35 +39,19 @@ void CavaProcessor::process() { values[i] = std::clamp(m_out[i], 0.0, 1.0); } - // --- spectral contrast (removes the "everything rises together" effect) - // QVector tmp = values; - // auto* b = tmp.data(); - // auto* e = b + tmp.size(); - // - // auto pct = [&](double p) -> double { - // const qsizetype n = tmp.size(); - // if (n <= 0) return 0.0; - // - // // p in [0,1] -> index in [0, n-1] - // const double pos = p * double(n - 1); - // qsizetype k = static_cast(std::llround(pos)); - // k = std::clamp(k, 0, n - 1); - // - // auto first = tmp.begin(); - // auto nth = first + k; - // std::nth_element(first, nth, tmp.end()); - // return *nth; - // }; - // - // const double floor = pct(0.25); - // const double ceil = pct(0.95); - // const double range = std::max(1e-6, ceil - floor); - // - // const double gamma = 1.6; // 1.3..2.2 range; higher = more contrast + // Left to right pass + // const double inv = 1.0 / 1.5; + // double carry = 0.0; // for (int i = 0; i < m_bars; ++i) { - // double x = (values[i] - floor) / range; - // x = std::clamp(x, 0.0, 1.0); - // values[i] = std::pow(x, gamma); + // carry = std::max(m_out[i], carry * inv); + // values[i] = carry; + // } + // + // // Right to left pass and combine + // carry = 0.0; + // for (int i = m_bars - 1; i >= 0; --i) { + // carry = std::max(m_out[i], carry * inv); + // values[i] = std::max(values[i], carry); // } // Update values diff --git a/nix/default.nix b/nix/default.nix index 15f4d8d..83edc51 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,4 +1,6 @@ { + fftw, + libcava, rev, lib, stdenv, @@ -23,7 +25,8 @@ pkg-config, pythonEnv, zshell-cli, -}: let +}: +let version = "1.0.0"; runtimeDeps = [ @@ -74,66 +77,66 @@ libqalculate pipewire aubio + libcava + fftw ]; dontWrapQtApps = true; - cmakeFlags = - [ - (lib.cmakeFeature "ENABLE_MODULES" "plugin") - (lib.cmakeFeature "INSTALL_QMLDIR" qt6.qtbase.qtQmlPrefix) - ] - ++ cmakeVersionFlags; + cmakeFlags = [ + (lib.cmakeFeature "ENABLE_MODULES" "plugin") + (lib.cmakeFeature "INSTALL_QMLDIR" qt6.qtbase.qtQmlPrefix) + ] + ++ cmakeVersionFlags; }; in - stdenv.mkDerivation { - inherit version cmakeBuildType; - pname = "zshell"; - src = ./..; +stdenv.mkDerivation { + inherit version cmakeBuildType; + pname = "zshell"; + src = ./..; - nativeBuildInputs = [ - cmake - ninja - makeWrapper - qt6.wrapQtAppsHook - ]; - buildInputs = [ - quickshell - plugin - qt6.qtbase - qt6.qtwayland - ]; - propagatedBuildInputs = runtimeDeps; + nativeBuildInputs = [ + cmake + ninja + makeWrapper + qt6.wrapQtAppsHook + ]; + buildInputs = [ + quickshell + plugin + qt6.qtbase + qt6.qtwayland + ]; + propagatedBuildInputs = runtimeDeps; - cmakeFlags = - [ - (lib.cmakeFeature "ENABLE_MODULES" "shell") - (lib.cmakeFeature "INSTALL_QSCONFDIR" "${placeholder "out"}/share/ZShell") - ] - ++ cmakeVersionFlags; + cmakeFlags = [ + (lib.cmakeFeature "ENABLE_MODULES" "shell") + (lib.cmakeFeature "INSTALL_QSCONFDIR" "${placeholder "out"}/share/ZShell") + ] + ++ cmakeVersionFlags; - prePatch = '' - substituteInPlace shell.qml \ - --replace-fail 'ShellRoot {' 'ShellRoot { settings.watchFiles: false' - ''; + prePatch = '' + substituteInPlace shell.qml \ + --replace-fail 'ShellRoot {' 'ShellRoot { settings.watchFiles: false' + ''; - postInstall = '' - makeWrapper ${quickshell}/bin/qs $out/bin/zshell \ - --prefix PATH : "${lib.makeBinPath runtimeDeps}" \ - --set FONTCONFIG_FILE "${fontconfig}" \ - --add-flags "-p $out/share/ZShell" + postInstall = '' + makeWrapper ${quickshell}/bin/qs $out/bin/zshell \ + --prefix PATH : "${lib.makeBinPath runtimeDeps}" \ + --set FONTCONFIG_FILE "${fontconfig}" \ + --add-flags "-p $out/share/ZShell" - echo "$out" - mkdir -p $out/lib - ''; + echo "$out" + mkdir -p $out/lib + ''; - passthru = { - inherit plugin; - }; + passthru = { + inherit plugin; + }; - meta = { - description = "A very segsy desktop shell"; - homepage = "https://github.com/Zacharias-Brohn/z-bar-qt"; - license = lib.licenses.gpl3Only; - mainProgram = "zshell"; - }; - } + meta = { + description = "A very segsy desktop shell"; + homepage = "https://github.com/Zacharias-Brohn/z-bar-qt"; + license = lib.licenses.gpl3Only; + mainProgram = "zshell"; + }; +}