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
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import qs.widgets
|
import qs.widgets
|
||||||
|
import qs.services
|
||||||
import qs.config
|
import qs.config
|
||||||
import qs.utils
|
import qs.utils
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
|
@ -11,30 +12,64 @@ import QtQuick.Layouts
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
spacing: Appearance.spacing.small / 2
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
StyledText {
|
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())
|
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 {
|
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 {
|
Repeater {
|
||||||
model: ScriptModel {
|
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 {
|
RowLayout {
|
||||||
id: device
|
id: device
|
||||||
|
|
||||||
required property var modelData
|
required property BluetoothDevice modelData
|
||||||
readonly property bool loading: device.modelData.state === BluetoothDeviceState.Connecting || device.modelData.state === BluetoothDeviceState.Disconnecting
|
readonly property bool loading: device.modelData.state === BluetoothDeviceState.Connecting || device.modelData.state === BluetoothDeviceState.Disconnecting
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Appearance.spacing.small / 2
|
Layout.rightMargin: Appearance.padding.small
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
opacity: 0
|
opacity: 0
|
||||||
scale: 0.7
|
scale: 0.7
|
||||||
|
|
@ -53,20 +88,24 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
MaterialIcon {
|
||||||
Layout.rightMargin: Appearance.spacing.small
|
|
||||||
text: Icons.getBluetoothIcon(device.modelData.icon)
|
text: Icons.getBluetoothIcon(device.modelData.icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
Layout.leftMargin: Appearance.spacing.small / 2
|
||||||
|
Layout.rightMargin: Appearance.spacing.small / 2
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: device.modelData.name
|
text: device.modelData.name
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
StyledRect {
|
||||||
id: connectBtn
|
id: connectBtn
|
||||||
|
|
||||||
implicitWidth: implicitHeight
|
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 {
|
StyledBusyIndicator {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
@ -83,7 +122,7 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
StateLayer {
|
StateLayer {
|
||||||
radius: Appearance.rounding.full
|
color: device.modelData.state === BluetoothDeviceState.Connected ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
|
||||||
disabled: device.loading
|
disabled: device.loading
|
||||||
|
|
||||||
function onClicked(): void {
|
function onClicked(): void {
|
||||||
|
|
@ -97,6 +136,7 @@ ColumnLayout {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
animate: true
|
animate: true
|
||||||
text: device.modelData.connected ? "link_off" : "link"
|
text: device.modelData.connected ? "link_off" : "link"
|
||||||
|
color: device.modelData.state === BluetoothDeviceState.Connected ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
|
||||||
|
|
||||||
opacity: device.loading ? 0 : 1
|
opacity: device.loading ? 0 : 1
|
||||||
|
|
||||||
|
|
@ -108,7 +148,7 @@ ColumnLayout {
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
active: device.modelData.paired
|
active: device.modelData.bonded
|
||||||
sourceComponent: Item {
|
sourceComponent: Item {
|
||||||
implicitWidth: connectBtn.implicitWidth
|
implicitWidth: connectBtn.implicitWidth
|
||||||
implicitHeight: connectBtn.implicitHeight
|
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 {
|
component Anim: NumberAnimation {
|
||||||
duration: Appearance.anim.durations.normal
|
duration: Appearance.anim.durations.normal
|
||||||
easing.type: Easing.BezierSpline
|
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