extract BluetoothApplet, fold BluetoothMenu into BluetoothModule with hover+pin

This commit is contained in:
Damocles 2026-04-22 21:54:20 +02:00
commit 5da7005ce7
4 changed files with 79 additions and 73 deletions

View file

@ -0,0 +1,110 @@
import QtQuick
import "../services" as S
Column {
id: root
required property color accentColor
Repeater {
model: S.BluetoothService.devices
delegate: Item {
id: entry
required property var modelData
required property int index
readonly property bool _pending: S.BluetoothService.pendingMac === entry.modelData.mac
width: root.width
height: 32
Rectangle {
anchors.fill: parent
anchors.leftMargin: 4
anchors.rightMargin: 4
color: entryHover.hovered ? S.Theme.base02 : "transparent"
radius: S.Theme.radius
}
Text {
id: btIcon
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "\uF294"
color: entry._pending || entry.modelData.connected ? root.accentColor : S.Theme.base04
font.pixelSize: S.Theme.fontSize + 1
font.family: S.Theme.iconFontFamily
}
Text {
anchors.left: btIcon.right
anchors.leftMargin: 8
anchors.right: _statusLabel.left
anchors.rightMargin: 4
anchors.verticalCenter: parent.verticalCenter
text: entry.modelData.name
color: entry._pending || entry.modelData.connected ? root.accentColor : S.Theme.base05
font.pixelSize: S.Theme.fontSize
font.family: S.Theme.fontFamily
font.bold: entry.modelData.connected || entry._pending
elide: Text.ElideRight
}
Text {
id: _statusLabel
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: entry._pending ? (entry.modelData.connected ? "disconnecting..." : "connecting...") : (entry.modelData.battery >= 0 ? entry.modelData.battery + "%" : "")
color: S.Theme.base04
font.pixelSize: S.Theme.fontSize - 1
font.family: S.Theme.fontFamily
font.italic: entry._pending
width: text ? implicitWidth : 0
}
// Pulse animation while pending
SequentialAnimation on opacity {
loops: Animation.Infinite
running: entry._pending
NumberAnimation {
to: 0.5
duration: 400
easing.type: Easing.InOutQuad
}
NumberAnimation {
to: 1
duration: 400
easing.type: Easing.InOutQuad
}
onRunningChanged: if (!running)
entry.opacity = 1
}
HoverHandler {
id: entryHover
cursorShape: Qt.PointingHandCursor
}
TapHandler {
onTapped: {
if (!entry._pending)
S.BluetoothService.toggleDevice(entry.modelData.mac, !entry.modelData.connected);
}
}
}
}
Text {
visible: S.BluetoothService.devices.length === 0
width: root.width
height: 32
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: S.BluetoothService.enabled ? "No paired devices" : "Bluetooth is off"
color: S.Theme.base04
font.pixelSize: S.Theme.fontSize
font.family: S.Theme.fontFamily
}
}

View file

@ -183,9 +183,7 @@ PanelWindow {
M.NetworkModule { M.NetworkModule {
visible: S.Modules.network.enable visible: S.Modules.network.enable
} }
M.BluetoothModule { M.BluetoothModule {}
bar: bar
}
} }
// Controls // Controls

View file

@ -2,18 +2,30 @@ import QtQuick
import Quickshell import Quickshell
import "." as M import "." as M
import "../services" as S import "../services" as S
import "../applets" as C
M.BarSection { M.BarSection {
id: root id: root
spacing: S.Theme.moduleSpacing spacing: S.Theme.moduleSpacing
opacity: S.Modules.bluetooth.enable && S.BluetoothService.state !== "unavailable" ? 1 : 0 opacity: S.Modules.bluetooth.enable && S.BluetoothService.state !== "unavailable" ? 1 : 0
visible: opacity > 0 visible: opacity > 0
tooltip: { tooltip: ""
if (S.BluetoothService.state === "off")
return "Bluetooth: off"; property bool _pinned: false
if (S.BluetoothService.state === "connected") readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered
return "Bluetooth: " + S.BluetoothService.device + (S.BluetoothService.batteryPct >= 0 ? "\nBattery: " + S.BluetoothService.batteryPct + "%" : ""); readonly property bool _showPanel: _anyHover || _pinned
return "Bluetooth: on";
on_AnyHoverChanged: {
if (_anyHover)
_unpinTimer.stop();
else if (_pinned)
_unpinTimer.start();
}
Timer {
id: _unpinTimer
interval: 500
onTriggered: root._pinned = false
} }
M.BarIcon { M.BarIcon {
@ -21,10 +33,7 @@ M.BarSection {
color: S.BluetoothService.state === "off" ? S.Theme.base04 : root.accentColor color: S.BluetoothService.state === "off" ? S.Theme.base04 : root.accentColor
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
TapHandler { TapHandler {
onTapped: { onTapped: root._pinned = !root._pinned
M.FlyoutState.visible = false;
btLoader.active = true;
}
} }
} }
M.BarLabel { M.BarLabel {
@ -32,23 +41,61 @@ M.BarSection {
label: S.BluetoothService.device + (S.BluetoothService.batteryPct >= 0 ? " " + S.BluetoothService.batteryPct + "%" : "") label: S.BluetoothService.device + (S.BluetoothService.batteryPct >= 0 ? " " + S.BluetoothService.batteryPct + "%" : "")
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
TapHandler { TapHandler {
onTapped: { onTapped: root._pinned = !root._pinned
M.FlyoutState.visible = false;
btLoader.active = true;
}
} }
} }
required property var bar M.HoverPanel {
id: hoverPanel
showPanel: root._showPanel
screen: QsWindow.window?.screen ?? null
anchorItem: root
accentColor: root.accentColor
panelNamespace: "nova-bluetooth"
panelTitle: "Bluetooth"
contentWidth: 250
titleActionsComponent: Component {
Item {
width: 20
height: 20
LazyLoader { Text {
id: btLoader anchors.centerIn: parent
active: false text: "\uF011"
M.BluetoothMenu { color: S.BluetoothService.enabled ? hoverPanel.accentColor : S.Theme.base04
font.pixelSize: S.Theme.fontSize
font.family: S.Theme.iconFontFamily
Behavior on color {
ColorAnimation {
duration: 100
}
}
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
TapHandler {
onTapped: S.BluetoothService.setPower(!S.BluetoothService.enabled)
}
}
}
onVisibleChanged: if (visible)
S.BluetoothService.refresh()
Connections {
target: S.BluetoothService
function onDevicesChanged() {
hoverPanel.keepOpen(500);
}
}
C.BluetoothApplet {
width: hoverPanel.contentWidth
accentColor: root.accentColor accentColor: root.accentColor
screen: QsWindow.window?.screen ?? null
anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)
onDismissed: btLoader.active = false
} }
} }
} }

View file

@ -8,7 +8,6 @@ BarIcon 1.0 BarIcon.qml
BarLabel 1.0 BarLabel.qml BarLabel 1.0 BarLabel.qml
BarSection 1.0 BarSection.qml BarSection 1.0 BarSection.qml
BatteryModule 1.0 BatteryModule.qml BatteryModule 1.0 BatteryModule.qml
BluetoothMenu 1.0 BluetoothMenu.qml
BluetoothModule 1.0 BluetoothModule.qml BluetoothModule 1.0 BluetoothModule.qml
ClockModule 1.0 ClockModule.qml ClockModule 1.0 ClockModule.qml
CpuModule 1.0 CpuModule.qml CpuModule 1.0 CpuModule.qml