diff --git a/shell/applets/qmldir b/shell/applets/qmldir index 1cfc65a..dc17e29 100644 --- a/shell/applets/qmldir +++ b/shell/applets/qmldir @@ -8,7 +8,6 @@ GpuApplet 1.0 GpuApplet.qml HexWaveBackground 1.0 HexWaveBackground.qml MemoryApplet 1.0 MemoryApplet.qml MprisApplet 1.0 MprisApplet.qml -NetworkApplet 1.0 NetworkApplet.qml TemperatureApplet 1.0 TemperatureApplet.qml VolumeApplet 1.0 VolumeApplet.qml # keep-sorted end diff --git a/shell/lock/LockClock.qml b/shell/lock/LockClock.qml index 05d0b40..279bb2b 100644 --- a/shell/lock/LockClock.qml +++ b/shell/lock/LockClock.qml @@ -10,12 +10,11 @@ Item { readonly property real _fontSize: Math.max(48, screenHeight * 0.28) - // Appear as wave reaches the clock (left edge), latch once fully revealed. - // With reducedMotion the wave never runs, so show immediately. + // Appear as wave reaches the clock (left edge), latch once fully revealed property bool _revealed: false on_RawProgressChanged: if (_rawProgress >= 1) _revealed = true - readonly property real _rawProgress: S.Theme.reducedMotion ? 1 : Math.max(0, Math.min(1, wavePhase / 300)) + readonly property real _rawProgress: Math.max(0, Math.min(1, wavePhase / 300)) readonly property real _progress: (_revealed ? 1 : _rawProgress) * unlockFade opacity: _progress property real _slideX: (1 - _progress) * -80 diff --git a/shell/lock/LockWidgets.qml b/shell/lock/LockWidgets.qml index 90f105b..516a145 100644 --- a/shell/lock/LockWidgets.qml +++ b/shell/lock/LockWidgets.qml @@ -12,12 +12,11 @@ Item { property real screenWidth: 0 property real unlockFade: 1 - // Fly in when wave exits the right edge, latch once fully revealed. - // With reducedMotion the wave never runs, so show immediately. + // Fly in when wave exits the right edge, latch once fully revealed property bool _revealed: false on_RawProgressChanged: if (_rawProgress >= 1) _revealed = true - readonly property real _rawProgress: S.Theme.reducedMotion ? 1 : (screenWidth > 0 ? Math.max(0, Math.min(1, (wavePhase - screenWidth) / 500)) : 0) + readonly property real _rawProgress: screenWidth > 0 ? Math.max(0, Math.min(1, (wavePhase - screenWidth) / 500)) : 0 readonly property real _progress: (_revealed ? 1 : _rawProgress) * unlockFade opacity: _progress property real _slideX: (1 - _progress) * 80 diff --git a/shell/modules/Bar.qml b/shell/modules/Bar.qml index fd86f92..343b682 100644 --- a/shell/modules/Bar.qml +++ b/shell/modules/Bar.qml @@ -181,9 +181,12 @@ PanelWindow { // Connectivity M.BarGroup { M.NetworkModule { + bar: bar visible: S.Modules.network.enable } - M.BluetoothModule {} + M.BluetoothModule { + bar: bar + } } // Controls diff --git a/shell/applets/BluetoothApplet.qml b/shell/modules/BluetoothMenu.qml similarity index 56% rename from shell/applets/BluetoothApplet.qml rename to shell/modules/BluetoothMenu.qml index 64d2584..f348e90 100644 --- a/shell/applets/BluetoothApplet.qml +++ b/shell/modules/BluetoothMenu.qml @@ -1,10 +1,46 @@ import QtQuick +import Quickshell +import "." as M import "../services" as S -Column { - id: root +M.HoverPanel { + id: menuWindow - required property color accentColor + contentWidth: 250 + panelNamespace: "nova-bluetooth" + popupMode: true + panelTitle: "Bluetooth" + titleActionsComponent: Component { + Item { + width: 20 + height: 20 + + Text { + anchors.centerIn: parent + text: "\uF011" + color: S.BluetoothService.enabled ? menuWindow.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() Repeater { model: S.BluetoothService.devices @@ -14,9 +50,7 @@ Column { required property var modelData required property int index - readonly property bool _pending: S.BluetoothService.pendingMac === entry.modelData.mac - - width: root.width + width: menuWindow.contentWidth height: 32 Rectangle { @@ -33,7 +67,7 @@ Column { anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: "\uF294" - color: entry._pending || entry.modelData.connected ? root.accentColor : S.Theme.base04 + color: entry.modelData.connected ? menuWindow.accentColor : S.Theme.base04 font.pixelSize: S.Theme.fontSize + 1 font.family: S.Theme.iconFontFamily } @@ -41,46 +75,27 @@ Column { Text { anchors.left: btIcon.right anchors.leftMargin: 8 - anchors.right: _statusLabel.left + anchors.right: batLabel.left anchors.rightMargin: 4 anchors.verticalCenter: parent.verticalCenter text: entry.modelData.name - color: entry._pending || entry.modelData.connected ? root.accentColor : S.Theme.base05 + color: entry.modelData.connected ? menuWindow.accentColor : S.Theme.base05 font.pixelSize: S.Theme.fontSize font.family: S.Theme.fontFamily - font.bold: entry.modelData.connected || entry._pending + font.bold: entry.modelData.connected elide: Text.ElideRight } Text { - id: _statusLabel + id: batLabel 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 + "%" : "") + text: 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 + width: entry.modelData.battery >= 0 ? implicitWidth : 0 } HoverHandler { @@ -89,8 +104,8 @@ Column { } TapHandler { onTapped: { - if (!entry._pending) - S.BluetoothService.toggleDevice(entry.modelData.mac, !entry.modelData.connected); + S.BluetoothService.toggleDevice(entry.modelData.mac, !entry.modelData.connected); + menuWindow.keepOpen(500); } } } @@ -98,7 +113,7 @@ Column { Text { visible: S.BluetoothService.devices.length === 0 - width: root.width + width: menuWindow.contentWidth height: 32 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter diff --git a/shell/modules/BluetoothModule.qml b/shell/modules/BluetoothModule.qml index d107f71..81e715e 100644 --- a/shell/modules/BluetoothModule.qml +++ b/shell/modules/BluetoothModule.qml @@ -2,30 +2,18 @@ import QtQuick import Quickshell import "." as M import "../services" as S -import "../applets" as C M.BarSection { id: root spacing: S.Theme.moduleSpacing opacity: S.Modules.bluetooth.enable && S.BluetoothService.state !== "unavailable" ? 1 : 0 visible: opacity > 0 - tooltip: "" - - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false + tooltip: { + if (S.BluetoothService.state === "off") + return "Bluetooth: off"; + if (S.BluetoothService.state === "connected") + return "Bluetooth: " + S.BluetoothService.device + (S.BluetoothService.batteryPct >= 0 ? "\nBattery: " + S.BluetoothService.batteryPct + "%" : ""); + return "Bluetooth: on"; } M.BarIcon { @@ -33,7 +21,10 @@ M.BarSection { color: S.BluetoothService.state === "off" ? S.Theme.base04 : root.accentColor anchors.verticalCenter: parent.verticalCenter TapHandler { - onTapped: root._pinned = !root._pinned + onTapped: { + M.FlyoutState.visible = false; + btLoader.active = true; + } } } M.BarLabel { @@ -41,61 +32,23 @@ M.BarSection { label: S.BluetoothService.device + (S.BluetoothService.batteryPct >= 0 ? " " + S.BluetoothService.batteryPct + "%" : "") anchors.verticalCenter: parent.verticalCenter TapHandler { - onTapped: root._pinned = !root._pinned + onTapped: { + M.FlyoutState.visible = false; + btLoader.active = true; + } } } - 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 + required property var bar - Text { - anchors.centerIn: parent - text: "\uF011" - 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 + LazyLoader { + id: btLoader + active: false + M.BluetoothMenu { 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 } } } diff --git a/shell/applets/NetworkApplet.qml b/shell/modules/NetworkMenu.qml similarity index 67% rename from shell/applets/NetworkApplet.qml rename to shell/modules/NetworkMenu.qml index c2aa8e7..f9c656f 100644 --- a/shell/applets/NetworkApplet.qml +++ b/shell/modules/NetworkMenu.qml @@ -1,10 +1,45 @@ import QtQuick +import Quickshell +import "." as M import "../services" as S -Column { - id: root +M.HoverPanel { + id: menuWindow - required property color accentColor + contentWidth: 250 + panelNamespace: "nova-network" + panelTitle: "Wi-Fi" + titleActionsComponent: Component { + Item { + width: 20 + height: 20 + + Text { + anchors.centerIn: parent + text: "\uF011" + color: S.NetworkService.wifiEnabled ? menuWindow.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.NetworkService.setWifi(!S.NetworkService.wifiEnabled) + } + } + } + + onVisibleChanged: if (visible) + S.NetworkService.refresh() Repeater { model: S.NetworkService.networks @@ -14,7 +49,7 @@ Column { required property var modelData required property int index - width: root.width + width: menuWindow.contentWidth height: 32 Rectangle { @@ -31,7 +66,7 @@ Column { anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: entry.modelData.isWifi ? "\uF1EB" : "\uDB80\uDE00" - color: entry.modelData.active ? root.accentColor : S.Theme.base05 + color: entry.modelData.active ? menuWindow.accentColor : S.Theme.base05 font.pixelSize: S.Theme.fontSize + 1 font.family: S.Theme.iconFontFamily } @@ -43,7 +78,7 @@ Column { anchors.rightMargin: 4 anchors.verticalCenter: parent.verticalCenter text: entry.modelData.name - color: entry.modelData.active ? root.accentColor : S.Theme.base05 + color: entry.modelData.active ? menuWindow.accentColor : S.Theme.base05 font.pixelSize: S.Theme.fontSize font.family: S.Theme.fontFamily font.bold: entry.modelData.active @@ -72,6 +107,7 @@ Column { S.NetworkService.disconnectNetwork(entry.modelData.uuid); else S.NetworkService.connectNetwork(entry.modelData.uuid); + menuWindow.keepOpen(500); } } } @@ -79,7 +115,7 @@ Column { Text { visible: S.NetworkService.networks.length === 0 - width: root.width + width: menuWindow.contentWidth height: 32 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter diff --git a/shell/modules/NetworkModule.qml b/shell/modules/NetworkModule.qml index 67194e0..d37eff8 100644 --- a/shell/modules/NetworkModule.qml +++ b/shell/modules/NetworkModule.qml @@ -2,7 +2,6 @@ import QtQuick import Quickshell import "." as M import "../services" as S -import "../applets" as C M.BarSection { id: root @@ -11,23 +10,6 @@ M.BarSection { readonly property string state: S.NetworkService.state - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } - M.BarIcon { icon: { if (root.state === "wifi") @@ -40,71 +22,23 @@ M.BarSection { } color: root.state === "disconnected" ? S.Theme.base08 : root.accentColor anchors.verticalCenter: parent.verticalCenter - TapHandler { - onTapped: root._pinned = !root._pinned - } } M.BarLabel { visible: root.state === "wifi" label: S.NetworkService.essid color: root.state === "disconnected" ? S.Theme.base08 : root.accentColor anchors.verticalCenter: parent.verticalCenter - TapHandler { - onTapped: root._pinned = !root._pinned - } } - M.HoverPanel { - id: hoverPanel - showPanel: root._showPanel + required property var bar + + readonly property bool _anyHover: root._hovered || networkMenu.panelHovered + + M.NetworkMenu { + id: networkMenu + showPanel: root._anyHover screen: QsWindow.window?.screen ?? null anchorItem: root accentColor: root.accentColor - panelNamespace: "nova-network" - panelTitle: "Wi-Fi" - contentWidth: 250 - titleActionsComponent: Component { - Item { - width: 20 - height: 20 - - Text { - anchors.centerIn: parent - text: "\uF011" - color: S.NetworkService.wifiEnabled ? 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.NetworkService.setWifi(!S.NetworkService.wifiEnabled) - } - } - } - - onVisibleChanged: if (visible) - S.NetworkService.refresh() - - Connections { - target: S.NetworkService - function onNetworksChanged() { - hoverPanel.keepOpen(500); - } - } - - C.NetworkApplet { - width: hoverPanel.contentWidth - accentColor: root.accentColor - } } } diff --git a/shell/modules/qmldir b/shell/modules/qmldir index b4a55ee..c7e2c9f 100644 --- a/shell/modules/qmldir +++ b/shell/modules/qmldir @@ -8,6 +8,7 @@ BarIcon 1.0 BarIcon.qml BarLabel 1.0 BarLabel.qml BarSection 1.0 BarSection.qml BatteryModule 1.0 BatteryModule.qml +BluetoothMenu 1.0 BluetoothMenu.qml BluetoothModule 1.0 BluetoothModule.qml ClockModule 1.0 ClockModule.qml CpuModule 1.0 CpuModule.qml @@ -18,6 +19,7 @@ HoverPanel 1.0 HoverPanel.qml IdleInhibitorModule 1.0 IdleInhibitorModule.qml MemoryModule 1.0 MemoryModule.qml MprisModule 1.0 MprisModule.qml +NetworkMenu 1.0 NetworkMenu.qml NetworkModule 1.0 NetworkModule.qml NotifCard 1.0 NotifCard.qml NotifCenter 1.0 NotifCenter.qml diff --git a/shell/services/BluetoothService.qml b/shell/services/BluetoothService.qml index 608f1c6..e648538 100644 --- a/shell/services/BluetoothService.qml +++ b/shell/services/BluetoothService.qml @@ -26,11 +26,7 @@ QtObject { _powerProc.running = true; } - // MAC address of device currently connecting/disconnecting - property string pendingMac: "" - function toggleDevice(mac, connect) { - pendingMac = mac; _toggleProc._action = connect ? "connect" : "disconnect"; _toggleProc._mac = mac; _toggleProc.running = true; @@ -124,9 +120,7 @@ QtObject { property string _action: "" property string _mac: "" command: ["bluetoothctl", _action, _mac] - onRunningChanged: if (!running) { - root.pendingMac = ""; - root.refresh(); - } + onRunningChanged: if (!running) + root.refresh() } }