diff options
Diffstat (limited to 'modules/controlcenter/bluetooth')
| -rw-r--r-- | modules/controlcenter/bluetooth/BtPane.qml | 137 | ||||
| -rw-r--r-- | modules/controlcenter/bluetooth/Details.qml | 654 | ||||
| -rw-r--r-- | modules/controlcenter/bluetooth/DeviceList.qml | 307 | ||||
| -rw-r--r-- | modules/controlcenter/bluetooth/Settings.qml | 32 |
4 files changed, 498 insertions, 632 deletions
diff --git a/modules/controlcenter/bluetooth/BtPane.qml b/modules/controlcenter/bluetooth/BtPane.qml index 96dc002..a987e75 100644 --- a/modules/controlcenter/bluetooth/BtPane.qml +++ b/modules/controlcenter/bluetooth/BtPane.qml @@ -1,134 +1,73 @@ pragma ComponentBehavior: Bound import ".." -import qs.components.effects +import "../components" +import "." +import qs.components +import qs.components.controls import qs.components.containers import qs.config import Quickshell.Widgets import Quickshell.Bluetooth import QtQuick -import QtQuick.Layouts -RowLayout { +SplitPaneWithDetails { id: root required property Session session anchors.fill: parent - spacing: 0 - - Item { - Layout.preferredWidth: Math.floor(parent.width * 0.4) - Layout.minimumWidth: 420 - Layout.fillHeight: true - - DeviceList { - anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - - session: root.session - } - - InnerBorder { - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 - } + activeItem: session.bt.active + paneIdGenerator: function(item) { + return item ? (item.address || "") : ""; } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - ClippingRectangle { - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: rightBorder.innerRadius - color: "transparent" + leftContent: Component { + StyledFlickable { + id: leftFlickable - Loader { - id: loader + flickableDirection: Flickable.VerticalFlick + contentHeight: deviceList.height - property BluetoothDevice pane: root.session.bt.active - - anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 + StyledScrollBar.vertical: StyledScrollBar { + flickable: leftFlickable + } - asynchronous: true - sourceComponent: pane ? details : settings + DeviceList { + id: deviceList - Behavior on pane { - SequentialAnimation { - ParallelAnimation { - Anim { - property: "opacity" - to: 0 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - Anim { - property: "scale" - to: 0.8 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - } - PropertyAction {} - ParallelAnimation { - Anim { - property: "opacity" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - Anim { - property: "scale" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - } - } - } + anchors.left: parent.left + anchors.right: parent.right + session: root.session } } + } - InnerBorder { - id: rightBorder - - leftThickness: Appearance.padding.normal / 2 + rightDetailsComponent: Component { + Details { + session: root.session } + } - Component { - id: settings - - StyledFlickable { - flickableDirection: Flickable.VerticalFlick - contentHeight: settingsInner.height - - Settings { - id: settingsInner + rightSettingsComponent: Component { + StyledFlickable { + id: settingsFlickable + flickableDirection: Flickable.VerticalFlick + contentHeight: settingsInner.height - anchors.left: parent.left - anchors.right: parent.right - session: root.session - } + StyledScrollBar.vertical: StyledScrollBar { + flickable: settingsFlickable } - } - Component { - id: details + Settings { + id: settingsInner - Details { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top session: root.session } } } - - component Anim: NumberAnimation { - target: loader - duration: Appearance.anim.durations.normal / 2 - easing.type: Easing.BezierSpline - } } diff --git a/modules/controlcenter/bluetooth/Details.qml b/modules/controlcenter/bluetooth/Details.qml index 104f673..5299045 100644 --- a/modules/controlcenter/bluetooth/Details.qml +++ b/modules/controlcenter/bluetooth/Details.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.effects @@ -12,409 +13,430 @@ import Quickshell.Bluetooth import QtQuick import QtQuick.Layouts -Item { +StyledFlickable { id: root required property Session session readonly property BluetoothDevice device: session.bt.active - StyledFlickable { - anchors.fill: parent + flickableDirection: Flickable.VerticalFlick + contentHeight: detailsWrapper.height - flickableDirection: Flickable.VerticalFlick - contentHeight: layout.height + StyledScrollBar.vertical: StyledScrollBar { + flickable: root + } + + Item { + id: detailsWrapper + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + implicitHeight: details.implicitHeight - ColumnLayout { - id: layout + DeviceDetails { + id: details anchors.left: parent.left anchors.right: parent.right - spacing: Appearance.spacing.normal + anchors.top: parent.top - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - animate: true - text: Icons.getBluetoothIcon(root.device.icon) - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } + session: root.session + device: root.device - StyledText { - Layout.alignment: Qt.AlignHCenter - animate: true - text: root.device?.name ?? "" - font.pointSize: Appearance.font.size.large - font.bold: true + headerComponent: Component { + SettingsHeader { + icon: Icons.getBluetoothIcon(root.device?.icon ?? "") + title: root.device?.name ?? "" + } } - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Connection status") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } + sections: [ + Component { + ColumnLayout { + spacing: Appearance.spacing.normal - StyledText { - text: qsTr("Connection settings for this device") - color: Colours.palette.m3outline - } + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Connection status") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("Connection settings for this device") + color: Colours.palette.m3outline + } - StyledRect { - Layout.fillWidth: true - implicitHeight: deviceStatus.implicitHeight + Appearance.padding.large * 2 + StyledRect { + Layout.fillWidth: true + implicitHeight: deviceStatus.implicitHeight + Appearance.padding.large * 2 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - ColumnLayout { - id: deviceStatus + ColumnLayout { + id: deviceStatus - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.larger + spacing: Appearance.spacing.larger - Toggle { - label: qsTr("Connected") - checked: root.device?.connected ?? false - toggle.onToggled: root.device.connected = checked - } + Toggle { + label: qsTr("Connected") + checked: root.device?.connected ?? false + toggle.onToggled: root.device.connected = checked + } + + Toggle { + label: qsTr("Paired") + checked: root.device?.paired ?? false + toggle.onToggled: { + if (root.device.paired) + root.device.forget(); + else + root.device.pair(); + } + } - Toggle { - label: qsTr("Paired") - checked: root.device?.paired ?? false - toggle.onToggled: { - if (root.device.paired) - root.device.forget(); - else - root.device.pair(); + Toggle { + label: qsTr("Blocked") + checked: root.device?.blocked ?? false + toggle.onToggled: root.device.blocked = checked + } + } } } + }, + Component { + ColumnLayout { + spacing: Appearance.spacing.normal - Toggle { - label: qsTr("Blocked") - checked: root.device?.blocked ?? false - toggle.onToggled: root.device.blocked = checked - } - } - } + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Device properties") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Device properties") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } + StyledText { + text: qsTr("Additional settings") + color: Colours.palette.m3outline + } - StyledText { - text: qsTr("Additional settings") - color: Colours.palette.m3outline - } + StyledRect { + Layout.fillWidth: true + implicitHeight: deviceProps.implicitHeight + Appearance.padding.large * 2 - StyledRect { - Layout.fillWidth: true - implicitHeight: deviceProps.implicitHeight + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer + ColumnLayout { + id: deviceProps - ColumnLayout { - id: deviceProps + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large + spacing: Appearance.spacing.larger - spacing: Appearance.spacing.larger + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + Item { + id: renameDevice - Item { - id: renameDevice + Layout.fillWidth: true + Layout.rightMargin: Appearance.spacing.small - Layout.fillWidth: true - Layout.rightMargin: Appearance.spacing.small + implicitHeight: renameLabel.implicitHeight + deviceNameEdit.implicitHeight - implicitHeight: renameLabel.implicitHeight + deviceNameEdit.implicitHeight + states: State { + name: "editingDeviceName" + when: root.session.bt.editingDeviceName - states: State { - name: "editingDeviceName" - when: root.session.bt.editingDeviceName + AnchorChanges { + target: deviceNameEdit + anchors.top: renameDevice.top + } + PropertyChanges { + renameDevice.implicitHeight: deviceNameEdit.implicitHeight + renameLabel.opacity: 0 + deviceNameEdit.padding: Appearance.padding.normal + } + } - AnchorChanges { - target: deviceNameEdit - anchors.top: renameDevice.top - } - PropertyChanges { - renameDevice.implicitHeight: deviceNameEdit.implicitHeight - renameLabel.opacity: 0 - deviceNameEdit.padding: Appearance.padding.normal - } - } + transitions: Transition { + AnchorAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } + Anim { + properties: "implicitHeight,opacity,padding" + } + } - transitions: Transition { - AnchorAnimation { - duration: Appearance.anim.durations.normal - easing.type: Easing.BezierSpline - easing.bezierCurve: Appearance.anim.curves.standard - } - Anim { - properties: "implicitHeight,opacity,padding" - } - } + StyledText { + id: renameLabel - StyledText { - id: renameLabel + anchors.left: parent.left - anchors.left: parent.left + text: qsTr("Device name") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } - text: qsTr("Device name") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } + StyledTextField { + id: deviceNameEdit - StyledTextField { - id: deviceNameEdit + anchors.left: parent.left + anchors.right: parent.right + anchors.top: renameLabel.bottom + anchors.leftMargin: root.session.bt.editingDeviceName ? 0 : -Appearance.padding.normal - anchors.left: parent.left - anchors.right: parent.right - anchors.top: renameLabel.bottom - anchors.leftMargin: root.session.bt.editingDeviceName ? 0 : -Appearance.padding.normal + text: root.device?.name ?? "" + readOnly: !root.session.bt.editingDeviceName + onAccepted: { + root.session.bt.editingDeviceName = false; + root.device.name = text; + } - text: root.device?.name ?? "" - readOnly: !root.session.bt.editingDeviceName - onAccepted: { - root.session.bt.editingDeviceName = false; - root.device.name = text; - } + leftPadding: Appearance.padding.normal + rightPadding: Appearance.padding.normal - leftPadding: Appearance.padding.normal - rightPadding: Appearance.padding.normal + background: StyledRect { + radius: Appearance.rounding.small + border.width: 2 + border.color: Colours.palette.m3primary + opacity: root.session.bt.editingDeviceName ? 1 : 0 - background: StyledRect { - radius: Appearance.rounding.small - border.width: 2 - border.color: Colours.palette.m3primary - opacity: root.session.bt.editingDeviceName ? 1 : 0 + Behavior on border.color { + CAnim {} + } - Behavior on border.color { - CAnim {} - } + Behavior on opacity { + Anim {} + } + } - Behavior on opacity { - Anim {} + Behavior on anchors.leftMargin { + Anim {} + } + } } - } - Behavior on anchors.leftMargin { - Anim {} - } - } - } + StyledRect { + implicitWidth: implicitHeight + implicitHeight: cancelEditIcon.implicitHeight + Appearance.padding.smaller * 2 - StyledRect { - implicitWidth: implicitHeight - implicitHeight: cancelEditIcon.implicitHeight + Appearance.padding.smaller * 2 + radius: Appearance.rounding.small + color: Colours.palette.m3secondaryContainer + opacity: root.session.bt.editingDeviceName ? 1 : 0 + scale: root.session.bt.editingDeviceName ? 1 : 0.5 - radius: Appearance.rounding.small - color: Colours.palette.m3secondaryContainer - opacity: root.session.bt.editingDeviceName ? 1 : 0 - scale: root.session.bt.editingDeviceName ? 1 : 0.5 + StateLayer { + color: Colours.palette.m3onSecondaryContainer + disabled: !root.session.bt.editingDeviceName - StateLayer { - color: Colours.palette.m3onSecondaryContainer - disabled: !root.session.bt.editingDeviceName + function onClicked(): void { + root.session.bt.editingDeviceName = false; + deviceNameEdit.text = Qt.binding(() => root.device?.name ?? ""); + } + } - function onClicked(): void { - root.session.bt.editingDeviceName = false; - deviceNameEdit.text = Qt.binding(() => root.device?.name ?? ""); - } - } + MaterialIcon { + id: cancelEditIcon - MaterialIcon { - id: cancelEditIcon + anchors.centerIn: parent + animate: true + text: "cancel" + color: Colours.palette.m3onSecondaryContainer + } - anchors.centerIn: parent - animate: true - text: "cancel" - color: Colours.palette.m3onSecondaryContainer - } + Behavior on opacity { + Anim {} + } - Behavior on opacity { - Anim {} - } + Behavior on scale { + Anim { + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial + } + } + } - Behavior on scale { - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial - } - } - } + StyledRect { + implicitWidth: implicitHeight + implicitHeight: editIcon.implicitHeight + Appearance.padding.smaller * 2 - StyledRect { - implicitWidth: implicitHeight - implicitHeight: editIcon.implicitHeight + Appearance.padding.smaller * 2 + radius: root.session.bt.editingDeviceName ? Appearance.rounding.small : implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) + color: Qt.alpha(Colours.palette.m3primary, root.session.bt.editingDeviceName ? 1 : 0) - radius: root.session.bt.editingDeviceName ? Appearance.rounding.small : implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) - color: Qt.alpha(Colours.palette.m3primary, root.session.bt.editingDeviceName ? 1 : 0) + StateLayer { + color: root.session.bt.editingDeviceName ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface - StateLayer { - color: root.session.bt.editingDeviceName ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface + function onClicked(): void { + root.session.bt.editingDeviceName = !root.session.bt.editingDeviceName; + if (root.session.bt.editingDeviceName) + deviceNameEdit.forceActiveFocus(); + else + deviceNameEdit.accepted(); + } + } - function onClicked(): void { - root.session.bt.editingDeviceName = !root.session.bt.editingDeviceName; - if (root.session.bt.editingDeviceName) - deviceNameEdit.forceActiveFocus(); - else - deviceNameEdit.accepted(); - } - } + MaterialIcon { + id: editIcon - MaterialIcon { - id: editIcon + anchors.centerIn: parent + animate: true + text: root.session.bt.editingDeviceName ? "check_circle" : "edit" + color: root.session.bt.editingDeviceName ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface + } - anchors.centerIn: parent - animate: true - text: root.session.bt.editingDeviceName ? "check_circle" : "edit" - color: root.session.bt.editingDeviceName ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface - } + Behavior on radius { + Anim {} + } + } + } - Behavior on radius { - Anim {} + Toggle { + label: qsTr("Trusted") + checked: root.device?.trusted ?? false + toggle.onToggled: root.device.trusted = checked + } + + Toggle { + label: qsTr("Wake allowed") + checked: root.device?.wakeAllowed ?? false + toggle.onToggled: root.device.wakeAllowed = checked + } } } } + }, + Component { + ColumnLayout { + spacing: Appearance.spacing.normal - Toggle { - label: qsTr("Trusted") - checked: root.device?.trusted ?? false - toggle.onToggled: root.device.trusted = checked - } - - Toggle { - label: qsTr("Wake allowed") - checked: root.device?.wakeAllowed ?? false - toggle.onToggled: root.device.wakeAllowed = checked - } - } - } - - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Device information") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Device information") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } - StyledText { - text: qsTr("Information about this device") - color: Colours.palette.m3outline - } + StyledText { + text: qsTr("Information about this device") + color: Colours.palette.m3outline + } - StyledRect { - Layout.fillWidth: true - implicitHeight: deviceInfo.implicitHeight + Appearance.padding.large * 2 + StyledRect { + Layout.fillWidth: true + implicitHeight: deviceInfo.implicitHeight + Appearance.padding.large * 2 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - ColumnLayout { - id: deviceInfo + ColumnLayout { + id: deviceInfo - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.small / 2 + spacing: Appearance.spacing.small / 2 - StyledText { - text: root.device?.batteryAvailable ? qsTr("Device battery (%1%)").arg(root.device.battery * 100) : qsTr("Battery unavailable") - } + StyledText { + text: root.device?.batteryAvailable ? qsTr("Device battery (%1%)").arg(root.device.battery * 100) : qsTr("Battery unavailable") + } - RowLayout { - Layout.topMargin: Appearance.spacing.small / 2 - Layout.fillWidth: true - Layout.preferredHeight: Appearance.padding.smaller - spacing: Appearance.spacing.small / 2 + RowLayout { + Layout.topMargin: Appearance.spacing.small / 2 + Layout.fillWidth: true + Layout.preferredHeight: Appearance.padding.smaller + spacing: Appearance.spacing.small / 2 - StyledRect { - Layout.fillHeight: true - implicitWidth: root.device?.batteryAvailable ? parent.width * root.device.battery : 0 - radius: Appearance.rounding.full - color: Colours.palette.m3primary - } + StyledRect { + Layout.fillHeight: true + implicitWidth: root.device?.batteryAvailable ? parent.width * root.device.battery : 0 + radius: Appearance.rounding.full + color: Colours.palette.m3primary + } - StyledRect { - Layout.fillWidth: true - Layout.fillHeight: true - radius: Appearance.rounding.full - color: Colours.palette.m3secondaryContainer + StyledRect { + Layout.fillWidth: true + Layout.fillHeight: true + radius: Appearance.rounding.full + color: Colours.palette.m3secondaryContainer - StyledRect { - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.margins: parent.height * 0.25 + StyledRect { + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.margins: parent.height * 0.25 - implicitWidth: height - radius: Appearance.rounding.full - color: Colours.palette.m3primary - } - } - } + implicitWidth: height + radius: Appearance.rounding.full + color: Colours.palette.m3primary + } + } + } - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Dbus path") - } + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Dbus path") + } - StyledText { - text: root.device?.dbusPath ?? "" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } + StyledText { + text: root.device?.dbusPath ?? "" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("MAC address") - } + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("MAC address") + } - StyledText { - text: root.device?.address ?? "" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } + StyledText { + text: root.device?.address ?? "" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Bonded") - } + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Bonded") + } - StyledText { - text: root.device?.bonded ? qsTr("Yes") : qsTr("No") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } + StyledText { + text: root.device?.bonded ? qsTr("Yes") : qsTr("No") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("System name") - } + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("System name") + } - StyledText { - text: root.device?.deviceName ?? "" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small + StyledText { + text: root.device?.deviceName ?? "" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + } + } } } - } + ] } } @@ -562,11 +584,11 @@ Item { Item { id: fabRoot - anchors.right: parent.right - anchors.bottom: parent.bottom - - implicitWidth: 64 - implicitHeight: 64 + x: root.contentX + root.width - width + y: root.contentY + root.height - height + width: 64 + height: 64 + z: 10000 StyledRect { id: fabBg diff --git a/modules/controlcenter/bluetooth/DeviceList.qml b/modules/controlcenter/bluetooth/DeviceList.qml index 3831e4a..b978a2d 100644 --- a/modules/controlcenter/bluetooth/DeviceList.qml +++ b/modules/controlcenter/bluetooth/DeviceList.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.containers @@ -12,172 +13,142 @@ import Quickshell.Bluetooth import QtQuick import QtQuick.Layouts -ColumnLayout { +DeviceList { id: root required property Session session readonly property bool smallDiscoverable: width <= 540 readonly property bool smallPairable: width <= 480 - spacing: Appearance.spacing.small + title: qsTr("Devices (%1)").arg(Bluetooth.devices.values.length) + description: qsTr("All available bluetooth devices") + activeItem: session.bt.active - RowLayout { - spacing: Appearance.spacing.smaller + model: ScriptModel { + id: deviceModel - StyledText { - text: qsTr("Settings") - font.pointSize: Appearance.font.size.large - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } + values: [...Bluetooth.devices.values].sort((a, b) => (b.connected - a.connected) || (b.paired - a.paired)) + } - ToggleButton { - toggled: Bluetooth.defaultAdapter?.enabled ?? false - icon: "power" - accent: "Tertiary" + headerComponent: Component { + RowLayout { + spacing: Appearance.spacing.smaller - function onClicked(): void { - const adapter = Bluetooth.defaultAdapter; - if (adapter) - adapter.enabled = !adapter.enabled; + StyledText { + text: qsTr("Bluetooth") + font.pointSize: Appearance.font.size.large + font.weight: 500 } - } - - ToggleButton { - toggled: Bluetooth.defaultAdapter?.discoverable ?? false - icon: root.smallDiscoverable ? "group_search" : "" - label: root.smallDiscoverable ? "" : qsTr("Discoverable") - function onClicked(): void { - const adapter = Bluetooth.defaultAdapter; - if (adapter) - adapter.discoverable = !adapter.discoverable; + Item { + Layout.fillWidth: true } - } - ToggleButton { - toggled: Bluetooth.defaultAdapter?.pairable ?? false - icon: "missing_controller" - label: root.smallPairable ? "" : qsTr("Pairable") + ToggleButton { + toggled: Bluetooth.defaultAdapter?.enabled ?? false + icon: "power" + accent: "Tertiary" + iconSize: Appearance.font.size.normal + horizontalPadding: Appearance.padding.normal + verticalPadding: Appearance.padding.smaller + tooltip: qsTr("Toggle Bluetooth") - function onClicked(): void { - const adapter = Bluetooth.defaultAdapter; - if (adapter) - adapter.pairable = !adapter.pairable; + onClicked: { + const adapter = Bluetooth.defaultAdapter; + if (adapter) + adapter.enabled = !adapter.enabled; + } } - } - ToggleButton { - toggled: !root.session.bt.active - icon: "settings" - accent: "Primary" + ToggleButton { + toggled: Bluetooth.defaultAdapter?.discoverable ?? false + icon: root.smallDiscoverable ? "group_search" : "" + label: root.smallDiscoverable ? "" : qsTr("Discoverable") + iconSize: Appearance.font.size.normal + horizontalPadding: Appearance.padding.normal + verticalPadding: Appearance.padding.smaller + tooltip: qsTr("Make discoverable") - function onClicked(): void { - if (root.session.bt.active) - root.session.bt.active = null; - else { - root.session.bt.active = deviceModel.values[0] ?? null; + onClicked: { + const adapter = Bluetooth.defaultAdapter; + if (adapter) + adapter.discoverable = !adapter.discoverable; } } - } - } - RowLayout { - Layout.topMargin: Appearance.spacing.large - Layout.fillWidth: true - spacing: Appearance.spacing.normal + ToggleButton { + toggled: Bluetooth.defaultAdapter?.pairable ?? false + icon: "missing_controller" + label: root.smallPairable ? "" : qsTr("Pairable") + iconSize: Appearance.font.size.normal + horizontalPadding: Appearance.padding.normal + verticalPadding: Appearance.padding.smaller + tooltip: qsTr("Make pairable") - ColumnLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small - - StyledText { - Layout.fillWidth: true - text: qsTr("Devices (%1)").arg(Bluetooth.devices.values.length) - font.pointSize: Appearance.font.size.large - font.weight: 500 - } - - StyledText { - Layout.fillWidth: true - text: qsTr("All available bluetooth devices") - color: Colours.palette.m3outline + onClicked: { + const adapter = Bluetooth.defaultAdapter; + if (adapter) + adapter.pairable = !adapter.pairable; + } } - } - - StyledRect { - implicitWidth: implicitHeight - implicitHeight: scanIcon.implicitHeight + Appearance.padding.normal * 2 - radius: Bluetooth.defaultAdapter?.discovering ? Appearance.rounding.normal : implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) - color: Bluetooth.defaultAdapter?.discovering ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer + ToggleButton { + toggled: Bluetooth.defaultAdapter?.discovering ?? false + icon: "bluetooth_searching" + accent: "Secondary" + iconSize: Appearance.font.size.normal + horizontalPadding: Appearance.padding.normal + verticalPadding: Appearance.padding.smaller + tooltip: qsTr("Scan for devices") - StateLayer { - color: Bluetooth.defaultAdapter?.discovering ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer - - function onClicked(): void { + onClicked: { const adapter = Bluetooth.defaultAdapter; if (adapter) adapter.discovering = !adapter.discovering; } } - MaterialIcon { - id: scanIcon - - anchors.centerIn: parent - animate: true - text: "bluetooth_searching" - color: Bluetooth.defaultAdapter?.discovering ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer - fill: Bluetooth.defaultAdapter?.discovering ? 1 : 0 - } + ToggleButton { + toggled: !root.session.bt.active + icon: "settings" + accent: "Primary" + iconSize: Appearance.font.size.normal + horizontalPadding: Appearance.padding.normal + verticalPadding: Appearance.padding.smaller + tooltip: qsTr("Bluetooth settings") - Behavior on radius { - Anim {} + onClicked: { + if (root.session.bt.active) + root.session.bt.active = null; + else { + root.session.bt.active = root.model.values[0] ?? null; + } + } } } } - StyledListView { - id: view - - model: ScriptModel { - id: deviceModel - - values: [...Bluetooth.devices.values].sort((a, b) => (b.connected - a.connected) || (b.paired - a.paired)) - } - - Layout.fillWidth: true - Layout.fillHeight: true - clip: true - spacing: Appearance.spacing.small / 2 - - StyledScrollBar.vertical: StyledScrollBar { - flickable: view - } - delegate: StyledRect { + delegate: Component { + StyledRect { id: device required property BluetoothDevice modelData - readonly property bool loading: modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting - readonly property bool connected: modelData.state === BluetoothDeviceState.Connected + readonly property bool loading: modelData && (modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting) + readonly property bool connected: modelData && modelData.state === BluetoothDeviceState.Connected - anchors.left: parent.left - anchors.right: parent.right + width: ListView.view ? ListView.view.width : undefined implicitHeight: deviceInner.implicitHeight + Appearance.padding.normal * 2 - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.bt.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.activeItem === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal StateLayer { id: stateLayer function onClicked(): void { - root.session.bt.active = device.modelData; + if (device.modelData) + root.session.bt.active = device.modelData; } } @@ -194,20 +165,20 @@ ColumnLayout { implicitHeight: icon.implicitHeight + Appearance.padding.normal * 2 radius: Appearance.rounding.normal - color: device.connected ? Colours.palette.m3primaryContainer : device.modelData.bonded ? Colours.palette.m3secondaryContainer : Colours.tPalette.m3surfaceContainerHigh + color: device.connected ? Colours.palette.m3primaryContainer : (device.modelData && device.modelData.bonded) ? Colours.palette.m3secondaryContainer : Colours.tPalette.m3surfaceContainerHigh StyledRect { anchors.fill: parent radius: parent.radius - color: Qt.alpha(device.connected ? Colours.palette.m3onPrimaryContainer : device.modelData.bonded ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface, stateLayer.pressed ? 0.1 : stateLayer.containsMouse ? 0.08 : 0) + color: Qt.alpha(device.connected ? Colours.palette.m3onPrimaryContainer : (device.modelData && device.modelData.bonded) ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface, stateLayer.pressed ? 0.1 : stateLayer.containsMouse ? 0.08 : 0) } MaterialIcon { id: icon anchors.centerIn: parent - text: Icons.getBluetoothIcon(device.modelData.icon) - color: device.connected ? Colours.palette.m3onPrimaryContainer : device.modelData.bonded ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface + text: Icons.getBluetoothIcon(device.modelData ? device.modelData.icon : "") + color: device.connected ? Colours.palette.m3onPrimaryContainer : (device.modelData && device.modelData.bonded) ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface font.pointSize: Appearance.font.size.large fill: device.connected ? 1 : 0 @@ -224,13 +195,13 @@ ColumnLayout { StyledText { Layout.fillWidth: true - text: device.modelData.name + text: device.modelData ? device.modelData.name : qsTr("Unknown") elide: Text.ElideRight } StyledText { Layout.fillWidth: true - text: device.modelData.address + (device.connected ? qsTr(" (Connected)") : device.modelData.bonded ? qsTr(" (Paired)") : "") + text: (device.modelData ? device.modelData.address : "") + (device.connected ? qsTr(" (Connected)") : (device.modelData && device.modelData.bonded) ? qsTr(" (Paired)") : "") color: Colours.palette.m3outline font.pointSize: Appearance.font.size.small elide: Text.ElideRight @@ -256,7 +227,18 @@ ColumnLayout { disabled: device.loading function onClicked(): void { - device.modelData.connected = !device.modelData.connected; + if (device.loading) + return; + + if (device.connected) { + device.modelData.connected = false; + } else { + if (device.modelData.bonded) { + device.modelData.connected = true; + } else { + device.modelData.pair(); + } + } } } @@ -265,7 +247,7 @@ ColumnLayout { anchors.centerIn: parent animate: true - text: device.modelData.connected ? "link_off" : "link" + text: device.connected ? "link_off" : "link" color: device.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface opacity: device.loading ? 0 : 1 @@ -279,78 +261,7 @@ ColumnLayout { } } - component ToggleButton: StyledRect { - id: toggleBtn - - required property bool toggled - property string icon - property string label - property string accent: "Secondary" - - function onClicked(): void { - } - - Layout.preferredWidth: implicitWidth + (toggleStateLayer.pressed ? Appearance.padding.normal * 2 : toggled ? Appearance.padding.small * 2 : 0) - implicitWidth: toggleBtnInner.implicitWidth + Appearance.padding.large * 2 - implicitHeight: toggleBtnIcon.implicitHeight + Appearance.padding.normal * 2 - - radius: toggled || toggleStateLayer.pressed ? Appearance.rounding.small : Math.min(width, height) / 2 * Math.min(1, Appearance.rounding.scale) - color: toggled ? Colours.palette[`m3${accent.toLowerCase()}`] : Colours.palette[`m3${accent.toLowerCase()}Container`] - - StateLayer { - id: toggleStateLayer - - color: toggleBtn.toggled ? Colours.palette[`m3on${toggleBtn.accent}`] : Colours.palette[`m3on${toggleBtn.accent}Container`] - - function onClicked(): void { - toggleBtn.onClicked(); - } - } - - RowLayout { - id: toggleBtnInner - - anchors.centerIn: parent - spacing: Appearance.spacing.normal - - MaterialIcon { - id: toggleBtnIcon - - visible: !!text - fill: toggleBtn.toggled ? 1 : 0 - text: toggleBtn.icon - color: toggleBtn.toggled ? Colours.palette[`m3on${toggleBtn.accent}`] : Colours.palette[`m3on${toggleBtn.accent}Container`] - font.pointSize: Appearance.font.size.large - - Behavior on fill { - Anim {} - } - } - - Loader { - asynchronous: true - active: !!toggleBtn.label - visible: active - - sourceComponent: StyledText { - text: toggleBtn.label - color: toggleBtn.toggled ? Colours.palette[`m3on${toggleBtn.accent}`] : Colours.palette[`m3on${toggleBtn.accent}Container`] - } - } - } - - Behavior on radius { - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial - } - } - - Behavior on Layout.preferredWidth { - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial - } - } + onItemSelected: function(item) { + session.bt.active = item; } } diff --git a/modules/controlcenter/bluetooth/Settings.qml b/modules/controlcenter/bluetooth/Settings.qml index fb493ff..c547240 100644 --- a/modules/controlcenter/bluetooth/Settings.qml +++ b/modules/controlcenter/bluetooth/Settings.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.effects @@ -17,18 +18,9 @@ ColumnLayout { spacing: Appearance.spacing.normal - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - text: "bluetooth" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Bluetooth settings") - font.pointSize: Appearance.font.size.large - font.bold: true + SettingsHeader { + icon: "bluetooth" + title: qsTr("Bluetooth Settings") } StyledText { @@ -284,8 +276,12 @@ ColumnLayout { CustomSpinBox { min: 0 - value: root.session.bt.currentAdapter.discoverableTimeout - onValueModified: value => root.session.bt.currentAdapter.discoverableTimeout = value + value: root.session.bt.currentAdapter?.discoverableTimeout ?? 0 + onValueModified: value => { + if (root.session.bt.currentAdapter) { + root.session.bt.currentAdapter.discoverableTimeout = value; + } + } } } @@ -332,7 +328,7 @@ ColumnLayout { anchors.left: parent.left - text: qsTr("Rename adapter (currently does not work)") // FIXME: remove disclaimer when fixed + text: qsTr("Rename adapter (currently does not work)") color: Colours.palette.m3outline font.pointSize: Appearance.font.size.small } @@ -345,12 +341,10 @@ ColumnLayout { anchors.top: renameLabel.bottom anchors.leftMargin: root.session.bt.editingAdapterName ? 0 : -Appearance.padding.normal - text: root.session.bt.currentAdapter.name + text: root.session.bt.currentAdapter?.name ?? "" readOnly: !root.session.bt.editingAdapterName onAccepted: { root.session.bt.editingAdapterName = false; - // Doesn't work for now, will be added to QS later - // root.session.bt.currentAdapter.name = text; } leftPadding: Appearance.padding.normal @@ -392,7 +386,7 @@ ColumnLayout { function onClicked(): void { root.session.bt.editingAdapterName = false; - adapterNameEdit.text = Qt.binding(() => root.session.bt.currentAdapter.name); + adapterNameEdit.text = Qt.binding(() => root.session.bt.currentAdapter?.name ?? ""); } } |