summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/BarConfig.qml8
-rw-r--r--modules/bar/Bar.qml36
-rw-r--r--modules/bar/components/StatusIcons.qml204
-rw-r--r--modules/bar/popouts/Audio.qml57
-rw-r--r--modules/bar/popouts/Content.qml7
5 files changed, 215 insertions, 97 deletions
diff --git a/config/BarConfig.qml b/config/BarConfig.qml
index c8a8bba..3f9f94d 100644
--- a/config/BarConfig.qml
+++ b/config/BarConfig.qml
@@ -5,6 +5,7 @@ JsonObject {
property bool showOnHover: true
property int dragThreshold: 20
property Workspaces workspaces: Workspaces {}
+ property Status status: Status {}
property Sizes sizes: Sizes {}
component Workspaces: JsonObject {
@@ -19,6 +20,13 @@ JsonObject {
property string activeLabel: "󰮯 "
}
+ component Status: JsonObject {
+ property bool showAudio: false
+ property bool showNetwork: true
+ property bool showBluetooth: true
+ property bool showBattery: true
+ }
+
component Sizes: JsonObject {
property int innerHeight: 30
property int windowPreviewSize: 400
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]