summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/bar/popouts/Bluetooth.qml81
-rw-r--r--widgets/StyledSwitch.qml142
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
+ }
+}