diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-clockwise-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-clockwise-filled.svg new file mode 100644 index 000000000..104ea8906 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-clockwise-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-clockwise.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-clockwise.svg new file mode 100644 index 000000000..f9554326f --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-clockwise.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-counterclockwise-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-counterclockwise-filled.svg new file mode 100644 index 000000000..a18d0d481 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-counterclockwise-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-counterclockwise.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-counterclockwise.svg new file mode 100644 index 000000000..68436b28a --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-counterclockwise.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/phone-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/phone-filled.svg new file mode 100644 index 000000000..819e1c161 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/phone-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/phone.svg b/dots/.config/quickshell/ii/assets/icons/fluent/phone.svg new file mode 100644 index 000000000..6bb5ffbe7 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/common/widgets/StyledSwitch.qml b/dots/.config/quickshell/ii/modules/common/widgets/StyledSwitch.qml index c701ff7cf..372ed9bd3 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/StyledSwitch.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/StyledSwitch.qml @@ -1,8 +1,6 @@ import qs.modules.common import QtQuick -import QtQuick.Layouts import QtQuick.Controls -import Qt5Compat.GraphicalEffects /** * Material 3 switch. See https://m3.material.io/components/switch/overview diff --git a/dots/.config/quickshell/ii/modules/ii/sidebarRight/bluetoothDevices/BluetoothDialog.qml b/dots/.config/quickshell/ii/modules/ii/sidebarRight/bluetoothDevices/BluetoothDialog.qml index 69e354704..8c56a8f12 100644 --- a/dots/.config/quickshell/ii/modules/ii/sidebarRight/bluetoothDevices/BluetoothDialog.qml +++ b/dots/.config/quickshell/ii/modules/ii/sidebarRight/bluetoothDevices/BluetoothDialog.qml @@ -44,20 +44,7 @@ WindowDialog { animateAppearance: false model: ScriptModel { - values: [...Bluetooth.devices.values].sort((a, b) => { - // Connected -> paired -> others - let conn = (b.connected - a.connected) || (b.paired - a.paired); - if (conn !== 0) return conn; - - // Ones with meaningful names before MAC addresses - const macRegex = /^([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}$/; - const aIsMac = macRegex.test(a.name); - const bIsMac = macRegex.test(b.name); - if (aIsMac !== bIsMac) return aIsMac ? 1 : -1; - - // Alphabetical by name - return a.name.localeCompare(b.name); - }) + values: BluetoothStatus.friendlyDeviceList } delegate: BluetoothDeviceItem { required property BluetoothDevice modelData diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ExpandableChoiceButton.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/ExpandableChoiceButton.qml new file mode 100644 index 000000000..33dae2240 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/ExpandableChoiceButton.qml @@ -0,0 +1,29 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.services.network +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks +import qs.modules.waffle.actionCenter + +WChoiceButton { + id: root + + property bool expanded: false + checked: expanded + clip: true + + horizontalPadding: 12 + verticalPadding: 6 + animateChoiceHighlight: false + + Behavior on implicitHeight { + animation: Looks.transition.resize.createObject(this) + } + onClicked: expanded = !expanded +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/FooterMoreButton.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/FooterMoreButton.qml new file mode 100644 index 000000000..678710b36 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/FooterMoreButton.qml @@ -0,0 +1,23 @@ +pragma ComponentBehavior: Bound +import QtQuick +import qs.modules.waffle.looks + +WButton { + id: root + implicitHeight: 40 + implicitWidth: contentItem.implicitWidth + 30 + color: "transparent" + + contentItem: Item { + id: contentItem + anchors.centerIn: parent + implicitWidth: buttonText.implicitWidth + + WText { + id: buttonText + anchors.centerIn: parent + color: root.pressed ? Looks.colors.fg : Looks.colors.fg1 + text: root.text + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothControl.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothControl.qml new file mode 100644 index 000000000..e3d62cd99 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothControl.qml @@ -0,0 +1,129 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import Quickshell.Bluetooth +import qs +import qs.services +import qs.services.network +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks +import qs.modules.waffle.actionCenter + +Item { + id: root + + Component.onCompleted: { + if (Bluetooth.defaultAdapter.enabled) Bluetooth.defaultAdapter.discovering = true; + } + Component.onDestruction: { + Bluetooth.defaultAdapter.discovering = false; + } + + PageColumn { + anchors.fill: parent + + BodyRectangle { + implicitHeight: 400 + implicitWidth: 50 + + ColumnLayout { + anchors.fill: parent + anchors.margins: 4 + spacing: 4 + + ColumnLayout { + implicitHeight: headerRow.implicitHeight + Layout.fillWidth: true + spacing: 0 + RowLayout { + Layout.fillWidth: true + spacing: 0 + HeaderRow { + id: headerRow + Layout.fillWidth: true + title: qsTr("Bluetooth") + } + WSwitch { + id: toggleSwitch + Layout.rightMargin: 12 + checked: Bluetooth.defaultAdapter?.enabled ?? false + onCheckedChanged: { + if (Bluetooth.defaultAdapter) { + Bluetooth.defaultAdapter.enabled = checked; + if (checked) { + Bluetooth.defaultAdapter.discovering = true; + } else { + Bluetooth.defaultAdapter.discovering = false; + } + } + } + } + } + FadeLoader { + Layout.leftMargin: -4 + Layout.rightMargin: -4 + Layout.fillWidth: true + shown: Bluetooth.defaultAdapter?.discovering ?? false + visible: true + sourceComponent: WIndeterminateProgressBar {} + } + } + + StyledListView { + id: listView + Layout.fillHeight: true + Layout.fillWidth: true + animateAppearance: false + + contentHeight: contentLayout.implicitHeight + contentWidth: width + clip: true + spacing: 4 + + model: ScriptModel { + values: BluetoothStatus.friendlyDeviceList + } + delegate: BluetoothDeviceItem { + required property BluetoothDevice modelData + device: modelData + width: ListView.view.width + } + } + } + } + + Separator {} + + FooterRectangle { + FooterMoreButton { + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + } + text: qsTr("More Bluetooth settings") + onClicked: { + Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "sidebarLeft", "toggle"]); + Quickshell.execDetached(["bash", "-c", Config.options.apps.bluetooth]); + } + } + WPanelFooterButton { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 12 + enabled: !Bluetooth.defaultAdapter?.discovering && Bluetooth.defaultAdapter?.enabled + + onClicked: { + Bluetooth.defaultAdapter.discovering = true; + } + + contentItem: FluentIcon { + icon: "arrow-counterclockwise" + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothDeviceItem.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothDeviceItem.qml new file mode 100644 index 000000000..ff672dde9 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothDeviceItem.qml @@ -0,0 +1,92 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Bluetooth +import qs +import qs.services +import qs.services.network +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.waffle.looks +import qs.modules.waffle.actionCenter + +ExpandableChoiceButton { + id: root + required property BluetoothDevice device + + contentItem: RowLayout { + id: contentItem + spacing: 20 + + // Device icon + FluentIcon { + Layout.topMargin: 4 + Layout.bottomMargin: 4 + Layout.alignment: Qt.AlignTop + icon: WIcons.bluetoothDeviceIcon(root?.device) + implicitSize: 18 + } + + ColumnLayout { + Layout.topMargin: 4 + Layout.bottomMargin: 4 + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + spacing: 1 + + Behavior on Layout.topMargin { + animation: Looks.transition.move.createObject(this) + } + + WText { + // Network name + Layout.fillWidth: true + elide: Text.ElideRight + font.pixelSize: Looks.font.pixelSize.large + text: root.device?.name || Translation.tr("Unknown device") + } + WText { // Status + id: statusText + Layout.fillWidth: true + elide: Text.ElideRight + font.pixelSize: Looks.font.pixelSize.large + color: Looks.colors.subfg + visible: root.device?.connected || root.expanded + Behavior on opacity { + animation: Looks.transition.opacity.createObject(this) + } + text: { + if (!root.device?.paired) + return Translation.tr("Not connected"); + let statusText = root.device?.connected ? Translation.tr("Connected") : Translation.tr("Paired"); + if (!root.device?.batteryAvailable) + return statusText; + statusText += ` • ${Math.round(root.device?.battery * 100)}%`; + return statusText; + } + } + + WButton { + Layout.alignment: Qt.AlignRight + horizontalAlignment: Text.AlignHCenter + visible: root.expanded + checked: !(root.device?.connected ?? false) + colBackground: Looks.colors.bg2 + colBackgroundHover: Looks.colors.bg2Hover + colBackgroundActive: Looks.colors.bg2Active + implicitHeight: 30 + implicitWidth: 148 + text: root.device?.connected ? Translation.tr("Disconnect") : Translation.tr("Connect") + + onClicked: { + if (root.device?.connected) { + root.device.disconnect(); + } else { + root.device.connect(); + } + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterTogglesDelegateChooser.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterTogglesDelegateChooser.qml index 7f7c2b47b..97c7977b7 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterTogglesDelegateChooser.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterTogglesDelegateChooser.qml @@ -10,6 +10,7 @@ import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter.wifi +import qs.modules.waffle.actionCenter.bluetooth DelegateChooser { id: root @@ -29,6 +30,9 @@ DelegateChooser { toggleModel: BluetoothToggle {} name: toggleModel.statusText icon: WIcons.bluetoothIcon + menu: Component { + BluetoothControl {} + } } } DelegateChoice { diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WWifiNetworkItem.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WWifiNetworkItem.qml index e7a02001d..9274b2e86 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WWifiNetworkItem.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WWifiNetworkItem.qml @@ -11,30 +11,16 @@ import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter -WChoiceButton { +ExpandableChoiceButton { id: root required property WifiAccessPoint wifiNetwork - property bool expanded: false - checked: expanded - clip: true - - horizontalPadding: 12 - verticalPadding: 6 - animateChoiceHighlight: false - - Behavior on implicitHeight { - animation: Looks.transition.resize.createObject(this) - } - - onClicked: expanded = !expanded - contentItem: RowLayout { id: contentItem spacing: 12 FluentIcon { // Duotone hack - Layout.bottomMargin: 2 + Layout.bottomMargin: 2 Layout.alignment: Qt.AlignTop property int strength: root.wifiNetwork?.strength ?? 0 icon: "wifi-1" diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml index 7badea6a5..705e03f6d 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml @@ -14,8 +14,6 @@ import qs.modules.waffle.actionCenter Item { id: root - implicitWidth: 360 - implicitHeight: 352 Component.onCompleted: { Network.rescanWifi(); @@ -37,29 +35,31 @@ Item { implicitHeight: headerRow.implicitHeight Layout.fillWidth: true spacing: 0 - HeaderRow { - id: headerRow + RowLayout { Layout.fillWidth: true - title: qsTr("Wi-Fi") + spacing: 0 + HeaderRow { + id: headerRow + Layout.fillWidth: true + title: qsTr("Wi-Fi") + } + WSwitch { + id: toggleSwitch + Layout.rightMargin: 12 + checked: Network.wifiStatus !== "disabled" + onCheckedChanged: { + Network.enableWifi(checked); + Network.rescanWifi(); + } + } } FadeLoader { Layout.leftMargin: -4 Layout.rightMargin: -4 Layout.fillWidth: true shown: Network.wifiScanning - sourceComponent: StyledIndeterminateProgressBar { - id: progressBar - implicitHeight: 3 - background: null - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: progressBar.width - height: progressBar.height - radius: progressBar.height / 2 - } - } - } + visible: true + sourceComponent: WIndeterminateProgressBar {} } } @@ -89,30 +89,29 @@ Item { Separator {} FooterRectangle { - WButton { - id: moreSettingsButton + FooterMoreButton { anchors { verticalCenter: parent.verticalCenter left: parent.left } - implicitHeight: 40 - implicitWidth: contentItem.implicitWidth + 30 - color: "transparent" - + text: qsTr("More Internet settings") onClicked: { Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "sidebarLeft", "toggle"]); Quickshell.execDetached(["bash", "-c", Config.options.apps.network]); } + } + WPanelFooterButton { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 12 + enabled: !Network.wifiScanning - contentItem: Item { - anchors.centerIn: parent - implicitWidth: buttonText.implicitWidth - WText { - id: buttonText - anchors.centerIn: parent - text: qsTr("More Internet settings") - color: moreSettingsButton.pressed ? Looks.colors.fg : Looks.colors.fg1 - } + onClicked: { + Network.rescanWifi(); + } + + contentItem: FluentIcon { + icon: "arrow-counterclockwise" } } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml index 56b50d8ef..a20839d12 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml @@ -17,7 +17,7 @@ Singleton { property string iconsPath: `${Directories.assetsPath}/icons/fluent` property bool dark: Appearance.m3colors.darkmode - property real backgroundTransparency: 0.13 + property real backgroundTransparency: 0.11 property real panelBackgroundTransparency: 0.12 property real panelLayerTransparency: root.dark ? 0.9 : 0.7 property real contentTransparency: root.dark ? 0.9 : 0.5 @@ -48,6 +48,7 @@ Singleton { property color fg: "#000000" property color fg1: "#626262" property color inactiveIcon: "#C4C4C4" + property color controlBgInactive: '#555458' property color controlBg: '#807F85' property color controlBgHover: '#57575B' property color controlFg: "#FFFFFF" @@ -74,6 +75,7 @@ Singleton { property color fg: "#FFFFFF" property color fg1: "#D1D1D1" property color inactiveIcon: "#494949" + property color controlBgInactive: "#CDCECF" property color controlBg: "#9B9B9B" property color controlBgHover: "#CFCED1" property color controlFg: "#454545" @@ -104,6 +106,7 @@ Singleton { property color fg: root.dark ? root.darkColors.fg : root.lightColors.fg property color fg1: root.dark ? root.darkColors.fg1 : root.lightColors.fg1 property color inactiveIcon: root.dark ? root.darkColors.inactiveIcon : root.lightColors.inactiveIcon + property color controlBgInactive: root.dark ? root.darkColors.controlBgInactive : root.lightColors.controlBgInactive property color controlBg: root.dark ? root.darkColors.controlBg : root.lightColors.controlBg property color controlBgHover: root.dark ? root.darkColors.controlBgHover : root.lightColors.controlBgHover property color controlFg: root.dark ? root.darkColors.controlFg : root.lightColors.controlFg diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml index 5dec5dffc..de0632dbd 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml @@ -19,6 +19,7 @@ Button { property color colForegroundToggled: Looks.colors.accentFg property alias backgroundOpacity: backgroundRect.opacity property color color: { + if (!root.enabled) return colBackground; if (root.checked) { if (root.down) { return root.colBackgroundToggledActive; diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml index c8a609d82..1d24170bc 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml @@ -107,4 +107,20 @@ Singleton { icon = AppSearch.guessIcon(node?.properties["node.name"] ?? ""); return icon; } + + function bluetoothDeviceIcon(device) { + const systemIconName = device?.icon || ""; + if (systemIconName.includes("headset") || systemIconName.includes("headphones")) + return "headphones"; + if (systemIconName.includes("audio")) + return "speaker"; + if (systemIconName.includes("phone")) + return "phone"; + if (systemIconName.includes("mouse")) + return "bluetooth"; + if (systemIconName.includes("keyboard")) + return "keyboard"; + return "bluetooth"; + } + } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WIndeterminateProgressBar.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WIndeterminateProgressBar.qml new file mode 100644 index 000000000..f1618be83 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WIndeterminateProgressBar.qml @@ -0,0 +1,22 @@ +import QtQuick +import Qt5Compat.GraphicalEffects +import qs +import qs.services +import qs.services.network +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.waffle.looks + +StyledIndeterminateProgressBar { + id: progressBar + implicitHeight: 3 + background: null + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: progressBar.width + height: progressBar.height + radius: progressBar.height / 2 + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WPanelFooterButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WPanelFooterButton.qml index 9b017d75f..0f0d99ad9 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WPanelFooterButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WPanelFooterButton.qml @@ -16,6 +16,7 @@ Button { property color color property color colForeground: Looks.colors.fg color: { + if (!root.enabled) return colBackground; if (root.down) { return root.colBackgroundActive } else if ((root.hovered && !root.down) || root.checked) { diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WSwitch.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WSwitch.qml new file mode 100644 index 000000000..369ae5f12 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WSwitch.qml @@ -0,0 +1,69 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.waffle.looks + +Switch { + id: root + + PointingHandInteraction {} + + implicitWidth: 40 + implicitHeight: 20 + property real indicatorHeight: 12 + property real indicatorPressedHeight: 14 + property real indicatorPressedWidth: 17 + property color checkedColor: Looks.colors.accent + property color uncheckedColor: Looks.colors.bg1 + property color borderColor: Looks.colors.controlBgInactive + + readonly property real indicatorPressedWidthDiff: indicatorPressedWidth - indicatorHeight + + background: Rectangle { + width: parent.width + height: parent.height + radius: height / 2 + color: root.checked ? root.checkedColor : root.uncheckedColor + border.width: 1 + border.color: root.checked ? root.checkedColor : root.borderColor + + Behavior on color { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + Behavior on border.color { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + } + + // Custom thumb styling + indicator: Rectangle { + implicitWidth: (root.pressed || root.down) ? root.indicatorPressedWidth : root.indicatorHeight + implicitHeight: (root.pressed || root.down) ? root.indicatorPressedHeight : root.indicatorHeight + radius: height / 2 + color: root.checked ? Looks.colors.accentFg : root.borderColor + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: { + if (root.checked) { + return 24 - (root.pressed || root.down ? root.indicatorPressedWidthDiff : 0); + } else { + return (root.pressed || root.down) ? 3 : 4 + } + } + + Behavior on anchors.leftMargin { + animation: Looks.transition.enter.createObject(this) + } + Behavior on implicitWidth { + animation: Looks.transition.resize.createObject(this) + } + Behavior on implicitHeight { + animation: Looks.transition.resize.createObject(this) + } + Behavior on color { + animation: Looks.transition.color.createObject(this) + } + } +} diff --git a/dots/.config/quickshell/ii/services/BluetoothStatus.qml b/dots/.config/quickshell/ii/services/BluetoothStatus.qml index e6815af78..479b9a959 100644 --- a/dots/.config/quickshell/ii/services/BluetoothStatus.qml +++ b/dots/.config/quickshell/ii/services/BluetoothStatus.qml @@ -6,9 +6,6 @@ import Quickshell.Bluetooth import Quickshell.Io import QtQuick -/** - * Network service with nmcli. - */ Singleton { id: root @@ -17,4 +14,24 @@ Singleton { readonly property BluetoothDevice firstActiveDevice: Bluetooth.defaultAdapter?.devices.values.find(device => device.connected) ?? null readonly property int activeDeviceCount: Bluetooth.defaultAdapter?.devices.values.filter(device => device.connected).length ?? 0 readonly property bool connected: Bluetooth.devices.values.some(d => d.connected) + + function sortFunction(a, b) { + // Ones with meaningful names before MAC addresses + const macRegex = /^([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}$/; + const aIsMac = macRegex.test(a.name); + const bIsMac = macRegex.test(b.name); + if (aIsMac !== bIsMac) + return aIsMac ? 1 : -1; + + // Alphabetical by name + return a.name.localeCompare(b.name); + } + property list connectedDevices: Bluetooth.devices.values.filter(d => d.connected).sort(sortFunction) + property list pairedButNotConnectedDevices: Bluetooth.devices.values.filter(d => d.paired && !d.connected).sort(sortFunction) + property list unpairedDevices: Bluetooth.devices.values.filter(d => !d.paired && !d.connected).sort(sortFunction) + property list friendlyDeviceList: [ + ...connectedDevices, + ...pairedButNotConnectedDevices, + ...unpairedDevices + ] }