From 9cdc30058a0d53602f6c065315b3956497a2f68c Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 14:03:10 -0500 Subject: controlcenter: font size adjustments on headings --- modules/controlcenter/bluetooth/DeviceList.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'modules/controlcenter/bluetooth/DeviceList.qml') diff --git a/modules/controlcenter/bluetooth/DeviceList.qml b/modules/controlcenter/bluetooth/DeviceList.qml index 3831e4a..8bf5daa 100644 --- a/modules/controlcenter/bluetooth/DeviceList.qml +++ b/modules/controlcenter/bluetooth/DeviceList.qml @@ -97,7 +97,7 @@ ColumnLayout { StyledText { Layout.fillWidth: true text: qsTr("Devices (%1)").arg(Bluetooth.devices.values.length) - font.pointSize: Appearance.font.size.large + font.pointSize: Appearance.font.size.normal font.weight: 500 } -- cgit v1.2.3-freya From 8c84f670e6d47f2c07f51c9bdfdf77a0e2b89b77 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Mon, 17 Nov 2025 08:13:44 -0500 Subject: controlcenter: corrected null anchor warn --- modules/controlcenter/bluetooth/DeviceList.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'modules/controlcenter/bluetooth/DeviceList.qml') diff --git a/modules/controlcenter/bluetooth/DeviceList.qml b/modules/controlcenter/bluetooth/DeviceList.qml index 8bf5daa..db1656b 100644 --- a/modules/controlcenter/bluetooth/DeviceList.qml +++ b/modules/controlcenter/bluetooth/DeviceList.qml @@ -166,8 +166,8 @@ ColumnLayout { readonly property bool loading: modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting readonly property bool connected: modelData.state === BluetoothDeviceState.Connected - anchors.left: parent.left - anchors.right: parent.right + anchors.left: view.contentItem.left + anchors.right: view.contentItem.right implicitHeight: deviceInner.implicitHeight + Appearance.padding.normal * 2 color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.bt.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) -- cgit v1.2.3-freya From 338b636de73606ca30494bf7f119dd77fc7bc9fd Mon Sep 17 00:00:00 2001 From: ATMDA Date: Mon, 17 Nov 2025 08:38:25 -0500 Subject: controlcenter: corrected typeerror null checks --- modules/controlcenter/bluetooth/DeviceList.qml | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'modules/controlcenter/bluetooth/DeviceList.qml') diff --git a/modules/controlcenter/bluetooth/DeviceList.qml b/modules/controlcenter/bluetooth/DeviceList.qml index db1656b..8e79e72 100644 --- a/modules/controlcenter/bluetooth/DeviceList.qml +++ b/modules/controlcenter/bluetooth/DeviceList.qml @@ -163,8 +163,8 @@ ColumnLayout { id: device required property BluetoothDevice modelData - readonly property bool loading: modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting - readonly property bool connected: modelData.state === BluetoothDeviceState.Connected + readonly property bool loading: modelData && (modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting) + readonly property bool connected: modelData && modelData.state === BluetoothDeviceState.Connected anchors.left: view.contentItem.left anchors.right: view.contentItem.right @@ -177,7 +177,8 @@ ColumnLayout { id: stateLayer function onClicked(): void { - root.session.bt.active = device.modelData; + if (device.modelData) + root.session.bt.active = device.modelData; } } @@ -194,20 +195,20 @@ ColumnLayout { implicitHeight: icon.implicitHeight + Appearance.padding.normal * 2 radius: Appearance.rounding.normal - color: device.connected ? Colours.palette.m3primaryContainer : device.modelData.bonded ? Colours.palette.m3secondaryContainer : Colours.tPalette.m3surfaceContainerHigh + color: device.connected ? Colours.palette.m3primaryContainer : (device.modelData && device.modelData.bonded) ? Colours.palette.m3secondaryContainer : Colours.tPalette.m3surfaceContainerHigh StyledRect { anchors.fill: parent radius: parent.radius - color: Qt.alpha(device.connected ? Colours.palette.m3onPrimaryContainer : device.modelData.bonded ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface, stateLayer.pressed ? 0.1 : stateLayer.containsMouse ? 0.08 : 0) + color: Qt.alpha(device.connected ? Colours.palette.m3onPrimaryContainer : (device.modelData && device.modelData.bonded) ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface, stateLayer.pressed ? 0.1 : stateLayer.containsMouse ? 0.08 : 0) } MaterialIcon { id: icon anchors.centerIn: parent - text: Icons.getBluetoothIcon(device.modelData.icon) - color: device.connected ? Colours.palette.m3onPrimaryContainer : device.modelData.bonded ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface + text: Icons.getBluetoothIcon(device.modelData ? device.modelData.icon : "") + color: device.connected ? Colours.palette.m3onPrimaryContainer : (device.modelData && device.modelData.bonded) ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface font.pointSize: Appearance.font.size.large fill: device.connected ? 1 : 0 @@ -224,13 +225,13 @@ ColumnLayout { StyledText { Layout.fillWidth: true - text: device.modelData.name + text: device.modelData ? device.modelData.name : qsTr("Unknown") elide: Text.ElideRight } StyledText { Layout.fillWidth: true - text: device.modelData.address + (device.connected ? qsTr(" (Connected)") : device.modelData.bonded ? qsTr(" (Paired)") : "") + text: (device.modelData ? device.modelData.address : "") + (device.connected ? qsTr(" (Connected)") : (device.modelData && device.modelData.bonded) ? qsTr(" (Paired)") : "") color: Colours.palette.m3outline font.pointSize: Appearance.font.size.small elide: Text.ElideRight @@ -256,7 +257,8 @@ ColumnLayout { disabled: device.loading function onClicked(): void { - device.modelData.connected = !device.modelData.connected; + if (device.modelData) + device.modelData.connected = !device.modelData.connected; } } @@ -265,7 +267,7 @@ ColumnLayout { anchors.centerIn: parent animate: true - text: device.modelData.connected ? "link_off" : "link" + text: (device.modelData && device.modelData.connected) ? "link_off" : "link" color: device.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface opacity: device.loading ? 0 : 1 -- cgit v1.2.3-freya From 23e5d7d13ce0432e17a7e2077b12b44278f919b6 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Mon, 17 Nov 2025 15:46:40 -0500 Subject: controlcenter: renamed panel titles from settings to panel name --- modules/controlcenter/appearance/AppearancePane.qml | 2 +- modules/controlcenter/audio/AudioPane.qml | 4 ++-- modules/controlcenter/bluetooth/DeviceList.qml | 2 +- modules/controlcenter/launcher/LauncherPane.qml | 2 +- modules/controlcenter/network/NetworkingPane.qml | 4 ++-- modules/controlcenter/taskbar/TaskbarPane.qml | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) (limited to 'modules/controlcenter/bluetooth/DeviceList.qml') diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index 177e7b9..891f64b 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -147,7 +147,7 @@ RowLayout { spacing: Appearance.spacing.smaller StyledText { - text: qsTr("Settings") + text: qsTr("Appearance") font.pointSize: Appearance.font.size.large font.weight: 500 } diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index 3440a2f..c2d60d8 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -74,13 +74,13 @@ RowLayout { anchors.right: parent.right spacing: Appearance.spacing.normal - // Settings header above the collapsible sections + // Audio header above the collapsible sections RowLayout { Layout.fillWidth: true spacing: Appearance.spacing.smaller StyledText { - text: qsTr("Settings") + text: qsTr("Audio") font.pointSize: Appearance.font.size.large font.weight: 500 } diff --git a/modules/controlcenter/bluetooth/DeviceList.qml b/modules/controlcenter/bluetooth/DeviceList.qml index 8e79e72..06700e8 100644 --- a/modules/controlcenter/bluetooth/DeviceList.qml +++ b/modules/controlcenter/bluetooth/DeviceList.qml @@ -25,7 +25,7 @@ ColumnLayout { spacing: Appearance.spacing.smaller StyledText { - text: qsTr("Settings") + text: qsTr("Bluetooth") font.pointSize: Appearance.font.size.large font.weight: 500 } diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index bf4e85f..300117a 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -194,7 +194,7 @@ RowLayout { spacing: Appearance.spacing.smaller StyledText { - text: qsTr("Settings") + text: qsTr("Launcher") font.pointSize: Appearance.font.size.large font.weight: 500 } diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index a632c2b..52499d8 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -82,13 +82,13 @@ Item { anchors.right: parent.right spacing: Appearance.spacing.normal - // Settings header above the collapsible sections + // Network header above the collapsible sections RowLayout { Layout.fillWidth: true spacing: Appearance.spacing.smaller StyledText { - text: qsTr("Settings") + text: qsTr("Network") font.pointSize: Appearance.font.size.large font.weight: 500 } diff --git a/modules/controlcenter/taskbar/TaskbarPane.qml b/modules/controlcenter/taskbar/TaskbarPane.qml index b798da8..507a239 100644 --- a/modules/controlcenter/taskbar/TaskbarPane.qml +++ b/modules/controlcenter/taskbar/TaskbarPane.qml @@ -171,7 +171,7 @@ Item { spacing: Appearance.spacing.smaller StyledText { - text: qsTr("Settings") + text: qsTr("Taskbar") font.pointSize: Appearance.font.size.large font.weight: 500 } -- cgit v1.2.3-freya From ff4e9bbdfd13077f94e1a30ac1dd7e7df2238438 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 19 Nov 2025 19:51:28 -0500 Subject: refactor: Updated Bluetooth, Ethernet, and Wireless lists to use DeviceList component and improved layout structure --- modules/controlcenter/bluetooth/BtPane.qml | 1 + modules/controlcenter/bluetooth/DeviceList.qml | 293 ++++++++------------- modules/controlcenter/components/DeviceDetails.qml | 108 ++++++++ modules/controlcenter/components/DeviceList.qml | 125 +++++++++ modules/controlcenter/network/EthernetList.qml | 93 +++---- modules/controlcenter/network/WirelessList.qml | 152 +++++------ 6 files changed, 446 insertions(+), 326 deletions(-) create mode 100644 modules/controlcenter/components/DeviceDetails.qml create mode 100644 modules/controlcenter/components/DeviceList.qml (limited to 'modules/controlcenter/bluetooth/DeviceList.qml') diff --git a/modules/controlcenter/bluetooth/BtPane.qml b/modules/controlcenter/bluetooth/BtPane.qml index 3cd6ef9..6877801 100644 --- a/modules/controlcenter/bluetooth/BtPane.qml +++ b/modules/controlcenter/bluetooth/BtPane.qml @@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound import ".." import "../components" +import "." import qs.components import qs.components.controls import qs.components.containers diff --git a/modules/controlcenter/bluetooth/DeviceList.qml b/modules/controlcenter/bluetooth/DeviceList.qml index 06700e8..b3db236 100644 --- a/modules/controlcenter/bluetooth/DeviceList.qml +++ b/modules/controlcenter/bluetooth/DeviceList.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.containers @@ -12,165 +13,144 @@ import Quickshell.Bluetooth import QtQuick import QtQuick.Layouts -ColumnLayout { +DeviceList { id: root required property Session session readonly property bool smallDiscoverable: width <= 540 readonly property bool smallPairable: width <= 480 - spacing: Appearance.spacing.small + title: qsTr("Devices (%1)").arg(Bluetooth.devices.values.length) + description: qsTr("All available bluetooth devices") + activeItem: session.bt.active - RowLayout { - spacing: Appearance.spacing.smaller + model: ScriptModel { + id: deviceModel - StyledText { - text: qsTr("Bluetooth") - font.pointSize: Appearance.font.size.large - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } - - ToggleButton { - toggled: Bluetooth.defaultAdapter?.enabled ?? false - icon: "power" - accent: "Tertiary" - - function onClicked(): void { - const adapter = Bluetooth.defaultAdapter; - if (adapter) - adapter.enabled = !adapter.enabled; - } - } - - ToggleButton { - toggled: Bluetooth.defaultAdapter?.discoverable ?? false - icon: root.smallDiscoverable ? "group_search" : "" - label: root.smallDiscoverable ? "" : qsTr("Discoverable") - - function onClicked(): void { - const adapter = Bluetooth.defaultAdapter; - if (adapter) - adapter.discoverable = !adapter.discoverable; - } - } - - ToggleButton { - toggled: Bluetooth.defaultAdapter?.pairable ?? false - icon: "missing_controller" - label: root.smallPairable ? "" : qsTr("Pairable") - - function onClicked(): void { - const adapter = Bluetooth.defaultAdapter; - if (adapter) - adapter.pairable = !adapter.pairable; - } - } - - ToggleButton { - toggled: !root.session.bt.active - icon: "settings" - accent: "Primary" - - function onClicked(): void { - if (root.session.bt.active) - root.session.bt.active = null; - else { - root.session.bt.active = deviceModel.values[0] ?? null; - } - } - } + values: [...Bluetooth.devices.values].sort((a, b) => (b.connected - a.connected) || (b.paired - a.paired)) } - RowLayout { - Layout.topMargin: Appearance.spacing.large - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - ColumnLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + headerComponent: Component { + RowLayout { + spacing: Appearance.spacing.smaller StyledText { - Layout.fillWidth: true - text: qsTr("Devices (%1)").arg(Bluetooth.devices.values.length) - font.pointSize: Appearance.font.size.normal + text: qsTr("Bluetooth") + font.pointSize: Appearance.font.size.large font.weight: 500 } - StyledText { + Item { Layout.fillWidth: true - text: qsTr("All available bluetooth devices") - color: Colours.palette.m3outline } - } - StyledRect { - implicitWidth: implicitHeight - implicitHeight: scanIcon.implicitHeight + Appearance.padding.normal * 2 + ToggleButton { + toggled: Bluetooth.defaultAdapter?.enabled ?? false + icon: "power" + accent: "Tertiary" - 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 + function onClicked(): void { + const adapter = Bluetooth.defaultAdapter; + if (adapter) + adapter.enabled = !adapter.enabled; + } + } - StateLayer { - color: Bluetooth.defaultAdapter?.discovering ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer + ToggleButton { + toggled: Bluetooth.defaultAdapter?.discoverable ?? false + icon: root.smallDiscoverable ? "group_search" : "" + label: root.smallDiscoverable ? "" : qsTr("Discoverable") function onClicked(): void { const adapter = Bluetooth.defaultAdapter; if (adapter) - adapter.discovering = !adapter.discovering; + adapter.discoverable = !adapter.discoverable; } } - MaterialIcon { - id: scanIcon + ToggleButton { + toggled: Bluetooth.defaultAdapter?.pairable ?? false + icon: "missing_controller" + label: root.smallPairable ? "" : qsTr("Pairable") - anchors.centerIn: parent - animate: true - text: "bluetooth_searching" - color: Bluetooth.defaultAdapter?.discovering ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer - fill: Bluetooth.defaultAdapter?.discovering ? 1 : 0 + function onClicked(): void { + const adapter = Bluetooth.defaultAdapter; + if (adapter) + adapter.pairable = !adapter.pairable; + } } - Behavior on radius { - Anim {} + ToggleButton { + toggled: !root.session.bt.active + icon: "settings" + accent: "Primary" + + function onClicked(): void { + if (root.session.bt.active) + root.session.bt.active = null; + else { + root.session.bt.active = root.model.values[0] ?? null; + } + } } } } - StyledListView { - id: view + titleSuffix: Component { + RowLayout { + spacing: Appearance.spacing.normal + + Item { + Layout.fillWidth: true + } + + StyledRect { + implicitWidth: implicitHeight + implicitHeight: scanIcon.implicitHeight + Appearance.padding.normal * 2 - model: ScriptModel { - id: deviceModel + 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 - values: [...Bluetooth.devices.values].sort((a, b) => (b.connected - a.connected) || (b.paired - a.paired)) - } + StateLayer { + color: Bluetooth.defaultAdapter?.discovering ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer + + function onClicked(): void { + const adapter = Bluetooth.defaultAdapter; + if (adapter) + adapter.discovering = !adapter.discovering; + } + } - Layout.fillWidth: true - Layout.fillHeight: true - clip: true - spacing: Appearance.spacing.small / 2 + MaterialIcon { + id: scanIcon - StyledScrollBar.vertical: StyledScrollBar { - flickable: view + 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: StyledRect { + delegate: Component { + StyledRect { id: device required property BluetoothDevice modelData readonly property bool loading: modelData && (modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting) readonly property bool connected: modelData && modelData.state === BluetoothDeviceState.Connected - anchors.left: view.contentItem.left - anchors.right: view.contentItem.right + anchors.left: parent.left + anchors.right: parent.right implicitHeight: deviceInner.implicitHeight + Appearance.padding.normal * 2 - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.bt.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.activeItem === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal StateLayer { @@ -257,8 +237,18 @@ ColumnLayout { disabled: device.loading function onClicked(): void { - if (device.modelData) - device.modelData.connected = !device.modelData.connected; + if (device.loading) + return; + + if (device.connected) { + device.modelData.connected = false; + } else { + if (device.modelData.bonded) { + device.modelData.connected = true; + } else { + device.modelData.pair(); + } + } } } @@ -267,7 +257,7 @@ ColumnLayout { anchors.centerIn: parent animate: true - text: (device.modelData && device.modelData.connected) ? "link_off" : "link" + text: device.connected ? "link_off" : "link" color: device.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface opacity: device.loading ? 0 : 1 @@ -281,78 +271,7 @@ ColumnLayout { } } - component ToggleButton: StyledRect { - id: toggleBtn - - required property bool toggled - property string icon - property string label - property string accent: "Secondary" - - function onClicked(): void { - } - - 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 - - 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`] - - StateLayer { - id: toggleStateLayer - - color: toggleBtn.toggled ? Colours.palette[`m3on${toggleBtn.accent}`] : Colours.palette[`m3on${toggleBtn.accent}Container`] - - function onClicked(): void { - toggleBtn.onClicked(); - } - } - - RowLayout { - id: toggleBtnInner - - anchors.centerIn: parent - spacing: Appearance.spacing.normal - - MaterialIcon { - id: toggleBtnIcon - - visible: !!text - fill: toggleBtn.toggled ? 1 : 0 - text: toggleBtn.icon - color: toggleBtn.toggled ? Colours.palette[`m3on${toggleBtn.accent}`] : Colours.palette[`m3on${toggleBtn.accent}Container`] - font.pointSize: Appearance.font.size.large - - Behavior on fill { - Anim {} - } - } - - Loader { - asynchronous: true - active: !!toggleBtn.label - visible: active - - sourceComponent: StyledText { - text: toggleBtn.label - color: toggleBtn.toggled ? Colours.palette[`m3on${toggleBtn.accent}`] : Colours.palette[`m3on${toggleBtn.accent}Container`] - } - } - } - - Behavior on radius { - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial - } - } - - Behavior on Layout.preferredWidth { - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial - } - } + onItemSelected: function(item) { + session.bt.active = item; } } diff --git a/modules/controlcenter/components/DeviceDetails.qml b/modules/controlcenter/components/DeviceDetails.qml new file mode 100644 index 0000000..256e689 --- /dev/null +++ b/modules/controlcenter/components/DeviceDetails.qml @@ -0,0 +1,108 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.components.containers +import qs.config +import QtQuick +import QtQuick.Layouts + +/** + * DeviceDetails + * + * A reusable base component for displaying device/network details with a standardized + * structure. Provides a header, connection status section, and flexible sections for + * device-specific information. + * + * This component eliminates duplication across WirelessDetails, EthernetDetails, and Bluetooth Details + * by providing a common structure while allowing full customization of sections. + * + * Usage: + * ```qml + * DeviceDetails { + * session: root.session + * device: session.network.active + * headerComponent: Component { + * ConnectionHeader { + * icon: "wifi" + * title: device?.ssid ?? "" + * } + * } + * sections: [ + * Component { + * // Connection status section + * }, + * Component { + * // Properties section + * } + * ] + * } + * ``` + */ +Item { + id: root + + required property Session session + property var device: null + + property Component headerComponent: null + property list sections: [] + + // Optional: Custom content to insert after header but before sections + property Component topContent: null + + // Optional: Custom content to insert after all sections + property Component bottomContent: null + + implicitWidth: layout.implicitWidth + implicitHeight: layout.implicitHeight + + ColumnLayout { + id: layout + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: Appearance.spacing.normal + + // Header component (e.g., ConnectionHeader or SettingsHeader) + Loader { + id: headerLoader + + Layout.fillWidth: true + sourceComponent: root.headerComponent + visible: root.headerComponent !== null + } + + // Top content (optional) + Loader { + id: topContentLoader + + Layout.fillWidth: true + sourceComponent: root.topContent + visible: root.topContent !== null + } + + // Sections + Repeater { + model: root.sections + + Loader { + Layout.fillWidth: true + sourceComponent: modelData + } + } + + // Bottom content (optional) + Loader { + id: bottomContentLoader + + Layout.fillWidth: true + sourceComponent: root.bottomContent + visible: root.bottomContent !== null + } + } +} + diff --git a/modules/controlcenter/components/DeviceList.qml b/modules/controlcenter/components/DeviceList.qml new file mode 100644 index 0000000..f8473ff --- /dev/null +++ b/modules/controlcenter/components/DeviceList.qml @@ -0,0 +1,125 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.containers +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +/** + * DeviceList + * + * A reusable base component for displaying lists of devices/networks with a standardized + * structure. Provides a header with action buttons, title/subtitle, and a scrollable list + * with customizable delegates. + * + * This component eliminates duplication across WirelessList, EthernetList, and Bluetooth DeviceList + * by providing a common structure while allowing full customization of headers and delegates. + * + * Usage: + * ```qml + * DeviceList { + * session: root.session + * title: qsTr("Networks (%1)").arg(Nmcli.networks.length) + * description: qsTr("All available WiFi networks") + * model: ScriptModel { + * values: [...Nmcli.networks].sort(...) + * } + * activeItem: session.network.active + * onItemSelected: (item) => { + * session.network.active = item; + * } + * headerComponent: Component { + * RowLayout { + * // Custom header buttons + * } + * } + * delegate: Component { + * // Custom delegate for each item + * } + * } + * ``` + */ +ColumnLayout { + id: root + + property Session session: null + property var model: null + property Component delegate: null + + property string title: "" + property string description: "" + property var activeItem: null + property Component headerComponent: null + property Component titleSuffix: null + + signal itemSelected(var item) + + spacing: Appearance.spacing.small + + // Header with action buttons (optional) + Loader { + id: headerLoader + + Layout.fillWidth: true + sourceComponent: root.headerComponent + visible: root.headerComponent !== null + } + + // Title and description row + RowLayout { + Layout.fillWidth: true + Layout.topMargin: root.headerComponent ? 0 : 0 + spacing: Appearance.spacing.small + visible: root.title !== "" || root.description !== "" + + StyledText { + visible: root.title !== "" + text: root.title + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + Loader { + sourceComponent: root.titleSuffix + visible: root.titleSuffix !== null + } + + Item { + Layout.fillWidth: true + } + } + + // Expose view for access from parent components + property alias view: view + + // Description text + StyledText { + visible: root.description !== "" + Layout.fillWidth: true + text: root.description + color: Colours.palette.m3outline + } + + // List view + StyledListView { + id: view + + Layout.fillWidth: true + Layout.fillHeight: true + + model: root.model + delegate: root.delegate + + spacing: Appearance.spacing.small / 2 + clip: true + + StyledScrollBar.vertical: StyledScrollBar { + flickable: view + } + } +} + diff --git a/modules/controlcenter/network/EthernetList.qml b/modules/controlcenter/network/EthernetList.qml index 45c9481..03bd37e 100644 --- a/modules/controlcenter/network/EthernetList.qml +++ b/modules/controlcenter/network/EthernetList.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.containers @@ -9,81 +10,57 @@ import qs.config import QtQuick import QtQuick.Layouts -ColumnLayout { +DeviceList { id: root required property Session session - spacing: Appearance.spacing.small + title: qsTr("Devices (%1)").arg(Nmcli.ethernetDevices.length) + description: qsTr("All available ethernet devices") + activeItem: session.ethernet.active - RowLayout { - spacing: Appearance.spacing.smaller + model: Nmcli.ethernetDevices - StyledText { - text: qsTr("Settings") - font.pointSize: Appearance.font.size.large - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } + headerComponent: Component { + RowLayout { + spacing: Appearance.spacing.smaller - ToggleButton { - toggled: !root.session.ethernet.active - icon: "settings" - accent: "Primary" + StyledText { + text: qsTr("Settings") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } - onClicked: { - if (root.session.ethernet.active) - root.session.ethernet.active = null; - else { - root.session.ethernet.active = view.model.get(0)?.modelData ?? null; - } + Item { + Layout.fillWidth: true } - } - } - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + ToggleButton { + toggled: !root.session.ethernet.active + icon: "settings" + accent: "Primary" - StyledText { - text: qsTr("Devices (%1)").arg(Nmcli.ethernetDevices.length) - font.pointSize: Appearance.font.size.large - font.weight: 500 + onClicked: { + if (root.session.ethernet.active) + root.session.ethernet.active = null; + else { + root.session.ethernet.active = root.view.model.get(0)?.modelData ?? null; + } + } + } } } - StyledText { - text: qsTr("All available ethernet devices") - color: Colours.palette.m3outline - } - - StyledListView { - id: view - - Layout.fillWidth: true - Layout.fillHeight: true - - model: Nmcli.ethernetDevices - - spacing: Appearance.spacing.small / 2 - clip: true - - StyledScrollBar.vertical: StyledScrollBar { - flickable: view - } - - delegate: StyledRect { + delegate: Component { + StyledRect { required property var modelData anchors.left: parent.left anchors.right: parent.right - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.ethernet.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.activeItem === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal - border.width: root.session.ethernet.active === modelData ? 1 : 0 + border.width: root.activeItem === modelData ? 1 : 0 border.color: Colours.palette.m3primary StateLayer { @@ -165,4 +142,8 @@ ColumnLayout { implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 } } -} \ No newline at end of file + + onItemSelected: function(item) { + session.ethernet.active = item; + } +} diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml index 18f728c..2f0288f 100644 --- a/modules/controlcenter/network/WirelessList.qml +++ b/modules/controlcenter/network/WirelessList.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import "." import qs.components import qs.components.controls @@ -11,116 +12,94 @@ import qs.utils import QtQuick import QtQuick.Layouts -ColumnLayout { +DeviceList { id: root required property Session session - spacing: Appearance.spacing.small - - RowLayout { - spacing: Appearance.spacing.smaller - + title: qsTr("Networks (%1)").arg(Nmcli.networks.length) + description: qsTr("All available WiFi networks") + activeItem: session.network.active + + titleSuffix: Component { StyledText { - text: qsTr("Settings") - font.pointSize: Appearance.font.size.large - font.weight: 500 + visible: Nmcli.scanning + text: qsTr("Scanning...") + color: Colours.palette.m3primary + font.pointSize: Appearance.font.size.small } + } - Item { - 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; + }) + } - ToggleButton { - toggled: Nmcli.wifiEnabled - icon: "wifi" - accent: "Tertiary" + headerComponent: Component { + RowLayout { + spacing: Appearance.spacing.smaller - onClicked: { - Nmcli.toggleWifi(null); + StyledText { + text: qsTr("Settings") + font.pointSize: Appearance.font.size.large + font.weight: 500 } - } - ToggleButton { - toggled: Nmcli.scanning - icon: "wifi_find" - accent: "Secondary" - - onClicked: { - Nmcli.rescanWifi(); + Item { + Layout.fillWidth: true } - } - ToggleButton { - toggled: !root.session.network.active - icon: "settings" - accent: "Primary" + ToggleButton { + toggled: Nmcli.wifiEnabled + icon: "wifi" + accent: "Tertiary" - onClicked: { - if (root.session.network.active) - root.session.network.active = null; - else { - root.session.network.active = view.model.get(0)?.modelData ?? null; + onClicked: { + Nmcli.toggleWifi(null); } } - } - } - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small - StyledText { - text: qsTr("Networks (%1)").arg(Nmcli.networks.length) - font.pointSize: Appearance.font.size.large - font.weight: 500 - } + ToggleButton { + toggled: Nmcli.scanning + icon: "wifi_find" + accent: "Secondary" - StyledText { - visible: Nmcli.scanning - text: qsTr("Scanning...") - color: Colours.palette.m3primary - font.pointSize: Appearance.font.size.small - } - } - - StyledText { - text: qsTr("All available WiFi networks") - color: Colours.palette.m3outline - } - - StyledListView { - id: view - - Layout.fillWidth: true - Layout.fillHeight: 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; - }) - } + onClicked: { + Nmcli.rescanWifi(); + } + } - spacing: Appearance.spacing.small / 2 - clip: true + ToggleButton { + toggled: !root.session.network.active + icon: "settings" + accent: "Primary" - StyledScrollBar.vertical: StyledScrollBar { - flickable: view + onClicked: { + if (root.session.network.active) + root.session.network.active = null; + else { + root.session.network.active = root.view.model.get(0)?.modelData ?? null; + } + } + } } + } - delegate: StyledRect { + delegate: Component { + StyledRect { required property var modelData anchors.left: parent.left anchors.right: parent.right - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.network.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.activeItem === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal - border.width: root.session.network.active === modelData ? 1 : 0 + border.width: root.activeItem === modelData ? 1 : 0 border.color: Colours.palette.m3primary StateLayer { @@ -213,9 +192,16 @@ ColumnLayout { } } + onItemSelected: function(item) { + session.network.active = item; + if (item && item.ssid) { + checkSavedProfileForNetwork(item.ssid); + } + } + function checkSavedProfileForNetwork(ssid: string): void { if (ssid && ssid.length > 0) { Nmcli.loadSavedConnections(() => {}); } } -} \ No newline at end of file +} -- cgit v1.2.3-freya From ef46a02b2f3561574c1ada5afefbbb806bcb6a3b Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 19 Nov 2025 21:04:32 -0500 Subject: refactor: ToggleButton Tooltips and DeviceList --- components/controls/ToggleButton.qml | 51 +- components/controls/Tooltip.qml | 170 +++++ modules/controlcenter/bluetooth/BtPane.qml | 19 +- modules/controlcenter/bluetooth/Details.qml | 681 +++++++++++---------- modules/controlcenter/bluetooth/DeviceList.qml | 84 ++- modules/controlcenter/components/DeviceDetails.qml | 2 + modules/controlcenter/components/DeviceList.qml | 14 +- modules/controlcenter/launcher/LauncherPane.qml | 4 + modules/controlcenter/network/EthernetDetails.qml | 140 +++-- modules/controlcenter/network/EthernetList.qml | 6 +- modules/controlcenter/network/NetworkingPane.qml | 377 ++---------- modules/controlcenter/network/WirelessDetails.qml | 182 +++--- modules/controlcenter/network/WirelessList.qml | 13 +- 13 files changed, 865 insertions(+), 878 deletions(-) create mode 100644 components/controls/Tooltip.qml (limited to 'modules/controlcenter/bluetooth/DeviceList.qml') diff --git a/components/controls/ToggleButton.qml b/components/controls/ToggleButton.qml index 9d8e094..b2c2afe 100644 --- a/components/controls/ToggleButton.qml +++ b/components/controls/ToggleButton.qml @@ -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 + } } diff --git a/components/controls/Tooltip.qml b/components/controls/Tooltip.qml new file mode 100644 index 0000000..bab8086 --- /dev/null +++ b/components/controls/Tooltip.qml @@ -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(); + } + } +} + diff --git a/modules/controlcenter/bluetooth/BtPane.qml b/modules/controlcenter/bluetooth/BtPane.qml index 6877801..a987e75 100644 --- a/modules/controlcenter/bluetooth/BtPane.qml +++ b/modules/controlcenter/bluetooth/BtPane.qml @@ -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 + } } } diff --git a/modules/controlcenter/bluetooth/Details.qml b/modules/controlcenter/bluetooth/Details.qml index 5496966..b260458 100644 --- a/modules/controlcenter/bluetooth/Details.qml +++ b/modules/controlcenter/bluetooth/Details.qml @@ -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 - - 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 + session: root.session + device: root.device - 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 - } - - 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 + sections: [ + Component { + ColumnLayout { + spacing: Appearance.spacing.normal - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Connection status") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } - spacing: Appearance.spacing.larger + StyledText { + text: qsTr("Connection settings for this device") + color: Colours.palette.m3outline + } - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + StyledRect { + Layout.fillWidth: true + implicitHeight: deviceStatus.implicitHeight + Appearance.padding.large * 2 - Item { - id: renameDevice + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - Layout.fillWidth: true - Layout.rightMargin: Appearance.spacing.small + ColumnLayout { + id: deviceStatus - implicitHeight: renameLabel.implicitHeight + deviceNameEdit.implicitHeight + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large - states: State { - name: "editingDeviceName" - when: root.session.bt.editingDeviceName + spacing: Appearance.spacing.larger - AnchorChanges { - target: deviceNameEdit - anchors.top: renameDevice.top - } - PropertyChanges { - renameDevice.implicitHeight: deviceNameEdit.implicitHeight - renameLabel.opacity: 0 - deviceNameEdit.padding: Appearance.padding.normal + Toggle { + label: qsTr("Connected") + checked: root.device?.connected ?? false + toggle.onToggled: root.device.connected = checked } - } - transitions: Transition { - AnchorAnimation { - duration: Appearance.anim.durations.normal - easing.type: Easing.BezierSpline - easing.bezierCurve: Appearance.anim.curves.standard + Toggle { + label: qsTr("Paired") + checked: root.device?.paired ?? false + toggle.onToggled: { + if (root.device.paired) + root.device.forget(); + else + root.device.pair(); + } } - Anim { - properties: "implicitHeight,opacity,padding" + + 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.large + text: qsTr("Device properties") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } - StyledText { - id: renameLabel + StyledText { + text: qsTr("Additional settings") + color: Colours.palette.m3outline + } - anchors.left: parent.left + StyledRect { + Layout.fillWidth: true + implicitHeight: deviceProps.implicitHeight + Appearance.padding.large * 2 - text: qsTr("Device name") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - StyledTextField { - id: deviceNameEdit + ColumnLayout { + id: deviceProps 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 + 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 {} + } + } + } - Behavior on border.color { - CAnim {} + 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 + } + } } - Behavior on opacity { - Anim {} + 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 {} + } } } - Behavior on anchors.leftMargin { - Anim {} + Toggle { + label: qsTr("Trusted") + checked: root.device?.trusted ?? false + toggle.onToggled: root.device.trusted = checked } - } - } - - 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 ?? ""); + Toggle { + label: qsTr("Wake allowed") + checked: root.device?.wakeAllowed ?? false + toggle.onToggled: root.device.wakeAllowed = checked } } + } + } + }, + Component { + ColumnLayout { + spacing: Appearance.spacing.normal + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Device information") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } - 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 - } - } + StyledText { + text: qsTr("Information about this device") + color: Colours.palette.m3outline } 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 {} - } - } - } + Layout.fillWidth: true + implicitHeight: deviceInfo.implicitHeight + Appearance.padding.large * 2 - Toggle { - label: qsTr("Trusted") - checked: root.device?.trusted ?? false - toggle.onToggled: root.device.trusted = checked - } + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - Toggle { - label: qsTr("Wake allowed") - checked: root.device?.wakeAllowed ?? false - toggle.onToggled: root.device.wakeAllowed = checked - } - } - } + ColumnLayout { + id: deviceInfo - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Device information") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large - StyledText { - text: qsTr("Information about this device") - color: Colours.palette.m3outline - } + spacing: Appearance.spacing.small / 2 - StyledRect { - Layout.fillWidth: true - implicitHeight: deviceInfo.implicitHeight + Appearance.padding.large * 2 + StyledText { + text: root.device?.batteryAvailable ? qsTr("Device battery (%1%)").arg(root.device.battery * 100) : qsTr("Battery unavailable") + } - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer + 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 + } - ColumnLayout { - id: deviceInfo + 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 + } + } + } - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Dbus path") + } - spacing: Appearance.spacing.small / 2 + StyledText { + text: root.device?.dbusPath ?? "" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } - StyledText { - text: root.device?.batteryAvailable ? qsTr("Device battery (%1%)").arg(root.device.battery * 100) : qsTr("Battery unavailable") - } + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("MAC address") + } - RowLayout { - Layout.topMargin: Appearance.spacing.small / 2 - Layout.fillWidth: true - Layout.preferredHeight: Appearance.padding.smaller - spacing: Appearance.spacing.small / 2 + StyledText { + text: root.device?.address ?? "" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } - StyledRect { - Layout.fillHeight: true - implicitWidth: root.device?.batteryAvailable ? parent.width * root.device.battery : 0 - radius: Appearance.rounding.full - color: Colours.palette.m3primary - } + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Bonded") + } - StyledRect { - Layout.fillWidth: true - Layout.fillHeight: true - radius: Appearance.rounding.full - color: Colours.palette.m3secondaryContainer + StyledText { + text: root.device?.bonded ? qsTr("Yes") : qsTr("No") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } - StyledRect { - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.margins: parent.height * 0.25 + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("System name") + } - implicitWidth: height - radius: Appearance.rounding.full - color: Colours.palette.m3primary + StyledText { + text: root.device?.deviceName ?? "" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } } } } - - 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 diff --git a/modules/controlcenter/bluetooth/DeviceList.qml b/modules/controlcenter/bluetooth/DeviceList.qml index b3db236..b978a2d 100644 --- a/modules/controlcenter/bluetooth/DeviceList.qml +++ b/modules/controlcenter/bluetooth/DeviceList.qml @@ -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) diff --git a/modules/controlcenter/components/DeviceDetails.qml b/modules/controlcenter/components/DeviceDetails.qml index 256e689..eef0aae 100644 --- a/modules/controlcenter/components/DeviceDetails.qml +++ b/modules/controlcenter/components/DeviceDetails.qml @@ -90,6 +90,8 @@ Item { model: root.sections Loader { + required property Component modelData + Layout.fillWidth: true sourceComponent: modelData } diff --git a/modules/controlcenter/components/DeviceList.qml b/modules/controlcenter/components/DeviceList.qml index f8473ff..bf7126f 100644 --- a/modules/controlcenter/components/DeviceList.qml +++ b/modules/controlcenter/components/DeviceList.qml @@ -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 } } diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index cf965e8..803d7e0 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -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) { diff --git a/modules/controlcenter/network/EthernetDetails.qml b/modules/controlcenter/network/EthernetDetails.qml index 7c2534a..ad078ec 100644 --- a/modules/controlcenter/network/EthernetDetails.qml +++ b/modules/controlcenter/network/EthernetDetails.qml @@ -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 - - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - spacing: Appearance.spacing.normal + headerComponent: Component { + ConnectionHeader { + icon: "cable" + title: root.ethernetDevice?.interface ?? qsTr("Unknown") + } + } - ConnectionHeader { - icon: "cable" - title: root.device?.interface ?? qsTr("Unknown") - } + sections: [ + Component { + ColumnLayout { + spacing: Appearance.spacing.normal - 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") + } - SectionHeader { - title: qsTr("Device properties") - description: qsTr("Additional information") - } - - SectionContainer { - contentSpacing: Appearance.spacing.small / 2 + SectionContainer { + contentSpacing: Appearance.spacing.small / 2 - PropertyRow { - label: qsTr("Interface") - value: root.device?.interface ?? qsTr("Unknown") - } + PropertyRow { + label: qsTr("Interface") + value: root.ethernetDevice?.interface ?? qsTr("Unknown") + } - PropertyRow { - showTopMargin: true - label: qsTr("Connection") - value: root.device?.connection || qsTr("Not connected") - } + PropertyRow { + showTopMargin: true + label: qsTr("Connection") + value: root.ethernetDevice?.connection || qsTr("Not connected") + } - PropertyRow { - showTopMargin: true - label: qsTr("State") - value: root.device?.state ?? qsTr("Unknown") + 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 + } } } - } - -} \ No newline at end of file + } + ] +} diff --git a/modules/controlcenter/network/EthernetList.qml b/modules/controlcenter/network/EthernetList.qml index 03bd37e..ea3ece5 100644 --- a/modules/controlcenter/network/EthernetList.qml +++ b/modules/controlcenter/network/EthernetList.qml @@ -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 diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index d76e8f5..e28d35c 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -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(() => {}); - } - } } - diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index 57c06c8..7f6a4aa 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -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") - } + headerComponent: Component { + 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") - } + sections: [ + Component { + ColumnLayout { + spacing: Appearance.spacing.normal - SectionContainer { - ToggleRow { - label: qsTr("Connected") - checked: root.network?.active ?? false - toggle.onToggled: { - if (checked) { - NetworkConnection.handleConnect(root.network, root.session, null); - } else { - Nmcli.disconnectFromNetwork(); - } - } + SectionHeader { + title: qsTr("Connection status") + description: qsTr("Connection settings for this network") } - 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; + SectionContainer { + ToggleRow { + label: qsTr("Connected") + checked: root.network?.active ?? false + toggle.onToggled: { + if (checked) { + NetworkConnection.handleConnect(root.network, root.session, null); + } else { + Nmcli.disconnectFromNetwork(); + } } - 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(); + 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); } - Nmcli.forgetNetwork(root.network.ssid); } } } } + }, + Component { + ColumnLayout { + spacing: Appearance.spacing.normal + + SectionHeader { + title: qsTr("Network properties") + description: qsTr("Additional information") + } - SectionHeader { - title: qsTr("Network properties") - description: qsTr("Additional information") - } - - SectionContainer { - contentSpacing: Appearance.spacing.small / 2 + SectionContainer { + contentSpacing: Appearance.spacing.small / 2 - PropertyRow { - label: qsTr("SSID") - value: root.network?.ssid ?? qsTr("Unknown") - } + 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("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("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("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") + 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") + } - SectionHeader { - title: qsTr("Connection information") - description: qsTr("Network connection details") - } - - SectionContainer { - ConnectionInfoSection { - deviceDetails: Nmcli.wirelessDeviceDetails + SectionContainer { + ConnectionInfoSection { + deviceDetails: Nmcli.wirelessDeviceDetails + } } } - } - + } + ] } diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml index 2f0288f..4726712 100644 --- a/modules/controlcenter/network/WirelessList.qml +++ b/modules/controlcenter/network/WirelessList.qml @@ -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 -- cgit v1.2.3-freya