diff options
| -rw-r--r-- | modules/bar/popouts/Bluetooth.qml | 81 | ||||
| -rw-r--r-- | widgets/StyledSwitch.qml | 142 |
2 files changed, 212 insertions, 11 deletions
diff --git a/modules/bar/popouts/Bluetooth.qml b/modules/bar/popouts/Bluetooth.qml index aefdf4d..5b1127f 100644 --- a/modules/bar/popouts/Bluetooth.qml +++ b/modules/bar/popouts/Bluetooth.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import qs.widgets +import qs.services import qs.config import qs.utils import Quickshell @@ -11,30 +12,64 @@ import QtQuick.Layouts ColumnLayout { id: root - spacing: Appearance.spacing.small / 2 + spacing: Appearance.spacing.small StyledText { - Layout.bottomMargin: Appearance.spacing.small + Layout.topMargin: Appearance.padding.normal + Layout.rightMargin: Appearance.padding.small text: qsTr("Bluetooth %1").arg(BluetoothAdapterState.toString(Bluetooth.defaultAdapter?.state).toLowerCase()) + font.weight: 500 + } + + Toggle { + label: qsTr("Enabled") + checked: Bluetooth.defaultAdapter?.enabled ?? false + toggle.onToggled: { + const adapter = Bluetooth.defaultAdapter; + if (adapter) + adapter.enabled = checked; + } + } + + Toggle { + label: qsTr("Discovering") + checked: Bluetooth.defaultAdapter?.discovering ?? false + toggle.onToggled: { + const adapter = Bluetooth.defaultAdapter; + if (adapter) + adapter.discovering = checked; + } } StyledText { - text: qsTr("%n connected device(s)", "", Bluetooth.devices.values.filter(d => d.connected).length) + Layout.topMargin: Appearance.spacing.small + Layout.rightMargin: Appearance.padding.small + text: { + const devices = Bluetooth.devices.values; + let available = qsTr("%1 device%2 available").arg(devices.length).arg(devices.length === 1 ? "" : "s"); + const connected = devices.filter(d => d.connected).length; + if (connected > 0) + available += qsTr(" (%1 connected)").arg(connected); + return available; + } + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.small } Repeater { model: ScriptModel { - values: [...Bluetooth.devices.values].sort((a, b) => (b.connected - a.connected) || (b.paired - a.paired)) + values: [...Bluetooth.devices.values].sort((a, b) => (b.connected - a.connected) || (b.paired - a.paired)).slice(0, 5) } RowLayout { id: device - required property var modelData + required property BluetoothDevice modelData readonly property bool loading: device.modelData.state === BluetoothDeviceState.Connecting || device.modelData.state === BluetoothDeviceState.Disconnecting Layout.fillWidth: true - spacing: Appearance.spacing.small / 2 + Layout.rightMargin: Appearance.padding.small + spacing: Appearance.spacing.small opacity: 0 scale: 0.7 @@ -53,20 +88,24 @@ ColumnLayout { } MaterialIcon { - Layout.rightMargin: Appearance.spacing.small text: Icons.getBluetoothIcon(device.modelData.icon) } StyledText { + Layout.leftMargin: Appearance.spacing.small / 2 + Layout.rightMargin: Appearance.spacing.small / 2 Layout.fillWidth: true text: device.modelData.name } - Item { + StyledRect { id: connectBtn implicitWidth: implicitHeight - implicitHeight: connectIcon.implicitHeight + Appearance.padding.small * 2 + implicitHeight: connectIcon.implicitHeight + Appearance.padding.small + + radius: Appearance.rounding.full + color: device.modelData.state === BluetoothDeviceState.Connected ? Colours.palette.m3primary : Colours.palette.m3surface StyledBusyIndicator { anchors.centerIn: parent @@ -83,7 +122,7 @@ ColumnLayout { } StateLayer { - radius: Appearance.rounding.full + color: device.modelData.state === BluetoothDeviceState.Connected ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface disabled: device.loading function onClicked(): void { @@ -97,6 +136,7 @@ ColumnLayout { anchors.centerIn: parent animate: true text: device.modelData.connected ? "link_off" : "link" + color: device.modelData.state === BluetoothDeviceState.Connected ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface opacity: device.loading ? 0 : 1 @@ -108,7 +148,7 @@ ColumnLayout { Loader { asynchronous: true - active: device.modelData.paired + active: device.modelData.bonded sourceComponent: Item { implicitWidth: connectBtn.implicitWidth implicitHeight: connectBtn.implicitHeight @@ -130,6 +170,25 @@ ColumnLayout { } } + component Toggle: RowLayout { + required property string label + property alias checked: toggle.checked + property alias toggle: toggle + + Layout.fillWidth: true + Layout.rightMargin: Appearance.padding.small + spacing: Appearance.spacing.normal + + StyledText { + Layout.fillWidth: true + text: parent.label + } + + StyledSwitch { + id: toggle + } + } + component Anim: NumberAnimation { duration: Appearance.anim.durations.normal easing.type: Easing.BezierSpline diff --git a/widgets/StyledSwitch.qml b/widgets/StyledSwitch.qml new file mode 100644 index 0000000..90957c0 --- /dev/null +++ b/widgets/StyledSwitch.qml @@ -0,0 +1,142 @@ +import qs.services +import qs.config +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes + +Switch { + id: root + + implicitWidth: implicitIndicatorWidth + implicitHeight: implicitIndicatorHeight + + indicator: StyledRect { + radius: Appearance.rounding.full + color: root.checked ? Colours.palette.m3primary : Colours.palette.m3surfaceContainerHighest + + implicitWidth: implicitHeight * 1.7 + implicitHeight: Appearance.font.size.normal + Appearance.padding.smaller * 2 + + StyledRect { + readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight + + radius: Appearance.rounding.full + color: root.checked ? Colours.palette.m3onPrimary : Colours.palette.m3outline + + x: root.checked ? parent.implicitWidth - nonAnimWidth - Appearance.padding.small / 2 : Appearance.padding.small / 2 + implicitWidth: nonAnimWidth + implicitHeight: parent.implicitHeight - Appearance.padding.small + anchors.verticalCenter: parent.verticalCenter + + Shape { + id: icon + + property point start1: { + if (root.pressed) + return Qt.point(width * 0.2, height / 2); + if (root.checked) + return Qt.point(width * 0.15, height / 2); + return Qt.point(width * 0.15, height * 0.15); + } + property point end1: { + if (root.pressed) { + if (root.checked) + return Qt.point(width * 0.4, height / 2); + return Qt.point(width * 0.8, height / 2); + } + if (root.checked) + return Qt.point(width * 0.4, height * 0.7); + return Qt.point(width * 0.85, height * 0.85); + } + property point start2: { + if (root.pressed) { + if (root.checked) + return Qt.point(width * 0.4, height / 2); + return Qt.point(width * 0.2, height / 2); + } + if (root.checked) + return Qt.point(width * 0.4, height * 0.7); + return Qt.point(width * 0.15, height * 0.85); + } + property point end2: { + if (root.pressed) + return Qt.point(width * 0.8, height / 2); + if (root.checked) + return Qt.point(width * 0.85, height * 0.2); + return Qt.point(width * 0.85, height * 0.15); + } + + anchors.centerIn: parent + width: height + height: parent.implicitHeight - Appearance.padding.small * 2 + preferredRendererType: Shape.CurveRenderer + + ShapePath { + strokeWidth: Appearance.font.size.larger * 0.15 + strokeColor: root.checked ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3surfaceContainerHighest + fillColor: "transparent" + capStyle: ShapePath.RoundCap + + startX: icon.start1.x + startY: icon.start1.y + + PathLine { + x: icon.end1.x + y: icon.end1.y + } + PathMove { + x: icon.start2.x + y: icon.start2.y + } + PathLine { + x: icon.end2.x + y: icon.end2.y + } + + Behavior on strokeColor { + ColorAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } + } + } + + Behavior on start1 { + Anim {} + } + Behavior on end1 { + Anim {} + } + Behavior on start2 { + Anim {} + } + Behavior on end2 { + Anim {} + } + } + + Behavior on x { + NumberAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } + } + + Behavior on implicitWidth { + NumberAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } + } + } + } + + component Anim: PropertyAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } +} |