refactor: ToggleButton Tooltips and DeviceList
This commit is contained in:
parent
ff4e9bbdfd
commit
ef46a02b2f
13 changed files with 914 additions and 927 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import ".."
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.components.effects
|
||||
import qs.services
|
||||
import qs.config
|
||||
|
|
@ -13,12 +14,31 @@ StyledRect {
|
|||
property string icon
|
||||
property string label
|
||||
property string accent: "Secondary"
|
||||
property real iconSize: Appearance.font.size.large
|
||||
property real horizontalPadding: Appearance.padding.large
|
||||
property real verticalPadding: Appearance.padding.normal
|
||||
property string tooltip: ""
|
||||
|
||||
property bool hovered: false
|
||||
signal clicked
|
||||
|
||||
Component.onCompleted: {
|
||||
hovered = toggleStateLayer.containsMouse;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: toggleStateLayer
|
||||
function onContainsMouseChanged() {
|
||||
const newHovered = toggleStateLayer.containsMouse;
|
||||
if (hovered !== newHovered) {
|
||||
hovered = newHovered;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Layout.preferredWidth: implicitWidth + (toggleStateLayer.pressed ? Appearance.padding.normal * 2 : toggled ? Appearance.padding.small * 2 : 0)
|
||||
implicitWidth: toggleBtnInner.implicitWidth + Appearance.padding.large * 2
|
||||
implicitHeight: toggleBtnIcon.implicitHeight + Appearance.padding.normal * 2
|
||||
implicitWidth: toggleBtnInner.implicitWidth + horizontalPadding * 2
|
||||
implicitHeight: toggleBtnIcon.implicitHeight + verticalPadding * 2
|
||||
|
||||
radius: toggled || toggleStateLayer.pressed ? Appearance.rounding.small : Math.min(width, height) / 2 * Math.min(1, Appearance.rounding.scale)
|
||||
color: toggled ? Colours.palette[`m3${accent.toLowerCase()}`] : Colours.palette[`m3${accent.toLowerCase()}Container`]
|
||||
|
|
@ -46,7 +66,7 @@ StyledRect {
|
|||
fill: root.toggled ? 1 : 0
|
||||
text: root.icon
|
||||
color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`]
|
||||
font.pointSize: Appearance.font.size.large
|
||||
font.pointSize: root.iconSize
|
||||
|
||||
Behavior on fill {
|
||||
Anim {}
|
||||
|
|
@ -78,5 +98,30 @@ StyledRect {
|
|||
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
|
||||
}
|
||||
}
|
||||
|
||||
// Tooltip - positioned absolutely, doesn't affect layout
|
||||
Loader {
|
||||
id: tooltipLoader
|
||||
active: root.tooltip !== ""
|
||||
asynchronous: true
|
||||
z: 10000
|
||||
width: 0
|
||||
height: 0
|
||||
sourceComponent: Component {
|
||||
Tooltip {
|
||||
target: root
|
||||
text: root.tooltip
|
||||
}
|
||||
}
|
||||
// Completely remove from layout
|
||||
Layout.fillWidth: false
|
||||
Layout.fillHeight: false
|
||||
Layout.preferredWidth: 0
|
||||
Layout.preferredHeight: 0
|
||||
Layout.maximumWidth: 0
|
||||
Layout.maximumHeight: 0
|
||||
Layout.minimumWidth: 0
|
||||
Layout.minimumHeight: 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
170
components/controls/Tooltip.qml
Normal file
170
components/controls/Tooltip.qml
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
import ".."
|
||||
import qs.components.effects
|
||||
import qs.services
|
||||
import qs.config
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
required property Item target
|
||||
required property string text
|
||||
property int delay: 500
|
||||
property int timeout: 0
|
||||
|
||||
property bool tooltipVisible: false
|
||||
property Timer showTimer: Timer {
|
||||
interval: root.delay
|
||||
onTriggered: root.tooltipVisible = true
|
||||
}
|
||||
property Timer hideTimer: Timer {
|
||||
interval: root.timeout
|
||||
onTriggered: root.tooltipVisible = false
|
||||
}
|
||||
|
||||
// Popup properties - doesn't affect layout
|
||||
parent: {
|
||||
let p = target;
|
||||
// Walk up to find the root Item (usually has anchors.fill: parent)
|
||||
while (p && p.parent) {
|
||||
const parentItem = p.parent;
|
||||
// Check if this looks like a root pane Item
|
||||
if (parentItem && parentItem.anchors && parentItem.anchors.fill !== undefined) {
|
||||
return parentItem;
|
||||
}
|
||||
p = parentItem;
|
||||
}
|
||||
// Fallback
|
||||
return target.parent?.parent?.parent ?? target.parent?.parent ?? target.parent ?? target;
|
||||
}
|
||||
|
||||
visible: tooltipVisible
|
||||
modal: false
|
||||
closePolicy: Popup.NoAutoClose
|
||||
padding: 0
|
||||
margins: 0
|
||||
|
||||
// Update position when target moves or tooltip becomes visible
|
||||
onTooltipVisibleChanged: {
|
||||
if (tooltipVisible) {
|
||||
Qt.callLater(updatePosition);
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: root.target
|
||||
function onXChanged() { if (root.tooltipVisible) root.updatePosition(); }
|
||||
function onYChanged() { if (root.tooltipVisible) root.updatePosition(); }
|
||||
function onWidthChanged() { if (root.tooltipVisible) root.updatePosition(); }
|
||||
function onHeightChanged() { if (root.tooltipVisible) root.updatePosition(); }
|
||||
}
|
||||
|
||||
function updatePosition() {
|
||||
if (!target || !parent) return;
|
||||
|
||||
// Wait for tooltipRect to have its size calculated
|
||||
Qt.callLater(() => {
|
||||
if (!target || !parent || !tooltipRect) return;
|
||||
|
||||
// Get target position in parent's coordinate system
|
||||
const targetPos = target.mapToItem(parent, 0, 0);
|
||||
const targetCenterX = targetPos.x + target.width / 2;
|
||||
|
||||
// Get tooltip size (use width/height if available, otherwise implicit)
|
||||
const tooltipWidth = tooltipRect.width > 0 ? tooltipRect.width : tooltipRect.implicitWidth;
|
||||
const tooltipHeight = tooltipRect.height > 0 ? tooltipRect.height : tooltipRect.implicitHeight;
|
||||
|
||||
// Center tooltip horizontally on target
|
||||
let newX = targetCenterX - tooltipWidth / 2;
|
||||
|
||||
// Position tooltip above target
|
||||
let newY = targetPos.y - tooltipHeight - Appearance.spacing.small;
|
||||
|
||||
// Keep within bounds
|
||||
const padding = Appearance.padding.normal;
|
||||
if (newX < padding) {
|
||||
newX = padding;
|
||||
} else if (newX + tooltipWidth > (parent.width - padding)) {
|
||||
newX = parent.width - tooltipWidth - padding;
|
||||
}
|
||||
|
||||
// Update popup position
|
||||
x = newX;
|
||||
y = newY;
|
||||
});
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Appearance.anim.durations.expressiveFastSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Appearance.anim.durations.expressiveFastSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
|
||||
}
|
||||
}
|
||||
|
||||
// Monitor hover state
|
||||
Connections {
|
||||
target: root.target
|
||||
function onHoveredChanged() {
|
||||
if (target.hovered) {
|
||||
showTimer.start();
|
||||
if (timeout > 0) {
|
||||
hideTimer.stop();
|
||||
hideTimer.start();
|
||||
}
|
||||
} else {
|
||||
showTimer.stop();
|
||||
hideTimer.stop();
|
||||
tooltipVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: StyledRect {
|
||||
id: tooltipRect
|
||||
|
||||
implicitWidth: tooltipText.implicitWidth + Appearance.padding.normal * 2
|
||||
implicitHeight: tooltipText.implicitHeight + Appearance.padding.smaller * 2
|
||||
|
||||
color: Colours.palette.m3surfaceContainerHighest
|
||||
radius: Appearance.rounding.small
|
||||
|
||||
// Add elevation for depth
|
||||
Elevation {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
z: -1
|
||||
level: 3
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: tooltipText
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: root.text
|
||||
color: Colours.palette.m3onSurface
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (tooltipVisible) {
|
||||
updatePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -24,8 +24,23 @@ SplitPaneWithDetails {
|
|||
}
|
||||
|
||||
leftContent: Component {
|
||||
DeviceList {
|
||||
session: root.session
|
||||
StyledFlickable {
|
||||
id: leftFlickable
|
||||
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
contentHeight: deviceList.height
|
||||
|
||||
StyledScrollBar.vertical: StyledScrollBar {
|
||||
flickable: leftFlickable
|
||||
}
|
||||
|
||||
DeviceList {
|
||||
id: deviceList
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
session: root.session
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,404 +20,427 @@ StyledFlickable {
|
|||
readonly property BluetoothDevice device: session.bt.active
|
||||
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
contentHeight: layoutWrapper.height
|
||||
contentHeight: detailsWrapper.height
|
||||
|
||||
StyledScrollBar.vertical: StyledScrollBar {
|
||||
flickable: root
|
||||
}
|
||||
|
||||
Item {
|
||||
id: layoutWrapper
|
||||
Item {
|
||||
id: detailsWrapper
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
implicitHeight: details.implicitHeight
|
||||
|
||||
DeviceDetails {
|
||||
id: details
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
implicitHeight: layout.height
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
session: root.session
|
||||
device: root.device
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
SettingsHeader {
|
||||
icon: Icons.getBluetoothIcon(root.device?.icon ?? "")
|
||||
title: root.device?.name ?? ""
|
||||
}
|
||||
|
||||
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: deviceStatus.implicitHeight + Appearance.padding.large * 2
|
||||
|
||||
radius: Appearance.rounding.normal
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
|
||||
ColumnLayout {
|
||||
id: deviceStatus
|
||||
|
||||
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 ?? false
|
||||
toggle.onToggled: root.device.connected = checked
|
||||
}
|
||||
|
||||
Toggle {
|
||||
label: qsTr("Paired")
|
||||
checked: root.device?.paired ?? false
|
||||
toggle.onToggled: {
|
||||
if (root.device.paired)
|
||||
root.device.forget();
|
||||
else
|
||||
root.device.pair();
|
||||
}
|
||||
}
|
||||
|
||||
Toggle {
|
||||
label: qsTr("Blocked")
|
||||
checked: root.device?.blocked ?? false
|
||||
toggle.onToggled: root.device.blocked = checked
|
||||
}
|
||||
headerComponent: Component {
|
||||
SettingsHeader {
|
||||
icon: Icons.getBluetoothIcon(root.device?.icon ?? "")
|
||||
title: root.device?.name ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.large
|
||||
text: qsTr("Device properties")
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
font.weight: 500
|
||||
}
|
||||
sections: [
|
||||
Component {
|
||||
ColumnLayout {
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Additional settings")
|
||||
color: Colours.palette.m3outline
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: deviceProps.implicitHeight + Appearance.padding.large * 2
|
||||
|
||||
radius: Appearance.rounding.normal
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
|
||||
ColumnLayout {
|
||||
id: deviceProps
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Appearance.padding.large
|
||||
|
||||
spacing: Appearance.spacing.larger
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
Item {
|
||||
id: renameDevice
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: Appearance.spacing.small
|
||||
|
||||
implicitHeight: renameLabel.implicitHeight + deviceNameEdit.implicitHeight
|
||||
|
||||
states: State {
|
||||
name: "editingDeviceName"
|
||||
when: root.session.bt.editingDeviceName
|
||||
|
||||
AnchorChanges {
|
||||
target: deviceNameEdit
|
||||
anchors.top: renameDevice.top
|
||||
}
|
||||
PropertyChanges {
|
||||
renameDevice.implicitHeight: deviceNameEdit.implicitHeight
|
||||
renameLabel.opacity: 0
|
||||
deviceNameEdit.padding: Appearance.padding.normal
|
||||
}
|
||||
}
|
||||
|
||||
transitions: Transition {
|
||||
AnchorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
Anim {
|
||||
properties: "implicitHeight,opacity,padding"
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: renameLabel
|
||||
|
||||
anchors.left: parent.left
|
||||
|
||||
text: qsTr("Device name")
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
|
||||
StyledTextField {
|
||||
id: deviceNameEdit
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: renameLabel.bottom
|
||||
anchors.leftMargin: root.session.bt.editingDeviceName ? 0 : -Appearance.padding.normal
|
||||
|
||||
text: root.device?.name ?? ""
|
||||
readOnly: !root.session.bt.editingDeviceName
|
||||
onAccepted: {
|
||||
root.session.bt.editingDeviceName = false;
|
||||
root.device.name = text;
|
||||
}
|
||||
|
||||
leftPadding: Appearance.padding.normal
|
||||
rightPadding: Appearance.padding.normal
|
||||
|
||||
background: StyledRect {
|
||||
radius: Appearance.rounding.small
|
||||
border.width: 2
|
||||
border.color: Colours.palette.m3primary
|
||||
opacity: root.session.bt.editingDeviceName ? 1 : 0
|
||||
|
||||
Behavior on border.color {
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on anchors.leftMargin {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.large
|
||||
text: qsTr("Connection status")
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: cancelEditIcon.implicitHeight + Appearance.padding.smaller * 2
|
||||
|
||||
radius: Appearance.rounding.small
|
||||
color: Colours.palette.m3secondaryContainer
|
||||
opacity: root.session.bt.editingDeviceName ? 1 : 0
|
||||
scale: root.session.bt.editingDeviceName ? 1 : 0.5
|
||||
|
||||
StateLayer {
|
||||
color: Colours.palette.m3onSecondaryContainer
|
||||
disabled: !root.session.bt.editingDeviceName
|
||||
|
||||
function onClicked(): void {
|
||||
root.session.bt.editingDeviceName = false;
|
||||
deviceNameEdit.text = Qt.binding(() => root.device?.name ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: cancelEditIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
animate: true
|
||||
text: "cancel"
|
||||
color: Colours.palette.m3onSecondaryContainer
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.expressiveFastSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: editIcon.implicitHeight + Appearance.padding.smaller * 2
|
||||
|
||||
radius: root.session.bt.editingDeviceName ? Appearance.rounding.small : implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
|
||||
color: Qt.alpha(Colours.palette.m3primary, root.session.bt.editingDeviceName ? 1 : 0)
|
||||
|
||||
StateLayer {
|
||||
color: root.session.bt.editingDeviceName ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
|
||||
|
||||
function onClicked(): void {
|
||||
root.session.bt.editingDeviceName = !root.session.bt.editingDeviceName;
|
||||
if (root.session.bt.editingDeviceName)
|
||||
deviceNameEdit.forceActiveFocus();
|
||||
else
|
||||
deviceNameEdit.accepted();
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: editIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
animate: true
|
||||
text: root.session.bt.editingDeviceName ? "check_circle" : "edit"
|
||||
color: root.session.bt.editingDeviceName ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Toggle {
|
||||
label: qsTr("Trusted")
|
||||
checked: root.device?.trusted ?? false
|
||||
toggle.onToggled: root.device.trusted = checked
|
||||
}
|
||||
|
||||
Toggle {
|
||||
label: qsTr("Wake allowed")
|
||||
checked: root.device?.wakeAllowed ?? false
|
||||
toggle.onToggled: root.device.wakeAllowed = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.large
|
||||
text: qsTr("Device information")
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Information about this device")
|
||||
color: Colours.palette.m3outline
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: deviceInfo.implicitHeight + Appearance.padding.large * 2
|
||||
|
||||
radius: Appearance.rounding.normal
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
|
||||
ColumnLayout {
|
||||
id: deviceInfo
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Appearance.padding.large
|
||||
|
||||
spacing: Appearance.spacing.small / 2
|
||||
|
||||
StyledText {
|
||||
text: root.device?.batteryAvailable ? qsTr("Device battery (%1%)").arg(root.device.battery * 100) : qsTr("Battery unavailable")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: Appearance.spacing.small / 2
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Appearance.padding.smaller
|
||||
spacing: Appearance.spacing.small / 2
|
||||
|
||||
StyledRect {
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: root.device?.batteryAvailable ? parent.width * root.device.battery : 0
|
||||
radius: Appearance.rounding.full
|
||||
color: Colours.palette.m3primary
|
||||
StyledText {
|
||||
text: qsTr("Connection settings for this device")
|
||||
color: Colours.palette.m3outline
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Appearance.rounding.full
|
||||
color: Colours.palette.m3secondaryContainer
|
||||
implicitHeight: deviceStatus.implicitHeight + Appearance.padding.large * 2
|
||||
|
||||
StyledRect {
|
||||
radius: Appearance.rounding.normal
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
|
||||
ColumnLayout {
|
||||
id: deviceStatus
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: parent.height * 0.25
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Appearance.padding.large
|
||||
|
||||
implicitWidth: height
|
||||
radius: Appearance.rounding.full
|
||||
color: Colours.palette.m3primary
|
||||
spacing: Appearance.spacing.larger
|
||||
|
||||
Toggle {
|
||||
label: qsTr("Connected")
|
||||
checked: root.device?.connected ?? false
|
||||
toggle.onToggled: root.device.connected = checked
|
||||
}
|
||||
|
||||
Toggle {
|
||||
label: qsTr("Paired")
|
||||
checked: root.device?.paired ?? false
|
||||
toggle.onToggled: {
|
||||
if (root.device.paired)
|
||||
root.device.forget();
|
||||
else
|
||||
root.device.pair();
|
||||
}
|
||||
}
|
||||
|
||||
Toggle {
|
||||
label: qsTr("Blocked")
|
||||
checked: root.device?.blocked ?? false
|
||||
toggle.onToggled: root.device.blocked = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Component {
|
||||
ColumnLayout {
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
text: qsTr("Dbus path")
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.large
|
||||
text: qsTr("Device properties")
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Additional settings")
|
||||
color: Colours.palette.m3outline
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: deviceProps.implicitHeight + Appearance.padding.large * 2
|
||||
|
||||
radius: Appearance.rounding.normal
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
|
||||
ColumnLayout {
|
||||
id: deviceProps
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Appearance.padding.large
|
||||
|
||||
spacing: Appearance.spacing.larger
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
Item {
|
||||
id: renameDevice
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: Appearance.spacing.small
|
||||
|
||||
implicitHeight: renameLabel.implicitHeight + deviceNameEdit.implicitHeight
|
||||
|
||||
states: State {
|
||||
name: "editingDeviceName"
|
||||
when: root.session.bt.editingDeviceName
|
||||
|
||||
AnchorChanges {
|
||||
target: deviceNameEdit
|
||||
anchors.top: renameDevice.top
|
||||
}
|
||||
PropertyChanges {
|
||||
renameDevice.implicitHeight: deviceNameEdit.implicitHeight
|
||||
renameLabel.opacity: 0
|
||||
deviceNameEdit.padding: Appearance.padding.normal
|
||||
}
|
||||
}
|
||||
|
||||
transitions: Transition {
|
||||
AnchorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
Anim {
|
||||
properties: "implicitHeight,opacity,padding"
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: renameLabel
|
||||
|
||||
anchors.left: parent.left
|
||||
|
||||
text: qsTr("Device name")
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
|
||||
StyledTextField {
|
||||
id: deviceNameEdit
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: renameLabel.bottom
|
||||
anchors.leftMargin: root.session.bt.editingDeviceName ? 0 : -Appearance.padding.normal
|
||||
|
||||
text: root.device?.name ?? ""
|
||||
readOnly: !root.session.bt.editingDeviceName
|
||||
onAccepted: {
|
||||
root.session.bt.editingDeviceName = false;
|
||||
root.device.name = text;
|
||||
}
|
||||
|
||||
leftPadding: Appearance.padding.normal
|
||||
rightPadding: Appearance.padding.normal
|
||||
|
||||
background: StyledRect {
|
||||
radius: Appearance.rounding.small
|
||||
border.width: 2
|
||||
border.color: Colours.palette.m3primary
|
||||
opacity: root.session.bt.editingDeviceName ? 1 : 0
|
||||
|
||||
Behavior on border.color {
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on anchors.leftMargin {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: cancelEditIcon.implicitHeight + Appearance.padding.smaller * 2
|
||||
|
||||
radius: Appearance.rounding.small
|
||||
color: Colours.palette.m3secondaryContainer
|
||||
opacity: root.session.bt.editingDeviceName ? 1 : 0
|
||||
scale: root.session.bt.editingDeviceName ? 1 : 0.5
|
||||
|
||||
StateLayer {
|
||||
color: Colours.palette.m3onSecondaryContainer
|
||||
disabled: !root.session.bt.editingDeviceName
|
||||
|
||||
function onClicked(): void {
|
||||
root.session.bt.editingDeviceName = false;
|
||||
deviceNameEdit.text = Qt.binding(() => root.device?.name ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: cancelEditIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
animate: true
|
||||
text: "cancel"
|
||||
color: Colours.palette.m3onSecondaryContainer
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.expressiveFastSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: editIcon.implicitHeight + Appearance.padding.smaller * 2
|
||||
|
||||
radius: root.session.bt.editingDeviceName ? Appearance.rounding.small : implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
|
||||
color: Qt.alpha(Colours.palette.m3primary, root.session.bt.editingDeviceName ? 1 : 0)
|
||||
|
||||
StateLayer {
|
||||
color: root.session.bt.editingDeviceName ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
|
||||
|
||||
function onClicked(): void {
|
||||
root.session.bt.editingDeviceName = !root.session.bt.editingDeviceName;
|
||||
if (root.session.bt.editingDeviceName)
|
||||
deviceNameEdit.forceActiveFocus();
|
||||
else
|
||||
deviceNameEdit.accepted();
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: editIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
animate: true
|
||||
text: root.session.bt.editingDeviceName ? "check_circle" : "edit"
|
||||
color: root.session.bt.editingDeviceName ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Toggle {
|
||||
label: qsTr("Trusted")
|
||||
checked: root.device?.trusted ?? false
|
||||
toggle.onToggled: root.device.trusted = checked
|
||||
}
|
||||
|
||||
Toggle {
|
||||
label: qsTr("Wake allowed")
|
||||
checked: root.device?.wakeAllowed ?? false
|
||||
toggle.onToggled: root.device.wakeAllowed = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Component {
|
||||
ColumnLayout {
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
StyledText {
|
||||
text: root.device?.dbusPath ?? ""
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.large
|
||||
text: qsTr("Device information")
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
text: qsTr("MAC address")
|
||||
}
|
||||
StyledText {
|
||||
text: qsTr("Information about this device")
|
||||
color: Colours.palette.m3outline
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.device?.address ?? ""
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
StyledRect {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: deviceInfo.implicitHeight + Appearance.padding.large * 2
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
text: qsTr("Bonded")
|
||||
}
|
||||
radius: Appearance.rounding.normal
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
|
||||
StyledText {
|
||||
text: root.device?.bonded ? qsTr("Yes") : qsTr("No")
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
ColumnLayout {
|
||||
id: deviceInfo
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
text: qsTr("System name")
|
||||
}
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Appearance.padding.large
|
||||
|
||||
StyledText {
|
||||
text: root.device?.deviceName ?? ""
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
spacing: Appearance.spacing.small / 2
|
||||
|
||||
StyledText {
|
||||
text: root.device?.batteryAvailable ? qsTr("Device battery (%1%)").arg(root.device.battery * 100) : qsTr("Battery unavailable")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: Appearance.spacing.small / 2
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Appearance.padding.smaller
|
||||
spacing: Appearance.spacing.small / 2
|
||||
|
||||
StyledRect {
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: root.device?.batteryAvailable ? parent.width * root.device.battery : 0
|
||||
radius: Appearance.rounding.full
|
||||
color: Colours.palette.m3primary
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Appearance.rounding.full
|
||||
color: Colours.palette.m3secondaryContainer
|
||||
|
||||
StyledRect {
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: parent.height * 0.25
|
||||
|
||||
implicitWidth: height
|
||||
radius: Appearance.rounding.full
|
||||
color: Colours.palette.m3primary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
text: qsTr("Dbus path")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.device?.dbusPath ?? ""
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
text: qsTr("MAC address")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.device?.address ?? ""
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
text: qsTr("Bonded")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.device?.bonded ? qsTr("Yes") : qsTr("No")
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
text: qsTr("System name")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.device?.deviceName ?? ""
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// FAB Menu (positioned absolutely relative to flickable)
|
||||
ColumnLayout {
|
||||
anchors.right: fabRoot.right
|
||||
anchors.bottom: fabRoot.top
|
||||
|
|
|
|||
|
|
@ -48,8 +48,12 @@ DeviceList {
|
|||
toggled: Bluetooth.defaultAdapter?.enabled ?? false
|
||||
icon: "power"
|
||||
accent: "Tertiary"
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
tooltip: qsTr("Toggle Bluetooth")
|
||||
|
||||
function onClicked(): void {
|
||||
onClicked: {
|
||||
const adapter = Bluetooth.defaultAdapter;
|
||||
if (adapter)
|
||||
adapter.enabled = !adapter.enabled;
|
||||
|
|
@ -60,8 +64,12 @@ DeviceList {
|
|||
toggled: Bluetooth.defaultAdapter?.discoverable ?? false
|
||||
icon: root.smallDiscoverable ? "group_search" : ""
|
||||
label: root.smallDiscoverable ? "" : qsTr("Discoverable")
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
tooltip: qsTr("Make discoverable")
|
||||
|
||||
function onClicked(): void {
|
||||
onClicked: {
|
||||
const adapter = Bluetooth.defaultAdapter;
|
||||
if (adapter)
|
||||
adapter.discoverable = !adapter.discoverable;
|
||||
|
|
@ -72,20 +80,44 @@ DeviceList {
|
|||
toggled: Bluetooth.defaultAdapter?.pairable ?? false
|
||||
icon: "missing_controller"
|
||||
label: root.smallPairable ? "" : qsTr("Pairable")
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
tooltip: qsTr("Make pairable")
|
||||
|
||||
function onClicked(): void {
|
||||
onClicked: {
|
||||
const adapter = Bluetooth.defaultAdapter;
|
||||
if (adapter)
|
||||
adapter.pairable = !adapter.pairable;
|
||||
}
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
toggled: Bluetooth.defaultAdapter?.discovering ?? false
|
||||
icon: "bluetooth_searching"
|
||||
accent: "Secondary"
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
tooltip: qsTr("Scan for devices")
|
||||
|
||||
onClicked: {
|
||||
const adapter = Bluetooth.defaultAdapter;
|
||||
if (adapter)
|
||||
adapter.discovering = !adapter.discovering;
|
||||
}
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
toggled: !root.session.bt.active
|
||||
icon: "settings"
|
||||
accent: "Primary"
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
tooltip: qsTr("Bluetooth settings")
|
||||
|
||||
function onClicked(): void {
|
||||
onClicked: {
|
||||
if (root.session.bt.active)
|
||||
root.session.bt.active = null;
|
||||
else {
|
||||
|
|
@ -96,47 +128,6 @@ DeviceList {
|
|||
}
|
||||
}
|
||||
|
||||
titleSuffix: Component {
|
||||
RowLayout {
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: scanIcon.implicitHeight + Appearance.padding.normal * 2
|
||||
|
||||
radius: Bluetooth.defaultAdapter?.discovering ? Appearance.rounding.normal : implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
|
||||
color: Bluetooth.defaultAdapter?.discovering ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer
|
||||
|
||||
StateLayer {
|
||||
color: Bluetooth.defaultAdapter?.discovering ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer
|
||||
|
||||
function onClicked(): void {
|
||||
const adapter = Bluetooth.defaultAdapter;
|
||||
if (adapter)
|
||||
adapter.discovering = !adapter.discovering;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: scanIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
animate: true
|
||||
text: "bluetooth_searching"
|
||||
color: Bluetooth.defaultAdapter?.discovering ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer
|
||||
fill: Bluetooth.defaultAdapter?.discovering ? 1 : 0
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Component {
|
||||
StyledRect {
|
||||
|
|
@ -146,8 +137,7 @@ DeviceList {
|
|||
readonly property bool loading: modelData && (modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting)
|
||||
readonly property bool connected: modelData && modelData.state === BluetoothDeviceState.Connected
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
width: ListView.view ? ListView.view.width : undefined
|
||||
implicitHeight: deviceInner.implicitHeight + Appearance.padding.normal * 2
|
||||
|
||||
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.activeItem === modelData ? Colours.tPalette.m3surfaceContainer.a : 0)
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ Item {
|
|||
model: root.sections
|
||||
|
||||
Loader {
|
||||
required property Component modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: modelData
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import qs.components.controls
|
|||
import qs.components.containers
|
||||
import qs.services
|
||||
import qs.config
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
|
|
@ -55,6 +56,7 @@ ColumnLayout {
|
|||
property var activeItem: null
|
||||
property Component headerComponent: null
|
||||
property Component titleSuffix: null
|
||||
property bool showHeader: true
|
||||
|
||||
signal itemSelected(var item)
|
||||
|
||||
|
|
@ -66,7 +68,7 @@ ColumnLayout {
|
|||
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: root.headerComponent
|
||||
visible: root.headerComponent !== null
|
||||
visible: root.headerComponent !== null && root.showHeader
|
||||
}
|
||||
|
||||
// Title and description row
|
||||
|
|
@ -109,17 +111,15 @@ ColumnLayout {
|
|||
id: view
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
// Use contentHeight to show all items without estimation
|
||||
implicitHeight: contentHeight
|
||||
|
||||
model: root.model
|
||||
delegate: root.delegate
|
||||
|
||||
spacing: Appearance.spacing.small / 2
|
||||
clip: true
|
||||
|
||||
StyledScrollBar.vertical: StyledScrollBar {
|
||||
flickable: view
|
||||
}
|
||||
interactive: false // Disable individual scrolling - parent pane handles it
|
||||
clip: false // Don't clip - let parent handle scrolling
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -183,6 +183,10 @@ Item {
|
|||
toggled: !root.session.launcher.active
|
||||
icon: "settings"
|
||||
accent: "Primary"
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
tooltip: qsTr("Launcher settings")
|
||||
|
||||
onClicked: {
|
||||
if (root.session.launcher.active) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import ".."
|
||||
import "../components"
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.components.effects
|
||||
|
|
@ -10,99 +11,108 @@ import qs.config
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
DeviceDetails {
|
||||
id: root
|
||||
|
||||
required property Session session
|
||||
readonly property var device: session.ethernet.active
|
||||
readonly property var ethernetDevice: session.ethernet.active
|
||||
|
||||
implicitWidth: layout.implicitWidth
|
||||
implicitHeight: layout.implicitHeight
|
||||
device: ethernetDevice
|
||||
|
||||
Component.onCompleted: {
|
||||
if (device && device.interface) {
|
||||
Nmcli.getEthernetDeviceDetails(device.interface, () => {});
|
||||
if (ethernetDevice && ethernetDevice.interface) {
|
||||
Nmcli.getEthernetDeviceDetails(ethernetDevice.interface, () => {});
|
||||
}
|
||||
}
|
||||
|
||||
onDeviceChanged: {
|
||||
if (device && device.interface) {
|
||||
Nmcli.getEthernetDeviceDetails(device.interface, () => {});
|
||||
onEthernetDeviceChanged: {
|
||||
if (ethernetDevice && ethernetDevice.interface) {
|
||||
Nmcli.getEthernetDeviceDetails(ethernetDevice.interface, () => {});
|
||||
} else {
|
||||
Nmcli.ethernetDeviceDetails = null;
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
headerComponent: Component {
|
||||
ConnectionHeader {
|
||||
icon: "cable"
|
||||
title: root.ethernetDevice?.interface ?? qsTr("Unknown")
|
||||
}
|
||||
}
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
spacing: Appearance.spacing.normal
|
||||
sections: [
|
||||
Component {
|
||||
ColumnLayout {
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
ConnectionHeader {
|
||||
icon: "cable"
|
||||
title: root.device?.interface ?? qsTr("Unknown")
|
||||
}
|
||||
SectionHeader {
|
||||
title: qsTr("Connection status")
|
||||
description: qsTr("Connection settings for this device")
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
title: qsTr("Connection status")
|
||||
description: qsTr("Connection settings for this device")
|
||||
}
|
||||
|
||||
SectionContainer {
|
||||
ToggleRow {
|
||||
label: qsTr("Connected")
|
||||
checked: root.device?.connected ?? false
|
||||
toggle.onToggled: {
|
||||
if (checked) {
|
||||
Nmcli.connectEthernet(root.device?.connection || "", root.device?.interface || "", () => {});
|
||||
} else {
|
||||
if (root.device?.connection) {
|
||||
Nmcli.disconnectEthernet(root.device.connection, () => {});
|
||||
SectionContainer {
|
||||
ToggleRow {
|
||||
label: qsTr("Connected")
|
||||
checked: root.ethernetDevice?.connected ?? false
|
||||
toggle.onToggled: {
|
||||
if (checked) {
|
||||
Nmcli.connectEthernet(root.ethernetDevice?.connection || "", root.ethernetDevice?.interface || "", () => {});
|
||||
} else {
|
||||
if (root.ethernetDevice?.connection) {
|
||||
Nmcli.disconnectEthernet(root.ethernetDevice.connection, () => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Component {
|
||||
ColumnLayout {
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
SectionHeader {
|
||||
title: qsTr("Device properties")
|
||||
description: qsTr("Additional information")
|
||||
}
|
||||
|
||||
SectionContainer {
|
||||
contentSpacing: Appearance.spacing.small / 2
|
||||
|
||||
PropertyRow {
|
||||
label: qsTr("Interface")
|
||||
value: root.device?.interface ?? qsTr("Unknown")
|
||||
SectionHeader {
|
||||
title: qsTr("Device properties")
|
||||
description: qsTr("Additional information")
|
||||
}
|
||||
|
||||
PropertyRow {
|
||||
showTopMargin: true
|
||||
label: qsTr("Connection")
|
||||
value: root.device?.connection || qsTr("Not connected")
|
||||
}
|
||||
SectionContainer {
|
||||
contentSpacing: Appearance.spacing.small / 2
|
||||
|
||||
PropertyRow {
|
||||
showTopMargin: true
|
||||
label: qsTr("State")
|
||||
value: root.device?.state ?? qsTr("Unknown")
|
||||
PropertyRow {
|
||||
label: qsTr("Interface")
|
||||
value: root.ethernetDevice?.interface ?? qsTr("Unknown")
|
||||
}
|
||||
|
||||
PropertyRow {
|
||||
showTopMargin: true
|
||||
label: qsTr("Connection")
|
||||
value: root.ethernetDevice?.connection || qsTr("Not connected")
|
||||
}
|
||||
|
||||
PropertyRow {
|
||||
showTopMargin: true
|
||||
label: qsTr("State")
|
||||
value: root.ethernetDevice?.state ?? qsTr("Unknown")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Component {
|
||||
ColumnLayout {
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
SectionHeader {
|
||||
title: qsTr("Connection information")
|
||||
description: qsTr("Network connection details")
|
||||
}
|
||||
SectionHeader {
|
||||
title: qsTr("Connection information")
|
||||
description: qsTr("Network connection details")
|
||||
}
|
||||
|
||||
SectionContainer {
|
||||
ConnectionInfoSection {
|
||||
deviceDetails: Nmcli.ethernetDeviceDetails
|
||||
SectionContainer {
|
||||
ConnectionInfoSection {
|
||||
deviceDetails: Nmcli.ethernetDeviceDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ DeviceList {
|
|||
toggled: !root.session.ethernet.active
|
||||
icon: "settings"
|
||||
accent: "Primary"
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
|
||||
onClicked: {
|
||||
if (root.session.ethernet.active)
|
||||
|
|
@ -55,8 +58,7 @@ DeviceList {
|
|||
StyledRect {
|
||||
required property var modelData
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
width: ListView.view ? ListView.view.width : undefined
|
||||
|
||||
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.activeItem === modelData ? Colours.tPalette.m3surfaceContainer.a : 0)
|
||||
radius: Appearance.rounding.normal
|
||||
|
|
|
|||
|
|
@ -28,23 +28,22 @@ Item {
|
|||
anchors.fill: parent
|
||||
|
||||
leftContent: Component {
|
||||
StyledFlickable {
|
||||
id: leftFlickable
|
||||
|
||||
StyledFlickable {
|
||||
id: leftFlickable
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
contentHeight: leftContent.height
|
||||
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
contentHeight: leftContent.height
|
||||
StyledScrollBar.vertical: StyledScrollBar {
|
||||
flickable: leftFlickable
|
||||
}
|
||||
|
||||
StyledScrollBar.vertical: StyledScrollBar {
|
||||
flickable: leftFlickable
|
||||
}
|
||||
ColumnLayout {
|
||||
id: leftContent
|
||||
|
||||
ColumnLayout {
|
||||
id: leftContent
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: Appearance.spacing.normal
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
// Network header above the collapsible sections
|
||||
RowLayout {
|
||||
|
|
@ -65,6 +64,10 @@ Item {
|
|||
toggled: Nmcli.wifiEnabled
|
||||
icon: "wifi"
|
||||
accent: "Tertiary"
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
tooltip: qsTr("Toggle WiFi")
|
||||
|
||||
onClicked: {
|
||||
Nmcli.toggleWifi(null);
|
||||
|
|
@ -75,6 +78,10 @@ Item {
|
|||
toggled: Nmcli.scanning
|
||||
icon: "wifi_find"
|
||||
accent: "Secondary"
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
tooltip: qsTr("Scan for networks")
|
||||
|
||||
onClicked: {
|
||||
Nmcli.rescanWifi();
|
||||
|
|
@ -85,6 +92,10 @@ Item {
|
|||
toggled: !root.session.ethernet.active && !root.session.network.active
|
||||
icon: "settings"
|
||||
accent: "Primary"
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
tooltip: qsTr("Network settings")
|
||||
|
||||
onClicked: {
|
||||
if (root.session.ethernet.active || root.session.network.active) {
|
||||
|
|
@ -109,127 +120,12 @@ Item {
|
|||
title: qsTr("Ethernet")
|
||||
expanded: true
|
||||
|
||||
ColumnLayout {
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Devices (%1)").arg(Nmcli.ethernetDevices.length)
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
font.weight: 500
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("All available ethernet devices")
|
||||
color: Colours.palette.m3outline
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: ethernetRepeater
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: Nmcli.ethernetDevices
|
||||
|
||||
delegate: StyledRect {
|
||||
required property var modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.ethernet.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0)
|
||||
radius: Appearance.rounding.normal
|
||||
|
||||
StateLayer {
|
||||
function onClicked(): void {
|
||||
root.session.network.active = null;
|
||||
root.session.ethernet.active = modelData;
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Appearance.padding.normal
|
||||
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
StyledRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: icon.implicitHeight + Appearance.padding.normal * 2
|
||||
|
||||
radius: Appearance.rounding.normal
|
||||
color: modelData.connected ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "cable"
|
||||
font.pointSize: Appearance.font.size.large
|
||||
fill: modelData.connected ? 1 : 0
|
||||
color: modelData.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
|
||||
text: modelData.interface || qsTr("Unknown")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: modelData.connected ? qsTr("Connected") : qsTr("Disconnected")
|
||||
color: modelData.connected ? Colours.palette.m3primary : Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
font.weight: modelData.connected ? 500 : 400
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: connectIcon.implicitHeight + Appearance.padding.smaller * 2
|
||||
|
||||
radius: Appearance.rounding.full
|
||||
color: Qt.alpha(Colours.palette.m3primaryContainer, modelData.connected ? 1 : 0)
|
||||
|
||||
StateLayer {
|
||||
function onClicked(): void {
|
||||
if (modelData.connected && modelData.connection) {
|
||||
Nmcli.disconnectEthernet(modelData.connection, () => {});
|
||||
} else {
|
||||
Nmcli.connectEthernet(modelData.connection || "", modelData.interface || "", () => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: connectIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: modelData.connected ? "link_off" : "link"
|
||||
color: modelData.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2
|
||||
sourceComponent: Component {
|
||||
EthernetList {
|
||||
session: root.session
|
||||
showHeader: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -242,195 +138,12 @@ Item {
|
|||
title: qsTr("Wireless")
|
||||
expanded: true
|
||||
|
||||
ColumnLayout {
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Networks (%1)").arg(Nmcli.networks.length)
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: Nmcli.scanning
|
||||
text: qsTr("Scanning...")
|
||||
color: Colours.palette.m3primary
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("All available WiFi networks")
|
||||
color: Colours.palette.m3outline
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: wirelessRepeater
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: ScriptModel {
|
||||
values: [...Nmcli.networks].sort((a, b) => {
|
||||
// Put active/connected network first
|
||||
if (a.active !== b.active)
|
||||
return b.active - a.active;
|
||||
// Then sort by signal strength
|
||||
return b.strength - a.strength;
|
||||
})
|
||||
}
|
||||
|
||||
delegate: StyledRect {
|
||||
required property var modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, (modelData && root.session.network.active === modelData) ? Colours.tPalette.m3surfaceContainer.a : 0)
|
||||
radius: Appearance.rounding.normal
|
||||
|
||||
StateLayer {
|
||||
function onClicked(): void {
|
||||
if (!modelData) {
|
||||
return;
|
||||
}
|
||||
root.session.ethernet.active = null;
|
||||
root.session.network.active = modelData;
|
||||
// Check if we need to refresh saved connections when selecting a network
|
||||
if (modelData.ssid) {
|
||||
checkSavedProfileForNetwork(modelData.ssid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: wirelessRowLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Appearance.padding.normal
|
||||
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
StyledRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: wirelessIcon.implicitHeight + Appearance.padding.normal * 2
|
||||
|
||||
radius: Appearance.rounding.normal
|
||||
color: (modelData && modelData.active) ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh
|
||||
|
||||
MaterialIcon {
|
||||
id: wirelessIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: Icons.getNetworkIcon(modelData && modelData.strength !== undefined ? modelData.strength : 0)
|
||||
font.pointSize: Appearance.font.size.large
|
||||
fill: (modelData && modelData.active) ? 1 : 0
|
||||
color: (modelData && modelData.active) ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: lockBadge
|
||||
|
||||
visible: modelData && modelData.isSecure
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: -Appearance.padding.smaller / 2
|
||||
|
||||
implicitWidth: lockIconSize + Appearance.padding.smaller
|
||||
implicitHeight: lockIconSize + Appearance.padding.smaller
|
||||
radius: Appearance.rounding.full
|
||||
color: Colours.palette.m3secondaryContainer
|
||||
|
||||
readonly property real lockIconSize: lockIcon.implicitWidth
|
||||
|
||||
Elevation {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
z: -1
|
||||
level: 2
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: lockIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "lock"
|
||||
font.pointSize: Appearance.font.size.small
|
||||
fill: 1
|
||||
color: Colours.palette.m3onSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
|
||||
text: (modelData && modelData.ssid) ? modelData.ssid : qsTr("Unknown")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Appearance.spacing.smaller
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: {
|
||||
if (!modelData) return qsTr("Open");
|
||||
if (modelData.active) return qsTr("Connected");
|
||||
if (modelData.isSecure && modelData.security && modelData.security.length > 0) {
|
||||
return modelData.security;
|
||||
}
|
||||
if (modelData.isSecure) return qsTr("Secured");
|
||||
return qsTr("Open");
|
||||
}
|
||||
color: (modelData && modelData.active) ? Colours.palette.m3primary : Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
font.weight: (modelData && modelData.active) ? 500 : 400
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: wirelessConnectIcon.implicitHeight + Appearance.padding.smaller * 2
|
||||
|
||||
radius: Appearance.rounding.full
|
||||
color: Qt.alpha(Colours.palette.m3primaryContainer, (modelData && modelData.active) ? 1 : 0)
|
||||
|
||||
StateLayer {
|
||||
function onClicked(): void {
|
||||
if (modelData && modelData.active) {
|
||||
Nmcli.disconnectFromNetwork();
|
||||
} else if (modelData) {
|
||||
NetworkConnection.handleConnect(modelData, root.session, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: wirelessConnectIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: (modelData && modelData.active) ? "link_off" : "link"
|
||||
color: (modelData && modelData.active) ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
implicitHeight: wirelessRowLayout.implicitHeight + Appearance.padding.normal * 2
|
||||
sourceComponent: Component {
|
||||
WirelessList {
|
||||
session: root.session
|
||||
showHeader: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -443,16 +156,17 @@ Item {
|
|||
Item {
|
||||
id: rightPaneItem
|
||||
|
||||
// Right pane - networking details/settings
|
||||
property var ethernetPane: root.session.ethernet.active
|
||||
property var wirelessPane: root.session.network.active
|
||||
property var pane: ethernetPane || wirelessPane
|
||||
property string paneId: ethernetPane ? ("eth:" + (ethernetPane.interface || "")) : (wirelessPane ? ("wifi:" + (wirelessPane.ssid || wirelessPane.bssid || "")) : "settings")
|
||||
property Component targetComponent: settings
|
||||
property Component nextComponent: settings
|
||||
property Component targetComponent: settingsComponent
|
||||
property Component nextComponent: settingsComponent
|
||||
|
||||
function getComponentForPane() {
|
||||
return pane ? (ethernetPane ? ethernetDetails : wirelessDetails) : settings;
|
||||
if (ethernetPane) return ethernetDetailsComponent;
|
||||
if (wirelessPane) return wirelessDetailsComponent;
|
||||
return settingsComponent;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
|
@ -507,7 +221,7 @@ Item {
|
|||
}
|
||||
|
||||
Component {
|
||||
id: settings
|
||||
id: settingsComponent
|
||||
|
||||
StyledFlickable {
|
||||
id: settingsFlickable
|
||||
|
|
@ -530,7 +244,7 @@ Item {
|
|||
}
|
||||
|
||||
Component {
|
||||
id: ethernetDetails
|
||||
id: ethernetDetailsComponent
|
||||
|
||||
StyledFlickable {
|
||||
id: ethernetFlickable
|
||||
|
|
@ -553,7 +267,7 @@ Item {
|
|||
}
|
||||
|
||||
Component {
|
||||
id: wirelessDetails
|
||||
id: wirelessDetailsComponent
|
||||
|
||||
StyledFlickable {
|
||||
id: wirelessFlickable
|
||||
|
|
@ -580,11 +294,4 @@ Item {
|
|||
session: root.session
|
||||
z: 1000
|
||||
}
|
||||
|
||||
function checkSavedProfileForNetwork(ssid: string): void {
|
||||
if (ssid && ssid.length > 0) {
|
||||
Nmcli.loadSavedConnections(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import ".."
|
||||
import "../components"
|
||||
import "."
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
|
|
@ -12,14 +13,13 @@ import qs.utils
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
DeviceDetails {
|
||||
id: root
|
||||
|
||||
required property Session session
|
||||
readonly property var network: session.network.active
|
||||
|
||||
implicitWidth: layout.implicitWidth
|
||||
implicitHeight: layout.implicitHeight
|
||||
device: network
|
||||
|
||||
Component.onCompleted: {
|
||||
updateDeviceDetails();
|
||||
|
|
@ -102,110 +102,120 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
ConnectionHeader {
|
||||
icon: root.network?.isSecure ? "lock" : "wifi"
|
||||
title: root.network?.ssid ?? qsTr("Unknown")
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
title: qsTr("Connection status")
|
||||
description: qsTr("Connection settings for this network")
|
||||
}
|
||||
|
||||
SectionContainer {
|
||||
ToggleRow {
|
||||
label: qsTr("Connected")
|
||||
checked: root.network?.active ?? false
|
||||
toggle.onToggled: {
|
||||
if (checked) {
|
||||
NetworkConnection.handleConnect(root.network, root.session, null);
|
||||
} else {
|
||||
Nmcli.disconnectFromNetwork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
Layout.minimumHeight: Appearance.font.size.normal + Appearance.padding.normal * 2
|
||||
visible: {
|
||||
if (!root.network || !root.network.ssid) {
|
||||
return false;
|
||||
}
|
||||
return Nmcli.hasSavedProfile(root.network.ssid);
|
||||
}
|
||||
inactiveColour: Colours.palette.m3secondaryContainer
|
||||
inactiveOnColour: Colours.palette.m3onSecondaryContainer
|
||||
text: qsTr("Forget Network")
|
||||
|
||||
onClicked: {
|
||||
if (root.network && root.network.ssid) {
|
||||
if (root.network.active) {
|
||||
Nmcli.disconnectFromNetwork();
|
||||
}
|
||||
Nmcli.forgetNetwork(root.network.ssid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
title: qsTr("Network properties")
|
||||
description: qsTr("Additional information")
|
||||
}
|
||||
|
||||
SectionContainer {
|
||||
contentSpacing: Appearance.spacing.small / 2
|
||||
|
||||
PropertyRow {
|
||||
label: qsTr("SSID")
|
||||
value: root.network?.ssid ?? qsTr("Unknown")
|
||||
}
|
||||
|
||||
PropertyRow {
|
||||
showTopMargin: true
|
||||
label: qsTr("BSSID")
|
||||
value: root.network?.bssid ?? qsTr("Unknown")
|
||||
}
|
||||
|
||||
PropertyRow {
|
||||
showTopMargin: true
|
||||
label: qsTr("Signal strength")
|
||||
value: root.network ? qsTr("%1%").arg(root.network.strength) : qsTr("N/A")
|
||||
}
|
||||
|
||||
PropertyRow {
|
||||
showTopMargin: true
|
||||
label: qsTr("Frequency")
|
||||
value: root.network ? qsTr("%1 MHz").arg(root.network.frequency) : qsTr("N/A")
|
||||
}
|
||||
|
||||
PropertyRow {
|
||||
showTopMargin: true
|
||||
label: qsTr("Security")
|
||||
value: root.network ? (root.network.isSecure ? root.network.security : qsTr("Open")) : qsTr("N/A")
|
||||
}
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
title: qsTr("Connection information")
|
||||
description: qsTr("Network connection details")
|
||||
}
|
||||
|
||||
SectionContainer {
|
||||
ConnectionInfoSection {
|
||||
deviceDetails: Nmcli.wirelessDeviceDetails
|
||||
}
|
||||
}
|
||||
headerComponent: Component {
|
||||
ConnectionHeader {
|
||||
icon: root.network?.isSecure ? "lock" : "wifi"
|
||||
title: root.network?.ssid ?? qsTr("Unknown")
|
||||
}
|
||||
}
|
||||
|
||||
sections: [
|
||||
Component {
|
||||
ColumnLayout {
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
SectionHeader {
|
||||
title: qsTr("Connection status")
|
||||
description: qsTr("Connection settings for this network")
|
||||
}
|
||||
|
||||
SectionContainer {
|
||||
ToggleRow {
|
||||
label: qsTr("Connected")
|
||||
checked: root.network?.active ?? false
|
||||
toggle.onToggled: {
|
||||
if (checked) {
|
||||
NetworkConnection.handleConnect(root.network, root.session, null);
|
||||
} else {
|
||||
Nmcli.disconnectFromNetwork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
Layout.minimumHeight: Appearance.font.size.normal + Appearance.padding.normal * 2
|
||||
visible: {
|
||||
if (!root.network || !root.network.ssid) {
|
||||
return false;
|
||||
}
|
||||
return Nmcli.hasSavedProfile(root.network.ssid);
|
||||
}
|
||||
inactiveColour: Colours.palette.m3secondaryContainer
|
||||
inactiveOnColour: Colours.palette.m3onSecondaryContainer
|
||||
text: qsTr("Forget Network")
|
||||
|
||||
onClicked: {
|
||||
if (root.network && root.network.ssid) {
|
||||
if (root.network.active) {
|
||||
Nmcli.disconnectFromNetwork();
|
||||
}
|
||||
Nmcli.forgetNetwork(root.network.ssid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Component {
|
||||
ColumnLayout {
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
SectionHeader {
|
||||
title: qsTr("Network properties")
|
||||
description: qsTr("Additional information")
|
||||
}
|
||||
|
||||
SectionContainer {
|
||||
contentSpacing: Appearance.spacing.small / 2
|
||||
|
||||
PropertyRow {
|
||||
label: qsTr("SSID")
|
||||
value: root.network?.ssid ?? qsTr("Unknown")
|
||||
}
|
||||
|
||||
PropertyRow {
|
||||
showTopMargin: true
|
||||
label: qsTr("BSSID")
|
||||
value: root.network?.bssid ?? qsTr("Unknown")
|
||||
}
|
||||
|
||||
PropertyRow {
|
||||
showTopMargin: true
|
||||
label: qsTr("Signal strength")
|
||||
value: root.network ? qsTr("%1%").arg(root.network.strength) : qsTr("N/A")
|
||||
}
|
||||
|
||||
PropertyRow {
|
||||
showTopMargin: true
|
||||
label: qsTr("Frequency")
|
||||
value: root.network ? qsTr("%1 MHz").arg(root.network.frequency) : qsTr("N/A")
|
||||
}
|
||||
|
||||
PropertyRow {
|
||||
showTopMargin: true
|
||||
label: qsTr("Security")
|
||||
value: root.network ? (root.network.isSecure ? root.network.security : qsTr("Open")) : qsTr("N/A")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Component {
|
||||
ColumnLayout {
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
SectionHeader {
|
||||
title: qsTr("Connection information")
|
||||
description: qsTr("Network connection details")
|
||||
}
|
||||
|
||||
SectionContainer {
|
||||
ConnectionInfoSection {
|
||||
deviceDetails: Nmcli.wirelessDeviceDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import qs.components.containers
|
|||
import qs.services
|
||||
import qs.config
|
||||
import qs.utils
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
|
|
@ -58,6 +59,9 @@ DeviceList {
|
|||
toggled: Nmcli.wifiEnabled
|
||||
icon: "wifi"
|
||||
accent: "Tertiary"
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
|
||||
onClicked: {
|
||||
Nmcli.toggleWifi(null);
|
||||
|
|
@ -68,6 +72,9 @@ DeviceList {
|
|||
toggled: Nmcli.scanning
|
||||
icon: "wifi_find"
|
||||
accent: "Secondary"
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
|
||||
onClicked: {
|
||||
Nmcli.rescanWifi();
|
||||
|
|
@ -78,6 +85,9 @@ DeviceList {
|
|||
toggled: !root.session.network.active
|
||||
icon: "settings"
|
||||
accent: "Primary"
|
||||
iconSize: Appearance.font.size.normal
|
||||
horizontalPadding: Appearance.padding.normal
|
||||
verticalPadding: Appearance.padding.smaller
|
||||
|
||||
onClicked: {
|
||||
if (root.session.network.active)
|
||||
|
|
@ -94,8 +104,7 @@ DeviceList {
|
|||
StyledRect {
|
||||
required property var modelData
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
width: ListView.view ? ListView.view.width : undefined
|
||||
|
||||
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.activeItem === modelData ? Colours.tPalette.m3surfaceContainer.a : 0)
|
||||
radius: Appearance.rounding.normal
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue