diff options
| author | Kaj <kajgiesbers@hotmail.com> | 2025-07-26 06:16:52 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-26 14:16:52 +1000 |
| commit | 81b8ff31d845d31bc8116c6a3cb3702c8a85be70 (patch) | |
| tree | f627a90f016cfb5af80e8d385ced6d4376842a69 /modules/bar | |
| parent | feat: improve network popout (#268) (diff) | |
| download | caelestia-shell-81b8ff31d845d31bc8116c6a3cb3702c8a85be70.tar.gz caelestia-shell-81b8ff31d845d31bc8116c6a3cb3702c8a85be70.tar.bz2 caelestia-shell-81b8ff31d845d31bc8116c6a3cb3702c8a85be70.zip | |
config: enable/disable status icons (#243)
* Adds ability to disable/enable status icons
Improves the hover calculations so that it's not hardcoded. This should make it easier to add/remove status icons.
Also adds an optional audio status icon.
* status: move config to barconfig
* fixes
* fix merge
* loader icons
* fix audio popout
---------
Co-authored-by: Kaj Giesbers <kajgiesbers@gmail.com>
Co-authored-by: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>
Diffstat (limited to 'modules/bar')
| -rw-r--r-- | modules/bar/Bar.qml | 36 | ||||
| -rw-r--r-- | modules/bar/components/StatusIcons.qml | 204 | ||||
| -rw-r--r-- | modules/bar/popouts/Audio.qml | 57 | ||||
| -rw-r--r-- | modules/bar/popouts/Content.qml | 7 |
4 files changed, 207 insertions, 97 deletions
diff --git a/modules/bar/Bar.qml b/modules/bar/Bar.qml index 9c84793..8d6f2bb 100644 --- a/modules/bar/Bar.qml +++ b/modules/bar/Bar.qml @@ -23,14 +23,24 @@ Item { const th = tray.implicitHeight; const trayItems = tray.items; - const n = statusIconsInner.network; - const ny = statusIcons.y + statusIconsInner.y + n.y - spacing / 2; + // Check status icons hover areas + let statusIconFound = false; + for (const area of statusIconsInner.hoverAreas) { + if (!area.enabled) + continue; - const bls = statusIcons.y + statusIconsInner.y + statusIconsInner.bs - spacing / 2; - const ble = statusIcons.y + statusIconsInner.y + statusIconsInner.be + spacing / 2; + const item = area.item; + const itemY = statusIcons.y + statusIconsInner.y + item.y - spacing / 2; + const itemHeight = item.implicitHeight + spacing; - const b = statusIconsInner.battery; - const by = statusIcons.y + statusIconsInner.y + b.y - spacing / 2; + if (y >= itemY && y <= itemY + itemHeight) { + popouts.currentName = area.name; + popouts.currentCenter = Qt.binding(() => statusIcons.y + statusIconsInner.y + item.y + item.implicitHeight / 2); + popouts.hasCurrent = true; + statusIconFound = true; + break; + } + } if (y >= awy && y <= awy + aw.implicitHeight) { popouts.currentName = "activewindow"; @@ -43,19 +53,7 @@ Item { popouts.currentName = `traymenu${index}`; popouts.currentCenter = Qt.binding(() => tray.y + item.y + item.implicitHeight / 2); popouts.hasCurrent = true; - } else if (y >= ny && y <= ny + n.implicitHeight + spacing) { - popouts.currentName = "network"; - popouts.currentCenter = Qt.binding(() => statusIcons.y + statusIconsInner.y + n.y + n.implicitHeight / 2); - popouts.hasCurrent = true; - } else if (y >= bls && y <= ble) { - popouts.currentName = "bluetooth"; - popouts.currentCenter = Qt.binding(() => statusIcons.y + statusIconsInner.y + statusIconsInner.bs + (statusIconsInner.be - statusIconsInner.bs) / 2); - popouts.hasCurrent = true; - } else if (y >= by && y <= by + b.implicitHeight + spacing) { - popouts.currentName = "battery"; - popouts.currentCenter = Qt.binding(() => statusIcons.y + statusIconsInner.y + b.y + b.implicitHeight / 2); - popouts.hasCurrent = true; - } else { + } else if (!statusIconFound) { popouts.hasCurrent = false; } } diff --git a/modules/bar/components/StatusIcons.qml b/modules/bar/components/StatusIcons.qml index 971ec4d..45f943e 100644 --- a/modules/bar/components/StatusIcons.qml +++ b/modules/bar/components/StatusIcons.qml @@ -1,3 +1,5 @@ +pragma ComponentBehavior: Bound + import qs.widgets import qs.services import qs.utils @@ -6,117 +8,163 @@ import Quickshell import Quickshell.Bluetooth import Quickshell.Services.UPower import QtQuick +import QtQuick.Layouts Item { id: root property color colour: Colours.palette.m3secondary - readonly property Item network: network - readonly property real bs: bluetooth.y - readonly property real be: repeater.count > 0 ? devices.y + devices.implicitHeight : bluetooth.y + bluetooth.implicitHeight - readonly property Item battery: battery + readonly property list<var> hoverAreas: [ + { + name: "audio", + item: audioIcon, + enabled: Config.bar.status.showAudio + }, + { + name: "network", + item: networkIcon, + enabled: Config.bar.status.showNetwork + }, + { + name: "bluetooth", + item: bluetoothGroup, + enabled: Config.bar.status.showBluetooth + }, + { + name: "battery", + item: batteryIcon, + enabled: Config.bar.status.showBattery + } + ] clip: true - implicitWidth: Math.max(network.implicitWidth, bluetooth.implicitWidth, devices.implicitWidth, battery.implicitWidth) - implicitHeight: network.implicitHeight + bluetooth.implicitHeight + bluetooth.anchors.topMargin + (repeater.count > 0 ? devices.implicitHeight + devices.anchors.topMargin : 0) + battery.implicitHeight + battery.anchors.topMargin + implicitWidth: iconColumn.implicitWidth + implicitHeight: iconColumn.implicitHeight - MaterialIcon { - id: network - - animate: true - text: Network.active ? Icons.getNetworkIcon(Network.active.strength ?? 0) : "wifi_off" - color: root.colour + ColumnLayout { + id: iconColumn anchors.horizontalCenter: parent.horizontalCenter - } - - MaterialIcon { - id: bluetooth - - anchors.horizontalCenter: network.horizontalCenter - anchors.top: network.bottom - anchors.topMargin: Appearance.spacing.smaller / 2 + spacing: Appearance.spacing.smaller / 2 - animate: true - text: Bluetooth.defaultAdapter?.enabled ? "bluetooth" : "bluetooth_disabled" - color: root.colour - } + // Audio icon + Loader { + id: audioIcon - Column { - id: devices + asynchronous: true + active: Config.bar.status.showAudio + visible: active - anchors.horizontalCenter: bluetooth.horizontalCenter - anchors.top: bluetooth.bottom - anchors.topMargin: Appearance.spacing.smaller / 2 + sourceComponent: MaterialIcon { + animate: true + text: Audio.muted ? "volume_off" : Audio.volume >= 0.66 ? "volume_up" : Audio.volume >= 0.33 ? "volume_down" : "volume_mute" + color: root.colour + } + } - spacing: Appearance.spacing.smaller / 2 + // Network icon + Loader { + id: networkIcon - Repeater { - id: repeater + asynchronous: true + active: Config.bar.status.showNetwork + visible: active - model: ScriptModel { - values: Bluetooth.devices.values.filter(d => d.state !== BluetoothDeviceState.Disconnected) + sourceComponent: MaterialIcon { + animate: true + text: Network.active ? Icons.getNetworkIcon(Network.active.strength ?? 0) : "wifi_off" + color: root.colour } + } - MaterialIcon { - id: device + // Bluetooth section (grouped for hover area) + Loader { + id: bluetoothGroup - required property BluetoothDevice modelData + asynchronous: true + active: Config.bar.status.showBluetooth + visible: active - animate: true - text: Icons.getBluetoothIcon(modelData.icon) - color: root.colour - fill: 1 + sourceComponent: ColumnLayout { + spacing: Appearance.spacing.smaller / 2 - SequentialAnimation on opacity { - running: device.modelData.state !== BluetoothDeviceState.Connected - alwaysRunToEnd: true - loops: Animation.Infinite + // Bluetooth icon + MaterialIcon { + animate: true + text: Bluetooth.defaultAdapter?.enabled ? "bluetooth" : "bluetooth_disabled" + color: root.colour + } - Anim { - from: 1 - to: 0 - easing.bezierCurve: Appearance.anim.curves.standardAccel + // Connected bluetooth devices + Repeater { + model: ScriptModel { + values: Bluetooth.devices.values.filter(d => d.state !== BluetoothDeviceState.Disconnected) } - Anim { - from: 0 - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel + + MaterialIcon { + id: device + + required property BluetoothDevice modelData + + animate: true + text: Icons.getBluetoothIcon(modelData.icon) + color: root.colour + fill: 1 + + SequentialAnimation on opacity { + running: device.modelData.state !== BluetoothDeviceState.Connected + alwaysRunToEnd: true + loops: Animation.Infinite + + Anim { + from: 1 + to: 0 + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + Anim { + from: 0 + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + } } } } } - } - MaterialIcon { - id: battery + // Battery icon + Loader { + id: batteryIcon - anchors.horizontalCenter: devices.horizontalCenter - anchors.top: repeater.count > 0 ? devices.bottom : bluetooth.bottom - anchors.topMargin: Appearance.spacing.smaller / 2 + asynchronous: true + active: Config.bar.status.showBattery + visible: active - animate: true - text: { - if (!UPower.displayDevice.isLaptopBattery) { - if (PowerProfiles.profile === PowerProfile.PowerSaver) - return "energy_savings_leaf"; - if (PowerProfiles.profile === PowerProfile.Performance) - return "rocket_launch"; - return "balance"; - } + sourceComponent: MaterialIcon { + animate: true + text: { + if (!UPower.displayDevice.isLaptopBattery) { + if (PowerProfiles.profile === PowerProfile.PowerSaver) + return "energy_savings_leaf"; + if (PowerProfiles.profile === PowerProfile.Performance) + return "rocket_launch"; + return "balance"; + } - const perc = UPower.displayDevice.percentage; - const charging = !UPower.onBattery; - if (perc === 1) - return charging ? "battery_charging_full" : "battery_full"; - let level = Math.floor(perc * 7); - if (charging && (level === 4 || level === 1)) - level--; - return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`; + const perc = UPower.displayDevice.percentage; + const charging = !UPower.onBattery; + if (perc === 1) + return charging ? "battery_charging_full" : "battery_full"; + let level = Math.floor(perc * 7); + if (charging && (level === 4 || level === 1)) + level--; + return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`; + } + color: !UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? root.colour : Colours.palette.m3error + fill: 1 + } } - color: !UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? root.colour : Colours.palette.m3error - fill: 1 } Behavior on implicitHeight { diff --git a/modules/bar/popouts/Audio.qml b/modules/bar/popouts/Audio.qml new file mode 100644 index 0000000..28667ec --- /dev/null +++ b/modules/bar/popouts/Audio.qml @@ -0,0 +1,57 @@ +import qs.widgets +import qs.services +import qs.config +import QtQuick.Layouts +import Quickshell + +ColumnLayout { + id: root + + required property var wrapper + + spacing: Appearance.spacing.normal + + VerticalSlider { + id: volumeSlider + + icon: { + if (Audio.muted) + return "no_sound"; + if (value >= 0.5) + return "volume_up"; + if (value > 0) + return "volume_down"; + return "volume_mute"; + } + + value: Audio.volume + onMoved: Audio.setVolume(value) + + implicitWidth: Config.osd.sizes.sliderWidth + implicitHeight: Config.osd.sizes.sliderHeight + } + + StyledRect { + id: pavuButton + + implicitWidth: implicitHeight + implicitHeight: icon.implicitHeight + Appearance.padding.small * 2 + + radius: Appearance.rounding.normal + color: Colours.palette.m3surfaceContainer + + StateLayer { + function onClicked(): void { + root.wrapper.hasCurrent = false; + Quickshell.execDetached(["pavucontrol"]); + } + } + + MaterialIcon { + id: icon + + anchors.centerIn: parent + text: "settings" + } + } +} diff --git a/modules/bar/popouts/Content.qml b/modules/bar/popouts/Content.qml index 42e138d..5b396a9 100644 --- a/modules/bar/popouts/Content.qml +++ b/modules/bar/popouts/Content.qml @@ -45,6 +45,13 @@ Item { source: "Battery.qml" } + Popout { + name: "audio" + sourceComponent: Audio { + wrapper: root.wrapper + } + } + Repeater { model: ScriptModel { values: [...SystemTray.items.values] |