popouts: better bluetooth popout
This commit is contained in:
parent
4709d65249
commit
b09b262f72
2 changed files with 212 additions and 11 deletions
|
|
@ -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
|
||||
|
|
|
|||
142
widgets/StyledSwitch.qml
Normal file
142
widgets/StyledSwitch.qml
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue