dcontent: impl bt device details

This commit is contained in:
2 * r + 2 * t 2025-08-03 18:43:25 +10:00
parent 806c29a02f
commit 365b993221
4 changed files with 348 additions and 6 deletions

View file

@ -16,5 +16,6 @@ QtObject {
property BluetoothDevice active property BluetoothDevice active
property BluetoothAdapter currentAdapter: Bluetooth.defaultAdapter property BluetoothAdapter currentAdapter: Bluetooth.defaultAdapter
property bool editingAdapterName property bool editingAdapterName
property bool fabMenuOpen
} }
} }

View file

@ -72,9 +72,6 @@ RowLayout {
id: details id: details
Details { Details {
anchors.margins: Appearance.padding.normal
anchors.leftMargin: Appearance.padding.normal / 2
session: root.session session: root.session
} }
} }

View file

@ -1,15 +1,337 @@
pragma ComponentBehavior: Bound
import ".." import ".."
import qs.widgets import qs.widgets
import qs.services import qs.services
import qs.config import qs.config
import Quickshell import qs.utils
import Quickshell.Bluetooth import Quickshell.Bluetooth
import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Effects
ColumnLayout { Item {
id: root id: root
required property Session session required property Session session
readonly property BluetoothDevice device: session.bt.active
spacing: Appearance.spacing.normal StyledFlickable {
anchors.fill: parent
flickableDirection: Flickable.VerticalFlick
contentHeight: layout.height
ColumnLayout {
id: layout
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.normal
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: Icons.getBluetoothIcon(root.device.icon)
font.pointSize: Appearance.font.size.extraLarge * 3
font.bold: true
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: root.device.name
font.pointSize: Appearance.font.size.large
font.bold: true
}
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: adapterStatus.implicitHeight + Appearance.padding.large * 2
radius: Appearance.rounding.normal
color: Colours.palette.m3surfaceContainer
ColumnLayout {
id: adapterStatus
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.larger
Toggle {
label: qsTr("Connected")
checked: root.device.connected
toggle.onToggled: root.device.connected = checked
}
Toggle {
label: qsTr("Paired")
checked: root.device.paired
toggle.onToggled: {
if (root.device.paired)
root.device.forget();
else
root.device.pair();
}
}
Toggle {
label: qsTr("Blocked")
checked: root.device.blocked
toggle.onToggled: root.device.blocked = checked
}
}
}
}
}
ColumnLayout {
anchors.right: fabRoot.right
anchors.bottom: fabRoot.top
anchors.bottomMargin: Appearance.padding.normal
Repeater {
id: fabMenu
model: [
{
icon: "handshake",
label: root.device.trusted ? qsTr("Untrust") : qsTr("Trust"),
onClicked: () => root.device.trusted = !root.device.trusted
},
{
icon: "block",
label: root.device.blocked ? qsTr("Unblock") : qsTr("Block"),
onClicked: () => root.device.blocked = !root.device.blocked
},
{
icon: "missing_controller",
label: root.device.paired ? qsTr("Unpair") : qsTr("Pair"),
onClicked: () => {
if (root.device.paired)
root.device.forget();
else
root.device.pair();
}
},
{
icon: "bluetooth_connected",
label: root.device.connected ? qsTr("Disconnect") : qsTr("Connect"),
onClicked: () => root.device.connected = !root.device.connected
}
]
StyledClippingRect {
id: fabMenuItem
required property var modelData
required property int index
Layout.alignment: Qt.AlignRight
implicitHeight: fabMenuItemInner.implicitHeight + Appearance.padding.larger * 2
radius: Appearance.rounding.full
color: Colours.palette.m3primaryContainer
opacity: 0
states: State {
name: "visible"
when: root.session.bt.fabMenuOpen
PropertyChanges {
fabMenuItem.implicitWidth: fabMenuItemInner.implicitWidth + Appearance.padding.large * 2
fabMenuItem.opacity: 1
fabMenuItemInner.opacity: 1
}
}
transitions: [
Transition {
to: "visible"
SequentialAnimation {
PauseAnimation {
duration: (fabMenu.count - 1 - fabMenuItem.index) * Appearance.anim.durations.small / 8
}
ParallelAnimation {
Anim {
property: "implicitWidth"
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
Anim {
property: "opacity"
duration: Appearance.anim.durations.small
}
}
}
},
Transition {
from: "visible"
SequentialAnimation {
PauseAnimation {
duration: fabMenuItem.index * Appearance.anim.durations.small / 8
}
ParallelAnimation {
Anim {
property: "implicitWidth"
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
Anim {
property: "opacity"
duration: Appearance.anim.durations.small
}
}
}
}
]
StateLayer {
function onClicked(): void {
root.session.bt.fabMenuOpen = false;
fabMenuItem.modelData.onClicked();
}
}
RowLayout {
id: fabMenuItemInner
anchors.centerIn: parent
spacing: Appearance.spacing.normal
opacity: 0
MaterialIcon {
text: fabMenuItem.modelData.icon
color: Colours.palette.m3onPrimaryContainer
fill: 1
}
StyledText {
text: fabMenuItem.modelData.label
color: Colours.palette.m3onPrimaryContainer
}
}
}
}
}
Item {
id: fabRoot
anchors.right: parent.right
anchors.bottom: parent.bottom
implicitWidth: 64
implicitHeight: 64
StyledRect {
id: fabBg
anchors.right: parent.right
anchors.top: parent.top
implicitWidth: 64
implicitHeight: 64
radius: Appearance.rounding.normal
color: root.session.bt.fabMenuOpen ? Colours.palette.m3primary : Colours.palette.m3primaryContainer
states: State {
name: "expanded"
when: root.session.bt.fabMenuOpen
PropertyChanges {
fabBg.implicitWidth: 48
fabBg.implicitHeight: 48
fabBg.radius: 48 / 2
fab.font.pointSize: Appearance.font.size.larger
}
}
transitions: Transition {
Anim {
properties: "implicitWidth,implicitHeight"
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
Anim {
properties: "radius,font.pointSize"
}
}
Elevation {
anchors.fill: parent
radius: parent.radius
z: -1
level: fabState.containsMouse && !fabState.pressed ? 4 : 3
}
StateLayer {
id: fabState
color: root.session.bt.fabMenuOpen ? Colours.palette.m3onPrimary : Colours.palette.m3onPrimaryContainer
function onClicked(): void {
root.session.bt.fabMenuOpen = !root.session.bt.fabMenuOpen;
}
}
MaterialIcon {
id: fab
anchors.centerIn: parent
animate: true
text: root.session.bt.fabMenuOpen ? "close" : "settings"
color: root.session.bt.fabMenuOpen ? Colours.palette.m3onPrimary : Colours.palette.m3onPrimaryContainer
font.pointSize: Appearance.font.size.large
fill: 1
}
Behavior on radius {
Anim {}
}
}
}
component Toggle: RowLayout {
required property string label
property alias checked: toggle.checked
property alias toggle: toggle
Layout.fillWidth: true
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
easing.bezierCurve: Appearance.anim.curves.standard
}
} }

22
widgets/Elevation.qml Normal file
View file

@ -0,0 +1,22 @@
import qs.services
import qs.config
import QtQuick
import QtQuick.Effects
RectangularShadow {
property int level
property real dp: [0, 1, 3, 6, 8, 12][level]
color: Qt.alpha(Colours.palette.m3shadow, 0.7)
blur: (dp * 5) ** 0.7
spread: -dp * 0.3 + (dp * 0.1) ** 2
offset.y: dp / 2
Behavior on dp {
NumberAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}