From 81a8157b67c4d2416d9c4550afc5b50c943352e4 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sun, 9 Nov 2025 19:17:26 -0500 Subject: fix: panels (i'm debugging) new file: modules/bar/components/Settings.qml new file: modules/bar/components/SettingsIcon.qml new file: modules/controlcenter/audio/AudioPane.qml new file: modules/controlcenter/network/Details.qml new file: modules/controlcenter/network/NetworkList.qml new file: modules/controlcenter/network/NetworkPane.qml new file: modules/controlcenter/network/Settings.qml --- modules/controlcenter/network/Details.qml | 204 ++++++++++++++++++++++ modules/controlcenter/network/NetworkList.qml | 240 ++++++++++++++++++++++++++ modules/controlcenter/network/NetworkPane.qml | 79 +++++++++ modules/controlcenter/network/Settings.qml | 166 ++++++++++++++++++ 4 files changed, 689 insertions(+) create mode 100644 modules/controlcenter/network/Details.qml create mode 100644 modules/controlcenter/network/NetworkList.qml create mode 100644 modules/controlcenter/network/NetworkPane.qml create mode 100644 modules/controlcenter/network/Settings.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/Details.qml b/modules/controlcenter/network/Details.qml new file mode 100644 index 0000000..86164f1 --- /dev/null +++ b/modules/controlcenter/network/Details.qml @@ -0,0 +1,204 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.components.containers +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +Item { + id: root + + required property Session session + readonly property var network: session.network.active + + StyledFlickable { + anchors.fill: parent + + flickableDirection: Flickable.VerticalFlick + contentHeight: layout.height + + ColumnLayout { + id: layout + + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.normal + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + animate: true + text: root.network?.isSecure ? "lock" : "wifi" + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + animate: true + text: root.network?.ssid ?? qsTr("Unknown") + font.pointSize: Appearance.font.size.large + font.bold: true + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Connection status") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("Connection settings for this network") + color: Colours.palette.m3outline + } + + StyledRect { + Layout.fillWidth: true + implicitHeight: networkStatus.implicitHeight + Appearance.padding.large * 2 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + + ColumnLayout { + id: networkStatus + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + + spacing: Appearance.spacing.larger + + Toggle { + label: qsTr("Connected") + checked: root.network?.active ?? false + toggle.onToggled: { + if (checked) { + if (root.network.isSecure) { + // TODO: Show password dialog + root.session.network.showPasswordDialog = true; + root.session.network.pendingNetwork = root.network; + } else { + Network.connectToNetwork(root.network.ssid, ""); + } + } else { + Network.disconnectFromNetwork(); + } + } + } + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Network properties") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("Additional information") + color: Colours.palette.m3outline + } + + StyledRect { + Layout.fillWidth: true + implicitHeight: networkProps.implicitHeight + Appearance.padding.large * 2 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + + ColumnLayout { + id: networkProps + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + + spacing: Appearance.spacing.small / 2 + + StyledText { + text: qsTr("SSID") + } + + StyledText { + text: root.network?.ssid ?? qsTr("Unknown") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("BSSID") + } + + StyledText { + text: root.network?.bssid ?? qsTr("Unknown") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Signal strength") + } + + StyledText { + text: root.network ? qsTr("%1%").arg(root.network.strength) : qsTr("N/A") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Frequency") + } + + StyledText { + text: root.network ? qsTr("%1 MHz").arg(root.network.frequency) : qsTr("N/A") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Security") + } + + StyledText { + text: root.network ? (root.network.isSecure ? root.network.security : qsTr("Open")) : qsTr("N/A") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + } + } + } + } + + component Toggle: RowLayout { + required property string label + property alias checked: toggle.checked + property alias toggle: toggle + + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + StyledText { + Layout.fillWidth: true + text: parent.label + } + + StyledSwitch { + id: toggle + + cLayer: 2 + } + } +} + diff --git a/modules/controlcenter/network/NetworkList.qml b/modules/controlcenter/network/NetworkList.qml new file mode 100644 index 0000000..8dfebc7 --- /dev/null +++ b/modules/controlcenter/network/NetworkList.qml @@ -0,0 +1,240 @@ +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 + +ColumnLayout { + id: root + + required property Session session + + spacing: Appearance.spacing.small + + RowLayout { + spacing: Appearance.spacing.smaller + + StyledText { + text: qsTr("Settings") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + Item { + Layout.fillWidth: true + } + + StyledRect { + implicitWidth: implicitHeight + implicitHeight: wifiIcon.implicitHeight + Appearance.padding.normal * 2 + + radius: Appearance.rounding.normal + color: Network.wifiEnabled ? Colours.palette.m3tertiary : Colours.palette.m3tertiaryContainer + + StateLayer { + function onClicked(): void { + Network.toggleWifi(); + } + } + + MaterialIcon { + id: wifiIcon + + anchors.centerIn: parent + text: "wifi" + color: Network.wifiEnabled ? Colours.palette.m3onTertiary : Colours.palette.m3onTertiaryContainer + } + } + + StyledRect { + implicitWidth: implicitHeight + implicitHeight: scanIcon.implicitHeight + Appearance.padding.normal * 2 + + radius: Appearance.rounding.normal + color: Network.scanning ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer + + StateLayer { + function onClicked(): void { + Network.rescanWifi(); + } + } + + MaterialIcon { + id: scanIcon + + anchors.centerIn: parent + text: "wifi_find" + color: Network.scanning ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer + } + } + + StyledRect { + implicitWidth: implicitHeight + implicitHeight: settingsIcon.implicitHeight + Appearance.padding.normal * 2 + + radius: Appearance.rounding.normal + color: !root.session.network.active ? Colours.palette.m3primary : Colours.palette.m3primaryContainer + + StateLayer { + function onClicked(): void { + if (root.session.network.active) + root.session.network.active = null; + else { + root.session.network.active = view.model.get(0)?.modelData ?? null; + } + } + } + + MaterialIcon { + id: settingsIcon + + anchors.centerIn: parent + text: "settings" + color: !root.session.network.active ? Colours.palette.m3onPrimary : Colours.palette.m3onPrimaryContainer + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small + + StyledText { + text: qsTr("Networks (%1)").arg(Network.networks.length) + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + StyledText { + visible: Network.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: Network.networks + + spacing: Appearance.spacing.small / 2 + clip: true + + StyledScrollBar.vertical: StyledScrollBar { + flickable: view + } + + delegate: 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) + radius: Appearance.rounding.normal + border.width: root.session.network.active === modelData ? 1 : 0 + border.color: Colours.palette.m3primary + + StateLayer { + function onClicked(): void { + root.session.network.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.active ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh + + MaterialIcon { + id: icon + + anchors.centerIn: parent + text: modelData.isSecure ? "lock" : "wifi" + font.pointSize: Appearance.font.size.large + fill: modelData.active ? 1 : 0 + color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface + } + } + + StyledText { + Layout.fillWidth: true + + text: modelData.ssid || qsTr("Unknown") + } + + StyledText { + text: modelData.active ? qsTr("Connected") : (modelData.isSecure ? qsTr("Secured") : qsTr("Open")) + color: modelData.active ? Colours.palette.m3primary : Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + font.weight: modelData.active ? 500 : 400 + } + + StyledText { + text: qsTr("%1%").arg(modelData.strength) + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledRect { + implicitWidth: implicitHeight + implicitHeight: connectIcon.implicitHeight + Appearance.padding.smaller * 2 + + radius: Appearance.rounding.full + color: Qt.alpha(Colours.palette.m3primaryContainer, modelData.active ? 1 : 0) + + StateLayer { + function onClicked(): void { + if (modelData.active) { + Network.disconnectFromNetwork(); + } else { + if (modelData.isSecure) { + root.session.network.showPasswordDialog = true; + root.session.network.pendingNetwork = modelData; + } else { + Network.connectToNetwork(modelData.ssid, ""); + } + } + } + } + + MaterialIcon { + id: connectIcon + + anchors.centerIn: parent + text: modelData.active ? "link_off" : "link" + color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface + } + } + } + + implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 + } + } +} diff --git a/modules/controlcenter/network/NetworkPane.qml b/modules/controlcenter/network/NetworkPane.qml new file mode 100644 index 0000000..f37eedd --- /dev/null +++ b/modules/controlcenter/network/NetworkPane.qml @@ -0,0 +1,79 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.effects +import qs.components.containers +import qs.config +import QtQuick +import QtQuick.Layouts + +RowLayout { + id: root + + required property Session session + + anchors.fill: parent + + spacing: 0 + + Item { + Layout.preferredWidth: Math.floor(parent.width * 0.4) + Layout.minimumWidth: 420 + Layout.fillHeight: true + + NetworkList { + anchors.fill: parent + anchors.margins: Appearance.padding.large + Appearance.padding.normal + anchors.leftMargin: Appearance.padding.large + anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + + session: root.session + } + + InnerBorder { + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + Loader { + id: loader + + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + sourceComponent: root.session.network.active ? details : settings + + Behavior on opacity { + NumberAnimation { + duration: 200 + } + } + } + + InnerBorder { + leftThickness: Appearance.padding.normal / 2 + } + + Component { + id: settings + + Settings { + session: root.session + } + } + + Component { + id: details + + Details { + session: root.session + } + } + } +} diff --git a/modules/controlcenter/network/Settings.qml b/modules/controlcenter/network/Settings.qml new file mode 100644 index 0000000..0794e6a --- /dev/null +++ b/modules/controlcenter/network/Settings.qml @@ -0,0 +1,166 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property Session session + + spacing: Appearance.spacing.normal + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + text: "wifi" + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Network settings") + font.pointSize: Appearance.font.size.large + font.bold: true + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("WiFi status") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("General WiFi settings") + color: Colours.palette.m3outline + } + + StyledRect { + Layout.fillWidth: true + implicitHeight: wifiStatus.implicitHeight + Appearance.padding.large * 2 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + + ColumnLayout { + id: wifiStatus + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + + spacing: Appearance.spacing.larger + + Toggle { + label: qsTr("WiFi enabled") + checked: Network.wifiEnabled + toggle.onToggled: { + Network.enableWifi(checked); + } + } + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Network information") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("Current network connection") + color: Colours.palette.m3outline + } + + StyledRect { + Layout.fillWidth: true + implicitHeight: networkInfo.implicitHeight + Appearance.padding.large * 2 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + + ColumnLayout { + id: networkInfo + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + + spacing: Appearance.spacing.small / 2 + + StyledText { + text: qsTr("Connected network") + } + + StyledText { + text: Network.active ? Network.active.ssid : qsTr("Not connected") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Signal strength") + } + + StyledText { + text: Network.active ? qsTr("%1%").arg(Network.active.strength) : qsTr("N/A") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Security") + } + + StyledText { + text: Network.active ? (Network.active.isSecure ? qsTr("Secured") : qsTr("Open")) : qsTr("N/A") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Frequency") + } + + StyledText { + text: Network.active ? qsTr("%1 MHz").arg(Network.active.frequency) : qsTr("N/A") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + } + } + + component Toggle: RowLayout { + required property string label + property alias checked: toggle.checked + property alias toggle: toggle + + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + StyledText { + Layout.fillWidth: true + text: parent.label + } + + StyledSwitch { + id: toggle + + cLayer: 2 + } + } +} + -- cgit v1.2.3-freya From d8de7c7e4b1b57470297dc238b210670f87c23ba Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sun, 9 Nov 2025 21:43:51 -0500 Subject: controlcenter: wifi passphrase input fix: dependability issues with nmcli --- modules/controlcenter/network/Details.qml | 30 ++- modules/controlcenter/network/NetworkList.qml | 29 ++- modules/controlcenter/network/NetworkPane.qml | 6 + modules/controlcenter/network/PasswordDialog.qml | 241 +++++++++++++++++++++++ services/Network.qml | 158 ++++++++++++++- 5 files changed, 451 insertions(+), 13 deletions(-) create mode 100644 modules/controlcenter/network/PasswordDialog.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/Details.qml b/modules/controlcenter/network/Details.qml index 86164f1..19e011f 100644 --- a/modules/controlcenter/network/Details.qml +++ b/modules/controlcenter/network/Details.qml @@ -79,17 +79,37 @@ Item { checked: root.network?.active ?? false toggle.onToggled: { if (checked) { - if (root.network.isSecure) { - // TODO: Show password dialog - root.session.network.showPasswordDialog = true; - root.session.network.pendingNetwork = root.network; + // If already connected to a different network, disconnect first + if (Network.active && Network.active.ssid !== root.network.ssid) { + Network.disconnectFromNetwork(); + // Wait a moment before connecting to new network + Qt.callLater(() => { + connectToNetwork(); + }); } else { - Network.connectToNetwork(root.network.ssid, ""); + connectToNetwork(); } } else { Network.disconnectFromNetwork(); } } + + function connectToNetwork(): void { + if (root.network.isSecure) { + // Try connecting without password first (in case it's saved) + Network.connectToNetworkWithPasswordCheck( + root.network.ssid, + root.network.isSecure, + () => { + // Callback: connection failed, show password dialog + root.session.network.showPasswordDialog = true; + root.session.network.pendingNetwork = root.network; + } + ); + } else { + Network.connectToNetwork(root.network.ssid, ""); + } + } } } } diff --git a/modules/controlcenter/network/NetworkList.qml b/modules/controlcenter/network/NetworkList.qml index 8dfebc7..df05de7 100644 --- a/modules/controlcenter/network/NetworkList.qml +++ b/modules/controlcenter/network/NetworkList.qml @@ -214,14 +214,35 @@ ColumnLayout { if (modelData.active) { Network.disconnectFromNetwork(); } else { - if (modelData.isSecure) { - root.session.network.showPasswordDialog = true; - root.session.network.pendingNetwork = modelData; + // If already connected to a different network, disconnect first + if (Network.active && Network.active.ssid !== modelData.ssid) { + Network.disconnectFromNetwork(); + // Wait a moment before connecting to new network + Qt.callLater(() => { + connectToNetwork(); + }); } else { - Network.connectToNetwork(modelData.ssid, ""); + connectToNetwork(); } } } + + function connectToNetwork(): void { + if (modelData.isSecure) { + // Try connecting without password first (in case it's saved) + Network.connectToNetworkWithPasswordCheck( + modelData.ssid, + modelData.isSecure, + () => { + // Callback: connection failed, show password dialog + root.session.network.showPasswordDialog = true; + root.session.network.pendingNetwork = modelData; + } + ); + } else { + Network.connectToNetwork(modelData.ssid, ""); + } + } } MaterialIcon { diff --git a/modules/controlcenter/network/NetworkPane.qml b/modules/controlcenter/network/NetworkPane.qml index f37eedd..5e8a75a 100644 --- a/modules/controlcenter/network/NetworkPane.qml +++ b/modules/controlcenter/network/NetworkPane.qml @@ -76,4 +76,10 @@ RowLayout { } } } + + PasswordDialog { + anchors.fill: parent + session: root.session + z: 1000 + } } diff --git a/modules/controlcenter/network/PasswordDialog.qml b/modules/controlcenter/network/PasswordDialog.qml new file mode 100644 index 0000000..fa4788c --- /dev/null +++ b/modules/controlcenter/network/PasswordDialog.qml @@ -0,0 +1,241 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.components.containers +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +Item { + id: root + + required property Session session + readonly property var network: session.network.pendingNetwork + + visible: session.network.showPasswordDialog + enabled: visible + focus: visible + + Keys.onEscapePressed: { + root.session.network.showPasswordDialog = false; + passwordField.text = ""; + } + + Rectangle { + anchors.fill: parent + color: Qt.rgba(0, 0, 0, 0.5) + opacity: root.visible ? 1 : 0 + + Behavior on opacity { + NumberAnimation { + duration: 200 + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + root.session.network.showPasswordDialog = false; + passwordField.text = ""; + } + } + } + + StyledRect { + id: dialog + + anchors.centerIn: parent + + implicitWidth: 400 + implicitHeight: content.implicitHeight + Appearance.padding.large * 2 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surface + opacity: root.visible ? 1 : 0 + scale: root.visible ? 1 : 0.9 + + Behavior on opacity { + NumberAnimation { + duration: 200 + } + } + + Behavior on scale { + NumberAnimation { + duration: 200 + } + } + + Keys.onEscapePressed: { + root.session.network.showPasswordDialog = false; + passwordField.text = ""; + } + + ColumnLayout { + id: content + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + + spacing: Appearance.spacing.normal + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + text: "lock" + font.pointSize: Appearance.font.size.extraLarge * 2 + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Enter password") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: root.network ? qsTr("Network: %1").arg(root.network.ssid) : "" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + Item { + Layout.topMargin: Appearance.spacing.large + Layout.fillWidth: true + implicitHeight: passwordField.implicitHeight + Appearance.padding.normal * 2 + + StyledRect { + anchors.fill: parent + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + border.width: passwordField.activeFocus ? 2 : 1 + border.color: passwordField.activeFocus ? Colours.palette.m3primary : Colours.palette.m3outline + + Behavior on border.color { + CAnim {} + } + } + + StyledTextField { + id: passwordField + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal + + echoMode: TextField.Password + placeholderText: qsTr("Password") + + Component.onCompleted: { + if (root.visible) { + forceActiveFocus(); + } + } + + Connections { + target: root + function onVisibleChanged(): void { + if (root.visible) { + passwordField.forceActiveFocus(); + passwordField.text = ""; + } + } + } + + Keys.onReturnPressed: { + if (connectButton.enabled) { + connectButton.clicked(); + } + } + Keys.onEnterPressed: { + if (connectButton.enabled) { + connectButton.clicked(); + } + } + } + } + + RowLayout { + Layout.topMargin: Appearance.spacing.normal + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + Button { + id: cancelButton + + Layout.fillWidth: true + color: Colours.palette.m3secondaryContainer + onColor: Colours.palette.m3onSecondaryContainer + text: qsTr("Cancel") + + function onClicked(): void { + root.session.network.showPasswordDialog = false; + passwordField.text = ""; + } + } + + Button { + id: connectButton + + Layout.fillWidth: true + color: Colours.palette.m3primary + onColor: Colours.palette.m3onPrimary + text: qsTr("Connect") + enabled: passwordField.text.length > 0 + + function onClicked(): void { + if (root.network && passwordField.text.length > 0) { + Network.connectToNetwork(root.network.ssid, passwordField.text); + root.session.network.showPasswordDialog = false; + passwordField.text = ""; + } + } + } + } + } + } + + component Button: StyledRect { + property color onColor: Colours.palette.m3onSurface + property alias disabled: stateLayer.disabled + property alias text: label.text + property alias enabled: stateLayer.enabled + + function onClicked(): void { + } + + radius: Appearance.rounding.small + implicitHeight: label.implicitHeight + Appearance.padding.small * 2 + opacity: enabled ? 1 : 0.5 + + StateLayer { + id: stateLayer + + enabled: parent.enabled + color: parent.onColor + + function onClicked(): void { + if (enabled) { + parent.onClicked(); + } + } + } + + StyledText { + id: label + + anchors.centerIn: parent + animate: true + color: parent.onColor + font.pointSize: Appearance.font.size.normal + } + } +} + diff --git a/services/Network.qml b/services/Network.qml index f2c403e..3ceadab 100644 --- a/services/Network.qml +++ b/services/Network.qml @@ -26,20 +26,42 @@ Singleton { rescanProc.running = true; } + property var pendingConnection: null + signal connectionFailed(string ssid) + function connectToNetwork(ssid: string, password: string): void { // First try to connect to an existing connection // If that fails, create a new connection if (password && password.length > 0) { connectProc.exec(["nmcli", "device", "wifi", "connect", ssid, "password", password]); } else { - // Try to connect to existing connection first + // Try to connect to existing connection first (will use saved password if available) + connectProc.exec(["nmcli", "device", "wifi", "connect", ssid]); + } + } + + function connectToNetworkWithPasswordCheck(ssid: string, isSecure: bool, callback: var): void { + // For secure networks, try connecting without password first + // If connection succeeds (saved password exists), we're done + // If it fails with password error, callback will be called to show password dialog + if (isSecure) { + root.pendingConnection = { ssid: ssid, callback: callback }; + // Try connecting without password - will use saved password if available connectProc.exec(["nmcli", "device", "wifi", "connect", ssid]); + // Start timer to check if connection succeeded + connectionCheckTimer.start(); + } else { + connectToNetwork(ssid, ""); } } function disconnectFromNetwork(): void { - if (active) { - // Find the device name first, then disconnect + // Try to disconnect - use connection name if available, otherwise use device + if (active && active.ssid) { + // First try to disconnect by connection name (more reliable) + disconnectByConnectionProc.exec(["nmcli", "connection", "down", active.ssid]); + } else { + // Fallback: disconnect by device disconnectProc.exec(["nmcli", "device", "disconnect", "wifi"]); } } @@ -90,18 +112,102 @@ Singleton { } } + Timer { + id: connectionCheckTimer + interval: 4000 + onTriggered: { + if (root.pendingConnection) { + // Final check - if connection still hasn't succeeded, show password dialog + const connected = root.active && root.active.ssid === root.pendingConnection.ssid; + if (!connected && root.pendingConnection.callback) { + // Connection didn't succeed after multiple checks, show password dialog + const pending = root.pendingConnection; + root.pendingConnection = null; + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + pending.callback(); + } else if (connected) { + // Connection succeeded, clear pending + root.pendingConnection = null; + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + } + } + } + } + + Timer { + id: immediateCheckTimer + interval: 500 + repeat: true + triggeredOnStart: false + property int checkCount: 0 + onTriggered: { + if (root.pendingConnection) { + checkCount++; + const connected = root.active && root.active.ssid === root.pendingConnection.ssid; + if (connected) { + // Connection succeeded, stop timers and clear pending + connectionCheckTimer.stop(); + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + root.pendingConnection = null; + } else if (checkCount >= 6) { + // Checked 6 times (3 seconds total), connection likely failed + // Stop immediate check, let the main timer handle it + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + } + } else { + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + } + } + } + Process { id: connectProc onExited: { // Refresh network list after connection attempt getNetworks.running = true; + + // Check if connection succeeded after a short delay (network list needs to update) + if (root.pendingConnection) { + immediateCheckTimer.start(); + } } stdout: SplitParser { onRead: getNetworks.running = true } stderr: StdioCollector { - onStreamFinished: console.warn("Network connection error:", text) + onStreamFinished: { + const error = text.trim(); + if (error && error.length > 0) { + // Check for specific errors that indicate password is needed + // Be careful not to match success messages + const needsPassword = (error.includes("Secrets were required") || + error.includes("No secrets provided") || + error.includes("802-11-wireless-security.psk") || + (error.includes("password") && !error.includes("Connection activated")) || + (error.includes("Secrets") && !error.includes("Connection activated")) || + (error.includes("802.11") && !error.includes("Connection activated"))) && + !error.includes("Connection activated") && + !error.includes("successfully"); + + if (needsPassword && root.pendingConnection && root.pendingConnection.callback) { + // Connection failed because password is needed - show dialog immediately + connectionCheckTimer.stop(); + immediateCheckTimer.stop(); + const pending = root.pendingConnection; + root.pendingConnection = null; + pending.callback(); + } else if (error && error.length > 0 && !error.includes("Connection activated")) { + // Only log non-success messages + console.warn("Network connection error:", error); + } + } + } } } @@ -115,6 +221,36 @@ Singleton { stdout: SplitParser { onRead: getNetworks.running = true } + stderr: StdioCollector { + onStreamFinished: { + const error = text.trim(); + if (error && error.length > 0 && !error.includes("successfully") && !error.includes("disconnected")) { + console.warn("Network device disconnect error:", error); + } + } + } + } + + Process { + id: disconnectByConnectionProc + + onExited: { + // Refresh network list after disconnection + getNetworks.running = true; + } + stdout: SplitParser { + onRead: getNetworks.running = true + } + stderr: StdioCollector { + onStreamFinished: { + const error = text.trim(); + if (error && error.length > 0 && !error.includes("successfully") && !error.includes("disconnected")) { + console.warn("Network connection disconnect error:", error); + // If connection down failed, try device disconnect as fallback + disconnectProc.exec(["nmcli", "device", "disconnect", "wifi"]); + } + } + } } Process { @@ -182,6 +318,20 @@ Singleton { })); } } + + // Check if pending connection succeeded after network list is fully updated + if (root.pendingConnection) { + Qt.callLater(() => { + const connected = root.active && root.active.ssid === root.pendingConnection.ssid; + if (connected) { + // Connection succeeded, stop timers and clear pending + connectionCheckTimer.stop(); + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + root.pendingConnection = null; + } + }); + } } } } -- cgit v1.2.3-freya From 615d1d9a495604d229764c225b363dbaaaacb2bc Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sun, 9 Nov 2025 23:36:44 -0500 Subject: controlcenter: cleaning up ui to match --- modules/controlcenter/network/NetworkList.qml | 153 ++++++++++++++++---------- modules/controlcenter/network/NetworkPane.qml | 91 +++++++++++++-- 2 files changed, 176 insertions(+), 68 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/NetworkList.qml b/modules/controlcenter/network/NetworkList.qml index df05de7..f528092 100644 --- a/modules/controlcenter/network/NetworkList.qml +++ b/modules/controlcenter/network/NetworkList.qml @@ -29,74 +29,38 @@ ColumnLayout { Layout.fillWidth: true } - StyledRect { - implicitWidth: implicitHeight - implicitHeight: wifiIcon.implicitHeight + Appearance.padding.normal * 2 + ToggleButton { + toggled: Network.wifiEnabled + icon: "wifi" + accent: "Tertiary" - radius: Appearance.rounding.normal - color: Network.wifiEnabled ? Colours.palette.m3tertiary : Colours.palette.m3tertiaryContainer - - StateLayer { - function onClicked(): void { - Network.toggleWifi(); - } - } - - MaterialIcon { - id: wifiIcon - - anchors.centerIn: parent - text: "wifi" - color: Network.wifiEnabled ? Colours.palette.m3onTertiary : Colours.palette.m3onTertiaryContainer + function onClicked(): void { + Network.toggleWifi(); } } - StyledRect { - implicitWidth: implicitHeight - implicitHeight: scanIcon.implicitHeight + Appearance.padding.normal * 2 - - radius: Appearance.rounding.normal - color: Network.scanning ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer + ToggleButton { + toggled: Network.scanning + icon: "wifi_find" + accent: "Secondary" - StateLayer { - function onClicked(): void { - Network.rescanWifi(); - } - } - - MaterialIcon { - id: scanIcon - - anchors.centerIn: parent - text: "wifi_find" - color: Network.scanning ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer + function onClicked(): void { + Network.rescanWifi(); } } - StyledRect { - implicitWidth: implicitHeight - implicitHeight: settingsIcon.implicitHeight + Appearance.padding.normal * 2 + ToggleButton { + toggled: !root.session.network.active + icon: "settings" + accent: "Primary" - radius: Appearance.rounding.normal - color: !root.session.network.active ? Colours.palette.m3primary : Colours.palette.m3primaryContainer - - StateLayer { - function onClicked(): void { - if (root.session.network.active) - root.session.network.active = null; - else { - root.session.network.active = view.model.get(0)?.modelData ?? null; - } + function onClicked(): void { + if (root.session.network.active) + root.session.network.active = null; + else { + root.session.network.active = view.model.get(0)?.modelData ?? null; } } - - MaterialIcon { - id: settingsIcon - - anchors.centerIn: parent - text: "settings" - color: !root.session.network.active ? Colours.palette.m3onPrimary : Colours.palette.m3onPrimaryContainer - } } } @@ -258,4 +222,79 @@ ColumnLayout { implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 } } + + 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 + } + } + } } diff --git a/modules/controlcenter/network/NetworkPane.qml b/modules/controlcenter/network/NetworkPane.qml index 5e8a75a..fe18274 100644 --- a/modules/controlcenter/network/NetworkPane.qml +++ b/modules/controlcenter/network/NetworkPane.qml @@ -5,6 +5,7 @@ import qs.components import qs.components.effects import qs.components.containers import qs.config +import Quickshell.Widgets import QtQuick import QtQuick.Layouts @@ -41,30 +42,92 @@ RowLayout { Layout.fillWidth: true Layout.fillHeight: true - Loader { - id: loader - + ClippingRectangle { anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 - - sourceComponent: root.session.network.active ? details : settings + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 + + radius: rightBorder.innerRadius + color: "transparent" + + Loader { + id: loader + + property var pane: root.session.network.active + property string paneId: pane ? (pane.ssid || pane.bssid || "") : "" + + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + opacity: 1 + scale: 1 + transformOrigin: Item.Center + + clip: false + asynchronous: true + sourceComponent: pane ? details : settings + + Behavior on paneId { + SequentialAnimation { + ParallelAnimation { + Anim { + target: loader + property: "opacity" + to: 0 + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + Anim { + target: loader + property: "scale" + to: 0.8 + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + } + PropertyAction {} + ParallelAnimation { + Anim { + target: loader + property: "opacity" + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + Anim { + target: loader + property: "scale" + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + } + } + } - Behavior on opacity { - NumberAnimation { - duration: 200 + onPaneChanged: { + paneId = pane ? (pane.ssid || pane.bssid || "") : ""; } } } InnerBorder { + id: rightBorder + leftThickness: Appearance.padding.normal / 2 } Component { id: settings - Settings { - session: root.session + StyledFlickable { + flickableDirection: Flickable.VerticalFlick + contentHeight: settingsInner.height + + Settings { + id: settingsInner + + anchors.left: parent.left + anchors.right: parent.right + session: root.session + } } } @@ -82,4 +145,10 @@ RowLayout { session: root.session z: 1000 } + + component Anim: NumberAnimation { + target: loader + duration: Appearance.anim.durations.normal / 2 + easing.type: Easing.BezierSpline + } } -- cgit v1.2.3-freya From bcb2bd494ae469f0890838813132070663029fc6 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Tue, 11 Nov 2025 16:15:10 -0500 Subject: controlcenter: truncating interface names if too long --- modules/controlcenter/ethernet/EthernetList.qml | 2 ++ modules/controlcenter/network/NetworkList.qml | 2 ++ 2 files changed, 4 insertions(+) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/ethernet/EthernetList.qml b/modules/controlcenter/ethernet/EthernetList.qml index 6ed50fd..ff7cee2 100644 --- a/modules/controlcenter/ethernet/EthernetList.qml +++ b/modules/controlcenter/ethernet/EthernetList.qml @@ -122,6 +122,8 @@ ColumnLayout { StyledText { Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 text: modelData.interface || qsTr("Unknown") } diff --git a/modules/controlcenter/network/NetworkList.qml b/modules/controlcenter/network/NetworkList.qml index f528092..60bd857 100644 --- a/modules/controlcenter/network/NetworkList.qml +++ b/modules/controlcenter/network/NetworkList.qml @@ -149,6 +149,8 @@ ColumnLayout { StyledText { Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 text: modelData.ssid || qsTr("Unknown") } -- cgit v1.2.3-freya From 5af1e9222e2f15c84102dc1ffb46e8643959e74a Mon Sep 17 00:00:00 2001 From: ATMDA Date: Tue, 11 Nov 2025 16:18:32 -0500 Subject: controlcenter: added connection information to wireless to match ethernet panel --- modules/controlcenter/network/Details.qml | 101 ++++++++++++++++++++++++++ services/Network.qml | 114 +++++++++++++++++++++++++++++- 2 files changed, 214 insertions(+), 1 deletion(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/Details.qml b/modules/controlcenter/network/Details.qml index 19e011f..31d20bc 100644 --- a/modules/controlcenter/network/Details.qml +++ b/modules/controlcenter/network/Details.qml @@ -16,6 +16,31 @@ Item { required property Session session readonly property var network: session.network.active + Component.onCompleted: { + if (network && network.active) { + Network.updateWirelessDeviceDetails(); + } + } + + onNetworkChanged: { + if (network && network.active) { + Network.updateWirelessDeviceDetails(); + } else { + Network.wirelessDeviceDetails = null; + } + } + + Connections { + target: Network + function onActiveChanged() { + if (root.network && root.network.active && Network.active && Network.active.ssid === root.network.ssid) { + Network.updateWirelessDeviceDetails(); + } else if (!root.network || !root.network.active) { + Network.wirelessDeviceDetails = null; + } + } + } + StyledFlickable { anchors.fill: parent @@ -198,6 +223,82 @@ Item { } } } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Connection information") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("Network connection details") + color: Colours.palette.m3outline + } + + StyledRect { + Layout.fillWidth: true + implicitHeight: connectionInfo.implicitHeight + Appearance.padding.large * 2 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + + ColumnLayout { + id: connectionInfo + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + + spacing: Appearance.spacing.small / 2 + + StyledText { + text: qsTr("IP Address") + } + + StyledText { + text: Network.wirelessDeviceDetails?.ipAddress || qsTr("Not available") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Subnet Mask") + } + + StyledText { + text: Network.wirelessDeviceDetails?.subnet || qsTr("Not available") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Gateway") + } + + StyledText { + text: Network.wirelessDeviceDetails?.gateway || qsTr("Not available") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("DNS Servers") + } + + StyledText { + text: (Network.wirelessDeviceDetails && Network.wirelessDeviceDetails.dns && Network.wirelessDeviceDetails.dns.length > 0) ? Network.wirelessDeviceDetails.dns.join(", ") : qsTr("Not available") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + wrapMode: Text.Wrap + Layout.maximumWidth: parent.width + } + } + } } } diff --git a/services/Network.qml b/services/Network.qml index acd4bcb..c8ab264 100644 --- a/services/Network.qml +++ b/services/Network.qml @@ -25,6 +25,7 @@ Singleton { property string ethernetDebugInfo: "" property bool ethernetProcessRunning: false property var ethernetDeviceDetails: null + property var wirelessDeviceDetails: null function enableWifi(enabled: bool): void { const cmd = enabled ? "on" : "off"; @@ -111,6 +112,11 @@ Singleton { } } + function updateWirelessDeviceDetails(): void { + // Find the wireless interface by looking for wifi devices + findWirelessInterfaceProc.exec(["nmcli", "device", "status"]); + } + function cidrToSubnetMask(cidr: string): string { // Convert CIDR notation (e.g., "24") to subnet mask (e.g., "255.255.255.0") const cidrNum = parseInt(cidr); @@ -587,7 +593,7 @@ Singleton { }; for (let i = 0; i < lines.length; i++) { - const line = lines[i]; +const line = lines[i]; const parts = line.split(":"); if (parts.length >= 2) { const key = parts[0].trim(); @@ -625,6 +631,112 @@ Singleton { } } + Process { + id: findWirelessInterfaceProc + + environment: ({ + LANG: "C.UTF-8", + LC_ALL: "C.UTF-8" + }) + stdout: StdioCollector { + onStreamFinished: { + const output = text.trim(); + if (!output || output.length === 0) { + root.wirelessDeviceDetails = null; + return; + } + + // Find the connected wifi interface from device status + const lines = output.split("\n"); + let wifiInterface = ""; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const parts = line.split(/\s+/); + // Format: DEVICE TYPE STATE CONNECTION + // Look for wifi devices that are connected + if (parts.length >= 3 && parts[1] === "wifi" && parts[2] === "connected") { + wifiInterface = parts[0]; + break; + } + } + + if (wifiInterface && wifiInterface.length > 0) { + getWirelessDetailsProc.exec(["nmcli", "device", "show", wifiInterface]); + } else { + root.wirelessDeviceDetails = null; + } + } + } + onExited: { + if (exitCode !== 0) { + root.wirelessDeviceDetails = null; + } + } + } + + Process { + id: getWirelessDetailsProc + + environment: ({ + LANG: "C.UTF-8", + LC_ALL: "C.UTF-8" + }) + stdout: StdioCollector { + onStreamFinished: { + const output = text.trim(); + if (!output || output.length === 0) { + root.wirelessDeviceDetails = null; + return; + } + + const lines = output.split("\n"); + const details = { + ipAddress: "", + gateway: "", + dns: [], + subnet: "", + macAddress: "", + speed: "" + }; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const parts = line.split(":"); + if (parts.length >= 2) { + const key = parts[0].trim(); + const value = parts.slice(1).join(":").trim(); + + if (key.startsWith("IP4.ADDRESS")) { + // Extract IP and subnet from format like "10.13.1.45/24" + const ipParts = value.split("/"); + details.ipAddress = ipParts[0] || ""; + if (ipParts[1]) { + // Convert CIDR notation to subnet mask + details.subnet = root.cidrToSubnetMask(ipParts[1]); + } else { + details.subnet = ""; + } + } else if (key === "IP4.GATEWAY") { + details.gateway = value; + } else if (key.startsWith("IP4.DNS")) { + details.dns.push(value); + } else if (key === "GENERAL.HWADDR") { + details.macAddress = value; + } + } + } + + root.wirelessDeviceDetails = details; + } + } + onExited: { + if (exitCode !== 0) { + root.wirelessDeviceDetails = null; + } + } + } + component AccessPoint: QtObject { required property var lastIpcObject readonly property string ssid: lastIpcObject.ssid -- cgit v1.2.3-freya From e92187293e4afa046ca05bd80796c1fa193097e5 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 12 Nov 2025 15:58:42 -0500 Subject: controlcenter: refactoring into components --- components/controls/CollapsibleSection.qml | 85 + components/controls/SpinBoxRow.qml | 51 + components/controls/SwitchRow.qml | 47 + components/controls/ToggleButton.qml | 82 + .../controlcenter/appearance/AppearancePane.qml | 1872 ++++++-------------- modules/controlcenter/ethernet/EthernetList.qml | 77 +- modules/controlcenter/network/NetworkList.qml | 81 +- modules/controlcenter/taskbar/TaskbarPane.qml | 1013 +++-------- 8 files changed, 1014 insertions(+), 2294 deletions(-) create mode 100644 components/controls/CollapsibleSection.qml create mode 100644 components/controls/SpinBoxRow.qml create mode 100644 components/controls/SwitchRow.qml create mode 100644 components/controls/ToggleButton.qml (limited to 'modules/controlcenter/network') diff --git a/components/controls/CollapsibleSection.qml b/components/controls/CollapsibleSection.qml new file mode 100644 index 0000000..945386c --- /dev/null +++ b/components/controls/CollapsibleSection.qml @@ -0,0 +1,85 @@ +import ".." +import qs.components +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property string title + property string description: "" + property bool expanded: false + + signal toggleRequested + + spacing: Appearance.spacing.small / 2 + Layout.fillWidth: true + + Item { + id: sectionHeaderItem + Layout.fillWidth: true + Layout.preferredHeight: sectionHeader.implicitHeight + + ColumnLayout { + id: sectionHeader + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.small + + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: root.title + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + Item { + Layout.fillWidth: true + } + + MaterialIcon { + text: "expand_more" + rotation: root.expanded ? 180 : 0 + color: Colours.palette.m3onSurface + Behavior on rotation { + Anim {} + } + } + } + + StateLayer { + anchors.fill: parent + anchors.leftMargin: -Appearance.padding.normal + anchors.rightMargin: -Appearance.padding.normal + function onClicked(): void { + root.toggleRequested(); + root.expanded = !root.expanded; + } + } + + StyledText { + visible: root.expanded && root.description !== "" + text: root.description + color: Colours.palette.m3outline + Layout.fillWidth: true + } + } + } + + default property alias content: contentColumn.data + + ColumnLayout { + id: contentColumn + Layout.fillWidth: true + visible: root.expanded + spacing: Appearance.spacing.small / 2 + } +} + diff --git a/components/controls/SpinBoxRow.qml b/components/controls/SpinBoxRow.qml new file mode 100644 index 0000000..a4441c5 --- /dev/null +++ b/components/controls/SpinBoxRow.qml @@ -0,0 +1,51 @@ +import ".." +import qs.components +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +StyledRect { + id: root + + required property string label + required property real value + required property real min + required property real max + property var onValueModified: function(value) {} + + Layout.fillWidth: true + implicitHeight: row.implicitHeight + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + + Behavior on implicitHeight { + Anim {} + } + + RowLayout { + id: row + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + spacing: Appearance.spacing.normal + + StyledText { + Layout.fillWidth: true + text: root.label + } + + CustomSpinBox { + min: root.min + max: root.max + value: root.value + onValueModified: value => { + root.onValueModified(value); + } + } + } +} + diff --git a/components/controls/SwitchRow.qml b/components/controls/SwitchRow.qml new file mode 100644 index 0000000..a486ee2 --- /dev/null +++ b/components/controls/SwitchRow.qml @@ -0,0 +1,47 @@ +import ".." +import qs.components +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +StyledRect { + id: root + + required property string label + required property bool checked + property var onToggled: function(checked) {} + + Layout.fillWidth: true + implicitHeight: row.implicitHeight + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + + Behavior on implicitHeight { + Anim {} + } + + RowLayout { + id: row + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + spacing: Appearance.spacing.normal + + StyledText { + Layout.fillWidth: true + text: root.label + } + + StyledSwitch { + checked: root.checked + onToggled: { + root.onToggled(checked); + } + } + } +} + diff --git a/components/controls/ToggleButton.qml b/components/controls/ToggleButton.qml new file mode 100644 index 0000000..9d8e094 --- /dev/null +++ b/components/controls/ToggleButton.qml @@ -0,0 +1,82 @@ +import ".." +import qs.components +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +StyledRect { + id: root + + required property bool toggled + property string icon + property string label + property string accent: "Secondary" + + signal clicked + + 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: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`] + + function onClicked(): void { + root.clicked(); + } + } + + RowLayout { + id: toggleBtnInner + + anchors.centerIn: parent + spacing: Appearance.spacing.normal + + MaterialIcon { + id: toggleBtnIcon + + visible: !!text + 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 + + Behavior on fill { + Anim {} + } + } + + Loader { + asynchronous: true + active: !!root.label + visible: active + + sourceComponent: StyledText { + text: root.label + color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.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 + } + } +} + diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index e3600ed..cfe5b56 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -255,1588 +255,770 @@ RowLayout { } } - Item { + CollapsibleSection { id: themeModeSection - Layout.fillWidth: true - Layout.preferredHeight: themeModeSectionHeader.implicitHeight - property bool expanded: false - - ColumnLayout { - id: themeModeSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small - - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Theme mode") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } - - MaterialIcon { - text: "expand_more" - rotation: themeModeSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} - } - } - } - - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = themeModeSection.expanded; - root.collapseAllSections(themeModeSection); - themeModeSection.expanded = !wasExpanded; - } - } - - StyledText { - visible: themeModeSection.expanded - text: qsTr("Light or dark theme") - color: Colours.palette.m3outline - Layout.fillWidth: true - } - } - } - - StyledRect { - visible: themeModeSection.expanded - Layout.fillWidth: true - implicitHeight: themeModeSection.expanded ? modeToggle.implicitHeight + Appearance.padding.large * 2 : 0 - - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} + title: qsTr("Theme mode") + description: qsTr("Light or dark theme") + onToggleRequested: { + root.collapseAllSections(themeModeSection); } - RowLayout { - id: modeToggle - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - - spacing: Appearance.spacing.normal + StyledRect { + Layout.fillWidth: true + implicitHeight: modeToggle.implicitHeight + Appearance.padding.large * 2 - StyledText { - Layout.fillWidth: true - text: qsTr("Dark mode") - } + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - StyledSwitch { - checked: !Colours.currentLight - onToggled: { - Colours.setMode(checked ? "dark" : "light"); - } + Behavior on implicitHeight { + Anim {} } - } - } - Item { - id: colorVariantSection - Layout.fillWidth: true - Layout.preferredHeight: colorVariantSectionHeader.implicitHeight - property bool expanded: false + RowLayout { + id: modeToggle - ColumnLayout { - id: colorVariantSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + spacing: Appearance.spacing.normal StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Color variant") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - Item { Layout.fillWidth: true + text: qsTr("Dark mode") } - MaterialIcon { - text: "expand_more" - rotation: colorVariantSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} + StyledSwitch { + checked: !Colours.currentLight + onToggled: { + Colours.setMode(checked ? "dark" : "light"); } } } - - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = colorVariantSection.expanded; - root.collapseAllSections(colorVariantSection); - colorVariantSection.expanded = !wasExpanded; - } - } - - StyledText { - visible: colorVariantSection.expanded - text: qsTr("Material theme variant") - color: Colours.palette.m3outline - Layout.fillWidth: true - } } } - StyledListView { - visible: colorVariantSection.expanded - Layout.fillWidth: true - implicitHeight: colorVariantSection.expanded ? Math.min(400, M3Variants.list.length * 60) : 0 - Layout.topMargin: 0 - - Behavior on implicitHeight { - Anim {} + CollapsibleSection { + id: colorVariantSection + title: qsTr("Color variant") + description: qsTr("Material theme variant") + onToggleRequested: { + root.collapseAllSections(colorVariantSection); } - model: M3Variants.list - spacing: Appearance.spacing.small / 2 - clip: true - - StyledScrollBar.vertical: StyledScrollBar { - flickable: parent - } + StyledListView { + Layout.fillWidth: true + implicitHeight: colorVariantSection.expanded ? Math.min(400, M3Variants.list.length * 60) : 0 - delegate: StyledRect { - required property var modelData + Behavior on implicitHeight { + Anim {} + } - anchors.left: parent.left - anchors.right: parent.right + model: M3Variants.list + spacing: Appearance.spacing.small / 2 + clip: true - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, modelData.variant === Schemes.currentVariant ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: modelData.variant === Schemes.currentVariant ? 1 : 0 - border.color: Colours.palette.m3primary - - StateLayer { - function onClicked(): void { - const variant = modelData.variant; - - // Optimistic update - set immediately - Schemes.currentVariant = variant; - - // Execute the command - Quickshell.execDetached(["caelestia", "scheme", "set", "-v", variant]); - - // Reload after a delay to confirm - Qt.callLater(() => { - reloadTimer.restart(); - }); - } - } - - Timer { - id: reloadTimer - interval: 300 - onTriggered: { - Schemes.reload(); - } + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent } - RowLayout { - id: variantRow + delegate: StyledRect { + required property var modelData anchors.left: parent.left anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal - spacing: Appearance.spacing.normal - - MaterialIcon { - text: modelData.icon - font.pointSize: Appearance.font.size.large - fill: modelData.variant === Schemes.currentVariant ? 1 : 0 - } + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, modelData.variant === Schemes.currentVariant ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: modelData.variant === Schemes.currentVariant ? 1 : 0 + border.color: Colours.palette.m3primary - StyledText { - Layout.fillWidth: true - text: modelData.name - font.weight: modelData.variant === Schemes.currentVariant ? 500 : 400 + StateLayer { + function onClicked(): void { + const variant = modelData.variant; + + // Optimistic update - set immediately + Schemes.currentVariant = variant; + + // Execute the command + Quickshell.execDetached(["caelestia", "scheme", "set", "-v", variant]); + + // Reload after a delay to confirm + Qt.callLater(() => { + reloadTimer.restart(); + }); + } } - - MaterialIcon { - visible: modelData.variant === Schemes.currentVariant - text: "check" - color: Colours.palette.m3primary - font.pointSize: Appearance.font.size.large + + Timer { + id: reloadTimer + interval: 300 + onTriggered: { + Schemes.reload(); + } } - } - implicitHeight: variantRow.implicitHeight + Appearance.padding.normal * 2 - } - } + RowLayout { + id: variantRow - Item { - id: colorSchemeSection - Layout.fillWidth: true - Layout.preferredHeight: colorSchemeSectionHeader.implicitHeight - property bool expanded: false - - ColumnLayout { - id: colorSchemeSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + spacing: Appearance.spacing.normal - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Color scheme") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } + MaterialIcon { + text: modelData.icon + font.pointSize: Appearance.font.size.large + fill: modelData.variant === Schemes.currentVariant ? 1 : 0 + } - MaterialIcon { - text: "expand_more" - rotation: colorSchemeSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} + StyledText { + Layout.fillWidth: true + text: modelData.name + font.weight: modelData.variant === Schemes.currentVariant ? 500 : 400 } - } - } - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = colorSchemeSection.expanded; - root.collapseAllSections(colorSchemeSection); - colorSchemeSection.expanded = !wasExpanded; + MaterialIcon { + visible: modelData.variant === Schemes.currentVariant + text: "check" + color: Colours.palette.m3primary + font.pointSize: Appearance.font.size.large + } } - } - StyledText { - visible: colorSchemeSection.expanded - text: qsTr("Available color schemes") - color: Colours.palette.m3outline - Layout.fillWidth: true + implicitHeight: variantRow.implicitHeight + Appearance.padding.normal * 2 } } } - StyledListView { - visible: colorSchemeSection.expanded - Layout.fillWidth: true - implicitHeight: colorSchemeSection.expanded ? Math.min(400, Schemes.list.length * 80) : 0 - Layout.topMargin: 0 - - Behavior on implicitHeight { - Anim {} - } - - model: Schemes.list - spacing: Appearance.spacing.small / 2 - clip: true - - StyledScrollBar.vertical: StyledScrollBar { - flickable: parent + CollapsibleSection { + id: colorSchemeSection + title: qsTr("Color scheme") + description: qsTr("Available color schemes") + onToggleRequested: { + root.collapseAllSections(colorSchemeSection); } - delegate: StyledRect { - required property var modelData + StyledListView { + Layout.fillWidth: true + implicitHeight: colorSchemeSection.expanded ? Math.min(400, Schemes.list.length * 80) : 0 - anchors.left: parent.left - anchors.right: parent.right + Behavior on implicitHeight { + Anim {} + } - readonly property string schemeKey: `${modelData.name} ${modelData.flavour}` - readonly property bool isCurrent: schemeKey === Schemes.currentScheme + model: Schemes.list + spacing: Appearance.spacing.small / 2 + clip: true - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: isCurrent ? 1 : 0 - border.color: Colours.palette.m3primary - - StateLayer { - function onClicked(): void { - const name = modelData.name; - const flavour = modelData.flavour; - const schemeKey = `${name} ${flavour}`; - - // Optimistic update - set immediately - Schemes.currentScheme = schemeKey; - - // Execute the command - Quickshell.execDetached(["caelestia", "scheme", "set", "-n", name, "-f", flavour]); - - // Reload after a delay to confirm - Qt.callLater(() => { - reloadTimer.restart(); - }); - } - } - - Timer { - id: reloadTimer - interval: 300 - onTriggered: { - Schemes.reload(); - } + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent } - RowLayout { - id: schemeRow + delegate: StyledRect { + required property var modelData anchors.left: parent.left anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal - spacing: Appearance.spacing.normal + readonly property string schemeKey: `${modelData.name} ${modelData.flavour}` + readonly property bool isCurrent: schemeKey === Schemes.currentScheme + + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: isCurrent ? 1 : 0 + border.color: Colours.palette.m3primary + + StateLayer { + function onClicked(): void { + const name = modelData.name; + const flavour = modelData.flavour; + const schemeKey = `${name} ${flavour}`; + + // Optimistic update - set immediately + Schemes.currentScheme = schemeKey; + + // Execute the command + Quickshell.execDetached(["caelestia", "scheme", "set", "-n", name, "-f", flavour]); + + // Reload after a delay to confirm + Qt.callLater(() => { + reloadTimer.restart(); + }); + } + } + + Timer { + id: reloadTimer + interval: 300 + onTriggered: { + Schemes.reload(); + } + } - StyledRect { - id: preview + RowLayout { + id: schemeRow + anchors.left: parent.left + anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal - border.width: 1 - border.color: Qt.alpha(`#${modelData.colours?.outline}`, 0.5) + spacing: Appearance.spacing.normal - color: `#${modelData.colours?.surface}` - radius: Appearance.rounding.full - implicitWidth: iconPlaceholder.implicitWidth - implicitHeight: iconPlaceholder.implicitWidth + StyledRect { + id: preview - MaterialIcon { - id: iconPlaceholder - visible: false - text: "circle" - font.pointSize: Appearance.font.size.large - } + anchors.verticalCenter: parent.verticalCenter - Item { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right + border.width: 1 + border.color: Qt.alpha(`#${modelData.colours?.outline}`, 0.5) - implicitWidth: parent.implicitWidth / 2 - clip: true + color: `#${modelData.colours?.surface}` + radius: Appearance.rounding.full + implicitWidth: iconPlaceholder.implicitWidth + implicitHeight: iconPlaceholder.implicitWidth - StyledRect { + MaterialIcon { + id: iconPlaceholder + visible: false + text: "circle" + font.pointSize: Appearance.font.size.large + } + + Item { anchors.top: parent.top anchors.bottom: parent.bottom anchors.right: parent.right - implicitWidth: preview.implicitWidth - color: `#${modelData.colours?.primary}` - radius: Appearance.rounding.full - } - } - } + implicitWidth: parent.implicitWidth / 2 + clip: true - Column { - Layout.fillWidth: true - spacing: 0 + StyledRect { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right - StyledText { - text: modelData.flavour ?? "" - font.pointSize: Appearance.font.size.normal + implicitWidth: preview.implicitWidth + color: `#${modelData.colours?.primary}` + radius: Appearance.rounding.full + } + } } - StyledText { - text: modelData.name ?? "" - font.pointSize: Appearance.font.size.small - color: Colours.palette.m3outline + Column { + Layout.fillWidth: true + spacing: 0 - elide: Text.ElideRight - anchors.left: parent.left - anchors.right: parent.right - } - } + StyledText { + text: modelData.flavour ?? "" + font.pointSize: Appearance.font.size.normal + } - Loader { - active: isCurrent - asynchronous: true + StyledText { + text: modelData.name ?? "" + font.pointSize: Appearance.font.size.small + color: Colours.palette.m3outline - sourceComponent: MaterialIcon { - text: "check" - color: Colours.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.large + elide: Text.ElideRight + anchors.left: parent.left + anchors.right: parent.right + } } - } - } - - implicitHeight: schemeRow.implicitHeight + Appearance.padding.normal * 2 - } - } - Item { - id: animationsSection - Layout.fillWidth: true - Layout.preferredHeight: animationsSectionHeader.implicitHeight - property bool expanded: false - - ColumnLayout { - id: animationsSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + Loader { + active: isCurrent + asynchronous: true - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Animations") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } - - MaterialIcon { - text: "expand_more" - rotation: animationsSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} + sourceComponent: MaterialIcon { + text: "check" + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.large + } } } - } - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = animationsSection.expanded; - root.collapseAllSections(animationsSection); - animationsSection.expanded = !wasExpanded; - } + implicitHeight: schemeRow.implicitHeight + Appearance.padding.normal * 2 } } } - StyledRect { - visible: animationsSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: animationsSection.expanded ? animDurationsScaleRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} + CollapsibleSection { + id: animationsSection + title: qsTr("Animations") + onToggleRequested: { + root.collapseAllSections(animationsSection); } - RowLayout { - id: animDurationsScaleRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Animation duration scale") - } - - CustomSpinBox { - id: animDurationsScaleSpinBox - min: 0.1 - max: 5 - value: root.animDurationsScale - onValueModified: value => { - root.animDurationsScale = value; - root.saveConfig(); - } + SpinBoxRow { + label: qsTr("Animation duration scale") + min: 0.1 + max: 5 + value: root.animDurationsScale + onValueModified: value => { + root.animDurationsScale = value; + root.saveConfig(); } } } - Item { + CollapsibleSection { id: fontsSection - Layout.fillWidth: true - Layout.preferredHeight: fontsSectionHeader.implicitHeight - property bool expanded: false - - ColumnLayout { - id: fontsSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small - - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Fonts") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } - - MaterialIcon { - text: "expand_more" - rotation: fontsSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} - } - } - } - - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = fontsSection.expanded; - root.collapseAllSections(fontsSection); - fontsSection.expanded = !wasExpanded; - } - } + title: qsTr("Fonts") + onToggleRequested: { + root.collapseAllSections(fontsSection); } - } - StyledText { - visible: fontsSection.expanded - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Material font family") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - StyledListView { - visible: fontsSection.expanded - Layout.fillWidth: true - implicitHeight: fontsSection.expanded ? Math.min(300, Qt.fontFamilies().length * 50) : 0 - Layout.topMargin: 0 - - Behavior on implicitHeight { - Anim {} - } - - model: Qt.fontFamilies() - spacing: Appearance.spacing.small / 2 - clip: true - - StyledScrollBar.vertical: StyledScrollBar { - flickable: parent + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Material font family") + font.pointSize: Appearance.font.size.larger + font.weight: 500 } - delegate: StyledRect { - required property string modelData + StyledListView { + Layout.fillWidth: true + implicitHeight: fontsSection.expanded ? Math.min(300, Qt.fontFamilies().length * 50) : 0 - anchors.left: parent.left - anchors.right: parent.right + Behavior on implicitHeight { + Anim {} + } - readonly property bool isCurrent: modelData === root.fontFamilyMaterial - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: isCurrent ? 1 : 0 - border.color: Colours.palette.m3primary + model: Qt.fontFamilies() + spacing: Appearance.spacing.small / 2 + clip: true - StateLayer { - function onClicked(): void { - root.fontFamilyMaterial = modelData; - root.saveConfig(); - } + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent } - RowLayout { - id: fontFamilyMaterialRow + delegate: StyledRect { + required property string modelData anchors.left: parent.left anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal - spacing: Appearance.spacing.normal - - StyledText { - text: modelData - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } + readonly property bool isCurrent: modelData === root.fontFamilyMaterial + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: isCurrent ? 1 : 0 + border.color: Colours.palette.m3primary - Loader { - active: isCurrent - asynchronous: true - - sourceComponent: MaterialIcon { - text: "check" - color: Colours.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.large + StateLayer { + function onClicked(): void { + root.fontFamilyMaterial = modelData; + root.saveConfig(); } } - } - - implicitHeight: fontFamilyMaterialRow.implicitHeight + Appearance.padding.normal * 2 - } - } - - StyledText { - visible: fontsSection.expanded - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Monospace font family") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - StyledListView { - visible: fontsSection.expanded - Layout.fillWidth: true - implicitHeight: fontsSection.expanded ? Math.min(300, Qt.fontFamilies().length * 50) : 0 - Layout.topMargin: 0 - - Behavior on implicitHeight { - Anim {} - } - - model: Qt.fontFamilies() - spacing: Appearance.spacing.small / 2 - clip: true - - StyledScrollBar.vertical: StyledScrollBar { - flickable: parent - } - - delegate: StyledRect { - required property string modelData - - anchors.left: parent.left - anchors.right: parent.right - - readonly property bool isCurrent: modelData === root.fontFamilyMono - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: isCurrent ? 1 : 0 - border.color: Colours.palette.m3primary - - StateLayer { - function onClicked(): void { - root.fontFamilyMono = modelData; - root.saveConfig(); - } - } - RowLayout { - id: fontFamilyMonoRow + RowLayout { + id: fontFamilyMaterialRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal - spacing: Appearance.spacing.normal + spacing: Appearance.spacing.normal - StyledText { - text: modelData - font.pointSize: Appearance.font.size.normal - } + StyledText { + text: modelData + font.pointSize: Appearance.font.size.normal + } - Item { - Layout.fillWidth: true - } + Item { + Layout.fillWidth: true + } - Loader { - active: isCurrent - asynchronous: true + Loader { + active: isCurrent + asynchronous: true - sourceComponent: MaterialIcon { - text: "check" - color: Colours.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.large + sourceComponent: MaterialIcon { + text: "check" + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.large + } } } - } - - implicitHeight: fontFamilyMonoRow.implicitHeight + Appearance.padding.normal * 2 - } - } - - StyledText { - visible: fontsSection.expanded - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Sans-serif font family") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - StyledListView { - visible: fontsSection.expanded - Layout.fillWidth: true - implicitHeight: fontsSection.expanded ? Math.min(300, Qt.fontFamilies().length * 50) : 0 - Layout.topMargin: 0 - Behavior on implicitHeight { - Anim {} + implicitHeight: fontFamilyMaterialRow.implicitHeight + Appearance.padding.normal * 2 + } } - model: Qt.fontFamilies() - spacing: Appearance.spacing.small / 2 - clip: true - - StyledScrollBar.vertical: StyledScrollBar { - flickable: parent + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Monospace font family") + font.pointSize: Appearance.font.size.larger + font.weight: 500 } - delegate: StyledRect { - required property string modelData + StyledListView { + Layout.fillWidth: true + implicitHeight: fontsSection.expanded ? Math.min(300, Qt.fontFamilies().length * 50) : 0 - anchors.left: parent.left - anchors.right: parent.right + Behavior on implicitHeight { + Anim {} + } - readonly property bool isCurrent: modelData === root.fontFamilySans - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: isCurrent ? 1 : 0 - border.color: Colours.palette.m3primary + model: Qt.fontFamilies() + spacing: Appearance.spacing.small / 2 + clip: true - StateLayer { - function onClicked(): void { - root.fontFamilySans = modelData; - root.saveConfig(); - } + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent } - RowLayout { - id: fontFamilySansRow + delegate: StyledRect { + required property string modelData anchors.left: parent.left anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal - spacing: Appearance.spacing.normal - - StyledText { - text: modelData - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } + readonly property bool isCurrent: modelData === root.fontFamilyMono + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: isCurrent ? 1 : 0 + border.color: Colours.palette.m3primary - Loader { - active: isCurrent - asynchronous: true - - sourceComponent: MaterialIcon { - text: "check" - color: Colours.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.large + StateLayer { + function onClicked(): void { + root.fontFamilyMono = modelData; + root.saveConfig(); } } - } - - implicitHeight: fontFamilySansRow.implicitHeight + Appearance.padding.normal * 2 - } - } - StyledRect { - visible: fontsSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: fontsSection.expanded ? fontSizeScaleRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer + RowLayout { + id: fontFamilyMonoRow - Behavior on implicitHeight { - Anim {} - } - - RowLayout { - id: fontSizeScaleRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Font size scale") - } - - CustomSpinBox { - id: fontSizeScaleSpinBox - min: 0.1 - max: 5 - value: root.fontSizeScale - onValueModified: value => { - root.fontSizeScale = value; - root.saveConfig(); - } - } - } - } - - Item { - id: scalesSection - Layout.fillWidth: true - Layout.preferredHeight: scalesSectionHeader.implicitHeight - property bool expanded: false + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal - ColumnLayout { - id: scalesSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small + spacing: Appearance.spacing.normal - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + StyledText { + text: modelData + font.pointSize: Appearance.font.size.normal + } - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Scales") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } + Item { + Layout.fillWidth: true + } - Item { - Layout.fillWidth: true - } + Loader { + active: isCurrent + asynchronous: true - MaterialIcon { - text: "expand_more" - rotation: scalesSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} + sourceComponent: MaterialIcon { + text: "check" + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.large + } } } - } - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = scalesSection.expanded; - root.collapseAllSections(scalesSection); - scalesSection.expanded = !wasExpanded; - } + implicitHeight: fontFamilyMonoRow.implicitHeight + Appearance.padding.normal * 2 } } - } - StyledRect { - visible: scalesSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: scalesSection.expanded ? paddingScaleRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Sans-serif font family") + font.pointSize: Appearance.font.size.larger + font.weight: 500 } - RowLayout { - id: paddingScaleRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Padding scale") - } + StyledListView { + Layout.fillWidth: true + implicitHeight: fontsSection.expanded ? Math.min(300, Qt.fontFamilies().length * 50) : 0 - CustomSpinBox { - id: paddingScaleSpinBox - min: 0.1 - max: 5 - value: root.paddingScale - onValueModified: value => { - root.paddingScale = value; - root.saveConfig(); - } + Behavior on implicitHeight { + Anim {} } - } - } - StyledRect { - visible: scalesSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: scalesSection.expanded ? roundingScaleRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } + model: Qt.fontFamilies() + spacing: Appearance.spacing.small / 2 + clip: true - RowLayout { - id: roundingScaleRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Rounding scale") + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent } - CustomSpinBox { - id: roundingScaleSpinBox - min: 0.1 - max: 5 - value: root.roundingScale - onValueModified: value => { - root.roundingScale = value; - root.saveConfig(); - } - } - } - } + delegate: StyledRect { + required property string modelData - StyledRect { - visible: scalesSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: scalesSection.expanded ? spacingScaleRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } + anchors.left: parent.left + anchors.right: parent.right - RowLayout { - id: spacingScaleRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Spacing scale") - } + readonly property bool isCurrent: modelData === root.fontFamilySans + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: isCurrent ? 1 : 0 + border.color: Colours.palette.m3primary - CustomSpinBox { - id: spacingScaleSpinBox - min: 0.1 - max: 5 - value: root.spacingScale - onValueModified: value => { - root.spacingScale = value; - root.saveConfig(); + StateLayer { + function onClicked(): void { + root.fontFamilySans = modelData; + root.saveConfig(); + } } - } - } - } - - Item { - id: transparencySection - Layout.fillWidth: true - Layout.preferredHeight: transparencySectionHeader.implicitHeight - property bool expanded: false - ColumnLayout { - id: transparencySectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small + RowLayout { + id: fontFamilySansRow - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Transparency") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } + spacing: Appearance.spacing.normal - Item { - Layout.fillWidth: true - } - - MaterialIcon { - text: "expand_more" - rotation: transparencySection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} + StyledText { + text: modelData + font.pointSize: Appearance.font.size.normal } - } - } - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = transparencySection.expanded; - root.collapseAllSections(transparencySection); - transparencySection.expanded = !wasExpanded; - } - } - } - } - - StyledRect { - visible: transparencySection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: transparencySection.expanded ? transparencyEnabledRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } + Item { + Layout.fillWidth: true + } - RowLayout { - id: transparencyEnabledRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Transparency enabled") - } + Loader { + active: isCurrent + asynchronous: true - StyledSwitch { - id: transparencyEnabledSwitch - checked: root.transparencyEnabled - onToggled: { - root.transparencyEnabled = checked; - root.saveConfig(); + sourceComponent: MaterialIcon { + text: "check" + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.large + } + } } - } - } - } - - StyledRect { - visible: transparencySection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: transparencySection.expanded ? transparencyBaseRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - Behavior on implicitHeight { - Anim {} - } - - RowLayout { - id: transparencyBaseRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Transparency base") + implicitHeight: fontFamilySansRow.implicitHeight + Appearance.padding.normal * 2 } - - CustomSpinBox { - id: transparencyBaseSpinBox - min: 0 - max: 1 - value: root.transparencyBase - onValueModified: value => { - root.transparencyBase = value; - root.saveConfig(); - } - } - } - } - - StyledRect { - visible: transparencySection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: transparencySection.expanded ? transparencyLayersRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} } - RowLayout { - id: transparencyLayersRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Transparency layers") - } + StyledRect { + Layout.fillWidth: true + implicitHeight: fontSizeScaleRow.implicitHeight + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - CustomSpinBox { - id: transparencyLayersSpinBox - min: 0 - max: 1 - value: root.transparencyLayers - onValueModified: value => { - root.transparencyLayers = value; - root.saveConfig(); - } + Behavior on implicitHeight { + Anim {} } - } - } - - Item { - id: borderSection - Layout.fillWidth: true - Layout.preferredHeight: borderSectionHeader.implicitHeight - property bool expanded: false - - ColumnLayout { - id: borderSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + id: fontSizeScaleRow + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + spacing: Appearance.spacing.normal StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Border") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - Item { Layout.fillWidth: true + text: qsTr("Font size scale") } - MaterialIcon { - text: "expand_more" - rotation: borderSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} + CustomSpinBox { + id: fontSizeScaleSpinBox + min: 0.1 + max: 5 + value: root.fontSizeScale + onValueModified: value => { + root.fontSizeScale = value; + root.saveConfig(); } } } - - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = borderSection.expanded; - root.collapseAllSections(borderSection); - borderSection.expanded = !wasExpanded; - } - } } } - StyledRect { - visible: borderSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: borderSection.expanded ? borderRoundingRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} + CollapsibleSection { + id: scalesSection + title: qsTr("Scales") + onToggleRequested: { + root.collapseAllSections(scalesSection); } - RowLayout { - id: borderRoundingRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Border rounding") + SpinBoxRow { + label: qsTr("Padding scale") + min: 0.1 + max: 5 + value: root.paddingScale + onValueModified: value => { + root.paddingScale = value; + root.saveConfig(); } - - CustomSpinBox { - id: borderRoundingSpinBox - min: 0.1 - max: 5 - value: root.borderRounding - onValueModified: value => { - root.borderRounding = value; - root.saveConfig(); - } - } - } - } - - StyledRect { - visible: borderSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: borderSection.expanded ? borderThicknessRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} } - RowLayout { - id: borderThicknessRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Border thickness") - } - - CustomSpinBox { - id: borderThicknessSpinBox - min: 0.1 - max: 5 - value: root.borderThickness - onValueModified: value => { - root.borderThickness = value; - root.saveConfig(); - } + SpinBoxRow { + label: qsTr("Rounding scale") + min: 0.1 + max: 5 + value: root.roundingScale + onValueModified: value => { + root.roundingScale = value; + root.saveConfig(); } } - } - - Item { - id: backgroundSection - Layout.fillWidth: true - Layout.preferredHeight: backgroundSectionHeader.implicitHeight - property bool expanded: false - - ColumnLayout { - id: backgroundSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small - - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Background") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } - MaterialIcon { - text: "expand_more" - rotation: backgroundSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} - } - } - } - - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = backgroundSection.expanded; - root.collapseAllSections(backgroundSection); - backgroundSection.expanded = !wasExpanded; - } + SpinBoxRow { + label: qsTr("Spacing scale") + min: 0.1 + max: 5 + value: root.spacingScale + onValueModified: value => { + root.spacingScale = value; + root.saveConfig(); } } } - StyledRect { - visible: backgroundSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: backgroundSection.expanded ? desktopClockRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} + CollapsibleSection { + id: transparencySection + title: qsTr("Transparency") + onToggleRequested: { + root.collapseAllSections(transparencySection); } - RowLayout { - id: desktopClockRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Desktop clock") + SwitchRow { + label: qsTr("Transparency enabled") + checked: root.transparencyEnabled + onToggled: checked => { + root.transparencyEnabled = checked; + root.saveConfig(); } - - StyledSwitch { - id: desktopClockSwitch - checked: root.desktopClockEnabled - onToggled: { - root.desktopClockEnabled = checked; - root.saveConfig(); - } - } - } - } - - StyledRect { - visible: backgroundSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: backgroundSection.expanded ? backgroundEnabledRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} } - RowLayout { - id: backgroundEnabledRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Background enabled") + SpinBoxRow { + label: qsTr("Transparency base") + min: 0 + max: 1 + value: root.transparencyBase + onValueModified: value => { + root.transparencyBase = value; + root.saveConfig(); } + } - StyledSwitch { - id: backgroundEnabledSwitch - checked: root.backgroundEnabled - onToggled: { - root.backgroundEnabled = checked; - root.saveConfig(); - } + SpinBoxRow { + label: qsTr("Transparency layers") + min: 0 + max: 1 + value: root.transparencyLayers + onValueModified: value => { + root.transparencyLayers = value; + root.saveConfig(); } } } - StyledText { - visible: backgroundSection.expanded - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Visualiser") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - StyledRect { - visible: backgroundSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: backgroundSection.expanded ? visualiserEnabledRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} + CollapsibleSection { + id: borderSection + title: qsTr("Border") + onToggleRequested: { + root.collapseAllSections(borderSection); } - RowLayout { - id: visualiserEnabledRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Visualiser enabled") + SpinBoxRow { + label: qsTr("Border rounding") + min: 0.1 + max: 5 + value: root.borderRounding + onValueModified: value => { + root.borderRounding = value; + root.saveConfig(); } + } - StyledSwitch { - id: visualiserEnabledSwitch - checked: root.visualiserEnabled - onToggled: { - root.visualiserEnabled = checked; - root.saveConfig(); - } + SpinBoxRow { + label: qsTr("Border thickness") + min: 0.1 + max: 5 + value: root.borderThickness + onValueModified: value => { + root.borderThickness = value; + root.saveConfig(); } } } - StyledRect { - visible: backgroundSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: backgroundSection.expanded ? visualiserAutoHideRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} + CollapsibleSection { + id: backgroundSection + title: qsTr("Background") + onToggleRequested: { + root.collapseAllSections(backgroundSection); } - RowLayout { - id: visualiserAutoHideRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Visualiser auto hide") + SwitchRow { + label: qsTr("Desktop clock") + checked: root.desktopClockEnabled + onToggled: checked => { + root.desktopClockEnabled = checked; + root.saveConfig(); } + } - StyledSwitch { - id: visualiserAutoHideSwitch - checked: root.visualiserAutoHide - onToggled: { - root.visualiserAutoHide = checked; - root.saveConfig(); - } + SwitchRow { + label: qsTr("Background enabled") + checked: root.backgroundEnabled + onToggled: checked => { + root.backgroundEnabled = checked; + root.saveConfig(); } } - } - StyledRect { - visible: backgroundSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: backgroundSection.expanded ? visualiserRoundingRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Visualiser") + font.pointSize: Appearance.font.size.larger + font.weight: 500 } - RowLayout { - id: visualiserRoundingRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Visualiser rounding") - } - - CustomSpinBox { - id: visualiserRoundingSpinBox - min: 0 - max: 10 - value: Math.round(root.visualiserRounding) - onValueModified: value => { - root.visualiserRounding = value; - root.saveConfig(); - } + SwitchRow { + label: qsTr("Visualiser enabled") + checked: root.visualiserEnabled + onToggled: checked => { + root.visualiserEnabled = checked; + root.saveConfig(); } } - } - - StyledRect { - visible: backgroundSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: backgroundSection.expanded ? visualiserSpacingRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - Behavior on implicitHeight { - Anim {} + SwitchRow { + label: qsTr("Visualiser auto hide") + checked: root.visualiserAutoHide + onToggled: checked => { + root.visualiserAutoHide = checked; + root.saveConfig(); + } } - RowLayout { - id: visualiserSpacingRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Visualiser spacing") + SpinBoxRow { + label: qsTr("Visualiser rounding") + min: 0 + max: 10 + value: Math.round(root.visualiserRounding) + onValueModified: value => { + root.visualiserRounding = value; + root.saveConfig(); } + } - CustomSpinBox { - id: visualiserSpacingSpinBox - min: 0 - max: 10 - value: Math.round(root.visualiserSpacing) - onValueModified: value => { - root.visualiserSpacing = value; - root.saveConfig(); - } + SpinBoxRow { + label: qsTr("Visualiser spacing") + min: 0 + max: 10 + value: Math.round(root.visualiserSpacing) + onValueModified: value => { + root.visualiserSpacing = value; + root.saveConfig(); } } } diff --git a/modules/controlcenter/ethernet/EthernetList.qml b/modules/controlcenter/ethernet/EthernetList.qml index ff7cee2..8b04c09 100644 --- a/modules/controlcenter/ethernet/EthernetList.qml +++ b/modules/controlcenter/ethernet/EthernetList.qml @@ -34,7 +34,7 @@ ColumnLayout { icon: "settings" accent: "Primary" - function onClicked(): void { + onClicked: { if (root.session.ethernet.active) root.session.ethernet.active = null; else { @@ -166,81 +166,6 @@ ColumnLayout { implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 } } - - 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 - } - } - } } diff --git a/modules/controlcenter/network/NetworkList.qml b/modules/controlcenter/network/NetworkList.qml index 60bd857..09d7352 100644 --- a/modules/controlcenter/network/NetworkList.qml +++ b/modules/controlcenter/network/NetworkList.qml @@ -34,7 +34,7 @@ ColumnLayout { icon: "wifi" accent: "Tertiary" - function onClicked(): void { + onClicked: { Network.toggleWifi(); } } @@ -44,7 +44,7 @@ ColumnLayout { icon: "wifi_find" accent: "Secondary" - function onClicked(): void { + onClicked: { Network.rescanWifi(); } } @@ -54,7 +54,7 @@ ColumnLayout { icon: "settings" accent: "Primary" - function onClicked(): void { + onClicked: { if (root.session.network.active) root.session.network.active = null; else { @@ -224,79 +224,4 @@ ColumnLayout { implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 } } - - 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 - } - } - } } diff --git a/modules/controlcenter/taskbar/TaskbarPane.qml b/modules/controlcenter/taskbar/TaskbarPane.qml index 22414c7..2bb50d8 100644 --- a/modules/controlcenter/taskbar/TaskbarPane.qml +++ b/modules/controlcenter/taskbar/TaskbarPane.qml @@ -239,73 +239,12 @@ RowLayout { } } - Item { + CollapsibleSection { id: clockSection - Layout.fillWidth: true - Layout.preferredHeight: clockSectionHeader.implicitHeight - property bool expanded: false - - ColumnLayout { - id: clockSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small - - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Clock") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } - - MaterialIcon { - text: "expand_more" - rotation: clockSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} - } - } - } - - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = clockSection.expanded; - root.collapseAllSections(clockSection); - clockSection.expanded = !wasExpanded; - } - } - - StyledText { - visible: clockSection.expanded - text: qsTr("Clock display settings") - color: Colours.palette.m3outline - Layout.fillWidth: true - } - } - } - - StyledRect { - Layout.fillWidth: true - visible: clockSection.expanded - implicitHeight: clockSection.expanded ? clockRow.implicitHeight + Appearance.padding.large * 2 : 0 - - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} + title: qsTr("Clock") + description: qsTr("Clock display settings") + onToggleRequested: { + root.collapseAllSections(clockSection); } RowLayout { @@ -333,835 +272,319 @@ RowLayout { } } - Item { + CollapsibleSection { id: barBehaviorSection - Layout.fillWidth: true - Layout.preferredHeight: barBehaviorSectionHeader.implicitHeight - property bool expanded: false - - ColumnLayout { - id: barBehaviorSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small - - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Bar Behavior") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } - - MaterialIcon { - text: "expand_more" - rotation: barBehaviorSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} - } - } - } - - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = barBehaviorSection.expanded; - root.collapseAllSections(barBehaviorSection); - barBehaviorSection.expanded = !wasExpanded; - } - } + title: qsTr("Bar Behavior") + onToggleRequested: { + root.collapseAllSections(barBehaviorSection); } - } - - StyledRect { - visible: barBehaviorSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: barBehaviorSection.expanded ? persistentRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } - - RowLayout { - id: persistentRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Persistent") - } - StyledSwitch { - checked: root.persistent - onToggled: { - root.persistent = checked; - root.saveConfig(); - } + SwitchRow { + label: qsTr("Persistent") + checked: root.persistent + onToggled: checked => { + root.persistent = checked; + root.saveConfig(); } } - } - - StyledRect { - visible: barBehaviorSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: barBehaviorSection.expanded ? showOnHoverRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } - - RowLayout { - id: showOnHoverRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Show on hover") - } - StyledSwitch { - checked: root.showOnHover - onToggled: { - root.showOnHover = checked; - root.saveConfig(); - } + SwitchRow { + label: qsTr("Show on hover") + checked: root.showOnHover + onToggled: checked => { + root.showOnHover = checked; + root.saveConfig(); } } - } - - StyledRect { - visible: barBehaviorSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: barBehaviorSection.expanded ? dragThresholdRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } - RowLayout { - id: dragThresholdRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Drag threshold") - } - - CustomSpinBox { - min: 0 - max: 100 - value: root.dragThreshold - onValueModified: value => { - root.dragThreshold = value; - root.saveConfig(); - } + SpinBoxRow { + label: qsTr("Drag threshold") + min: 0 + max: 100 + value: root.dragThreshold + onValueModified: value => { + root.dragThreshold = value; + root.saveConfig(); } } } - Item { + CollapsibleSection { id: statusIconsSection - Layout.fillWidth: true - Layout.preferredHeight: statusIconsSectionHeader.implicitHeight - property bool expanded: false - - ColumnLayout { - id: statusIconsSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small - - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Status Icons") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } - - MaterialIcon { - text: "expand_more" - rotation: statusIconsSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} - } - } - } - - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = statusIconsSection.expanded; - root.collapseAllSections(statusIconsSection); - statusIconsSection.expanded = !wasExpanded; - } - } + title: qsTr("Status Icons") + onToggleRequested: { + root.collapseAllSections(statusIconsSection); } - } - - StyledRect { - visible: statusIconsSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: statusIconsSection.expanded ? showAudioRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } - - RowLayout { - id: showAudioRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Show audio") - } - StyledSwitch { - checked: root.showAudio - onToggled: { - root.showAudio = checked; - root.saveConfig(); - } + SwitchRow { + label: qsTr("Show audio") + checked: root.showAudio + onToggled: checked => { + root.showAudio = checked; + root.saveConfig(); } } - } - - StyledRect { - visible: statusIconsSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: statusIconsSection.expanded ? showMicrophoneRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } - RowLayout { - id: showMicrophoneRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Show microphone") + SwitchRow { + label: qsTr("Show microphone") + checked: root.showMicrophone + onToggled: checked => { + root.showMicrophone = checked; + root.saveConfig(); } - - StyledSwitch { - checked: root.showMicrophone - onToggled: { - root.showMicrophone = checked; - root.saveConfig(); - } - } - } - } - - StyledRect { - visible: statusIconsSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: statusIconsSection.expanded ? showKbLayoutRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} } - RowLayout { - id: showKbLayoutRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Show keyboard layout") + SwitchRow { + label: qsTr("Show keyboard layout") + checked: root.showKbLayout + onToggled: checked => { + root.showKbLayout = checked; + root.saveConfig(); } + } - StyledSwitch { - checked: root.showKbLayout - onToggled: { - root.showKbLayout = checked; - root.saveConfig(); - } + SwitchRow { + label: qsTr("Show network") + checked: root.showNetwork + onToggled: checked => { + root.showNetwork = checked; + root.saveConfig(); } } - } - StyledRect { - visible: statusIconsSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: statusIconsSection.expanded ? showNetworkRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} + SwitchRow { + label: qsTr("Show bluetooth") + checked: root.showBluetooth + onToggled: checked => { + root.showBluetooth = checked; + root.saveConfig(); + } } - RowLayout { - id: showNetworkRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Show network") + SwitchRow { + label: qsTr("Show battery") + checked: root.showBattery + onToggled: checked => { + root.showBattery = checked; + root.saveConfig(); } + } - StyledSwitch { - checked: root.showNetwork - onToggled: { - root.showNetwork = checked; - root.saveConfig(); - } + SwitchRow { + label: qsTr("Show lock status") + checked: root.showLockStatus + onToggled: checked => { + root.showLockStatus = checked; + root.saveConfig(); } } } - StyledRect { - visible: statusIconsSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: statusIconsSection.expanded ? showBluetoothRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} + CollapsibleSection { + id: traySettingsSection + title: qsTr("Tray Settings") + onToggleRequested: { + root.collapseAllSections(traySettingsSection); } - RowLayout { - id: showBluetoothRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Show bluetooth") + SwitchRow { + label: qsTr("Background") + checked: root.trayBackground + onToggled: checked => { + root.trayBackground = checked; + root.saveConfig(); } - - StyledSwitch { - checked: root.showBluetooth - onToggled: { - root.showBluetooth = checked; - root.saveConfig(); - } - } - } - } - - StyledRect { - visible: statusIconsSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: statusIconsSection.expanded ? showBatteryRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} } - RowLayout { - id: showBatteryRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Show battery") + SwitchRow { + label: qsTr("Compact") + checked: root.trayCompact + onToggled: checked => { + root.trayCompact = checked; + root.saveConfig(); } + } - StyledSwitch { - checked: root.showBattery - onToggled: { - root.showBattery = checked; - root.saveConfig(); - } + SwitchRow { + label: qsTr("Recolour") + checked: root.trayRecolour + onToggled: checked => { + root.trayRecolour = checked; + root.saveConfig(); } } } - StyledRect { - visible: statusIconsSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: statusIconsSection.expanded ? showLockStatusRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} + CollapsibleSection { + id: workspacesSection + title: qsTr("Workspaces") + onToggleRequested: { + root.collapseAllSections(workspacesSection); } - RowLayout { - id: showLockStatusRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Show lock status") - } + StyledRect { + Layout.fillWidth: true + implicitHeight: workspacesShownRow.implicitHeight + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - StyledSwitch { - checked: root.showLockStatus - onToggled: { - root.showLockStatus = checked; - root.saveConfig(); - } + Behavior on implicitHeight { + Anim {} } - } - } - - Item { - id: traySettingsSection - Layout.fillWidth: true - Layout.preferredHeight: traySettingsSectionHeader.implicitHeight - property bool expanded: false - - ColumnLayout { - id: traySettingsSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + id: workspacesShownRow + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + spacing: Appearance.spacing.normal StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Tray Settings") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - Item { Layout.fillWidth: true + text: qsTr("Shown") } - MaterialIcon { - text: "expand_more" - rotation: traySettingsSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} + CustomSpinBox { + min: 1 + max: 20 + value: root.workspacesShown + onValueModified: value => { + root.workspacesShown = value; + root.saveConfig(); } } } - - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = traySettingsSection.expanded; - root.collapseAllSections(traySettingsSection); - traySettingsSection.expanded = !wasExpanded; - } - } } - } - - StyledRect { - visible: traySettingsSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: traySettingsSection.expanded ? trayBackgroundRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } - - RowLayout { - id: trayBackgroundRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - StyledText { - Layout.fillWidth: true - text: qsTr("Background") - } + StyledRect { + Layout.fillWidth: true + implicitHeight: workspacesActiveIndicatorRow.implicitHeight + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - StyledSwitch { - checked: root.trayBackground - onToggled: { - root.trayBackground = checked; - root.saveConfig(); - } + Behavior on implicitHeight { + Anim {} } - } - } - - StyledRect { - visible: traySettingsSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: traySettingsSection.expanded ? trayCompactRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - Behavior on implicitHeight { - Anim {} - } - - RowLayout { - id: trayCompactRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Compact") - } + RowLayout { + id: workspacesActiveIndicatorRow + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + spacing: Appearance.spacing.normal - StyledSwitch { - checked: root.trayCompact - onToggled: { - root.trayCompact = checked; - root.saveConfig(); + StyledText { + Layout.fillWidth: true + text: qsTr("Active indicator") } - } - } - } - - StyledRect { - visible: traySettingsSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: traySettingsSection.expanded ? trayRecolourRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } - RowLayout { - id: trayRecolourRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Recolour") - } - - StyledSwitch { - checked: root.trayRecolour - onToggled: { - root.trayRecolour = checked; - root.saveConfig(); + StyledSwitch { + checked: root.workspacesActiveIndicator + onToggled: { + root.workspacesActiveIndicator = checked; + root.saveConfig(); + } } } } - } - Item { - id: workspacesSection - Layout.fillWidth: true - Layout.preferredHeight: workspacesSectionHeader.implicitHeight - property bool expanded: false + StyledRect { + Layout.fillWidth: true + implicitHeight: workspacesOccupiedBgRow.implicitHeight + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - ColumnLayout { - id: workspacesSectionHeader - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small + Behavior on implicitHeight { + Anim {} + } RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + id: workspacesOccupiedBgRow + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + spacing: Appearance.spacing.normal StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Workspaces") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - Item { Layout.fillWidth: true + text: qsTr("Occupied background") } - MaterialIcon { - text: "expand_more" - rotation: workspacesSection.expanded ? 180 : 0 - color: Colours.palette.m3onSurface - Behavior on rotation { - Anim {} + StyledSwitch { + checked: root.workspacesOccupiedBg + onToggled: { + root.workspacesOccupiedBg = checked; + root.saveConfig(); } } } - - StateLayer { - anchors.fill: parent - anchors.leftMargin: -Appearance.padding.normal - anchors.rightMargin: -Appearance.padding.normal - function onClicked(): void { - const wasExpanded = workspacesSection.expanded; - root.collapseAllSections(workspacesSection); - workspacesSection.expanded = !wasExpanded; - } - } } - } - StyledRect { - visible: workspacesSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: workspacesSection.expanded ? workspacesShownRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } - - RowLayout { - id: workspacesShownRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Shown") - } + StyledRect { + Layout.fillWidth: true + implicitHeight: workspacesShowWindowsRow.implicitHeight + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - CustomSpinBox { - min: 1 - max: 20 - value: root.workspacesShown - onValueModified: value => { - root.workspacesShown = value; - root.saveConfig(); - } + Behavior on implicitHeight { + Anim {} } - } - } - StyledRect { - visible: workspacesSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: workspacesSection.expanded ? workspacesActiveIndicatorRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } - - RowLayout { - id: workspacesActiveIndicatorRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Active indicator") - } + RowLayout { + id: workspacesShowWindowsRow + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + spacing: Appearance.spacing.normal - StyledSwitch { - checked: root.workspacesActiveIndicator - onToggled: { - root.workspacesActiveIndicator = checked; - root.saveConfig(); + StyledText { + Layout.fillWidth: true + text: qsTr("Show windows") } - } - } - } - - StyledRect { - visible: workspacesSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: workspacesSection.expanded ? workspacesOccupiedBgRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } - - RowLayout { - id: workspacesOccupiedBgRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Occupied background") - } - StyledSwitch { - checked: root.workspacesOccupiedBg - onToggled: { - root.workspacesOccupiedBg = checked; - root.saveConfig(); + StyledSwitch { + checked: root.workspacesShowWindows + onToggled: { + root.workspacesShowWindows = checked; + root.saveConfig(); + } } } } - } - StyledRect { - visible: workspacesSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: workspacesSection.expanded ? workspacesShowWindowsRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } - - RowLayout { - id: workspacesShowWindowsRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: qsTr("Show windows") - } + StyledRect { + Layout.fillWidth: true + implicitHeight: workspacesPerMonitorRow.implicitHeight + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer - StyledSwitch { - checked: root.workspacesShowWindows - onToggled: { - root.workspacesShowWindows = checked; - root.saveConfig(); - } + Behavior on implicitHeight { + Anim {} } - } - } - StyledRect { - visible: workspacesSection.expanded - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.small / 2 - implicitHeight: workspacesSection.expanded ? workspacesPerMonitorRow.implicitHeight + Appearance.padding.large * 2 : 0 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - Behavior on implicitHeight { - Anim {} - } - - RowLayout { - id: workspacesPerMonitorRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - spacing: Appearance.spacing.normal + RowLayout { + id: workspacesPerMonitorRow + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + spacing: Appearance.spacing.normal - StyledText { - Layout.fillWidth: true - text: qsTr("Per monitor workspaces") - } + StyledText { + Layout.fillWidth: true + text: qsTr("Per monitor workspaces") + } - StyledSwitch { - checked: root.workspacesPerMonitor - onToggled: { - root.workspacesPerMonitor = checked; - root.saveConfig(); + StyledSwitch { + checked: root.workspacesPerMonitor + onToggled: { + root.workspacesPerMonitor = checked; + root.saveConfig(); + } } } } -- cgit v1.2.3-freya From 893a91a95a11cb57ffe2fbfa0ec221c1a309528b Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 12 Nov 2025 16:36:42 -0500 Subject: controlcenter: refactored wifi/ethernet panels --- components/ConnectionHeader.qml | 32 +++ components/ConnectionInfoSection.qml | 60 ++++ components/PropertyRow.qml | 27 ++ components/SectionContainer.qml | 31 +++ components/SectionHeader.qml | 28 ++ components/controls/ToggleRow.qml | 29 ++ modules/controlcenter/ethernet/EthernetDetails.qml | 238 +++------------- modules/controlcenter/network/Details.qml | 305 +++++---------------- plan.plan.md | 113 ++++++++ 9 files changed, 432 insertions(+), 431 deletions(-) create mode 100644 components/ConnectionHeader.qml create mode 100644 components/ConnectionInfoSection.qml create mode 100644 components/PropertyRow.qml create mode 100644 components/SectionContainer.qml create mode 100644 components/SectionHeader.qml create mode 100644 components/controls/ToggleRow.qml create mode 100644 plan.plan.md (limited to 'modules/controlcenter/network') diff --git a/components/ConnectionHeader.qml b/components/ConnectionHeader.qml new file mode 100644 index 0000000..3f77fd9 --- /dev/null +++ b/components/ConnectionHeader.qml @@ -0,0 +1,32 @@ +import qs.components +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property string icon + required property string title + + spacing: Appearance.spacing.normal + Layout.alignment: Qt.AlignHCenter + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + animate: true + text: root.icon + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + animate: true + text: root.title + font.pointSize: Appearance.font.size.large + font.bold: true + } +} + diff --git a/components/ConnectionInfoSection.qml b/components/ConnectionInfoSection.qml new file mode 100644 index 0000000..88c6b3a --- /dev/null +++ b/components/ConnectionInfoSection.qml @@ -0,0 +1,60 @@ +import qs.components +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property var deviceDetails + + spacing: Appearance.spacing.small / 2 + + StyledText { + text: qsTr("IP Address") + } + + StyledText { + text: root.deviceDetails?.ipAddress || qsTr("Not available") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Subnet Mask") + } + + StyledText { + text: root.deviceDetails?.subnet || qsTr("Not available") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Gateway") + } + + StyledText { + text: root.deviceDetails?.gateway || qsTr("Not available") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("DNS Servers") + } + + StyledText { + text: (root.deviceDetails && root.deviceDetails.dns && root.deviceDetails.dns.length > 0) ? root.deviceDetails.dns.join(", ") : qsTr("Not available") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + wrapMode: Text.Wrap + Layout.maximumWidth: parent.width + } +} + diff --git a/components/PropertyRow.qml b/components/PropertyRow.qml new file mode 100644 index 0000000..697830a --- /dev/null +++ b/components/PropertyRow.qml @@ -0,0 +1,27 @@ +import qs.components +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property string label + required property string value + property bool showTopMargin: false + + spacing: Appearance.spacing.small / 2 + + StyledText { + Layout.topMargin: root.showTopMargin ? Appearance.spacing.normal : 0 + text: root.label + } + + StyledText { + text: root.value + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } +} + diff --git a/components/SectionContainer.qml b/components/SectionContainer.qml new file mode 100644 index 0000000..d41254b --- /dev/null +++ b/components/SectionContainer.qml @@ -0,0 +1,31 @@ +import qs.components +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +StyledRect { + id: root + + default property alias content: contentColumn.data + property real contentSpacing: Appearance.spacing.larger + + Layout.fillWidth: true + implicitHeight: contentColumn.implicitHeight + Appearance.padding.large * 2 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + + ColumnLayout { + id: contentColumn + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + + spacing: root.contentSpacing + } +} + diff --git a/components/SectionHeader.qml b/components/SectionHeader.qml new file mode 100644 index 0000000..897e63a --- /dev/null +++ b/components/SectionHeader.qml @@ -0,0 +1,28 @@ +import qs.components +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property string title + property string description: "" + + spacing: 0 + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: root.title + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + visible: root.description !== "" + text: root.description + color: Colours.palette.m3outline + } +} + diff --git a/components/controls/ToggleRow.qml b/components/controls/ToggleRow.qml new file mode 100644 index 0000000..23dc2a2 --- /dev/null +++ b/components/controls/ToggleRow.qml @@ -0,0 +1,29 @@ +import qs.components +import qs.components.controls +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +RowLayout { + id: root + + required property string label + property alias checked: toggle.checked + property alias toggle: toggle + + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + StyledText { + Layout.fillWidth: true + text: root.label + } + + StyledSwitch { + id: toggle + + cLayer: 2 + } +} + diff --git a/modules/controlcenter/ethernet/EthernetDetails.qml b/modules/controlcenter/ethernet/EthernetDetails.qml index 1db3db0..a49eb4f 100644 --- a/modules/controlcenter/ethernet/EthernetDetails.qml +++ b/modules/controlcenter/ethernet/EthernetDetails.qml @@ -43,229 +43,73 @@ Item { anchors.right: parent.right spacing: Appearance.spacing.normal - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - animate: true - text: "cable" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true + ConnectionHeader { + icon: "cable" + title: root.device?.interface ?? qsTr("Unknown") } - StyledText { - Layout.alignment: Qt.AlignHCenter - animate: true - text: root.device?.interface ?? qsTr("Unknown") - font.pointSize: Appearance.font.size.large - font.bold: true + SectionHeader { + title: qsTr("Connection status") + description: qsTr("Connection settings for this device") } - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Connection status") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - StyledText { - text: qsTr("Connection settings for this device") - color: Colours.palette.m3outline - } - - StyledRect { - Layout.fillWidth: true - implicitHeight: deviceStatus.implicitHeight + Appearance.padding.large * 2 - - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - ColumnLayout { - id: deviceStatus - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - - spacing: Appearance.spacing.larger - - Toggle { - label: qsTr("Connected") - checked: root.device?.connected ?? false - toggle.onToggled: { - if (checked) { - // Use connection name if available, otherwise use interface - Network.connectEthernet(root.device?.connection || "", root.device?.interface || ""); - } else { - if (root.device?.connection) { - Network.disconnectEthernet(root.device.connection); - } + SectionContainer { + ToggleRow { + label: qsTr("Connected") + checked: root.device?.connected ?? false + toggle.onToggled: { + if (checked) { + // Use connection name if available, otherwise use interface + Network.connectEthernet(root.device?.connection || "", root.device?.interface || ""); + } else { + if (root.device?.connection) { + Network.disconnectEthernet(root.device.connection); } } } } } - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Device properties") - font.pointSize: Appearance.font.size.larger - font.weight: 500 + SectionHeader { + title: qsTr("Device properties") + description: qsTr("Additional information") } - StyledText { - text: qsTr("Additional information") - color: Colours.palette.m3outline - } - - StyledRect { - Layout.fillWidth: true - implicitHeight: deviceProps.implicitHeight + Appearance.padding.large * 2 - - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer + SectionContainer { + contentSpacing: Appearance.spacing.small / 2 - ColumnLayout { - id: deviceProps - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - - spacing: Appearance.spacing.small / 2 - - StyledText { - text: qsTr("Interface") - } - - StyledText { - text: root.device?.interface ?? qsTr("Unknown") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Connection") - } - - StyledText { - text: root.device?.connection || qsTr("Not connected") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("State") - } + PropertyRow { + label: qsTr("Interface") + value: root.device?.interface ?? qsTr("Unknown") + } - StyledText { - text: root.device?.state ?? qsTr("Unknown") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } + PropertyRow { + showTopMargin: true + label: qsTr("Connection") + value: root.device?.connection || qsTr("Not connected") } - } - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Connection information") - font.pointSize: Appearance.font.size.larger - font.weight: 500 + PropertyRow { + showTopMargin: true + label: qsTr("State") + value: root.device?.state ?? qsTr("Unknown") + } } - StyledText { - text: qsTr("Network connection details") - color: Colours.palette.m3outline + SectionHeader { + title: qsTr("Connection information") + description: qsTr("Network connection details") } - StyledRect { - Layout.fillWidth: true - implicitHeight: connectionInfo.implicitHeight + Appearance.padding.large * 2 - - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - ColumnLayout { - id: connectionInfo - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - - spacing: Appearance.spacing.small / 2 - - StyledText { - text: qsTr("IP Address") - } - - StyledText { - text: Network.ethernetDeviceDetails?.ipAddress || qsTr("Not available") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Subnet Mask") - } - - StyledText { - text: Network.ethernetDeviceDetails?.subnet || qsTr("Not available") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Gateway") - } - - StyledText { - text: Network.ethernetDeviceDetails?.gateway || qsTr("Not available") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("DNS Servers") - } - - StyledText { - text: (Network.ethernetDeviceDetails && Network.ethernetDeviceDetails.dns && Network.ethernetDeviceDetails.dns.length > 0) ? Network.ethernetDeviceDetails.dns.join(", ") : qsTr("Not available") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - wrapMode: Text.Wrap - Layout.maximumWidth: parent.width - } + SectionContainer { + ConnectionInfoSection { + deviceDetails: Network.ethernetDeviceDetails } } } } - component Toggle: RowLayout { - required property string label - property alias checked: toggle.checked - property alias toggle: toggle - - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: parent.label - } - - StyledSwitch { - id: toggle - - cLayer: 2 - } - } } diff --git a/modules/controlcenter/network/Details.qml b/modules/controlcenter/network/Details.qml index 31d20bc..a53f62e 100644 --- a/modules/controlcenter/network/Details.qml +++ b/modules/controlcenter/network/Details.qml @@ -54,272 +54,109 @@ Item { anchors.right: parent.right spacing: Appearance.spacing.normal - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - animate: true - text: root.network?.isSecure ? "lock" : "wifi" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true + ConnectionHeader { + icon: root.network?.isSecure ? "lock" : "wifi" + title: root.network?.ssid ?? qsTr("Unknown") } - StyledText { - Layout.alignment: Qt.AlignHCenter - animate: true - text: root.network?.ssid ?? qsTr("Unknown") - font.pointSize: Appearance.font.size.large - font.bold: true + SectionHeader { + title: qsTr("Connection status") + description: qsTr("Connection settings for this network") } - 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 network") - color: Colours.palette.m3outline - } - - StyledRect { - Layout.fillWidth: true - implicitHeight: networkStatus.implicitHeight + Appearance.padding.large * 2 - - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - ColumnLayout { - id: networkStatus - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - - spacing: Appearance.spacing.larger - - Toggle { - label: qsTr("Connected") - checked: root.network?.active ?? false - toggle.onToggled: { - if (checked) { - // If already connected to a different network, disconnect first - if (Network.active && Network.active.ssid !== root.network.ssid) { - Network.disconnectFromNetwork(); - // Wait a moment before connecting to new network - Qt.callLater(() => { - connectToNetwork(); - }); - } else { + SectionContainer { + ToggleRow { + label: qsTr("Connected") + checked: root.network?.active ?? false + toggle.onToggled: { + if (checked) { + // If already connected to a different network, disconnect first + if (Network.active && Network.active.ssid !== root.network.ssid) { + Network.disconnectFromNetwork(); + // Wait a moment before connecting to new network + Qt.callLater(() => { connectToNetwork(); - } + }); } else { - Network.disconnectFromNetwork(); + connectToNetwork(); } + } else { + Network.disconnectFromNetwork(); } + } - function connectToNetwork(): void { - if (root.network.isSecure) { - // Try connecting without password first (in case it's saved) - Network.connectToNetworkWithPasswordCheck( - root.network.ssid, - root.network.isSecure, - () => { - // Callback: connection failed, show password dialog - root.session.network.showPasswordDialog = true; - root.session.network.pendingNetwork = root.network; - } - ); - } else { - Network.connectToNetwork(root.network.ssid, ""); - } + function connectToNetwork(): void { + if (root.network.isSecure) { + // Try connecting without password first (in case it's saved) + Network.connectToNetworkWithPasswordCheck( + root.network.ssid, + root.network.isSecure, + () => { + // Callback: connection failed, show password dialog + root.session.network.showPasswordDialog = true; + root.session.network.pendingNetwork = root.network; + } + ); + } else { + Network.connectToNetwork(root.network.ssid, ""); } } } } - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Network properties") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - StyledText { - text: qsTr("Additional information") - color: Colours.palette.m3outline + SectionHeader { + title: qsTr("Network properties") + description: qsTr("Additional information") } - StyledRect { - Layout.fillWidth: true - implicitHeight: networkProps.implicitHeight + Appearance.padding.large * 2 + SectionContainer { + contentSpacing: Appearance.spacing.small / 2 - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - ColumnLayout { - id: networkProps - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - - spacing: Appearance.spacing.small / 2 - - StyledText { - text: qsTr("SSID") - } - - StyledText { - text: root.network?.ssid ?? qsTr("Unknown") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("BSSID") - } - - StyledText { - text: root.network?.bssid ?? qsTr("Unknown") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Signal strength") - } - - StyledText { - text: root.network ? qsTr("%1%").arg(root.network.strength) : qsTr("N/A") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Frequency") - } + PropertyRow { + label: qsTr("SSID") + value: root.network?.ssid ?? qsTr("Unknown") + } - StyledText { - text: root.network ? qsTr("%1 MHz").arg(root.network.frequency) : qsTr("N/A") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } + PropertyRow { + showTopMargin: true + label: qsTr("BSSID") + value: root.network?.bssid ?? qsTr("Unknown") + } - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Security") - } + PropertyRow { + showTopMargin: true + label: qsTr("Signal strength") + value: root.network ? qsTr("%1%").arg(root.network.strength) : qsTr("N/A") + } - StyledText { - text: root.network ? (root.network.isSecure ? root.network.security : qsTr("Open")) : qsTr("N/A") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } + PropertyRow { + showTopMargin: true + label: qsTr("Frequency") + value: root.network ? qsTr("%1 MHz").arg(root.network.frequency) : qsTr("N/A") } - } - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Connection information") - font.pointSize: Appearance.font.size.larger - font.weight: 500 + PropertyRow { + showTopMargin: true + label: qsTr("Security") + value: root.network ? (root.network.isSecure ? root.network.security : qsTr("Open")) : qsTr("N/A") + } } - StyledText { - text: qsTr("Network connection details") - color: Colours.palette.m3outline + SectionHeader { + title: qsTr("Connection information") + description: qsTr("Network connection details") } - StyledRect { - Layout.fillWidth: true - implicitHeight: connectionInfo.implicitHeight + Appearance.padding.large * 2 - - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - ColumnLayout { - id: connectionInfo - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - - spacing: Appearance.spacing.small / 2 - - StyledText { - text: qsTr("IP Address") - } - - StyledText { - text: Network.wirelessDeviceDetails?.ipAddress || qsTr("Not available") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Subnet Mask") - } - - StyledText { - text: Network.wirelessDeviceDetails?.subnet || qsTr("Not available") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Gateway") - } - - StyledText { - text: Network.wirelessDeviceDetails?.gateway || qsTr("Not available") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("DNS Servers") - } - - StyledText { - text: (Network.wirelessDeviceDetails && Network.wirelessDeviceDetails.dns && Network.wirelessDeviceDetails.dns.length > 0) ? Network.wirelessDeviceDetails.dns.join(", ") : qsTr("Not available") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - wrapMode: Text.Wrap - Layout.maximumWidth: parent.width - } + SectionContainer { + ConnectionInfoSection { + deviceDetails: Network.wirelessDeviceDetails } } } } - component Toggle: RowLayout { - required property string label - property alias checked: toggle.checked - property alias toggle: toggle - - Layout.fillWidth: true - spacing: Appearance.spacing.normal +} - StyledText { - Layout.fillWidth: true - text: parent.label - } - StyledSwitch { - id: toggle - cLayer: 2 - } - } -} diff --git a/plan.plan.md b/plan.plan.md new file mode 100644 index 0000000..5b6f1f2 --- /dev/null +++ b/plan.plan.md @@ -0,0 +1,113 @@ +# Refactoring Plan: Control Center Panes + +## Overview + +After analyzing the last 30 commits, I've identified significant code duplication and opportunities for modularization in the control center panels. This plan focuses on extracting common patterns into reusable components. + +## Key Refactoring Opportunities + +### 1. Details Component Consolidation + +**Files affected:** `modules/controlcenter/network/Details.qml`, `modules/controlcenter/ethernet/EthernetDetails.qml` + +**Issue:** Both files share identical structure: + +- Header with icon and title +- Connection status section +- Properties section +- Connection information section (IP, subnet, gateway, DNS) + +**Solution:** Create `components/ConnectionDetails.qml` that accepts: + +- Device/network object +- Icon name +- Title property path +- Details source (wirelessDeviceDetails vs ethernetDeviceDetails) + +**Impact:** Reduces ~200 lines of duplication. + +### 2. ToggleButton Component Extraction + +**Files affected:** `modules/controlcenter/network/NetworkList.qml`, `modules/controlcenter/ethernet/EthernetList.qml` + +**Issue:** Both files define identical `ToggleButton` component (lines 228-301 in NetworkList, 170-243 in EthernetList). + +**Solution:** Move to `components/controls/ToggleButton.qml` and import in both files. + +**Impact:** Eliminates ~70 lines of duplication. + +### 3. Switch/SpinBox Row Components + +**Files affected:** `modules/controlcenter/appearance/AppearancePane.qml`, `modules/controlcenter/taskbar/TaskbarPane.qml` + +**Issue:** Repeated patterns for: + +- Switch rows (label + StyledSwitch) +- SpinBox rows (label + CustomSpinBox) +- Same layout, spacing, and styling + +**Solution:** Create: + +- `components/controls/SwitchRow.qml` - label + switch with config save callback +- `components/controls/SpinBoxRow.qml` - label + spinbox with config save callback + +**Impact:** Reduces ~30-40 lines per row instance (20+ instances total). + +### 4. Font List Delegate Consolidation + +**Files affected:** `modules/controlcenter/appearance/AppearancePane.qml` + +**Issue:** Three nearly identical font list implementations (Material, Mono, Sans) with only the property binding differing. + +**Solution:** Create `components/FontList.qml` that accepts: + +- Current font property +- Save callback function +- Title text + +**Impact:** Reduces ~150 lines of duplication. + +### 5. List Item Selection Pattern + +**Files affected:** Multiple list delegates across panes + +**Issue:** Repeated pattern for selected item highlighting: + +- Color with alpha based on selection +- Border width/color based on selection +- StateLayer click handler + +**Solution:** Create `components/SelectableListItem.qml` wrapper that handles selection styling. + +**Impact:** Reduces ~10-15 lines per list delegate. + +## Implementation Order + +1. **ConnectionDetails consolidation** (medium impact) +2. **FontList consolidation** (low-medium impact) +3. **SelectableListItem pattern** (nice-to-have, lower priority) + +## Files to Create + +- `components/controls/SelectableListItem.qml` +- `components/ConnectionDetails.qml` +- `components/FontList.qml` + +## Completed Items + +- ✅ `components/controls/CollapsibleSection.qml` - DONE +- ✅ `components/controls/SwitchRow.qml` - DONE +- ✅ `components/controls/SpinBoxRow.qml` - DONE +- ✅ `components/controls/ToggleButton.qml` - DONE + +## Estimated Impact + +- **Lines removed:** ~400-500 lines of duplicated code (from remaining items) +- **Maintainability:** Significantly improved - changes to common patterns only need to be made once +- **Readability:** Panes become more declarative and easier to understand +- **Testability:** Reusable components can be tested independently + +## Completed Refactoring + +- **Lines removed so far:** ~1300+ lines of duplicated code +- **Components created:** CollapsibleSection, SwitchRow, SpinBoxRow, ToggleButton \ No newline at end of file -- cgit v1.2.3-freya From 7e6f3270911d9a3b7a73532b18670a5fa613ee92 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 12 Nov 2025 19:57:42 -0500 Subject: conrolcenter: debug/rewrite of wireless panel --- modules/bar/popouts/Network.qml | 2 +- modules/controlcenter/network/Details.qml | 55 +++- modules/controlcenter/network/NetworkList.qml | 5 +- modules/controlcenter/network/PasswordDialog.qml | 283 +++++++++++++++++++- services/Network.qml | 316 ++++++++++++++++++++++- 5 files changed, 637 insertions(+), 24 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/bar/popouts/Network.qml b/modules/bar/popouts/Network.qml index f21a92d..f040b6a 100644 --- a/modules/bar/popouts/Network.qml +++ b/modules/bar/popouts/Network.qml @@ -118,7 +118,7 @@ ColumnLayout { Network.disconnectFromNetwork(); } else { root.connectingToSsid = networkItem.modelData.ssid; - Network.connectToNetwork(networkItem.modelData.ssid, ""); + Network.connectToNetwork(networkItem.modelData.ssid, "", networkItem.modelData.bssid, null); } } } diff --git a/modules/controlcenter/network/Details.qml b/modules/controlcenter/network/Details.qml index a53f62e..5e636a2 100644 --- a/modules/controlcenter/network/Details.qml +++ b/modules/controlcenter/network/Details.qml @@ -95,10 +95,31 @@ Item { // Callback: connection failed, show password dialog root.session.network.showPasswordDialog = true; root.session.network.pendingNetwork = root.network; - } + }, + root.network.bssid ); } else { - Network.connectToNetwork(root.network.ssid, ""); + Network.connectToNetwork(root.network.ssid, "", root.network.bssid, null); + } + } + } + + Button { + Layout.fillWidth: true + Layout.topMargin: Appearance.spacing.normal + visible: root.network && root.network.ssid && Network.savedConnections.includes(root.network.ssid) + color: Colours.palette.m3errorContainer + onColor: Colours.palette.m3onErrorContainer + text: qsTr("Forget Network") + + onClicked: { + if (root.network && root.network.ssid) { + // Disconnect first if connected + if (root.network.active) { + Network.disconnectFromNetwork(); + } + // Delete the connection profile + Network.forgetNetwork(root.network.ssid); } } } @@ -152,9 +173,39 @@ Item { deviceDetails: Network.wirelessDeviceDetails } } + } } + component Button: StyledRect { + property color onColor: Colours.palette.m3onSurface + property alias disabled: stateLayer.disabled + property alias text: label.text + property alias enabled: stateLayer.enabled + + Layout.fillWidth: true + implicitHeight: label.implicitHeight + Appearance.padding.normal * 2 + radius: Appearance.rounding.normal + + StateLayer { + id: stateLayer + color: parent.onColor + function onClicked(): void { + if (parent.enabled !== false) { + parent.clicked(); + } + } + } + + StyledText { + id: label + anchors.centerIn: parent + color: parent.onColor + } + + signal clicked + } + } diff --git a/modules/controlcenter/network/NetworkList.qml b/modules/controlcenter/network/NetworkList.qml index 09d7352..6c4158c 100644 --- a/modules/controlcenter/network/NetworkList.qml +++ b/modules/controlcenter/network/NetworkList.qml @@ -203,10 +203,11 @@ ColumnLayout { // Callback: connection failed, show password dialog root.session.network.showPasswordDialog = true; root.session.network.pendingNetwork = modelData; - } + }, + modelData.bssid ); } else { - Network.connectToNetwork(modelData.ssid, ""); + Network.connectToNetwork(modelData.ssid, "", modelData.bssid, null); } } } diff --git a/modules/controlcenter/network/PasswordDialog.qml b/modules/controlcenter/network/PasswordDialog.qml index fa4788c..7aa698b 100644 --- a/modules/controlcenter/network/PasswordDialog.qml +++ b/modules/controlcenter/network/PasswordDialog.qml @@ -14,11 +14,37 @@ Item { id: root required property Session session - readonly property var network: session.network.pendingNetwork + readonly property var network: { + // Try pendingNetwork first, then fall back to active network selection + if (session.network.pendingNetwork) { + return session.network.pendingNetwork; + } + // Fallback to active network if available + if (session.network.active) { + return session.network.active; + } + return null; + } visible: session.network.showPasswordDialog enabled: visible focus: visible + + // Ensure network is set when dialog opens + Component.onCompleted: { + if (visible && !session.network.pendingNetwork && session.network.active) { + session.network.pendingNetwork = session.network.active; + } + } + + Connections { + target: root + function onVisibleChanged(): void { + if (visible && !session.network.pendingNetwork && session.network.active) { + session.network.pendingNetwork = session.network.active; + } + } + } Keys.onEscapePressed: { root.session.network.showPasswordDialog = false; @@ -105,6 +131,35 @@ Item { font.pointSize: Appearance.font.size.small } + StyledText { + id: statusText + + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: Appearance.spacing.small + visible: Network.connectionStatus.length > 0 || connectButton.connecting + text: { + if (Network.connectionStatus.length > 0) { + return Network.connectionStatus; + } else if (connectButton.connecting) { + return qsTr("Connecting..."); + } + return ""; + } + color: { + const status = Network.connectionStatus; + if (status.includes("Error") || status.includes("error") || status.includes("failed")) { + return Colours.palette.m3error; + } else if (status.includes("successful") || status.includes("Connected") || status.includes("success")) { + return Colours.palette.m3primary; + } + return Colours.palette.m3onSurfaceVariant; + } + font.pointSize: Appearance.font.size.small + font.weight: (Network.connectionStatus.includes("Error") || Network.connectionStatus.includes("error")) ? 500 : 400 + wrapMode: Text.WordWrap + Layout.maximumWidth: parent.width - Appearance.padding.large * 2 + } + Item { Layout.topMargin: Appearance.spacing.large Layout.fillWidth: true @@ -145,9 +200,17 @@ Item { if (root.visible) { passwordField.forceActiveFocus(); passwordField.text = ""; + Network.connectionStatus = ""; } } } + + Connections { + target: Network + function onConnectionStatusChanged(): void { + // Status updated, ensure it's visible + } + } Keys.onReturnPressed: { if (connectButton.enabled) { @@ -190,12 +253,222 @@ Item { text: qsTr("Connect") enabled: passwordField.text.length > 0 + property bool connecting: false + function onClicked(): void { - if (root.network && passwordField.text.length > 0) { - Network.connectToNetwork(root.network.ssid, passwordField.text); - root.session.network.showPasswordDialog = false; - passwordField.text = ""; + Network.connectionStatus = ""; + + // Get password first + const password = passwordField.text; + + // Try multiple ways to get the network + let networkToUse = null; + + // Try 1: root.network (computed property) + if (root.network) { + networkToUse = root.network; + } + + // Try 2: pendingNetwork + if (!networkToUse && root.session.network.pendingNetwork) { + networkToUse = root.session.network.pendingNetwork; + } + + // Try 3: active network + if (!networkToUse && root.session.network.active) { + networkToUse = root.session.network.active; + root.session.network.pendingNetwork = networkToUse; + } + + // Check all conditions + const hasNetwork = !!networkToUse; + const hasPassword = password && password.length > 0; + const notConnecting = !connecting; + + if (hasNetwork && hasPassword && notConnecting) { + // Set status immediately + Network.connectionStatus = qsTr("Preparing to connect..."); + + // Keep dialog open and track connection + connecting = true; + connectButton.enabled = false; + connectButton.text = qsTr("Connecting..."); + + // Force immediate UI update + statusText.visible = true; + + // Store target SSID for later comparison + const ssidToConnect = networkToUse.ssid || ""; + const bssidToConnect = networkToUse.bssid || ""; + + // Store the SSID we're connecting to so we can compare later + // even if root.network changes + content.connectingToSsid = ssidToConnect; + + // Execute connection immediately + Network.connectToNetwork( + ssidToConnect, + password, + bssidToConnect, + () => { + // Callback if connection fails - keep dialog open + connecting = false; + connectButton.enabled = true; + connectButton.text = qsTr("Connect"); + content.connectingToSsid = ""; // Clear on failure + } + ); + + // Start connection check timer immediately + connectionCheckTimer.checkCount = 0; + connectionCheckTimer.start(); + + // Also check immediately after a short delay to catch quick connections + Qt.callLater(() => { + if (root.visible) { + closeDialogIfConnected(); + } + }); + } else { + // Show error in status + Network.connectionStatus = qsTr("Error: Cannot connect - missing network or password"); + } + } + } + } + + // Store the SSID we're connecting to when connection starts + property string connectingToSsid: "" + + property string targetSsid: { + // Track the SSID we're trying to connect to + // Prefer explicitly stored connectingToSsid, then computed values + if (connectingToSsid && connectingToSsid.length > 0) { + return connectingToSsid; + } + if (root.network && root.network.ssid) { + return root.network.ssid; + } + if (root.session.network.pendingNetwork && root.session.network.pendingNetwork.ssid) { + return root.session.network.pendingNetwork.ssid; + } + return ""; + } + + function closeDialogIfConnected(): bool { + // Check if we're connected to the network we're trying to connect to + const ssid = targetSsid; + + if (!ssid || ssid.length === 0) { + return false; + } + + if (!Network.active) { + return false; + } + + const activeSsid = Network.active.ssid || ""; + + if (activeSsid === ssid) { + // Connection succeeded - close dialog + connectionCheckTimer.stop(); + aggressiveCheckTimer.stop(); + connectionCheckTimer.checkCount = 0; + connectButton.connecting = false; + Network.connectionStatus = ""; + root.session.network.showPasswordDialog = false; + passwordField.text = ""; + content.connectingToSsid = ""; // Clear stored SSID + return true; + } + return false; + } + + Timer { + id: connectionCheckTimer + interval: 1000 // Check every 1 second for faster response + repeat: true + triggeredOnStart: false + property int checkCount: 0 + + onTriggered: { + checkCount++; + + // Try to close dialog if connected + const closed = content.closeDialogIfConnected(); + if (closed) { + return; + } + + if (connectButton.connecting) { + // Still connecting, check again + // Limit to 20 checks (20 seconds total) + if (checkCount >= 20) { + connectionCheckTimer.stop(); + connectionCheckTimer.checkCount = 0; + connectButton.connecting = false; + connectButton.enabled = true; + connectButton.text = qsTr("Connect"); + } + } else { + // Not connecting anymore, stop timer + connectionCheckTimer.stop(); + connectionCheckTimer.checkCount = 0; + } + } + } + + Connections { + target: Network + function onActiveChanged(): void { + // Check immediately when active network changes + if (root.visible) { + // Check immediately - if connected, close right away + if (content.closeDialogIfConnected()) { + return; + } + + // Also check after a delay in case the active network isn't fully updated yet + Qt.callLater(() => { + if (root.visible) { + content.closeDialogIfConnected(); + } + }); + } + } + } + + // Also check when dialog becomes visible + Connections { + target: root + function onVisibleChanged(): void { + if (root.visible) { + // Check immediately when dialog opens + Qt.callLater(() => { + if (root.visible) { + closeDialogIfConnected(); + } + }); + } + } + } + + // Aggressive polling timer - checks every 500ms when dialog is visible and connecting + // This ensures we catch the connection even if signals are missed + Timer { + id: aggressiveCheckTimer + interval: 500 + repeat: true + running: root.visible && connectButton.connecting + triggeredOnStart: true + + onTriggered: { + if (root.visible && connectButton.connecting) { + if (content.closeDialogIfConnected()) { + stop(); } + } else { + stop(); } } } diff --git a/services/Network.qml b/services/Network.qml index c8ab264..ea5c3e7 100644 --- a/services/Network.qml +++ b/services/Network.qml @@ -12,6 +12,8 @@ Singleton { Qt.callLater(() => { getEthernetDevices(); }); + // Load saved connections on startup + listConnectionsProc.running = true; } readonly property list networks: [] @@ -26,6 +28,34 @@ Singleton { property bool ethernetProcessRunning: false property var ethernetDeviceDetails: null property var wirelessDeviceDetails: null + property string connectionStatus: "" + property string connectionDebug: "" + + function clearConnectionStatus(): void { + connectionStatus = ""; + // Don't clear debug - keep it for reference + // connectionDebug = ""; + } + + function setConnectionStatus(status: string): void { + connectionStatus = status; + } + + function addDebugInfo(info: string): void { + const timestamp = new Date().toLocaleTimeString(); + const newInfo = "[" + timestamp + "] " + info; + // CRITICAL: Always append - NEVER replace + // Get current value - NEVER allow it to be empty/cleared + let current = connectionDebug; + if (!current || current === undefined || current === null) { + current = ""; + } + // ALWAYS append - never replace + // If current is empty, just use newInfo, otherwise append with newline + const updated = (current.length > 0) ? (current + "\n" + newInfo) : newInfo; + // CRITICAL: Only assign if we're appending, never replace + connectionDebug = updated; + } function enableWifi(enabled: bool): void { const cmd = enabled ? "on" : "off"; @@ -44,30 +74,118 @@ Singleton { property var pendingConnection: null signal connectionFailed(string ssid) - function connectToNetwork(ssid: string, password: string): void { - // First try to connect to an existing connection - // If that fails, create a new connection + function connectToNetwork(ssid: string, password: string, bssid: string, callback: var): void { + // When password is provided, use BSSID for more reliable connection + // When no password, use SSID (will use saved password if available) + const hasBssid = bssid !== undefined && bssid !== null && bssid.length > 0; + let cmd = []; + + // Set up pending connection tracking if callback provided + if (callback) { + root.pendingConnection = { ssid: ssid, bssid: hasBssid ? bssid : "", callback: callback }; + } + if (password && password.length > 0) { - connectProc.exec(["nmcli", "device", "wifi", "connect", ssid, "password", password]); + // When password is provided, try BSSID first if available, otherwise use SSID + if (hasBssid) { + // Use BSSID when password is provided - ensure BSSID is uppercase + const bssidUpper = bssid.toUpperCase(); + // Create connection profile with all required properties for BSSID + password + // First remove any existing connection with this name + cmd = ["nmcli", "connection", "add", + "type", "wifi", + "con-name", ssid, + "ifname", "*", + "ssid", ssid, + "802-11-wireless.bssid", bssidUpper, + "802-11-wireless-security.key-mgmt", "wpa-psk", + "802-11-wireless-security.psk", password]; + root.setConnectionStatus(qsTr("Connecting to %1 (BSSID: %2)...").arg(ssid).arg(bssidUpper)); + root.addDebugInfo(qsTr("Using BSSID: %1 for SSID: %2").arg(bssidUpper).arg(ssid)); + root.addDebugInfo(qsTr("Creating connection profile with password and key-mgmt")); + } else { + // Fallback to SSID if BSSID not available - use device wifi connect + cmd = ["nmcli", "device", "wifi", "connect", ssid, "password", password]; + root.setConnectionStatus(qsTr("Connecting to %1...").arg(ssid)); + root.addDebugInfo(qsTr("Using SSID only (no BSSID): %1").arg(ssid)); + } } else { // Try to connect to existing connection first (will use saved password if available) - connectProc.exec(["nmcli", "device", "wifi", "connect", ssid]); + cmd = ["nmcli", "device", "wifi", "connect", ssid]; + root.setConnectionStatus(qsTr("Connecting to %1 (using saved password)...").arg(ssid)); + root.addDebugInfo(qsTr("Using saved password for: %1").arg(ssid)); + } + + // Show the exact command being executed + const cmdStr = cmd.join(" "); + root.addDebugInfo(qsTr("=== COMMAND TO EXECUTE ===")); + root.addDebugInfo(qsTr("Command: %1").arg(cmdStr)); + root.addDebugInfo(qsTr("Command array: [%1]").arg(cmd.map((arg, i) => `"${arg}"`).join(", "))); + root.addDebugInfo(qsTr("Command array length: %1").arg(cmd.length)); + root.addDebugInfo(qsTr("===========================")); + + // Set command and start process + root.addDebugInfo(qsTr("Setting command property...")); + connectProc.command = cmd; + const setCmdStr = connectProc.command ? connectProc.command.join(" ") : "null"; + root.addDebugInfo(qsTr("Command property set, value: %1").arg(setCmdStr)); + root.addDebugInfo(qsTr("Command property verified: %1").arg(setCmdStr === cmdStr ? "Match" : "MISMATCH")); + + // If we're creating a connection profile, we need to activate it after creation + const isConnectionAdd = cmd.length > 0 && cmd[0] === "nmcli" && cmd[1] === "connection" && cmd[2] === "add"; + + // Wait a moment before starting to ensure command is set + Qt.callLater(() => { + root.addDebugInfo(qsTr("=== STARTING PROCESS ===")); + root.addDebugInfo(qsTr("Current running state: %1").arg(connectProc.running)); + root.addDebugInfo(qsTr("Command to run: %1").arg(connectProc.command ? connectProc.command.join(" ") : "NOT SET")); + root.addDebugInfo(qsTr("Is connection add command: %1").arg(isConnectionAdd)); + connectProc.running = true; + root.addDebugInfo(qsTr("Process running set to: %1").arg(connectProc.running)); + root.addDebugInfo(qsTr("========================")); + + // Check if process actually started after a short delay + Qt.callLater(() => { + root.addDebugInfo(qsTr("Process status check (100ms later):")); + root.addDebugInfo(qsTr(" Running: %1").arg(connectProc.running)); + root.addDebugInfo(qsTr(" Command: %1").arg(connectProc.command ? connectProc.command.join(" ") : "null")); + if (!connectProc.running) { + root.addDebugInfo(qsTr("WARNING: Process did not start!")); + root.setConnectionStatus(qsTr("Error: Process failed to start")); + } + }, 100); + }); + + // Start connection check timer if we have a callback + if (callback) { + root.addDebugInfo(qsTr("Starting connection check timer (4 second interval)")); + connectionCheckTimer.start(); + } else { + root.addDebugInfo(qsTr("No callback provided - not starting connection check timer")); } } - function connectToNetworkWithPasswordCheck(ssid: string, isSecure: bool, callback: var): void { + function connectToNetworkWithPasswordCheck(ssid: string, isSecure: bool, callback: var, bssid: string): void { + root.addDebugInfo(qsTr("=== connectToNetworkWithPasswordCheck ===")); + root.addDebugInfo(qsTr("SSID: %1, isSecure: %2").arg(ssid).arg(isSecure)); + // For secure networks, try connecting without password first // If connection succeeds (saved password exists), we're done // If it fails with password error, callback will be called to show password dialog if (isSecure) { - root.pendingConnection = { ssid: ssid, callback: callback }; + const hasBssid = bssid !== undefined && bssid !== null && bssid.length > 0; + root.pendingConnection = { ssid: ssid, bssid: hasBssid ? bssid : "", callback: callback }; + root.addDebugInfo(qsTr("Trying to connect without password (will use saved if available)")); // Try connecting without password - will use saved password if available connectProc.exec(["nmcli", "device", "wifi", "connect", ssid]); // Start timer to check if connection succeeded + root.addDebugInfo(qsTr("Starting connection check timer")); connectionCheckTimer.start(); } else { - connectToNetwork(ssid, ""); + root.addDebugInfo(qsTr("Network is not secure, connecting directly")); + connectToNetwork(ssid, "", bssid, null); } + root.addDebugInfo(qsTr("=========================================")); } function disconnectFromNetwork(): void { @@ -80,6 +198,50 @@ Singleton { disconnectProc.exec(["nmcli", "device", "disconnect", "wifi"]); } } + + function forgetNetwork(ssid: string): void { + // Delete the connection profile for this network + // This will remove the saved password and connection settings + if (ssid && ssid.length > 0) { + deleteConnectionProc.exec(["nmcli", "connection", "delete", ssid]); + // Also refresh network list after deletion + Qt.callLater(() => { + getNetworks.running = true; + }, 500); + } + } + + function hasConnectionProfile(ssid: string): bool { + // Check if a connection profile exists for this SSID + // This is synchronous check - returns true if connection exists + if (!ssid || ssid.length === 0) { + return false; + } + // Use nmcli to check if connection exists + // We'll use a Process to check, but for now return false + // The actual check will be done asynchronously + return false; + } + + property list savedConnections: [] + + Process { + id: listConnectionsProc + command: ["nmcli", "-t", "-f", "NAME", "connection", "show"] + onExited: { + if (exitCode === 0) { + // Parse connection names from output + const connections = stdout.text.trim().split("\n").filter(name => name.length > 0); + root.savedConnections = connections; + } + } + stdout: StdioCollector { + onStreamFinished: { + const connections = text.trim().split("\n").filter(name => name.length > 0); + root.savedConnections = connections; + } + } + } function getWifiStatus(): void { wifiStatusProc.running = true; @@ -184,11 +346,17 @@ Singleton { id: connectionCheckTimer interval: 4000 onTriggered: { + root.addDebugInfo(qsTr("=== CONNECTION CHECK TIMER (4s) ===")); if (root.pendingConnection) { - // Final check - if connection still hasn't succeeded, show password dialog const connected = root.active && root.active.ssid === root.pendingConnection.ssid; + root.addDebugInfo(qsTr("Checking connection status...")); + root.addDebugInfo(qsTr(" Pending SSID: %1").arg(root.pendingConnection.ssid)); + root.addDebugInfo(qsTr(" Active SSID: %1").arg(root.active ? root.active.ssid : "None")); + root.addDebugInfo(qsTr(" Connected: %1").arg(connected)); + if (!connected && root.pendingConnection.callback) { // Connection didn't succeed after multiple checks, show password dialog + root.addDebugInfo(qsTr("Connection failed - calling password dialog callback")); const pending = root.pendingConnection; root.pendingConnection = null; immediateCheckTimer.stop(); @@ -196,11 +364,19 @@ Singleton { pending.callback(); } else if (connected) { // Connection succeeded, clear pending + root.addDebugInfo(qsTr("Connection succeeded!")); + root.setConnectionStatus(qsTr("Connected successfully!")); root.pendingConnection = null; immediateCheckTimer.stop(); immediateCheckTimer.checkCount = 0; + } else { + root.addDebugInfo(qsTr("Still connecting...")); + root.setConnectionStatus(qsTr("Still connecting...")); } + } else { + root.addDebugInfo(qsTr("No pending connection")); } + root.addDebugInfo(qsTr("================================")); } } @@ -210,23 +386,37 @@ Singleton { repeat: true triggeredOnStart: false property int checkCount: 0 + + onRunningChanged: { + if (running) { + root.addDebugInfo(qsTr("Immediate check timer started (checks every 500ms)")); + } + } + onTriggered: { if (root.pendingConnection) { checkCount++; const connected = root.active && root.active.ssid === root.pendingConnection.ssid; + root.addDebugInfo(qsTr("Immediate check #%1: Connected=%2").arg(checkCount).arg(connected)); + if (connected) { // Connection succeeded, stop timers and clear pending + root.addDebugInfo(qsTr("Connection succeeded on check #%1!").arg(checkCount)); + root.setConnectionStatus(qsTr("Connected successfully!")); connectionCheckTimer.stop(); immediateCheckTimer.stop(); immediateCheckTimer.checkCount = 0; root.pendingConnection = null; } else if (checkCount >= 6) { + root.addDebugInfo(qsTr("Checked %1 times (3 seconds) - connection taking longer").arg(checkCount)); + root.setConnectionStatus(qsTr("Connection taking longer than expected...")); // Checked 6 times (3 seconds total), connection likely failed // Stop immediate check, let the main timer handle it immediateCheckTimer.stop(); immediateCheckTimer.checkCount = 0; } } else { + root.addDebugInfo(qsTr("Immediate check: No pending connection, stopping timer")); immediateCheckTimer.stop(); immediateCheckTimer.checkCount = 0; } @@ -236,22 +426,92 @@ Singleton { Process { id: connectProc + onRunningChanged: { + root.addDebugInfo(qsTr("Process running changed to: %1").arg(running)); + } + + onStarted: { + root.addDebugInfo(qsTr("Process started successfully")); + } + onExited: { + root.addDebugInfo(qsTr("=== PROCESS EXITED ===")); + root.addDebugInfo(qsTr("Exit code: %1").arg(exitCode)); + root.addDebugInfo(qsTr("(Exit code 0 = success, non-zero = error)")); + + // Check if this was a "connection add" command - if so, we need to activate it + const wasConnectionAdd = connectProc.command && connectProc.command.length > 0 + && connectProc.command[0] === "nmcli" + && connectProc.command[1] === "connection" + && connectProc.command[2] === "add"; + + if (wasConnectionAdd && exitCode === 0 && root.pendingConnection) { + // Connection profile was created successfully, now activate it + const ssid = root.pendingConnection.ssid; + root.addDebugInfo(qsTr("Connection profile created successfully, now activating: %1").arg(ssid)); + root.setConnectionStatus(qsTr("Activating connection...")); + + // Update saved connections list since we just created one + listConnectionsProc.running = true; + + // Activate the connection we just created + connectProc.command = ["nmcli", "connection", "up", ssid]; + Qt.callLater(() => { + connectProc.running = true; + }); + // Don't start timers yet - wait for activation to complete + return; + } + // Refresh network list after connection attempt getNetworks.running = true; // Check if connection succeeded after a short delay (network list needs to update) if (root.pendingConnection) { - immediateCheckTimer.start(); + if (exitCode === 0) { + // Process succeeded, start checking connection status + root.setConnectionStatus(qsTr("Connection command succeeded, verifying...")); + root.addDebugInfo(qsTr("Command succeeded, checking connection status...")); + root.addDebugInfo(qsTr("Starting immediate check timer (500ms intervals)")); + immediateCheckTimer.start(); + } else { + // Process failed, but wait a moment to see if connection still works + root.setConnectionStatus(qsTr("Connection command exited with code %1, checking status...").arg(exitCode)); + root.addDebugInfo(qsTr("Command exited with error code %1").arg(exitCode)); + root.addDebugInfo(qsTr("This usually means the command failed")); + root.addDebugInfo(qsTr("Checking connection status anyway...")); + root.addDebugInfo(qsTr("Starting immediate check timer (500ms intervals)")); + immediateCheckTimer.start(); + } + } else { + root.addDebugInfo(qsTr("No pending connection - not starting immediate check timer")); } + root.addDebugInfo(qsTr("======================")); } stdout: SplitParser { - onRead: getNetworks.running = true + onRead: { + getNetworks.running = true; + // Also log output for debugging + if (text && text.trim().length > 0) { + root.addDebugInfo(qsTr("STDOUT: %1").arg(text.trim())); + root.setConnectionStatus(qsTr("Status: %1").arg(text.trim())); + } + } } stderr: StdioCollector { onStreamFinished: { const error = text.trim(); + root.addDebugInfo(qsTr("=== STDERR OUTPUT ===")); if (error && error.length > 0) { + // Split error into lines and add each one + const errorLines = error.split("\n"); + for (let i = 0; i < errorLines.length; i++) { + const line = errorLines[i].trim(); + if (line.length > 0) { + root.addDebugInfo(qsTr("STDERR: %1").arg(line)); + } + } + // Check for specific errors that indicate password is needed // Be careful not to match success messages const needsPassword = (error.includes("Secrets were required") || @@ -270,11 +530,19 @@ Singleton { const pending = root.pendingConnection; root.pendingConnection = null; pending.callback(); - } else if (error && error.length > 0 && !error.includes("Connection activated")) { - // Only log non-success messages - console.warn("Network connection error:", error); + } else if (error && error.length > 0 && !error.includes("Connection activated") && !error.includes("successfully")) { + // Log all errors (except success messages) + root.setConnectionStatus(qsTr("Error: %1").arg(errorLines[0] || error)); + // Emit signal for UI to handle + root.connectionFailed(root.pendingConnection ? root.pendingConnection.ssid : ""); + } else if (error && (error.includes("Connection activated") || error.includes("successfully"))) { + root.addDebugInfo(qsTr("Connection successful!")); + root.setConnectionStatus(qsTr("Connection successful!")); } + } else { + root.addDebugInfo(qsTr("STDERR: (empty)")); } + root.addDebugInfo(qsTr("====================")); } } } @@ -321,6 +589,26 @@ Singleton { } } + Process { + id: deleteConnectionProc + + // Delete connection profile - refresh network list and saved connections after deletion + onExited: { + // Refresh network list and saved connections after deletion + getNetworks.running = true; + listConnectionsProc.running = true; + } + stderr: StdioCollector { + onStreamFinished: { + const error = text.trim(); + if (error && error.length > 0) { + // Log error but don't fail - connection might not exist + console.warn("Network connection delete error:", error); + } + } + } + } + Process { id: getNetworks -- cgit v1.2.3-freya From fc223237f0938c7904360d8c2674368bffa53373 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 12 Nov 2025 21:58:14 -0500 Subject: controlcenter: wireless panel rewrite --- modules/controlcenter/Panes.qml | 2 +- modules/controlcenter/network/Details.qml | 213 --------- modules/controlcenter/network/NetworkList.qml | 228 --------- modules/controlcenter/network/NetworkPane.qml | 154 ------ modules/controlcenter/network/PasswordDialog.qml | 514 --------------------- modules/controlcenter/network/Settings.qml | 166 ------- modules/controlcenter/network/SimpleButton.qml | 36 ++ .../network/WirelessConnectionHelper.qml | 46 ++ modules/controlcenter/network/WirelessDetails.qml | 158 +++++++ modules/controlcenter/network/WirelessList.qml | 207 +++++++++ modules/controlcenter/network/WirelessPane.qml | 155 +++++++ .../network/WirelessPasswordDialog.qml | 299 ++++++++++++ modules/controlcenter/network/WirelessSettings.qml | 82 ++++ 13 files changed, 984 insertions(+), 1276 deletions(-) delete mode 100644 modules/controlcenter/network/Details.qml delete mode 100644 modules/controlcenter/network/NetworkList.qml delete mode 100644 modules/controlcenter/network/NetworkPane.qml delete mode 100644 modules/controlcenter/network/PasswordDialog.qml delete mode 100644 modules/controlcenter/network/Settings.qml create mode 100644 modules/controlcenter/network/SimpleButton.qml create mode 100644 modules/controlcenter/network/WirelessConnectionHelper.qml create mode 100644 modules/controlcenter/network/WirelessDetails.qml create mode 100644 modules/controlcenter/network/WirelessList.qml create mode 100644 modules/controlcenter/network/WirelessPane.qml create mode 100644 modules/controlcenter/network/WirelessPasswordDialog.qml create mode 100644 modules/controlcenter/network/WirelessSettings.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/Panes.qml b/modules/controlcenter/Panes.qml index 94bea9a..11e60d3 100644 --- a/modules/controlcenter/Panes.qml +++ b/modules/controlcenter/Panes.qml @@ -36,7 +36,7 @@ ClippingRectangle { Pane { index: 1 - sourceComponent: NetworkPane { + sourceComponent: WirelessPane { session: root.session } } diff --git a/modules/controlcenter/network/Details.qml b/modules/controlcenter/network/Details.qml deleted file mode 100644 index 5e636a2..0000000 --- a/modules/controlcenter/network/Details.qml +++ /dev/null @@ -1,213 +0,0 @@ -pragma ComponentBehavior: Bound - -import ".." -import qs.components -import qs.components.controls -import qs.components.effects -import qs.components.containers -import qs.services -import qs.config -import QtQuick -import QtQuick.Layouts - -Item { - id: root - - required property Session session - readonly property var network: session.network.active - - Component.onCompleted: { - if (network && network.active) { - Network.updateWirelessDeviceDetails(); - } - } - - onNetworkChanged: { - if (network && network.active) { - Network.updateWirelessDeviceDetails(); - } else { - Network.wirelessDeviceDetails = null; - } - } - - Connections { - target: Network - function onActiveChanged() { - if (root.network && root.network.active && Network.active && Network.active.ssid === root.network.ssid) { - Network.updateWirelessDeviceDetails(); - } else if (!root.network || !root.network.active) { - Network.wirelessDeviceDetails = null; - } - } - } - - StyledFlickable { - anchors.fill: parent - - flickableDirection: Flickable.VerticalFlick - contentHeight: layout.height - - ColumnLayout { - id: layout - - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.normal - - ConnectionHeader { - icon: root.network?.isSecure ? "lock" : "wifi" - title: root.network?.ssid ?? qsTr("Unknown") - } - - SectionHeader { - title: qsTr("Connection status") - description: qsTr("Connection settings for this network") - } - - SectionContainer { - ToggleRow { - label: qsTr("Connected") - checked: root.network?.active ?? false - toggle.onToggled: { - if (checked) { - // If already connected to a different network, disconnect first - if (Network.active && Network.active.ssid !== root.network.ssid) { - Network.disconnectFromNetwork(); - // Wait a moment before connecting to new network - Qt.callLater(() => { - connectToNetwork(); - }); - } else { - connectToNetwork(); - } - } else { - Network.disconnectFromNetwork(); - } - } - - function connectToNetwork(): void { - if (root.network.isSecure) { - // Try connecting without password first (in case it's saved) - Network.connectToNetworkWithPasswordCheck( - root.network.ssid, - root.network.isSecure, - () => { - // Callback: connection failed, show password dialog - root.session.network.showPasswordDialog = true; - root.session.network.pendingNetwork = root.network; - }, - root.network.bssid - ); - } else { - Network.connectToNetwork(root.network.ssid, "", root.network.bssid, null); - } - } - } - - Button { - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.normal - visible: root.network && root.network.ssid && Network.savedConnections.includes(root.network.ssid) - color: Colours.palette.m3errorContainer - onColor: Colours.palette.m3onErrorContainer - text: qsTr("Forget Network") - - onClicked: { - if (root.network && root.network.ssid) { - // Disconnect first if connected - if (root.network.active) { - Network.disconnectFromNetwork(); - } - // Delete the connection profile - Network.forgetNetwork(root.network.ssid); - } - } - } - } - - SectionHeader { - title: qsTr("Network properties") - description: qsTr("Additional information") - } - - SectionContainer { - contentSpacing: Appearance.spacing.small / 2 - - PropertyRow { - label: qsTr("SSID") - value: root.network?.ssid ?? qsTr("Unknown") - } - - PropertyRow { - showTopMargin: true - label: qsTr("BSSID") - value: root.network?.bssid ?? qsTr("Unknown") - } - - PropertyRow { - showTopMargin: true - label: qsTr("Signal strength") - value: root.network ? qsTr("%1%").arg(root.network.strength) : qsTr("N/A") - } - - PropertyRow { - showTopMargin: true - label: qsTr("Frequency") - value: root.network ? qsTr("%1 MHz").arg(root.network.frequency) : qsTr("N/A") - } - - PropertyRow { - showTopMargin: true - label: qsTr("Security") - value: root.network ? (root.network.isSecure ? root.network.security : qsTr("Open")) : qsTr("N/A") - } - } - - SectionHeader { - title: qsTr("Connection information") - description: qsTr("Network connection details") - } - - SectionContainer { - ConnectionInfoSection { - deviceDetails: Network.wirelessDeviceDetails - } - } - - } - } - - component Button: StyledRect { - property color onColor: Colours.palette.m3onSurface - property alias disabled: stateLayer.disabled - property alias text: label.text - property alias enabled: stateLayer.enabled - - Layout.fillWidth: true - implicitHeight: label.implicitHeight + Appearance.padding.normal * 2 - radius: Appearance.rounding.normal - - StateLayer { - id: stateLayer - color: parent.onColor - function onClicked(): void { - if (parent.enabled !== false) { - parent.clicked(); - } - } - } - - StyledText { - id: label - anchors.centerIn: parent - color: parent.onColor - } - - signal clicked - } - -} - - - - diff --git a/modules/controlcenter/network/NetworkList.qml b/modules/controlcenter/network/NetworkList.qml deleted file mode 100644 index 6c4158c..0000000 --- a/modules/controlcenter/network/NetworkList.qml +++ /dev/null @@ -1,228 +0,0 @@ -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 - -ColumnLayout { - id: root - - required property Session session - - spacing: Appearance.spacing.small - - RowLayout { - spacing: Appearance.spacing.smaller - - StyledText { - text: qsTr("Settings") - font.pointSize: Appearance.font.size.large - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } - - ToggleButton { - toggled: Network.wifiEnabled - icon: "wifi" - accent: "Tertiary" - - onClicked: { - Network.toggleWifi(); - } - } - - ToggleButton { - toggled: Network.scanning - icon: "wifi_find" - accent: "Secondary" - - onClicked: { - Network.rescanWifi(); - } - } - - ToggleButton { - toggled: !root.session.network.active - icon: "settings" - accent: "Primary" - - onClicked: { - if (root.session.network.active) - root.session.network.active = null; - else { - root.session.network.active = view.model.get(0)?.modelData ?? null; - } - } - } - } - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small - - StyledText { - text: qsTr("Networks (%1)").arg(Network.networks.length) - font.pointSize: Appearance.font.size.large - font.weight: 500 - } - - StyledText { - visible: Network.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: Network.networks - - spacing: Appearance.spacing.small / 2 - clip: true - - StyledScrollBar.vertical: StyledScrollBar { - flickable: view - } - - delegate: 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) - radius: Appearance.rounding.normal - border.width: root.session.network.active === modelData ? 1 : 0 - border.color: Colours.palette.m3primary - - StateLayer { - function onClicked(): void { - root.session.network.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.active ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh - - MaterialIcon { - id: icon - - anchors.centerIn: parent - text: modelData.isSecure ? "lock" : "wifi" - font.pointSize: Appearance.font.size.large - fill: modelData.active ? 1 : 0 - color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface - } - } - - StyledText { - Layout.fillWidth: true - elide: Text.ElideRight - maximumLineCount: 1 - - text: modelData.ssid || qsTr("Unknown") - } - - StyledText { - text: modelData.active ? qsTr("Connected") : (modelData.isSecure ? qsTr("Secured") : qsTr("Open")) - color: modelData.active ? Colours.palette.m3primary : Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - font.weight: modelData.active ? 500 : 400 - } - - StyledText { - text: qsTr("%1%").arg(modelData.strength) - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledRect { - implicitWidth: implicitHeight - implicitHeight: connectIcon.implicitHeight + Appearance.padding.smaller * 2 - - radius: Appearance.rounding.full - color: Qt.alpha(Colours.palette.m3primaryContainer, modelData.active ? 1 : 0) - - StateLayer { - function onClicked(): void { - if (modelData.active) { - Network.disconnectFromNetwork(); - } else { - // If already connected to a different network, disconnect first - if (Network.active && Network.active.ssid !== modelData.ssid) { - Network.disconnectFromNetwork(); - // Wait a moment before connecting to new network - Qt.callLater(() => { - connectToNetwork(); - }); - } else { - connectToNetwork(); - } - } - } - - function connectToNetwork(): void { - if (modelData.isSecure) { - // Try connecting without password first (in case it's saved) - Network.connectToNetworkWithPasswordCheck( - modelData.ssid, - modelData.isSecure, - () => { - // Callback: connection failed, show password dialog - root.session.network.showPasswordDialog = true; - root.session.network.pendingNetwork = modelData; - }, - modelData.bssid - ); - } else { - Network.connectToNetwork(modelData.ssid, "", modelData.bssid, null); - } - } - } - - MaterialIcon { - id: connectIcon - - anchors.centerIn: parent - text: modelData.active ? "link_off" : "link" - color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface - } - } - } - - implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 - } - } -} diff --git a/modules/controlcenter/network/NetworkPane.qml b/modules/controlcenter/network/NetworkPane.qml deleted file mode 100644 index fe18274..0000000 --- a/modules/controlcenter/network/NetworkPane.qml +++ /dev/null @@ -1,154 +0,0 @@ -pragma ComponentBehavior: Bound - -import ".." -import qs.components -import qs.components.effects -import qs.components.containers -import qs.config -import Quickshell.Widgets -import QtQuick -import QtQuick.Layouts - -RowLayout { - id: root - - required property Session session - - anchors.fill: parent - - spacing: 0 - - Item { - Layout.preferredWidth: Math.floor(parent.width * 0.4) - Layout.minimumWidth: 420 - Layout.fillHeight: true - - NetworkList { - anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - - session: root.session - } - - InnerBorder { - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 - } - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - ClippingRectangle { - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: rightBorder.innerRadius - color: "transparent" - - Loader { - id: loader - - property var pane: root.session.network.active - property string paneId: pane ? (pane.ssid || pane.bssid || "") : "" - - anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 - - opacity: 1 - scale: 1 - transformOrigin: Item.Center - - clip: false - asynchronous: true - sourceComponent: pane ? details : settings - - Behavior on paneId { - SequentialAnimation { - ParallelAnimation { - Anim { - target: loader - property: "opacity" - to: 0 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - Anim { - target: loader - property: "scale" - to: 0.8 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - } - PropertyAction {} - ParallelAnimation { - Anim { - target: loader - property: "opacity" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - Anim { - target: loader - property: "scale" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - } - } - } - - onPaneChanged: { - paneId = pane ? (pane.ssid || pane.bssid || "") : ""; - } - } - } - - InnerBorder { - id: rightBorder - - leftThickness: Appearance.padding.normal / 2 - } - - Component { - id: settings - - StyledFlickable { - flickableDirection: Flickable.VerticalFlick - contentHeight: settingsInner.height - - Settings { - id: settingsInner - - anchors.left: parent.left - anchors.right: parent.right - session: root.session - } - } - } - - Component { - id: details - - Details { - session: root.session - } - } - } - - PasswordDialog { - anchors.fill: parent - session: root.session - z: 1000 - } - - component Anim: NumberAnimation { - target: loader - duration: Appearance.anim.durations.normal / 2 - easing.type: Easing.BezierSpline - } -} diff --git a/modules/controlcenter/network/PasswordDialog.qml b/modules/controlcenter/network/PasswordDialog.qml deleted file mode 100644 index 7aa698b..0000000 --- a/modules/controlcenter/network/PasswordDialog.qml +++ /dev/null @@ -1,514 +0,0 @@ -pragma ComponentBehavior: Bound - -import ".." -import qs.components -import qs.components.controls -import qs.components.effects -import qs.components.containers -import qs.services -import qs.config -import QtQuick -import QtQuick.Layouts - -Item { - id: root - - required property Session session - readonly property var network: { - // Try pendingNetwork first, then fall back to active network selection - if (session.network.pendingNetwork) { - return session.network.pendingNetwork; - } - // Fallback to active network if available - if (session.network.active) { - return session.network.active; - } - return null; - } - - visible: session.network.showPasswordDialog - enabled: visible - focus: visible - - // Ensure network is set when dialog opens - Component.onCompleted: { - if (visible && !session.network.pendingNetwork && session.network.active) { - session.network.pendingNetwork = session.network.active; - } - } - - Connections { - target: root - function onVisibleChanged(): void { - if (visible && !session.network.pendingNetwork && session.network.active) { - session.network.pendingNetwork = session.network.active; - } - } - } - - Keys.onEscapePressed: { - root.session.network.showPasswordDialog = false; - passwordField.text = ""; - } - - Rectangle { - anchors.fill: parent - color: Qt.rgba(0, 0, 0, 0.5) - opacity: root.visible ? 1 : 0 - - Behavior on opacity { - NumberAnimation { - duration: 200 - } - } - - MouseArea { - anchors.fill: parent - onClicked: { - root.session.network.showPasswordDialog = false; - passwordField.text = ""; - } - } - } - - StyledRect { - id: dialog - - anchors.centerIn: parent - - implicitWidth: 400 - implicitHeight: content.implicitHeight + Appearance.padding.large * 2 - - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surface - opacity: root.visible ? 1 : 0 - scale: root.visible ? 1 : 0.9 - - Behavior on opacity { - NumberAnimation { - duration: 200 - } - } - - Behavior on scale { - NumberAnimation { - duration: 200 - } - } - - Keys.onEscapePressed: { - root.session.network.showPasswordDialog = false; - passwordField.text = ""; - } - - ColumnLayout { - id: content - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - - spacing: Appearance.spacing.normal - - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - text: "lock" - font.pointSize: Appearance.font.size.extraLarge * 2 - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Enter password") - font.pointSize: Appearance.font.size.large - font.weight: 500 - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: root.network ? qsTr("Network: %1").arg(root.network.ssid) : "" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - id: statusText - - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: Appearance.spacing.small - visible: Network.connectionStatus.length > 0 || connectButton.connecting - text: { - if (Network.connectionStatus.length > 0) { - return Network.connectionStatus; - } else if (connectButton.connecting) { - return qsTr("Connecting..."); - } - return ""; - } - color: { - const status = Network.connectionStatus; - if (status.includes("Error") || status.includes("error") || status.includes("failed")) { - return Colours.palette.m3error; - } else if (status.includes("successful") || status.includes("Connected") || status.includes("success")) { - return Colours.palette.m3primary; - } - return Colours.palette.m3onSurfaceVariant; - } - font.pointSize: Appearance.font.size.small - font.weight: (Network.connectionStatus.includes("Error") || Network.connectionStatus.includes("error")) ? 500 : 400 - wrapMode: Text.WordWrap - Layout.maximumWidth: parent.width - Appearance.padding.large * 2 - } - - Item { - Layout.topMargin: Appearance.spacing.large - Layout.fillWidth: true - implicitHeight: passwordField.implicitHeight + Appearance.padding.normal * 2 - - StyledRect { - anchors.fill: parent - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - border.width: passwordField.activeFocus ? 2 : 1 - border.color: passwordField.activeFocus ? Colours.palette.m3primary : Colours.palette.m3outline - - Behavior on border.color { - CAnim {} - } - } - - StyledTextField { - id: passwordField - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal - - echoMode: TextField.Password - placeholderText: qsTr("Password") - - Component.onCompleted: { - if (root.visible) { - forceActiveFocus(); - } - } - - Connections { - target: root - function onVisibleChanged(): void { - if (root.visible) { - passwordField.forceActiveFocus(); - passwordField.text = ""; - Network.connectionStatus = ""; - } - } - } - - Connections { - target: Network - function onConnectionStatusChanged(): void { - // Status updated, ensure it's visible - } - } - - Keys.onReturnPressed: { - if (connectButton.enabled) { - connectButton.clicked(); - } - } - Keys.onEnterPressed: { - if (connectButton.enabled) { - connectButton.clicked(); - } - } - } - } - - RowLayout { - Layout.topMargin: Appearance.spacing.normal - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - Button { - id: cancelButton - - Layout.fillWidth: true - color: Colours.palette.m3secondaryContainer - onColor: Colours.palette.m3onSecondaryContainer - text: qsTr("Cancel") - - function onClicked(): void { - root.session.network.showPasswordDialog = false; - passwordField.text = ""; - } - } - - Button { - id: connectButton - - Layout.fillWidth: true - color: Colours.palette.m3primary - onColor: Colours.palette.m3onPrimary - text: qsTr("Connect") - enabled: passwordField.text.length > 0 - - property bool connecting: false - - function onClicked(): void { - Network.connectionStatus = ""; - - // Get password first - const password = passwordField.text; - - // Try multiple ways to get the network - let networkToUse = null; - - // Try 1: root.network (computed property) - if (root.network) { - networkToUse = root.network; - } - - // Try 2: pendingNetwork - if (!networkToUse && root.session.network.pendingNetwork) { - networkToUse = root.session.network.pendingNetwork; - } - - // Try 3: active network - if (!networkToUse && root.session.network.active) { - networkToUse = root.session.network.active; - root.session.network.pendingNetwork = networkToUse; - } - - // Check all conditions - const hasNetwork = !!networkToUse; - const hasPassword = password && password.length > 0; - const notConnecting = !connecting; - - if (hasNetwork && hasPassword && notConnecting) { - // Set status immediately - Network.connectionStatus = qsTr("Preparing to connect..."); - - // Keep dialog open and track connection - connecting = true; - connectButton.enabled = false; - connectButton.text = qsTr("Connecting..."); - - // Force immediate UI update - statusText.visible = true; - - // Store target SSID for later comparison - const ssidToConnect = networkToUse.ssid || ""; - const bssidToConnect = networkToUse.bssid || ""; - - // Store the SSID we're connecting to so we can compare later - // even if root.network changes - content.connectingToSsid = ssidToConnect; - - // Execute connection immediately - Network.connectToNetwork( - ssidToConnect, - password, - bssidToConnect, - () => { - // Callback if connection fails - keep dialog open - connecting = false; - connectButton.enabled = true; - connectButton.text = qsTr("Connect"); - content.connectingToSsid = ""; // Clear on failure - } - ); - - // Start connection check timer immediately - connectionCheckTimer.checkCount = 0; - connectionCheckTimer.start(); - - // Also check immediately after a short delay to catch quick connections - Qt.callLater(() => { - if (root.visible) { - closeDialogIfConnected(); - } - }); - } else { - // Show error in status - Network.connectionStatus = qsTr("Error: Cannot connect - missing network or password"); - } - } - } - } - - // Store the SSID we're connecting to when connection starts - property string connectingToSsid: "" - - property string targetSsid: { - // Track the SSID we're trying to connect to - // Prefer explicitly stored connectingToSsid, then computed values - if (connectingToSsid && connectingToSsid.length > 0) { - return connectingToSsid; - } - if (root.network && root.network.ssid) { - return root.network.ssid; - } - if (root.session.network.pendingNetwork && root.session.network.pendingNetwork.ssid) { - return root.session.network.pendingNetwork.ssid; - } - return ""; - } - - function closeDialogIfConnected(): bool { - // Check if we're connected to the network we're trying to connect to - const ssid = targetSsid; - - if (!ssid || ssid.length === 0) { - return false; - } - - if (!Network.active) { - return false; - } - - const activeSsid = Network.active.ssid || ""; - - if (activeSsid === ssid) { - // Connection succeeded - close dialog - connectionCheckTimer.stop(); - aggressiveCheckTimer.stop(); - connectionCheckTimer.checkCount = 0; - connectButton.connecting = false; - Network.connectionStatus = ""; - root.session.network.showPasswordDialog = false; - passwordField.text = ""; - content.connectingToSsid = ""; // Clear stored SSID - return true; - } - return false; - } - - Timer { - id: connectionCheckTimer - interval: 1000 // Check every 1 second for faster response - repeat: true - triggeredOnStart: false - property int checkCount: 0 - - onTriggered: { - checkCount++; - - // Try to close dialog if connected - const closed = content.closeDialogIfConnected(); - if (closed) { - return; - } - - if (connectButton.connecting) { - // Still connecting, check again - // Limit to 20 checks (20 seconds total) - if (checkCount >= 20) { - connectionCheckTimer.stop(); - connectionCheckTimer.checkCount = 0; - connectButton.connecting = false; - connectButton.enabled = true; - connectButton.text = qsTr("Connect"); - } - } else { - // Not connecting anymore, stop timer - connectionCheckTimer.stop(); - connectionCheckTimer.checkCount = 0; - } - } - } - - Connections { - target: Network - function onActiveChanged(): void { - // Check immediately when active network changes - if (root.visible) { - // Check immediately - if connected, close right away - if (content.closeDialogIfConnected()) { - return; - } - - // Also check after a delay in case the active network isn't fully updated yet - Qt.callLater(() => { - if (root.visible) { - content.closeDialogIfConnected(); - } - }); - } - } - } - - // Also check when dialog becomes visible - Connections { - target: root - function onVisibleChanged(): void { - if (root.visible) { - // Check immediately when dialog opens - Qt.callLater(() => { - if (root.visible) { - closeDialogIfConnected(); - } - }); - } - } - } - - // Aggressive polling timer - checks every 500ms when dialog is visible and connecting - // This ensures we catch the connection even if signals are missed - Timer { - id: aggressiveCheckTimer - interval: 500 - repeat: true - running: root.visible && connectButton.connecting - triggeredOnStart: true - - onTriggered: { - if (root.visible && connectButton.connecting) { - if (content.closeDialogIfConnected()) { - stop(); - } - } else { - stop(); - } - } - } - } - } - - component Button: StyledRect { - property color onColor: Colours.palette.m3onSurface - property alias disabled: stateLayer.disabled - property alias text: label.text - property alias enabled: stateLayer.enabled - - function onClicked(): void { - } - - radius: Appearance.rounding.small - implicitHeight: label.implicitHeight + Appearance.padding.small * 2 - opacity: enabled ? 1 : 0.5 - - StateLayer { - id: stateLayer - - enabled: parent.enabled - color: parent.onColor - - function onClicked(): void { - if (enabled) { - parent.onClicked(); - } - } - } - - StyledText { - id: label - - anchors.centerIn: parent - animate: true - color: parent.onColor - font.pointSize: Appearance.font.size.normal - } - } -} - diff --git a/modules/controlcenter/network/Settings.qml b/modules/controlcenter/network/Settings.qml deleted file mode 100644 index 0794e6a..0000000 --- a/modules/controlcenter/network/Settings.qml +++ /dev/null @@ -1,166 +0,0 @@ -pragma ComponentBehavior: Bound - -import ".." -import qs.components -import qs.components.controls -import qs.components.effects -import qs.services -import qs.config -import QtQuick -import QtQuick.Layouts - -ColumnLayout { - id: root - - required property Session session - - spacing: Appearance.spacing.normal - - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - text: "wifi" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Network settings") - font.pointSize: Appearance.font.size.large - font.bold: true - } - - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("WiFi status") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - StyledText { - text: qsTr("General WiFi settings") - color: Colours.palette.m3outline - } - - StyledRect { - Layout.fillWidth: true - implicitHeight: wifiStatus.implicitHeight + Appearance.padding.large * 2 - - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - ColumnLayout { - id: wifiStatus - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - - spacing: Appearance.spacing.larger - - Toggle { - label: qsTr("WiFi enabled") - checked: Network.wifiEnabled - toggle.onToggled: { - Network.enableWifi(checked); - } - } - } - } - - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Network information") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - StyledText { - text: qsTr("Current network connection") - color: Colours.palette.m3outline - } - - StyledRect { - Layout.fillWidth: true - implicitHeight: networkInfo.implicitHeight + Appearance.padding.large * 2 - - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - ColumnLayout { - id: networkInfo - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - - spacing: Appearance.spacing.small / 2 - - StyledText { - text: qsTr("Connected network") - } - - StyledText { - text: Network.active ? Network.active.ssid : qsTr("Not connected") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Signal strength") - } - - StyledText { - text: Network.active ? qsTr("%1%").arg(Network.active.strength) : qsTr("N/A") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Security") - } - - StyledText { - text: Network.active ? (Network.active.isSecure ? qsTr("Secured") : qsTr("Open")) : qsTr("N/A") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Frequency") - } - - StyledText { - text: Network.active ? qsTr("%1 MHz").arg(Network.active.frequency) : qsTr("N/A") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - } - } - - component Toggle: RowLayout { - required property string label - property alias checked: toggle.checked - property alias toggle: toggle - - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - Layout.fillWidth: true - text: parent.label - } - - StyledSwitch { - id: toggle - - cLayer: 2 - } - } -} - diff --git a/modules/controlcenter/network/SimpleButton.qml b/modules/controlcenter/network/SimpleButton.qml new file mode 100644 index 0000000..49ab2f4 --- /dev/null +++ b/modules/controlcenter/network/SimpleButton.qml @@ -0,0 +1,36 @@ +import qs.components +import qs.components.effects +import qs.config +import QtQuick + +StyledRect { + id: root + + property color onColor: Colours.palette.m3onSurface + property alias disabled: stateLayer.disabled + property alias text: label.text + property alias enabled: stateLayer.enabled + + implicitWidth: label.implicitWidth + Appearance.padding.normal * 2 + implicitHeight: label.implicitHeight + Appearance.padding.normal * 2 + radius: Appearance.rounding.normal + + StateLayer { + id: stateLayer + color: parent.onColor + function onClicked(): void { + if (parent.enabled !== false) { + parent.clicked(); + } + } + } + + StyledText { + id: label + anchors.centerIn: parent + color: parent.onColor + } + + signal clicked +} + diff --git a/modules/controlcenter/network/WirelessConnectionHelper.qml b/modules/controlcenter/network/WirelessConnectionHelper.qml new file mode 100644 index 0000000..d63b359 --- /dev/null +++ b/modules/controlcenter/network/WirelessConnectionHelper.qml @@ -0,0 +1,46 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.services +import QtQuick + +QtObject { + id: root + + required property Session session + + function connectToNetwork(network: var): void { + if (!network) { + return; + } + + // If already connected to a different network, disconnect first + if (Network.active && Network.active.ssid !== network.ssid) { + Network.disconnectFromNetwork(); + Qt.callLater(() => { + performConnect(network); + }); + } else { + performConnect(network); + } + } + + function performConnect(network: var): void { + if (network.isSecure) { + // Try connecting without password first (in case it's saved) + Network.connectToNetworkWithPasswordCheck( + network.ssid, + network.isSecure, + () => { + // Callback: connection failed, show password dialog + root.session.network.showPasswordDialog = true; + root.session.network.pendingNetwork = network; + }, + network.bssid + ); + } else { + Network.connectToNetwork(network.ssid, "", network.bssid, null); + } + } +} + diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml new file mode 100644 index 0000000..e858fbc --- /dev/null +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -0,0 +1,158 @@ +pragma ComponentBehavior: Bound + +import ".." +import "." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.components.containers +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +Item { + id: root + + required property Session session + readonly property var network: session.network.active + + readonly property var connectionHelper: WirelessConnectionHelper { + session: root.session + } + + Component.onCompleted: { + if (network && network.active) { + Network.updateWirelessDeviceDetails(); + } + } + + onNetworkChanged: { + if (network && network.active) { + Network.updateWirelessDeviceDetails(); + } else { + Network.wirelessDeviceDetails = null; + } + } + + Connections { + target: Network + function onActiveChanged() { + if (root.network && root.network.active && Network.active && Network.active.ssid === root.network.ssid) { + Network.updateWirelessDeviceDetails(); + } else if (!root.network || !root.network.active) { + Network.wirelessDeviceDetails = null; + } + } + } + + StyledFlickable { + anchors.fill: parent + + flickableDirection: Flickable.VerticalFlick + contentHeight: layout.height + + ColumnLayout { + id: layout + + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.normal + + ConnectionHeader { + icon: root.network?.isSecure ? "lock" : "wifi" + title: root.network?.ssid ?? qsTr("Unknown") + } + + SectionHeader { + title: qsTr("Connection status") + description: qsTr("Connection settings for this network") + } + + SectionContainer { + ToggleRow { + label: qsTr("Connected") + checked: root.network?.active ?? false + toggle.onToggled: { + if (checked) { + root.connectionHelper.connectToNetwork(root.network); + } else { + Network.disconnectFromNetwork(); + } + } + } + + SimpleButton { + Layout.fillWidth: true + Layout.topMargin: Appearance.spacing.normal + visible: root.network && root.network.ssid && Network.savedConnections.includes(root.network.ssid) + color: Colours.palette.m3errorContainer + onColor: Colours.palette.m3onErrorContainer + text: qsTr("Forget Network") + + onClicked: { + if (root.network && root.network.ssid) { + // Disconnect first if connected + if (root.network.active) { + Network.disconnectFromNetwork(); + } + // Delete the connection profile + Network.forgetNetwork(root.network.ssid); + } + } + } + } + + SectionHeader { + title: qsTr("Network properties") + description: qsTr("Additional information") + } + + SectionContainer { + contentSpacing: Appearance.spacing.small / 2 + + PropertyRow { + label: qsTr("SSID") + value: root.network?.ssid ?? qsTr("Unknown") + } + + PropertyRow { + showTopMargin: true + label: qsTr("BSSID") + value: root.network?.bssid ?? qsTr("Unknown") + } + + PropertyRow { + showTopMargin: true + label: qsTr("Signal strength") + value: root.network ? qsTr("%1%").arg(root.network.strength) : qsTr("N/A") + } + + PropertyRow { + showTopMargin: true + label: qsTr("Frequency") + value: root.network ? qsTr("%1 MHz").arg(root.network.frequency) : qsTr("N/A") + } + + PropertyRow { + showTopMargin: true + label: qsTr("Security") + value: root.network ? (root.network.isSecure ? root.network.security : qsTr("Open")) : qsTr("N/A") + } + } + + SectionHeader { + title: qsTr("Connection information") + description: qsTr("Network connection details") + } + + SectionContainer { + ConnectionInfoSection { + deviceDetails: Network.wirelessDeviceDetails + } + } + + } + } +} + diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml new file mode 100644 index 0000000..1a4ba00 --- /dev/null +++ b/modules/controlcenter/network/WirelessList.qml @@ -0,0 +1,207 @@ +pragma ComponentBehavior: Bound + +import ".." +import "." +import qs.components +import qs.components.controls +import qs.components.containers +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property Session session + + readonly property var connectionHelper: WirelessConnectionHelper { + session: root.session + } + + spacing: Appearance.spacing.small + + RowLayout { + spacing: Appearance.spacing.smaller + + StyledText { + text: qsTr("Settings") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + Item { + Layout.fillWidth: true + } + + ToggleButton { + toggled: Network.wifiEnabled + icon: "wifi" + accent: "Tertiary" + + onClicked: { + Network.toggleWifi(); + } + } + + ToggleButton { + toggled: Network.scanning + icon: "wifi_find" + accent: "Secondary" + + onClicked: { + Network.rescanWifi(); + } + } + + ToggleButton { + toggled: !root.session.network.active + icon: "settings" + accent: "Primary" + + onClicked: { + if (root.session.network.active) + root.session.network.active = null; + else { + root.session.network.active = view.model.get(0)?.modelData ?? null; + } + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small + + StyledText { + text: qsTr("Networks (%1)").arg(Network.networks.length) + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + StyledText { + visible: Network.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: Network.networks + + spacing: Appearance.spacing.small / 2 + clip: true + + StyledScrollBar.vertical: StyledScrollBar { + flickable: view + } + + delegate: 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) + radius: Appearance.rounding.normal + border.width: root.session.network.active === modelData ? 1 : 0 + border.color: Colours.palette.m3primary + + StateLayer { + function onClicked(): void { + root.session.network.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.active ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh + + MaterialIcon { + id: icon + + anchors.centerIn: parent + text: modelData.isSecure ? "lock" : "wifi" + font.pointSize: Appearance.font.size.large + fill: modelData.active ? 1 : 0 + color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface + } + } + + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 + + text: modelData.ssid || qsTr("Unknown") + } + + StyledText { + text: modelData.active ? qsTr("Connected") : (modelData.isSecure ? qsTr("Secured") : qsTr("Open")) + color: modelData.active ? Colours.palette.m3primary : Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + font.weight: modelData.active ? 500 : 400 + } + + StyledText { + text: qsTr("%1%").arg(modelData.strength) + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledRect { + implicitWidth: implicitHeight + implicitHeight: connectIcon.implicitHeight + Appearance.padding.smaller * 2 + + radius: Appearance.rounding.full + color: Qt.alpha(Colours.palette.m3primaryContainer, modelData.active ? 1 : 0) + + StateLayer { + function onClicked(): void { + if (modelData.active) { + Network.disconnectFromNetwork(); + } else { + root.connectionHelper.connectToNetwork(modelData); + } + } + } + + MaterialIcon { + id: connectIcon + + anchors.centerIn: parent + text: modelData.active ? "link_off" : "link" + color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface + } + } + } + + implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 + } + } +} + diff --git a/modules/controlcenter/network/WirelessPane.qml b/modules/controlcenter/network/WirelessPane.qml new file mode 100644 index 0000000..a23b6e8 --- /dev/null +++ b/modules/controlcenter/network/WirelessPane.qml @@ -0,0 +1,155 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.effects +import qs.components.containers +import qs.config +import Quickshell.Widgets +import QtQuick +import QtQuick.Layouts + +RowLayout { + id: root + + required property Session session + + anchors.fill: parent + + spacing: 0 + + Item { + Layout.preferredWidth: Math.floor(parent.width * 0.4) + Layout.minimumWidth: 420 + Layout.fillHeight: true + + WirelessList { + anchors.fill: parent + anchors.margins: Appearance.padding.large + Appearance.padding.normal + anchors.leftMargin: Appearance.padding.large + anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + + session: root.session + } + + InnerBorder { + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + ClippingRectangle { + anchors.fill: parent + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 + + radius: rightBorder.innerRadius + color: "transparent" + + Loader { + id: loader + + property var pane: root.session.network.active + property string paneId: pane ? (pane.ssid || pane.bssid || "") : "" + + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + opacity: 1 + scale: 1 + transformOrigin: Item.Center + + clip: false + asynchronous: true + sourceComponent: pane ? details : settings + + Behavior on paneId { + SequentialAnimation { + ParallelAnimation { + Anim { + target: loader + property: "opacity" + to: 0 + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + Anim { + target: loader + property: "scale" + to: 0.8 + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + } + PropertyAction {} + ParallelAnimation { + Anim { + target: loader + property: "opacity" + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + Anim { + target: loader + property: "scale" + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + } + } + } + + onPaneChanged: { + paneId = pane ? (pane.ssid || pane.bssid || "") : ""; + } + } + } + + InnerBorder { + id: rightBorder + + leftThickness: Appearance.padding.normal / 2 + } + + Component { + id: settings + + StyledFlickable { + flickableDirection: Flickable.VerticalFlick + contentHeight: settingsInner.height + + WirelessSettings { + id: settingsInner + + anchors.left: parent.left + anchors.right: parent.right + session: root.session + } + } + } + + Component { + id: details + + WirelessDetails { + session: root.session + } + } + } + + WirelessPasswordDialog { + anchors.fill: parent + session: root.session + z: 1000 + } + + component Anim: NumberAnimation { + target: loader + duration: Appearance.anim.durations.normal / 2 + easing.type: Easing.BezierSpline + } +} + diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml new file mode 100644 index 0000000..778fb4f --- /dev/null +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -0,0 +1,299 @@ +pragma ComponentBehavior: Bound + +import ".." +import "." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.components.containers +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +Item { + id: root + + required property Session session + + readonly property var network: { + // Prefer pendingNetwork, then active network + if (session.network.pendingNetwork) { + return session.network.pendingNetwork; + } + if (session.network.active) { + return session.network.active; + } + return null; + } + + visible: session.network.showPasswordDialog + enabled: visible + focus: visible + + Keys.onEscapePressed: { + closeDialog(); + } + + Rectangle { + anchors.fill: parent + color: Qt.rgba(0, 0, 0, 0.5) + opacity: root.visible ? 1 : 0 + + Behavior on opacity { + NumberAnimation { duration: 200 } + } + + MouseArea { + anchors.fill: parent + onClicked: closeDialog(); + } + } + + StyledRect { + id: dialog + + anchors.centerIn: parent + + implicitWidth: 400 + implicitHeight: content.implicitHeight + Appearance.padding.large * 2 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surface + opacity: root.visible ? 1 : 0 + scale: root.visible ? 1 : 0.9 + + Behavior on opacity { + NumberAnimation { duration: 200 } + } + + Behavior on scale { + NumberAnimation { duration: 200 } + } + + Keys.onEscapePressed: closeDialog(); + + ColumnLayout { + id: content + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + + spacing: Appearance.spacing.normal + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + text: "lock" + font.pointSize: Appearance.font.size.extraLarge * 2 + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Enter password") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: root.network ? qsTr("Network: %1").arg(root.network.ssid) : "" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + id: statusText + + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: Appearance.spacing.small + visible: Network.connectionStatus.length > 0 || connectButton.connecting + text: { + if (Network.connectionStatus.length > 0) { + return Network.connectionStatus; + } else if (connectButton.connecting) { + return qsTr("Connecting..."); + } + return ""; + } + color: { + const status = Network.connectionStatus; + if (status.includes("Error") || status.includes("error") || status.includes("failed")) { + return Colours.palette.m3error; + } else if (status.includes("successful") || status.includes("Connected") || status.includes("success")) { + return Colours.palette.m3primary; + } + return Colours.palette.m3onSurfaceVariant; + } + font.pointSize: Appearance.font.size.small + font.weight: (Network.connectionStatus.includes("Error") || Network.connectionStatus.includes("error")) ? 500 : 400 + wrapMode: Text.WordWrap + Layout.maximumWidth: parent.width - Appearance.padding.large * 2 + } + + Item { + Layout.topMargin: Appearance.spacing.large + Layout.fillWidth: true + implicitHeight: passwordField.implicitHeight + Appearance.padding.normal * 2 + + StyledRect { + anchors.fill: parent + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + border.width: passwordField.activeFocus ? 2 : 1 + border.color: passwordField.activeFocus ? Colours.palette.m3primary : Colours.palette.m3outline + + Behavior on border.color { + CAnim {} + } + } + + StyledTextField { + id: passwordField + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal + + echoMode: TextField.Password + placeholderText: qsTr("Password") + + Connections { + target: root + function onVisibleChanged(): void { + if (root.visible) { + passwordField.forceActiveFocus(); + passwordField.text = ""; + Network.clearConnectionStatus(); + } + } + } + + Keys.onReturnPressed: { + if (connectButton.enabled) { + connectButton.clicked(); + } + } + Keys.onEnterPressed: { + if (connectButton.enabled) { + connectButton.clicked(); + } + } + } + } + + RowLayout { + Layout.topMargin: Appearance.spacing.normal + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + SimpleButton { + id: cancelButton + + Layout.fillWidth: true + color: Colours.palette.m3secondaryContainer + onColor: Colours.palette.m3onSecondaryContainer + text: qsTr("Cancel") + + onClicked: closeDialog(); + } + + SimpleButton { + id: connectButton + + Layout.fillWidth: true + color: Colours.palette.m3primary + onColor: Colours.palette.m3onPrimary + text: qsTr("Connect") + enabled: passwordField.text.length > 0 && !connecting + + property bool connecting: false + + onClicked: { + if (!root.network || connecting) { + return; + } + + const password = passwordField.text; + if (!password || password.length === 0) { + return; + } + + // Set connecting state + connecting = true; + enabled = false; + text = qsTr("Connecting..."); + Network.clearConnectionStatus(); + + // Connect to network + Network.connectToNetwork( + root.network.ssid, + password, + root.network.bssid || "", + null + ); + + // Start monitoring connection + connectionMonitor.start(); + } + } + } + } + } + + Timer { + id: connectionMonitor + interval: 1000 + repeat: true + triggeredOnStart: false + + onTriggered: { + // Check if we're connected to the target network + if (root.network && Network.active && Network.active.ssid === root.network.ssid) { + // Successfully connected + stop(); + connectButton.connecting = false; + connectButton.text = qsTr("Connect"); + closeDialog(); + } else if (connectButton.connecting) { + // Still connecting, check status + const status = Network.connectionStatus; + if (status.includes("Error") || status.includes("error") || status.includes("failed")) { + // Connection failed + stop(); + connectButton.connecting = false; + connectButton.enabled = true; + connectButton.text = qsTr("Connect"); + } + } else { + // Not connecting anymore + stop(); + } + } + } + + Connections { + target: Network + function onActiveChanged() { + if (root.visible && root.network && Network.active && Network.active.ssid === root.network.ssid) { + // Connected successfully + connectionMonitor.stop(); + connectButton.connecting = false; + connectButton.text = qsTr("Connect"); + closeDialog(); + } + } + } + + function closeDialog(): void { + session.network.showPasswordDialog = false; + passwordField.text = ""; + connectButton.connecting = false; + connectButton.text = qsTr("Connect"); + connectionMonitor.stop(); + Network.clearConnectionStatus(); + } +} + diff --git a/modules/controlcenter/network/WirelessSettings.qml b/modules/controlcenter/network/WirelessSettings.qml new file mode 100644 index 0000000..7890099 --- /dev/null +++ b/modules/controlcenter/network/WirelessSettings.qml @@ -0,0 +1,82 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property Session session + + spacing: Appearance.spacing.normal + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + text: "wifi" + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Network settings") + font.pointSize: Appearance.font.size.large + font.bold: true + } + + SectionHeader { + Layout.topMargin: Appearance.spacing.large + title: qsTr("WiFi status") + description: qsTr("General WiFi settings") + } + + SectionContainer { + ToggleRow { + label: qsTr("WiFi enabled") + checked: Network.wifiEnabled + toggle.onToggled: { + Network.enableWifi(checked); + } + } + } + + SectionHeader { + Layout.topMargin: Appearance.spacing.large + title: qsTr("Network information") + description: qsTr("Current network connection") + } + + SectionContainer { + contentSpacing: Appearance.spacing.small / 2 + + PropertyRow { + label: qsTr("Connected network") + value: Network.active ? Network.active.ssid : qsTr("Not connected") + } + + PropertyRow { + showTopMargin: true + label: qsTr("Signal strength") + value: Network.active ? qsTr("%1%").arg(Network.active.strength) : qsTr("N/A") + } + + PropertyRow { + showTopMargin: true + label: qsTr("Security") + value: Network.active ? (Network.active.isSecure ? qsTr("Secured") : qsTr("Open")) : qsTr("N/A") + } + + PropertyRow { + showTopMargin: true + label: qsTr("Frequency") + value: Network.active ? qsTr("%1 MHz").arg(Network.active.frequency) : qsTr("N/A") + } + } +} + -- cgit v1.2.3-freya From b62a22b13d6d0c9704b58cf9d79ee89d4b156a74 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 12 Nov 2025 22:09:51 -0500 Subject: controlcenter: wireless panel refactoring --- .../network/WirelessConnectionHelper.qml | 46 -------------- modules/controlcenter/network/WirelessDetails.qml | 71 ++++++++++++++++------ modules/controlcenter/network/WirelessList.qml | 46 ++++++++++++-- .../network/WirelessPasswordDialog.qml | 56 +++++++++-------- 4 files changed, 123 insertions(+), 96 deletions(-) delete mode 100644 modules/controlcenter/network/WirelessConnectionHelper.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/WirelessConnectionHelper.qml b/modules/controlcenter/network/WirelessConnectionHelper.qml deleted file mode 100644 index d63b359..0000000 --- a/modules/controlcenter/network/WirelessConnectionHelper.qml +++ /dev/null @@ -1,46 +0,0 @@ -pragma ComponentBehavior: Bound - -import ".." -import qs.services -import QtQuick - -QtObject { - id: root - - required property Session session - - function connectToNetwork(network: var): void { - if (!network) { - return; - } - - // If already connected to a different network, disconnect first - if (Network.active && Network.active.ssid !== network.ssid) { - Network.disconnectFromNetwork(); - Qt.callLater(() => { - performConnect(network); - }); - } else { - performConnect(network); - } - } - - function performConnect(network: var): void { - if (network.isSecure) { - // Try connecting without password first (in case it's saved) - Network.connectToNetworkWithPasswordCheck( - network.ssid, - network.isSecure, - () => { - // Callback: connection failed, show password dialog - root.session.network.showPasswordDialog = true; - root.session.network.pendingNetwork = network; - }, - network.bssid - ); - } else { - Network.connectToNetwork(network.ssid, "", network.bssid, null); - } - } -} - diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index e858fbc..418c463 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -16,33 +16,28 @@ Item { required property Session session readonly property var network: session.network.active - - readonly property var connectionHelper: WirelessConnectionHelper { - session: root.session - } Component.onCompleted: { - if (network && network.active) { - Network.updateWirelessDeviceDetails(); - } + updateDeviceDetails(); } onNetworkChanged: { - if (network && network.active) { - Network.updateWirelessDeviceDetails(); - } else { - Network.wirelessDeviceDetails = null; - } + updateDeviceDetails(); } Connections { target: Network function onActiveChanged() { - if (root.network && root.network.active && Network.active && Network.active.ssid === root.network.ssid) { - Network.updateWirelessDeviceDetails(); - } else if (!root.network || !root.network.active) { - Network.wirelessDeviceDetails = null; - } + updateDeviceDetails(); + } + } + + function updateDeviceDetails(): void { + // Only update details if the selected network is currently active + if (network && Network.active && Network.active.ssid === network.ssid) { + Network.updateWirelessDeviceDetails(); + } else { + Network.wirelessDeviceDetails = null; } } @@ -75,7 +70,7 @@ Item { checked: root.network?.active ?? false toggle.onToggled: { if (checked) { - root.connectionHelper.connectToNetwork(root.network); + handleConnect(); } else { Network.disconnectFromNetwork(); } @@ -154,5 +149,45 @@ Item { } } + + function handleConnect(): void { + // If already connected to a different network, disconnect first + if (Network.active && Network.active.ssid !== root.network.ssid) { + Network.disconnectFromNetwork(); + Qt.callLater(() => { + connectToNetwork(); + }); + } else { + connectToNetwork(); + } + } + + function connectToNetwork(): void { + if (root.network.isSecure) { + // Check if we have a saved connection profile for this network + const hasSavedProfile = Network.savedConnections.includes(root.network.ssid); + + if (hasSavedProfile) { + // Try connecting with saved password - don't show dialog if it fails + // The saved password should work, but if connection fails for other reasons, + // we'll let the user try manually later + Network.connectToNetwork(root.network.ssid, "", root.network.bssid, null); + } else { + // No saved profile, try connecting without password first + Network.connectToNetworkWithPasswordCheck( + root.network.ssid, + root.network.isSecure, + () => { + // Callback: connection failed, show password dialog + root.session.network.showPasswordDialog = true; + root.session.network.pendingNetwork = root.network; + }, + root.network.bssid + ); + } + } else { + Network.connectToNetwork(root.network.ssid, "", root.network.bssid, null); + } + } } diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml index 1a4ba00..c64c4be 100644 --- a/modules/controlcenter/network/WirelessList.qml +++ b/modules/controlcenter/network/WirelessList.qml @@ -15,10 +15,6 @@ ColumnLayout { required property Session session - readonly property var connectionHelper: WirelessConnectionHelper { - session: root.session - } - spacing: Appearance.spacing.small RowLayout { @@ -185,7 +181,7 @@ ColumnLayout { if (modelData.active) { Network.disconnectFromNetwork(); } else { - root.connectionHelper.connectToNetwork(modelData); + handleConnect(modelData); } } } @@ -203,5 +199,45 @@ ColumnLayout { implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 } } + + function handleConnect(network): void { + // If already connected to a different network, disconnect first + if (Network.active && Network.active.ssid !== network.ssid) { + Network.disconnectFromNetwork(); + Qt.callLater(() => { + connectToNetwork(network); + }); + } else { + connectToNetwork(network); + } + } + + function connectToNetwork(network): void { + if (network.isSecure) { + // Check if we have a saved connection profile for this network + const hasSavedProfile = Network.savedConnections.includes(network.ssid); + + if (hasSavedProfile) { + // Try connecting with saved password - don't show dialog if it fails + // The saved password should work, but if connection fails for other reasons, + // we'll let the user try manually later + Network.connectToNetwork(network.ssid, "", network.bssid, null); + } else { + // No saved profile, try connecting without password first + Network.connectToNetworkWithPasswordCheck( + network.ssid, + network.isSecure, + () => { + // Callback: connection failed, show password dialog + root.session.network.showPasswordDialog = true; + root.session.network.pendingNetwork = network; + }, + network.bssid + ); + } + } else { + Network.connectToNetwork(network.ssid, "", network.bssid, null); + } + } } diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index 778fb4f..df8a8cf 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -243,6 +243,32 @@ Item { } } + function checkConnectionStatus(): void { + if (!root.visible || !connectButton.connecting) { + return; + } + + // Check if we're connected to the target network + if (root.network && Network.active && Network.active.ssid === root.network.ssid) { + // Successfully connected + connectionMonitor.stop(); + connectButton.connecting = false; + connectButton.text = qsTr("Connect"); + closeDialog(); + return; + } + + // Check for connection errors + const status = Network.connectionStatus; + if (status.includes("Error") || status.includes("error") || status.includes("failed")) { + // Connection failed + connectionMonitor.stop(); + connectButton.connecting = false; + connectButton.enabled = true; + connectButton.text = qsTr("Connect"); + } + } + Timer { id: connectionMonitor interval: 1000 @@ -250,39 +276,15 @@ Item { triggeredOnStart: false onTriggered: { - // Check if we're connected to the target network - if (root.network && Network.active && Network.active.ssid === root.network.ssid) { - // Successfully connected - stop(); - connectButton.connecting = false; - connectButton.text = qsTr("Connect"); - closeDialog(); - } else if (connectButton.connecting) { - // Still connecting, check status - const status = Network.connectionStatus; - if (status.includes("Error") || status.includes("error") || status.includes("failed")) { - // Connection failed - stop(); - connectButton.connecting = false; - connectButton.enabled = true; - connectButton.text = qsTr("Connect"); - } - } else { - // Not connecting anymore - stop(); - } + checkConnectionStatus(); } } Connections { target: Network function onActiveChanged() { - if (root.visible && root.network && Network.active && Network.active.ssid === root.network.ssid) { - // Connected successfully - connectionMonitor.stop(); - connectButton.connecting = false; - connectButton.text = qsTr("Connect"); - closeDialog(); + if (root.visible) { + checkConnectionStatus(); } } } -- cgit v1.2.3-freya From 6ae1313b6b61c965ccc5f2d9d61458d7a5ed21b8 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 12 Nov 2025 22:31:11 -0500 Subject: controlcenter: wireless panel refactoring --- modules/controlcenter/network/WirelessDetails.qml | 24 +- modules/controlcenter/network/WirelessList.qml | 18 +- .../network/WirelessPasswordDialog.qml | 53 +++-- services/Network.qml | 257 ++++++++++++++++++--- 4 files changed, 300 insertions(+), 52 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index 418c463..3e48b55 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -19,10 +19,22 @@ Item { Component.onCompleted: { updateDeviceDetails(); + checkSavedProfile(); } onNetworkChanged: { updateDeviceDetails(); + checkSavedProfile(); + } + + function checkSavedProfile(): void { + // Refresh saved connections list to ensure it's up to date + // This ensures the "Forget Network" button visibility is accurate + if (network && network.ssid) { + // Always refresh to ensure we have the latest saved connections + // This is important when networks are selected or changed + Network.listConnectionsProc.running = true; + } } Connections { @@ -80,7 +92,13 @@ Item { SimpleButton { Layout.fillWidth: true Layout.topMargin: Appearance.spacing.normal - visible: root.network && root.network.ssid && Network.savedConnections.includes(root.network.ssid) + visible: { + if (!root.network || !root.network.ssid) { + return false; + } + // Check if profile exists - this will update reactively when savedConnectionSsids changes + return Network.hasSavedProfile(root.network.ssid); + } color: Colours.palette.m3errorContainer onColor: Colours.palette.m3onErrorContainer text: qsTr("Forget Network") @@ -164,8 +182,8 @@ Item { function connectToNetwork(): void { if (root.network.isSecure) { - // Check if we have a saved connection profile for this network - const hasSavedProfile = Network.savedConnections.includes(root.network.ssid); + // Check if we have a saved connection profile for this network (by SSID) + const hasSavedProfile = Network.hasSavedProfile(root.network.ssid); if (hasSavedProfile) { // Try connecting with saved password - don't show dialog if it fails diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml index c64c4be..aabfc4b 100644 --- a/modules/controlcenter/network/WirelessList.qml +++ b/modules/controlcenter/network/WirelessList.qml @@ -117,6 +117,10 @@ ColumnLayout { StateLayer { function onClicked(): void { root.session.network.active = modelData; + // Check if we need to refresh saved connections when selecting a network + if (modelData && modelData.ssid) { + root.checkSavedProfileForNetwork(modelData.ssid); + } } } @@ -200,6 +204,16 @@ ColumnLayout { } } + function checkSavedProfileForNetwork(ssid: string): void { + // Refresh saved connections list to ensure it's up to date + // This ensures accurate profile detection when selecting networks + if (ssid && ssid.length > 0) { + // Always refresh to ensure we have the latest saved connections + // This is important when a network is selected from the list + Network.listConnectionsProc.running = true; + } + } + function handleConnect(network): void { // If already connected to a different network, disconnect first if (Network.active && Network.active.ssid !== network.ssid) { @@ -214,8 +228,8 @@ ColumnLayout { function connectToNetwork(network): void { if (network.isSecure) { - // Check if we have a saved connection profile for this network - const hasSavedProfile = Network.savedConnections.includes(network.ssid); + // Check if we have a saved connection profile for this network (by SSID) + const hasSavedProfile = Network.hasSavedProfile(network.ssid); if (hasSavedProfile) { // Try connecting with saved password - don't show dialog if it fails diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index df8a8cf..5bcf33c 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -248,24 +248,47 @@ Item { return; } - // Check if we're connected to the target network - if (root.network && Network.active && Network.active.ssid === root.network.ssid) { - // Successfully connected - connectionMonitor.stop(); - connectButton.connecting = false; - connectButton.text = qsTr("Connect"); - closeDialog(); + // Check connection status message for success indicators + const status = Network.connectionStatus; + const statusLower = status.toLowerCase(); + + // Check for success indicators in status message + const hasSuccessIndicator = statusLower.includes("connection activated") || + statusLower.includes("successfully") || + statusLower.includes("connected successfully") || + (statusLower.includes("connected") && !statusLower.includes("error") && !statusLower.includes("failed")); + + // Check if we're connected to the target network (case-insensitive SSID comparison) + const isConnected = root.network && Network.active && Network.active.ssid && + Network.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); + + if (isConnected || hasSuccessIndicator) { + // Successfully connected - give it a moment for network list to update + Qt.callLater(() => { + // Double-check connection is still active + if (root.visible && Network.active && Network.active.ssid) { + const stillConnected = Network.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); + if (stillConnected || hasSuccessIndicator) { + connectionMonitor.stop(); + connectButton.connecting = false; + connectButton.text = qsTr("Connect"); + closeDialog(); + } + } + }, 500); return; } - // Check for connection errors - const status = Network.connectionStatus; - if (status.includes("Error") || status.includes("error") || status.includes("failed")) { - // Connection failed - connectionMonitor.stop(); - connectButton.connecting = false; - connectButton.enabled = true; - connectButton.text = qsTr("Connect"); + // Check for connection errors (but not warnings about duplicate names) + if (status.includes("Error") || (status.includes("error") && !status.includes("Warning"))) { + // Only treat as error if it's not just a warning about duplicate names + if (!status.includes("another connection with the name") && !status.includes("Reference the connection by its uuid")) { + // Connection failed + connectionMonitor.stop(); + connectButton.connecting = false; + connectButton.enabled = true; + connectButton.text = qsTr("Connect"); + } } } diff --git a/services/Network.qml b/services/Network.qml index ea5c3e7..0b936b8 100644 --- a/services/Network.qml +++ b/services/Network.qml @@ -90,19 +90,26 @@ Singleton { if (hasBssid) { // Use BSSID when password is provided - ensure BSSID is uppercase const bssidUpper = bssid.toUpperCase(); - // Create connection profile with all required properties for BSSID + password - // First remove any existing connection with this name - cmd = ["nmcli", "connection", "add", - "type", "wifi", - "con-name", ssid, - "ifname", "*", - "ssid", ssid, - "802-11-wireless.bssid", bssidUpper, - "802-11-wireless-security.key-mgmt", "wpa-psk", - "802-11-wireless-security.psk", password]; - root.setConnectionStatus(qsTr("Connecting to %1 (BSSID: %2)...").arg(ssid).arg(bssidUpper)); - root.addDebugInfo(qsTr("Using BSSID: %1 for SSID: %2").arg(bssidUpper).arg(ssid)); - root.addDebugInfo(qsTr("Creating connection profile with password and key-mgmt")); + + // Check if a connection with this SSID already exists + const existingConnection = root.savedConnections.find(conn => + conn && conn.toLowerCase().trim() === ssid.toLowerCase().trim() + ); + + if (existingConnection) { + // Connection already exists - delete it first, then create new one with updated password + root.addDebugInfo(qsTr("Connection '%1' already exists, deleting it first...").arg(existingConnection)); + deleteConnectionProc.exec(["nmcli", "connection", "delete", existingConnection]); + // Wait a moment for deletion to complete, then create new connection + Qt.callLater(() => { + createConnectionWithPassword(ssid, bssidUpper, password); + }, 300); + return; + } else { + // No existing connection, create new one + createConnectionWithPassword(ssid, bssidUpper, password); + return; + } } else { // Fallback to SSID if BSSID not available - use device wifi connect cmd = ["nmcli", "device", "wifi", "connect", ssid, "password", password]; @@ -164,6 +171,29 @@ Singleton { root.addDebugInfo(qsTr("No callback provided - not starting connection check timer")); } } + + function createConnectionWithPassword(ssid: string, bssidUpper: string, password: string): void { + // Create connection profile with all required properties for BSSID + password + const cmd = ["nmcli", "connection", "add", + "type", "wifi", + "con-name", ssid, + "ifname", "*", + "ssid", ssid, + "802-11-wireless.bssid", bssidUpper, + "802-11-wireless-security.key-mgmt", "wpa-psk", + "802-11-wireless-security.psk", password]; + + root.setConnectionStatus(qsTr("Connecting to %1 (BSSID: %2)...").arg(ssid).arg(bssidUpper)); + root.addDebugInfo(qsTr("Using BSSID: %1 for SSID: %2").arg(bssidUpper).arg(ssid)); + root.addDebugInfo(qsTr("Creating connection profile with password and key-mgmt")); + + // Set command and start process + connectProc.command = cmd; + + Qt.callLater(() => { + connectProc.running = true; + }); + } function connectToNetworkWithPasswordCheck(ssid: string, isSecure: bool, callback: var, bssid: string): void { root.addDebugInfo(qsTr("=== connectToNetworkWithPasswordCheck ===")); @@ -224,23 +254,150 @@ Singleton { } property list savedConnections: [] + property list savedConnectionSsids: [] + property var wifiConnectionQueue: [] + property int currentSsidQueryIndex: 0 Process { id: listConnectionsProc - command: ["nmcli", "-t", "-f", "NAME", "connection", "show"] + command: ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show"] + environment: ({ + LANG: "C.UTF-8", + LC_ALL: "C.UTF-8" + }) + onExited: { + if (exitCode === 0) { + parseConnectionList(stdout.text); + } + } + stdout: StdioCollector { + onStreamFinished: { + parseConnectionList(text); + } + } + } + + function parseConnectionList(output: string): void { + const lines = output.trim().split("\n").filter(line => line.length > 0); + const wifiConnections = []; + const connections = []; + + // First pass: identify WiFi connections + for (const line of lines) { + const parts = line.split(":"); + if (parts.length >= 2) { + const name = parts[0]; + const type = parts[1]; + connections.push(name); + + if (type === "802-11-wireless") { + wifiConnections.push(name); + } + } + } + + root.savedConnections = connections; + + // Second pass: get SSIDs for WiFi connections + if (wifiConnections.length > 0) { + root.wifiConnectionQueue = wifiConnections; + root.currentSsidQueryIndex = 0; + root.savedConnectionSsids = []; + // Start querying SSIDs one by one + queryNextSsid(); + } else { + root.savedConnectionSsids = []; + root.wifiConnectionQueue = []; + } + } + + Process { + id: getSsidProc + + environment: ({ + LANG: "C.UTF-8", + LC_ALL: "C.UTF-8" + }) onExited: { if (exitCode === 0) { - // Parse connection names from output - const connections = stdout.text.trim().split("\n").filter(name => name.length > 0); - root.savedConnections = connections; + processSsidOutput(stdout.text); + } else { + // Move to next connection even if this one failed + queryNextSsid(); } } stdout: StdioCollector { onStreamFinished: { - const connections = text.trim().split("\n").filter(name => name.length > 0); - root.savedConnections = connections; + processSsidOutput(text); + } + } + } + + function processSsidOutput(output: string): void { + // Parse "802-11-wireless.ssid:SSID_NAME" format + const lines = output.trim().split("\n"); + for (const line of lines) { + if (line.startsWith("802-11-wireless.ssid:")) { + const ssid = line.substring("802-11-wireless.ssid:".length).trim(); + if (ssid && ssid.length > 0) { + // Add to list if not already present (case-insensitive) + const ssidLower = ssid.toLowerCase(); + if (!root.savedConnectionSsids.some(s => s && s.toLowerCase() === ssidLower)) { + // Create new array to trigger QML property change notification + const newList = root.savedConnectionSsids.slice(); + newList.push(ssid); + root.savedConnectionSsids = newList; + } + } + } + } + + // Query next connection + queryNextSsid(); + } + + function queryNextSsid(): void { + if (root.currentSsidQueryIndex < root.wifiConnectionQueue.length) { + const connectionName = root.wifiConnectionQueue[root.currentSsidQueryIndex]; + root.currentSsidQueryIndex++; + getSsidProc.command = ["nmcli", "-t", "-f", "802-11-wireless.ssid", "connection", "show", connectionName]; + getSsidProc.running = true; + } else { + // All SSIDs retrieved + root.wifiConnectionQueue = []; + root.currentSsidQueryIndex = 0; + } + } + + function hasSavedProfile(ssid: string): bool { + if (!ssid || ssid.length === 0) { + return false; + } + const ssidLower = ssid.toLowerCase().trim(); + + // If currently connected to this network, it definitely has a saved profile + if (root.active && root.active.ssid) { + const activeSsidLower = root.active.ssid.toLowerCase().trim(); + if (activeSsidLower === ssidLower) { + return true; } } + + // Check if SSID is in saved connections (case-insensitive comparison) + const hasSsid = root.savedConnectionSsids.some(savedSsid => + savedSsid && savedSsid.toLowerCase().trim() === ssidLower + ); + + if (hasSsid) { + return true; + } + + // Fallback: also check if connection name matches SSID (some connections use SSID as name) + const hasConnectionName = root.savedConnections.some(connName => + connName && connName.toLowerCase().trim() === ssidLower + ); + + return hasConnectionName; } function getWifiStatus(): void { @@ -445,22 +602,58 @@ Singleton { && connectProc.command[1] === "connection" && connectProc.command[2] === "add"; - if (wasConnectionAdd && exitCode === 0 && root.pendingConnection) { - // Connection profile was created successfully, now activate it + if (wasConnectionAdd && root.pendingConnection) { const ssid = root.pendingConnection.ssid; - root.addDebugInfo(qsTr("Connection profile created successfully, now activating: %1").arg(ssid)); - root.setConnectionStatus(qsTr("Activating connection...")); - // Update saved connections list since we just created one - listConnectionsProc.running = true; + // Check for duplicate connection warning in stderr text + const stderrText = connectProc.stderr ? connectProc.stderr.text : ""; + const hasDuplicateWarning = stderrText && ( + stderrText.includes("another connection with the name") || + stderrText.includes("Reference the connection by its uuid") + ); - // Activate the connection we just created - connectProc.command = ["nmcli", "connection", "up", ssid]; - Qt.callLater(() => { - connectProc.running = true; - }); - // Don't start timers yet - wait for activation to complete - return; + // Even with duplicate warning (or if connection already exists), we should try to activate it + // Also try if exit code is non-zero but small (might be a warning, not a real error) + if (exitCode === 0 || hasDuplicateWarning || (exitCode > 0 && exitCode < 10)) { + if (hasDuplicateWarning) { + root.addDebugInfo(qsTr("Connection with name '%1' already exists (warning), will try to activate it").arg(ssid)); + root.setConnectionStatus(qsTr("Activating existing connection...")); + } else { + root.addDebugInfo(qsTr("Connection profile created successfully, now activating: %1").arg(ssid)); + root.setConnectionStatus(qsTr("Activating connection...")); + } + + // Update saved connections list + listConnectionsProc.running = true; + + // Try to activate the connection by SSID (connection name) + connectProc.command = ["nmcli", "connection", "up", ssid]; + Qt.callLater(() => { + connectProc.running = true; + }); + // Don't start timers yet - wait for activation to complete + return; + } else { + // Connection add failed - try using device wifi connect as fallback + root.addDebugInfo(qsTr("Connection add failed (exit code %1), trying device wifi connect as fallback").arg(exitCode)); + // Extract password from the command if available + let password = ""; + if (connectProc.command) { + const pskIndex = connectProc.command.findIndex(arg => arg === "802-11-wireless-security.psk"); + if (pskIndex >= 0 && pskIndex + 1 < connectProc.command.length) { + password = connectProc.command[pskIndex + 1]; + } + } + + if (password && password.length > 0) { + root.addDebugInfo(qsTr("Using device wifi connect with password as fallback")); + connectProc.command = ["nmcli", "device", "wifi", "connect", ssid, "password", password]; + Qt.callLater(() => { + connectProc.running = true; + }); + return; + } + } } // Refresh network list after connection attempt -- cgit v1.2.3-freya From 0a33ecf4337fc52ad83c0d5d977fdf4df06b2616 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 12 Nov 2025 22:41:10 -0500 Subject: tray: minor button updates to match --- modules/bar/popouts/Audio.qml | 39 ++++++------------------- modules/bar/popouts/Bluetooth.qml | 40 ++++++-------------------- modules/controlcenter/network/SimpleButton.qml | 27 +++++++++++++---- 3 files changed, 39 insertions(+), 67 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/bar/popouts/Audio.qml b/modules/bar/popouts/Audio.qml index 0954b10..827f868 100644 --- a/modules/bar/popouts/Audio.qml +++ b/modules/bar/popouts/Audio.qml @@ -9,6 +9,7 @@ import Quickshell.Services.Pipewire import QtQuick import QtQuick.Layouts import QtQuick.Controls +import "../../controlcenter/network" Item { id: root @@ -104,40 +105,16 @@ Item { } } - StyledRect { + SimpleButton { + Layout.fillWidth: true Layout.topMargin: Appearance.spacing.normal - - implicitWidth: expandBtn.implicitWidth + Appearance.padding.normal * 2 - implicitHeight: expandBtn.implicitHeight + Appearance.padding.small - - radius: Appearance.rounding.normal color: Colours.palette.m3primaryContainer + onColor: Colours.palette.m3onPrimaryContainer + text: qsTr("Open Settings") + icon: "chevron_right" - StateLayer { - color: Colours.palette.m3onPrimaryContainer - - function onClicked(): void { - root.wrapper.detach("audio"); - } - } - - RowLayout { - id: expandBtn - - anchors.centerIn: parent - spacing: Appearance.spacing.small - - StyledText { - Layout.leftMargin: Appearance.padding.smaller - text: qsTr("Open Settings") - color: Colours.palette.m3onPrimaryContainer - } - - MaterialIcon { - text: "chevron_right" - color: Colours.palette.m3onPrimaryContainer - font.pointSize: Appearance.font.size.large - } + onClicked: { + root.wrapper.detach("audio"); } } } diff --git a/modules/bar/popouts/Bluetooth.qml b/modules/bar/popouts/Bluetooth.qml index a43bba1..60af466 100644 --- a/modules/bar/popouts/Bluetooth.qml +++ b/modules/bar/popouts/Bluetooth.qml @@ -9,6 +9,7 @@ import Quickshell import Quickshell.Bluetooth import QtQuick import QtQuick.Layouts +import "../../controlcenter/network" ColumnLayout { id: root @@ -164,39 +165,16 @@ ColumnLayout { } } - StyledRect { - Layout.topMargin: Appearance.spacing.small - implicitWidth: expandBtn.implicitWidth + Appearance.padding.normal * 2 - implicitHeight: expandBtn.implicitHeight + Appearance.padding.small - - radius: Appearance.rounding.normal + SimpleButton { + Layout.fillWidth: true + Layout.topMargin: Appearance.spacing.normal color: Colours.palette.m3primaryContainer + onColor: Colours.palette.m3onPrimaryContainer + text: qsTr("Open Settings") + icon: "chevron_right" - StateLayer { - color: Colours.palette.m3onPrimaryContainer - - function onClicked(): void { - root.wrapper.detach("bluetooth"); - } - } - - RowLayout { - id: expandBtn - - anchors.centerIn: parent - spacing: Appearance.spacing.small - - StyledText { - Layout.leftMargin: Appearance.padding.smaller - text: qsTr("Open Settings") - color: Colours.palette.m3onPrimaryContainer - } - - MaterialIcon { - text: "chevron_right" - color: Colours.palette.m3onPrimaryContainer - font.pointSize: Appearance.font.size.large - } + onClicked: { + root.wrapper.detach("bluetooth"); } } diff --git a/modules/controlcenter/network/SimpleButton.qml b/modules/controlcenter/network/SimpleButton.qml index 49ab2f4..c949db3 100644 --- a/modules/controlcenter/network/SimpleButton.qml +++ b/modules/controlcenter/network/SimpleButton.qml @@ -1,7 +1,9 @@ import qs.components +import qs.components.controls import qs.components.effects import qs.config import QtQuick +import QtQuick.Layouts StyledRect { id: root @@ -10,9 +12,10 @@ StyledRect { property alias disabled: stateLayer.disabled property alias text: label.text property alias enabled: stateLayer.enabled + property string icon: "" - implicitWidth: label.implicitWidth + Appearance.padding.normal * 2 - implicitHeight: label.implicitHeight + Appearance.padding.normal * 2 + implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2 + implicitHeight: rowLayout.implicitHeight + Appearance.padding.small radius: Appearance.rounding.normal StateLayer { @@ -25,10 +28,24 @@ StyledRect { } } - StyledText { - id: label + RowLayout { + id: rowLayout anchors.centerIn: parent - color: parent.onColor + spacing: Appearance.spacing.small + + StyledText { + id: label + Layout.leftMargin: root.icon.length > 0 ? Appearance.padding.smaller : 0 + color: parent.parent.onColor + } + + MaterialIcon { + id: iconItem + visible: root.icon.length > 0 + text: root.icon + color: root.onColor + font.pointSize: Appearance.font.size.large + } } signal clicked -- cgit v1.2.3-freya From 81203ba7bccdc367b03a40f492b99bfad0445bda Mon Sep 17 00:00:00 2001 From: ATMDA Date: Thu, 13 Nov 2025 08:11:27 -0500 Subject: tray: changed settings icon in button --- modules/bar/popouts/Audio.qml | 2 +- modules/bar/popouts/Bluetooth.qml | 2 +- modules/controlcenter/network/SimpleButton.qml | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/bar/popouts/Audio.qml b/modules/bar/popouts/Audio.qml index 827f868..20e98e5 100644 --- a/modules/bar/popouts/Audio.qml +++ b/modules/bar/popouts/Audio.qml @@ -111,7 +111,7 @@ Item { color: Colours.palette.m3primaryContainer onColor: Colours.palette.m3onPrimaryContainer text: qsTr("Open Settings") - icon: "chevron_right" + icon: "settings" onClicked: { root.wrapper.detach("audio"); diff --git a/modules/bar/popouts/Bluetooth.qml b/modules/bar/popouts/Bluetooth.qml index 60af466..c1f2029 100644 --- a/modules/bar/popouts/Bluetooth.qml +++ b/modules/bar/popouts/Bluetooth.qml @@ -171,7 +171,7 @@ ColumnLayout { color: Colours.palette.m3primaryContainer onColor: Colours.palette.m3onPrimaryContainer text: qsTr("Open Settings") - icon: "chevron_right" + icon: "settings" onClicked: { root.wrapper.detach("bluetooth"); diff --git a/modules/controlcenter/network/SimpleButton.qml b/modules/controlcenter/network/SimpleButton.qml index c949db3..00e5a4e 100644 --- a/modules/controlcenter/network/SimpleButton.qml +++ b/modules/controlcenter/network/SimpleButton.qml @@ -33,12 +33,6 @@ StyledRect { anchors.centerIn: parent spacing: Appearance.spacing.small - StyledText { - id: label - Layout.leftMargin: root.icon.length > 0 ? Appearance.padding.smaller : 0 - color: parent.parent.onColor - } - MaterialIcon { id: iconItem visible: root.icon.length > 0 @@ -46,6 +40,12 @@ StyledRect { color: root.onColor font.pointSize: Appearance.font.size.large } + + StyledText { + id: label + Layout.leftMargin: root.icon.length > 0 ? Appearance.padding.smaller : 0 + color: parent.parent.onColor + } } signal clicked -- cgit v1.2.3-freya From e9113a1710af3dd031c3fa413059d70aed18c4e5 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Thu, 13 Nov 2025 14:12:34 -0500 Subject: cleanup: trailing whitespace --- modules/controlcenter/audio/AudioPane.qml | 4 +--- modules/controlcenter/ethernet/EthernetDetails.qml | 26 +--------------------- modules/controlcenter/ethernet/EthernetList.qml | 23 +------------------ modules/controlcenter/ethernet/EthernetPane.qml | 10 +-------- .../controlcenter/ethernet/EthernetSettings.qml | 23 +------------------ modules/controlcenter/network/SimpleButton.qml | 3 +-- modules/controlcenter/network/WirelessDetails.qml | 3 +-- modules/controlcenter/network/WirelessList.qml | 3 +-- modules/controlcenter/network/WirelessPane.qml | 3 +-- .../network/WirelessPasswordDialog.qml | 3 +-- modules/controlcenter/network/WirelessSettings.qml | 3 +-- 11 files changed, 11 insertions(+), 93 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index 6c478e5..502134a 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -345,6 +345,4 @@ RowLayout { leftThickness: Appearance.padding.normal / 2 } } -} - - +} \ No newline at end of file diff --git a/modules/controlcenter/ethernet/EthernetDetails.qml b/modules/controlcenter/ethernet/EthernetDetails.qml index a49eb4f..d9a004b 100644 --- a/modules/controlcenter/ethernet/EthernetDetails.qml +++ b/modules/controlcenter/ethernet/EthernetDetails.qml @@ -110,28 +110,4 @@ Item { } } -} - - - - - - - - - - - - - - - - - - - - - - - - +} \ No newline at end of file diff --git a/modules/controlcenter/ethernet/EthernetList.qml b/modules/controlcenter/ethernet/EthernetList.qml index 8b04c09..b8c485b 100644 --- a/modules/controlcenter/ethernet/EthernetList.qml +++ b/modules/controlcenter/ethernet/EthernetList.qml @@ -166,25 +166,4 @@ ColumnLayout { implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 } } -} - - - - - - - - - - - - - - - - - - - - - +} \ No newline at end of file diff --git a/modules/controlcenter/ethernet/EthernetPane.qml b/modules/controlcenter/ethernet/EthernetPane.qml index fc3e1c0..b3ff317 100644 --- a/modules/controlcenter/ethernet/EthernetPane.qml +++ b/modules/controlcenter/ethernet/EthernetPane.qml @@ -145,12 +145,4 @@ RowLayout { duration: Appearance.anim.durations.normal / 2 easing.type: Easing.BezierSpline } -} - - - - - - - - +} \ No newline at end of file diff --git a/modules/controlcenter/ethernet/EthernetSettings.qml b/modules/controlcenter/ethernet/EthernetSettings.qml index 33b1449..68a1f61 100644 --- a/modules/controlcenter/ethernet/EthernetSettings.qml +++ b/modules/controlcenter/ethernet/EthernetSettings.qml @@ -81,25 +81,4 @@ ColumnLayout { } } } -} - - - - - - - - - - - - - - - - - - - - - +} \ No newline at end of file diff --git a/modules/controlcenter/network/SimpleButton.qml b/modules/controlcenter/network/SimpleButton.qml index 00e5a4e..7d85e4f 100644 --- a/modules/controlcenter/network/SimpleButton.qml +++ b/modules/controlcenter/network/SimpleButton.qml @@ -49,5 +49,4 @@ StyledRect { } signal clicked -} - +} \ No newline at end of file diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index 3e48b55..7039720 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -207,5 +207,4 @@ Item { Network.connectToNetwork(root.network.ssid, "", root.network.bssid, null); } } -} - +} \ No newline at end of file diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml index aabfc4b..f861db4 100644 --- a/modules/controlcenter/network/WirelessList.qml +++ b/modules/controlcenter/network/WirelessList.qml @@ -253,5 +253,4 @@ ColumnLayout { Network.connectToNetwork(network.ssid, "", network.bssid, null); } } -} - +} \ No newline at end of file diff --git a/modules/controlcenter/network/WirelessPane.qml b/modules/controlcenter/network/WirelessPane.qml index a23b6e8..9d48729 100644 --- a/modules/controlcenter/network/WirelessPane.qml +++ b/modules/controlcenter/network/WirelessPane.qml @@ -151,5 +151,4 @@ RowLayout { duration: Appearance.anim.durations.normal / 2 easing.type: Easing.BezierSpline } -} - +} \ No newline at end of file diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index 5bcf33c..2b33b43 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -320,5 +320,4 @@ Item { connectionMonitor.stop(); Network.clearConnectionStatus(); } -} - +} \ No newline at end of file diff --git a/modules/controlcenter/network/WirelessSettings.qml b/modules/controlcenter/network/WirelessSettings.qml index 7890099..073b09c 100644 --- a/modules/controlcenter/network/WirelessSettings.qml +++ b/modules/controlcenter/network/WirelessSettings.qml @@ -78,5 +78,4 @@ ColumnLayout { value: Network.active ? qsTr("%1 MHz").arg(Network.active.frequency) : qsTr("N/A") } } -} - +} \ No newline at end of file -- cgit v1.2.3-freya From 1da9c68be8f336a671f9514cf5feaaf5998da981 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Thu, 13 Nov 2025 14:41:14 -0500 Subject: cleanup: trailing whitespace removeal (entire project) --- components/controls/CollapsibleSection.qml | 2 +- .../controlcenter/appearance/AppearancePane.qml | 50 +++---- modules/controlcenter/launcher/LauncherPane.qml | 10 +- modules/controlcenter/network/WirelessDetails.qml | 6 +- modules/controlcenter/network/WirelessList.qml | 2 +- .../network/WirelessPasswordDialog.qml | 10 +- modules/controlcenter/taskbar/TaskbarPane.qml | 8 +- modules/drawers/Interactions.qml | 22 +-- services/Network.qml | 154 ++++++++++----------- services/VPN.qml | 6 +- utils/Icons.qml | 4 +- 11 files changed, 137 insertions(+), 137 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/components/controls/CollapsibleSection.qml b/components/controls/CollapsibleSection.qml index 945386c..cb6e62a 100644 --- a/components/controls/CollapsibleSection.qml +++ b/components/controls/CollapsibleSection.qml @@ -12,7 +12,7 @@ ColumnLayout { required property string title property string description: "" property bool expanded: false - + signal toggleRequested spacing: Appearance.spacing.small / 2 diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index 68e2e2d..fc338f9 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -310,20 +310,20 @@ RowLayout { StateLayer { function onClicked(): void { const variant = modelData.variant; - + // Optimistic update - set immediately Schemes.currentVariant = variant; - + // Execute the command Quickshell.execDetached(["caelestia", "scheme", "set", "-v", variant]); - + // Reload after a delay to confirm Qt.callLater(() => { reloadTimer.restart(); }); } } - + Timer { id: reloadTimer interval: 300 @@ -410,20 +410,20 @@ RowLayout { const name = modelData.name; const flavour = modelData.flavour; const schemeKey = `${name} ${flavour}`; - + // Optimistic update - set immediately Schemes.currentScheme = schemeKey; - + // Execute the command Quickshell.execDetached(["caelestia", "scheme", "set", "-n", name, "-f", flavour]); - + // Reload after a delay to confirm Qt.callLater(() => { reloadTimer.restart(); }); } } - + Timer { id: reloadTimer interval: 300 @@ -1053,7 +1053,7 @@ RowLayout { columns: Math.max(1, Math.floor(parent.width / 200)) rowSpacing: Appearance.spacing.normal columnSpacing: Appearance.spacing.normal - + // Center the grid content Layout.maximumWidth: { const cols = columns; @@ -1100,16 +1100,16 @@ RowLayout { path: modelData.path anchors.fill: parent - + // Ensure sourceSize is always set to valid dimensions sourceSize: Qt.size( Math.max(1, Math.floor(parent.width)), Math.max(1, Math.floor(parent.height)) ) - + // Show when ready, hide if fallback is showing opacity: status === Image.Ready && !fallbackImage.visible ? 1 : 0 - + Behavior on opacity { NumberAnimation { duration: 200 @@ -1129,11 +1129,11 @@ RowLayout { Math.max(1, Math.floor(parent.width)), Math.max(1, Math.floor(parent.height)) ) - + // Show if caching image hasn't loaded after a delay visible: opacity > 0 opacity: 0 - + Timer { id: fallbackTimer interval: 500 @@ -1144,7 +1144,7 @@ RowLayout { } } } - + // Also check status changes onStatusChanged: { if (status === Image.Ready && cachingImage.status !== Image.Ready) { @@ -1155,7 +1155,7 @@ RowLayout { }); } } - + Behavior on opacity { NumberAnimation { duration: 200 @@ -1182,26 +1182,26 @@ RowLayout { anchors.right: parent.right anchors.bottom: parent.bottom height: filenameText.implicitHeight + Appearance.padding.normal * 2 - + // Match the parent's rounded corners at the bottom radius: Appearance.rounding.normal - + gradient: Gradient { GradientStop { position: 0.0; color: Qt.rgba(0, 0, 0, 0) } GradientStop { position: 0.3; color: Qt.rgba(0, 0, 0, 0.3) } GradientStop { position: 0.7; color: Qt.rgba(0, 0, 0, 0.75) } GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.85) } } - + opacity: 0 - + Behavior on opacity { NumberAnimation { duration: 200 easing.type: Easing.OutCubic } } - + Component.onCompleted: { opacity = 1; } @@ -1228,20 +1228,20 @@ RowLayout { color: isCurrent ? Colours.palette.m3primary : "#FFFFFF" elide: Text.ElideMiddle maximumLineCount: 1 - + // Text shadow for better readability style: Text.Outline styleColor: Qt.rgba(0, 0, 0, 0.6) - + opacity: 0 - + Behavior on opacity { NumberAnimation { duration: 200 easing.type: Easing.OutCubic } } - + Component.onCompleted: { opacity = 1; } diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index 9b2570a..dd00877 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -53,7 +53,7 @@ RowLayout { try { const config = JSON.parse(configFile.text()); const appId = root.selectedApp.id || root.selectedApp.entry?.id; - + if (config.launcher && config.launcher.hiddenApps) { root.hideFromLauncherChecked = config.launcher.hiddenApps.includes(appId); } else { @@ -72,12 +72,12 @@ RowLayout { try { const config = JSON.parse(configFile.text()); const appId = root.selectedApp.id || root.selectedApp.entry?.id; - + if (!config.launcher) config.launcher = {}; if (!config.launcher.hiddenApps) config.launcher.hiddenApps = []; - + const hiddenApps = config.launcher.hiddenApps; - + if (isHidden) { // Add to hiddenApps if not already there if (!hiddenApps.includes(appId)) { @@ -90,7 +90,7 @@ RowLayout { hiddenApps.splice(index, 1); } } - + const jsonString = JSON.stringify(config, null, 4); configFile.setText(jsonString); } catch (e) { diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index 7039720..d5abc9d 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -26,7 +26,7 @@ Item { updateDeviceDetails(); checkSavedProfile(); } - + function checkSavedProfile(): void { // Refresh saved connections list to ensure it's up to date // This ensures the "Forget Network" button visibility is accurate @@ -102,7 +102,7 @@ Item { color: Colours.palette.m3errorContainer onColor: Colours.palette.m3onErrorContainer text: qsTr("Forget Network") - + onClicked: { if (root.network && root.network.ssid) { // Disconnect first if connected @@ -184,7 +184,7 @@ Item { if (root.network.isSecure) { // Check if we have a saved connection profile for this network (by SSID) const hasSavedProfile = Network.hasSavedProfile(root.network.ssid); - + if (hasSavedProfile) { // Try connecting with saved password - don't show dialog if it fails // The saved password should work, but if connection fails for other reasons, diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml index f861db4..ca6947a 100644 --- a/modules/controlcenter/network/WirelessList.qml +++ b/modules/controlcenter/network/WirelessList.qml @@ -230,7 +230,7 @@ ColumnLayout { if (network.isSecure) { // Check if we have a saved connection profile for this network (by SSID) const hasSavedProfile = Network.hasSavedProfile(network.ssid); - + if (hasSavedProfile) { // Try connecting with saved password - don't show dialog if it fails // The saved password should work, but if connection fails for other reasons, diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index 2b33b43..8a71fa8 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -15,7 +15,7 @@ Item { id: root required property Session session - + readonly property var network: { // Prefer pendingNetwork, then active network if (session.network.pendingNetwork) { @@ -105,7 +105,7 @@ Item { StyledText { id: statusText - + Layout.alignment: Qt.AlignHCenter Layout.topMargin: Appearance.spacing.small visible: Network.connectionStatus.length > 0 || connectButton.connecting @@ -251,15 +251,15 @@ Item { // Check connection status message for success indicators const status = Network.connectionStatus; const statusLower = status.toLowerCase(); - + // Check for success indicators in status message - const hasSuccessIndicator = statusLower.includes("connection activated") || + const hasSuccessIndicator = statusLower.includes("connection activated") || statusLower.includes("successfully") || statusLower.includes("connected successfully") || (statusLower.includes("connected") && !statusLower.includes("error") && !statusLower.includes("failed")); // Check if we're connected to the target network (case-insensitive SSID comparison) - const isConnected = root.network && Network.active && Network.active.ssid && + const isConnected = root.network && Network.active && Network.active.ssid && Network.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); if (isConnected || hasSuccessIndicator) { diff --git a/modules/controlcenter/taskbar/TaskbarPane.qml b/modules/controlcenter/taskbar/TaskbarPane.qml index 2bb50d8..cf52fd3 100644 --- a/modules/controlcenter/taskbar/TaskbarPane.qml +++ b/modules/controlcenter/taskbar/TaskbarPane.qml @@ -120,13 +120,13 @@ RowLayout { if (!configFile.loaded) { return; } - + try { const config = JSON.parse(configFile.text()); - + // Ensure bar object exists if (!config.bar) config.bar = {}; - + // Update clock setting if (!config.bar.clock) config.bar.clock = {}; config.bar.clock.showIcon = clockShowIconSwitch.checked; @@ -163,7 +163,7 @@ RowLayout { // Update entries from the model (same approach as clock - use provided value if available) if (!config.bar.entries) config.bar.entries = []; config.bar.entries = []; - + for (let i = 0; i < entriesModel.count; i++) { const entry = entriesModel.get(i); // If this is the entry being updated, use the provided value (same as clock toggle reads from switch) diff --git a/modules/drawers/Interactions.qml b/modules/drawers/Interactions.qml index 10190a4..2d0c115 100644 --- a/modules/drawers/Interactions.qml +++ b/modules/drawers/Interactions.qml @@ -204,18 +204,18 @@ CustomMouseArea { const panelWidth = panels.notifications.width || panels.notifications.implicitWidth || Config.notifs.sizes.width; const panelX = bar.implicitWidth + panels.notifications.x; const isPanelCollapsed = panelHeight < 10; // Consider collapsed if height is very small - + let showNotifications = inTopPanel(panels.notifications, x, y); - + // Only use fallback corner detection when panel is collapsed if (!showNotifications && isPanelCollapsed) { // Use panel's actual width and position for fallback, with some padding const cornerPadding = Config.border.rounding || 20; - showNotifications = x >= panelX - cornerPadding && - x <= panelX + panelWidth + cornerPadding && + showNotifications = x >= panelX - cornerPadding && + x <= panelX + panelWidth + cornerPadding && y < Config.border.thickness + cornerPadding; } - + // Check if mouse is over the clear all button area // Button is positioned to the left of the notification panel if (!showNotifications && panels.notifications.height > 0 && panels.clearAllButton && panels.clearAllButton.visible) { @@ -223,17 +223,17 @@ CustomMouseArea { const buttonY = Config.border.thickness + panels.clearAllButton.y; const buttonWidth = panels.clearAllButton.width; const buttonHeight = panels.clearAllButton.height; - - const inButtonArea = x >= buttonX && - x <= buttonX + buttonWidth && - y >= buttonY && + + const inButtonArea = x >= buttonX && + x <= buttonX + buttonWidth && + y >= buttonY && y <= buttonY + buttonHeight; - + if (inButtonArea) { showNotifications = true; } } - + // Show or hide notification panel based on hover if (panels.notifications.content) { if (showNotifications) { diff --git a/services/Network.qml b/services/Network.qml index 0b936b8..7732a1c 100644 --- a/services/Network.qml +++ b/services/Network.qml @@ -30,17 +30,17 @@ Singleton { property var wirelessDeviceDetails: null property string connectionStatus: "" property string connectionDebug: "" - + function clearConnectionStatus(): void { connectionStatus = ""; // Don't clear debug - keep it for reference // connectionDebug = ""; } - + function setConnectionStatus(status: string): void { connectionStatus = status; } - + function addDebugInfo(info: string): void { const timestamp = new Date().toLocaleTimeString(); const newInfo = "[" + timestamp + "] " + info; @@ -79,23 +79,23 @@ Singleton { // When no password, use SSID (will use saved password if available) const hasBssid = bssid !== undefined && bssid !== null && bssid.length > 0; let cmd = []; - + // Set up pending connection tracking if callback provided if (callback) { root.pendingConnection = { ssid: ssid, bssid: hasBssid ? bssid : "", callback: callback }; } - + if (password && password.length > 0) { // When password is provided, try BSSID first if available, otherwise use SSID if (hasBssid) { // Use BSSID when password is provided - ensure BSSID is uppercase const bssidUpper = bssid.toUpperCase(); - + // Check if a connection with this SSID already exists - const existingConnection = root.savedConnections.find(conn => + const existingConnection = root.savedConnections.find(conn => conn && conn.toLowerCase().trim() === ssid.toLowerCase().trim() ); - + if (existingConnection) { // Connection already exists - delete it first, then create new one with updated password root.addDebugInfo(qsTr("Connection '%1' already exists, deleting it first...").arg(existingConnection)); @@ -122,7 +122,7 @@ Singleton { root.setConnectionStatus(qsTr("Connecting to %1 (using saved password)...").arg(ssid)); root.addDebugInfo(qsTr("Using saved password for: %1").arg(ssid)); } - + // Show the exact command being executed const cmdStr = cmd.join(" "); root.addDebugInfo(qsTr("=== COMMAND TO EXECUTE ===")); @@ -130,17 +130,17 @@ Singleton { root.addDebugInfo(qsTr("Command array: [%1]").arg(cmd.map((arg, i) => `"${arg}"`).join(", "))); root.addDebugInfo(qsTr("Command array length: %1").arg(cmd.length)); root.addDebugInfo(qsTr("===========================")); - + // Set command and start process root.addDebugInfo(qsTr("Setting command property...")); connectProc.command = cmd; const setCmdStr = connectProc.command ? connectProc.command.join(" ") : "null"; root.addDebugInfo(qsTr("Command property set, value: %1").arg(setCmdStr)); root.addDebugInfo(qsTr("Command property verified: %1").arg(setCmdStr === cmdStr ? "Match" : "MISMATCH")); - + // If we're creating a connection profile, we need to activate it after creation const isConnectionAdd = cmd.length > 0 && cmd[0] === "nmcli" && cmd[1] === "connection" && cmd[2] === "add"; - + // Wait a moment before starting to ensure command is set Qt.callLater(() => { root.addDebugInfo(qsTr("=== STARTING PROCESS ===")); @@ -150,7 +150,7 @@ Singleton { connectProc.running = true; root.addDebugInfo(qsTr("Process running set to: %1").arg(connectProc.running)); root.addDebugInfo(qsTr("========================")); - + // Check if process actually started after a short delay Qt.callLater(() => { root.addDebugInfo(qsTr("Process status check (100ms later):")); @@ -162,7 +162,7 @@ Singleton { } }, 100); }); - + // Start connection check timer if we have a callback if (callback) { root.addDebugInfo(qsTr("Starting connection check timer (4 second interval)")); @@ -171,25 +171,25 @@ Singleton { root.addDebugInfo(qsTr("No callback provided - not starting connection check timer")); } } - + function createConnectionWithPassword(ssid: string, bssidUpper: string, password: string): void { // Create connection profile with all required properties for BSSID + password - const cmd = ["nmcli", "connection", "add", - "type", "wifi", + const cmd = ["nmcli", "connection", "add", + "type", "wifi", "con-name", ssid, "ifname", "*", "ssid", ssid, "802-11-wireless.bssid", bssidUpper, "802-11-wireless-security.key-mgmt", "wpa-psk", "802-11-wireless-security.psk", password]; - + root.setConnectionStatus(qsTr("Connecting to %1 (BSSID: %2)...").arg(ssid).arg(bssidUpper)); root.addDebugInfo(qsTr("Using BSSID: %1 for SSID: %2").arg(bssidUpper).arg(ssid)); root.addDebugInfo(qsTr("Creating connection profile with password and key-mgmt")); - + // Set command and start process connectProc.command = cmd; - + Qt.callLater(() => { connectProc.running = true; }); @@ -198,7 +198,7 @@ Singleton { function connectToNetworkWithPasswordCheck(ssid: string, isSecure: bool, callback: var, bssid: string): void { root.addDebugInfo(qsTr("=== connectToNetworkWithPasswordCheck ===")); root.addDebugInfo(qsTr("SSID: %1, isSecure: %2").arg(ssid).arg(isSecure)); - + // For secure networks, try connecting without password first // If connection succeeds (saved password exists), we're done // If it fails with password error, callback will be called to show password dialog @@ -228,7 +228,7 @@ Singleton { disconnectProc.exec(["nmcli", "device", "disconnect", "wifi"]); } } - + function forgetNetwork(ssid: string): void { // Delete the connection profile for this network // This will remove the saved password and connection settings @@ -240,7 +240,7 @@ Singleton { }, 500); } } - + function hasConnectionProfile(ssid: string): bool { // Check if a connection profile exists for this SSID // This is synchronous check - returns true if connection exists @@ -252,12 +252,12 @@ Singleton { // The actual check will be done asynchronously return false; } - + property list savedConnections: [] property list savedConnectionSsids: [] property var wifiConnectionQueue: [] property int currentSsidQueryIndex: 0 - + Process { id: listConnectionsProc command: ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show"] @@ -276,12 +276,12 @@ Singleton { } } } - + function parseConnectionList(output: string): void { const lines = output.trim().split("\n").filter(line => line.length > 0); const wifiConnections = []; const connections = []; - + // First pass: identify WiFi connections for (const line of lines) { const parts = line.split(":"); @@ -289,15 +289,15 @@ Singleton { const name = parts[0]; const type = parts[1]; connections.push(name); - + if (type === "802-11-wireless") { wifiConnections.push(name); } } } - + root.savedConnections = connections; - + // Second pass: get SSIDs for WiFi connections if (wifiConnections.length > 0) { root.wifiConnectionQueue = wifiConnections; @@ -310,10 +310,10 @@ Singleton { root.wifiConnectionQueue = []; } } - + Process { id: getSsidProc - + environment: ({ LANG: "C.UTF-8", LC_ALL: "C.UTF-8" @@ -332,7 +332,7 @@ Singleton { } } } - + function processSsidOutput(output: string): void { // Parse "802-11-wireless.ssid:SSID_NAME" format const lines = output.trim().split("\n"); @@ -351,11 +351,11 @@ Singleton { } } } - + // Query next connection queryNextSsid(); } - + function queryNextSsid(): void { if (root.currentSsidQueryIndex < root.wifiConnectionQueue.length) { const connectionName = root.wifiConnectionQueue[root.currentSsidQueryIndex]; @@ -368,13 +368,13 @@ Singleton { root.currentSsidQueryIndex = 0; } } - + function hasSavedProfile(ssid: string): bool { if (!ssid || ssid.length === 0) { return false; } const ssidLower = ssid.toLowerCase().trim(); - + // If currently connected to this network, it definitely has a saved profile if (root.active && root.active.ssid) { const activeSsidLower = root.active.ssid.toLowerCase().trim(); @@ -382,21 +382,21 @@ Singleton { return true; } } - + // Check if SSID is in saved connections (case-insensitive comparison) - const hasSsid = root.savedConnectionSsids.some(savedSsid => + const hasSsid = root.savedConnectionSsids.some(savedSsid => savedSsid && savedSsid.toLowerCase().trim() === ssidLower ); - + if (hasSsid) { return true; } - + // Fallback: also check if connection name matches SSID (some connections use SSID as name) - const hasConnectionName = root.savedConnections.some(connName => + const hasConnectionName = root.savedConnections.some(connName => connName && connName.toLowerCase().trim() === ssidLower ); - + return hasConnectionName; } @@ -442,7 +442,7 @@ Singleton { if (isNaN(cidrNum) || cidrNum < 0 || cidrNum > 32) { return ""; } - + const mask = (0xffffffff << (32 - cidrNum)) >>> 0; const octets = [ (mask >>> 24) & 0xff, @@ -450,7 +450,7 @@ Singleton { (mask >>> 8) & 0xff, mask & 0xff ]; - + return octets.join("."); } @@ -510,7 +510,7 @@ Singleton { root.addDebugInfo(qsTr(" Pending SSID: %1").arg(root.pendingConnection.ssid)); root.addDebugInfo(qsTr(" Active SSID: %1").arg(root.active ? root.active.ssid : "None")); root.addDebugInfo(qsTr(" Connected: %1").arg(connected)); - + if (!connected && root.pendingConnection.callback) { // Connection didn't succeed after multiple checks, show password dialog root.addDebugInfo(qsTr("Connection failed - calling password dialog callback")); @@ -543,19 +543,19 @@ Singleton { repeat: true triggeredOnStart: false property int checkCount: 0 - + onRunningChanged: { if (running) { root.addDebugInfo(qsTr("Immediate check timer started (checks every 500ms)")); } } - + onTriggered: { if (root.pendingConnection) { checkCount++; const connected = root.active && root.active.ssid === root.pendingConnection.ssid; root.addDebugInfo(qsTr("Immediate check #%1: Connected=%2").arg(checkCount).arg(connected)); - + if (connected) { // Connection succeeded, stop timers and clear pending root.addDebugInfo(qsTr("Connection succeeded on check #%1!").arg(checkCount)); @@ -586,32 +586,32 @@ Singleton { onRunningChanged: { root.addDebugInfo(qsTr("Process running changed to: %1").arg(running)); } - + onStarted: { root.addDebugInfo(qsTr("Process started successfully")); } - + onExited: { root.addDebugInfo(qsTr("=== PROCESS EXITED ===")); root.addDebugInfo(qsTr("Exit code: %1").arg(exitCode)); root.addDebugInfo(qsTr("(Exit code 0 = success, non-zero = error)")); - + // Check if this was a "connection add" command - if so, we need to activate it - const wasConnectionAdd = connectProc.command && connectProc.command.length > 0 - && connectProc.command[0] === "nmcli" - && connectProc.command[1] === "connection" + const wasConnectionAdd = connectProc.command && connectProc.command.length > 0 + && connectProc.command[0] === "nmcli" + && connectProc.command[1] === "connection" && connectProc.command[2] === "add"; - + if (wasConnectionAdd && root.pendingConnection) { const ssid = root.pendingConnection.ssid; - + // Check for duplicate connection warning in stderr text const stderrText = connectProc.stderr ? connectProc.stderr.text : ""; const hasDuplicateWarning = stderrText && ( stderrText.includes("another connection with the name") || stderrText.includes("Reference the connection by its uuid") ); - + // Even with duplicate warning (or if connection already exists), we should try to activate it // Also try if exit code is non-zero but small (might be a warning, not a real error) if (exitCode === 0 || hasDuplicateWarning || (exitCode > 0 && exitCode < 10)) { @@ -622,10 +622,10 @@ Singleton { root.addDebugInfo(qsTr("Connection profile created successfully, now activating: %1").arg(ssid)); root.setConnectionStatus(qsTr("Activating connection...")); } - + // Update saved connections list listConnectionsProc.running = true; - + // Try to activate the connection by SSID (connection name) connectProc.command = ["nmcli", "connection", "up", ssid]; Qt.callLater(() => { @@ -644,7 +644,7 @@ Singleton { password = connectProc.command[pskIndex + 1]; } } - + if (password && password.length > 0) { root.addDebugInfo(qsTr("Using device wifi connect with password as fallback")); connectProc.command = ["nmcli", "device", "wifi", "connect", ssid, "password", password]; @@ -655,10 +655,10 @@ Singleton { } } } - + // Refresh network list after connection attempt getNetworks.running = true; - + // Check if connection succeeded after a short delay (network list needs to update) if (root.pendingConnection) { if (exitCode === 0) { @@ -704,10 +704,10 @@ Singleton { root.addDebugInfo(qsTr("STDERR: %1").arg(line)); } } - + // Check for specific errors that indicate password is needed // Be careful not to match success messages - const needsPassword = (error.includes("Secrets were required") || + const needsPassword = (error.includes("Secrets were required") || error.includes("No secrets provided") || error.includes("802-11-wireless-security.psk") || (error.includes("password") && !error.includes("Connection activated")) || @@ -715,7 +715,7 @@ Singleton { (error.includes("802.11") && !error.includes("Connection activated"))) && !error.includes("Connection activated") && !error.includes("successfully"); - + if (needsPassword && root.pendingConnection && root.pendingConnection.callback) { // Connection failed because password is needed - show dialog immediately connectionCheckTimer.stop(); @@ -784,7 +784,7 @@ Singleton { Process { id: deleteConnectionProc - + // Delete connection profile - refresh network list and saved connections after deletion onExited: { // Refresh network list and saved connections after deletion @@ -924,12 +924,12 @@ Singleton { onStreamFinished: { const output = text.trim(); root.ethernetDebugInfo = "Output received in onStreamFinished! Length: " + output.length + ", First 100 chars: " + output.substring(0, 100); - + if (!output || output.length === 0) { root.ethernetDebugInfo = "No output received (empty)"; return; } - + root.processEthernetOutput(output); } } @@ -942,7 +942,7 @@ Singleton { const lines = output.split("\n"); root.ethernetDebugInfo = "Processing " + lines.length + " lines"; - + const allDevices = lines.map(d => { const dev = d.replace(rep, PLACEHOLDER).split(":"); return { @@ -952,9 +952,9 @@ Singleton { connection: dev[3]?.replace(rep2, ":") ?? "" }; }); - + root.ethernetDebugInfo = "All devices: " + allDevices.length + ", Types: " + allDevices.map(d => d.type).join(", "); - + const ethernetOnly = allDevices.filter(d => d.type === "ethernet"); root.ethernetDebugInfo = "Ethernet devices found: " + ethernetOnly.length; @@ -975,7 +975,7 @@ Singleton { speed: "" }; }); - + root.ethernetDebugInfo = "Ethernet devices processed: " + ethernetDevices.length + ", First device: " + (ethernetDevices[0]?.interface || "none"); // Update the list - replace the entire array to ensure QML detects the change @@ -984,13 +984,13 @@ Singleton { for (let i = 0; i < ethernetDevices.length; i++) { newDevices.push(ethernetDevices[i]); } - + // Replace the entire list root.ethernetDevices = newDevices; - + // Force QML to detect the change by updating a property root.ethernetDeviceCount = ethernetDevices.length; - + // Force QML to re-evaluate the list by accessing it Qt.callLater(() => { const count = root.ethernetDevices.length; @@ -1130,7 +1130,7 @@ const line = lines[i]; // Find the connected wifi interface from device status const lines = output.split("\n"); let wifiInterface = ""; - + for (let i = 0; i < lines.length; i++) { const line = lines[i]; const parts = line.split(/\s+/); diff --git a/services/VPN.qml b/services/VPN.qml index 10e5e7e..412bda4 100644 --- a/services/VPN.qml +++ b/services/VPN.qml @@ -21,7 +21,7 @@ Singleton { const name = providerName; const iface = interfaceName; const defaults = getBuiltinDefaults(name, iface); - + if (isCustomProvider) { const custom = providerInput; return { @@ -31,7 +31,7 @@ Singleton { displayName: custom.displayName || defaults.displayName }; } - + return defaults; } @@ -62,7 +62,7 @@ Singleton { displayName: "Tailscale" } }; - + return builtins[name] || { connectCmd: [name, "up"], disconnectCmd: [name, "down"], diff --git a/utils/Icons.qml b/utils/Icons.qml index e946c4f..389eca3 100644 --- a/utils/Icons.qml +++ b/utils/Icons.qml @@ -194,13 +194,13 @@ Singleton { function getSpecialWsIcon(name: string): string { name = name.toLowerCase().slice("special:".length); - + for (const iconConfig of Config.bar.workspaces.specialWorkspaceIcons) { if (iconConfig.name === name) { return iconConfig.icon; } } - + if (name === "special") return "star"; if (name === "communication") -- cgit v1.2.3-freya From 7799ec67fda75d8b06f59c491ef119b15d9daf3d Mon Sep 17 00:00:00 2001 From: ATMDA Date: Thu, 13 Nov 2025 19:52:24 -0500 Subject: nmcli: migrated all of wireless controlcenter --- modules/controlcenter/network/WirelessDetails.qml | 61 +++++++++----------- modules/controlcenter/network/WirelessList.qml | 55 +++++++++--------- .../network/WirelessPasswordDialog.qml | 66 ++++++++-------------- modules/controlcenter/network/WirelessSettings.qml | 12 ++-- 4 files changed, 84 insertions(+), 110 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index d5abc9d..4f11589 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -28,28 +28,23 @@ Item { } function checkSavedProfile(): void { - // Refresh saved connections list to ensure it's up to date - // This ensures the "Forget Network" button visibility is accurate if (network && network.ssid) { - // Always refresh to ensure we have the latest saved connections - // This is important when networks are selected or changed - Network.listConnectionsProc.running = true; + Nmcli.loadSavedConnections(() => {}); } } Connections { - target: Network + target: Nmcli function onActiveChanged() { updateDeviceDetails(); } } function updateDeviceDetails(): void { - // Only update details if the selected network is currently active - if (network && Network.active && Network.active.ssid === network.ssid) { - Network.updateWirelessDeviceDetails(); + if (network && Nmcli.active && Nmcli.active.ssid === network.ssid) { + Nmcli.getWirelessDeviceDetails("", () => {}); } else { - Network.wirelessDeviceDetails = null; + Nmcli.wirelessDeviceDetails = null; } } @@ -84,7 +79,7 @@ Item { if (checked) { handleConnect(); } else { - Network.disconnectFromNetwork(); + Nmcli.disconnectFromNetwork(); } } } @@ -96,8 +91,7 @@ Item { if (!root.network || !root.network.ssid) { return false; } - // Check if profile exists - this will update reactively when savedConnectionSsids changes - return Network.hasSavedProfile(root.network.ssid); + return Nmcli.hasSavedProfile(root.network.ssid); } color: Colours.palette.m3errorContainer onColor: Colours.palette.m3onErrorContainer @@ -105,12 +99,10 @@ Item { onClicked: { if (root.network && root.network.ssid) { - // Disconnect first if connected if (root.network.active) { - Network.disconnectFromNetwork(); + Nmcli.disconnectFromNetwork(); } - // Delete the connection profile - Network.forgetNetwork(root.network.ssid); + Nmcli.forgetNetwork(root.network.ssid, () => {}); } } } @@ -161,7 +153,7 @@ Item { SectionContainer { ConnectionInfoSection { - deviceDetails: Network.wirelessDeviceDetails + deviceDetails: Nmcli.wirelessDeviceDetails } } @@ -169,9 +161,8 @@ Item { } function handleConnect(): void { - // If already connected to a different network, disconnect first - if (Network.active && Network.active.ssid !== root.network.ssid) { - Network.disconnectFromNetwork(); + if (Nmcli.active && Nmcli.active.ssid !== root.network.ssid) { + Nmcli.disconnectFromNetwork(); Qt.callLater(() => { connectToNetwork(); }); @@ -182,29 +173,31 @@ Item { function connectToNetwork(): void { if (root.network.isSecure) { - // Check if we have a saved connection profile for this network (by SSID) - const hasSavedProfile = Network.hasSavedProfile(root.network.ssid); + const hasSavedProfile = Nmcli.hasSavedProfile(root.network.ssid); if (hasSavedProfile) { - // Try connecting with saved password - don't show dialog if it fails - // The saved password should work, but if connection fails for other reasons, - // we'll let the user try manually later - Network.connectToNetwork(root.network.ssid, "", root.network.bssid, null); + Nmcli.connectToNetwork(root.network.ssid, "", root.network.bssid, null); } else { - // No saved profile, try connecting without password first - Network.connectToNetworkWithPasswordCheck( + Nmcli.connectToNetworkWithPasswordCheck( root.network.ssid, root.network.isSecure, - () => { - // Callback: connection failed, show password dialog - root.session.network.showPasswordDialog = true; - root.session.network.pendingNetwork = root.network; + (result) => { + if (result.needsPassword) { + if (Nmcli.pendingConnection) { + Nmcli.connectionCheckTimer.stop(); + Nmcli.immediateCheckTimer.stop(); + Nmcli.immediateCheckTimer.checkCount = 0; + Nmcli.pendingConnection = null; + } + root.session.network.showPasswordDialog = true; + root.session.network.pendingNetwork = root.network; + } }, root.network.bssid ); } } else { - Network.connectToNetwork(root.network.ssid, "", root.network.bssid, null); + Nmcli.connectToNetwork(root.network.ssid, "", root.network.bssid, null); } } } \ No newline at end of file diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml index ca6947a..f4e76b4 100644 --- a/modules/controlcenter/network/WirelessList.qml +++ b/modules/controlcenter/network/WirelessList.qml @@ -31,22 +31,22 @@ ColumnLayout { } ToggleButton { - toggled: Network.wifiEnabled + toggled: Nmcli.wifiEnabled icon: "wifi" accent: "Tertiary" onClicked: { - Network.toggleWifi(); + Nmcli.toggleWifi(null); } } ToggleButton { - toggled: Network.scanning + toggled: Nmcli.scanning icon: "wifi_find" accent: "Secondary" onClicked: { - Network.rescanWifi(); + Nmcli.rescanWifi(); } } @@ -70,13 +70,13 @@ ColumnLayout { spacing: Appearance.spacing.small StyledText { - text: qsTr("Networks (%1)").arg(Network.networks.length) + text: qsTr("Networks (%1)").arg(Nmcli.networks.length) font.pointSize: Appearance.font.size.large font.weight: 500 } StyledText { - visible: Network.scanning + visible: Nmcli.scanning text: qsTr("Scanning...") color: Colours.palette.m3primary font.pointSize: Appearance.font.size.small @@ -94,7 +94,7 @@ ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true - model: Network.networks + model: Nmcli.networks spacing: Appearance.spacing.small / 2 clip: true @@ -183,7 +183,7 @@ ColumnLayout { StateLayer { function onClicked(): void { if (modelData.active) { - Network.disconnectFromNetwork(); + Nmcli.disconnectFromNetwork(); } else { handleConnect(modelData); } @@ -205,19 +205,14 @@ ColumnLayout { } function checkSavedProfileForNetwork(ssid: string): void { - // Refresh saved connections list to ensure it's up to date - // This ensures accurate profile detection when selecting networks if (ssid && ssid.length > 0) { - // Always refresh to ensure we have the latest saved connections - // This is important when a network is selected from the list - Network.listConnectionsProc.running = true; + Nmcli.loadSavedConnections(() => {}); } } function handleConnect(network): void { - // If already connected to a different network, disconnect first - if (Network.active && Network.active.ssid !== network.ssid) { - Network.disconnectFromNetwork(); + if (Nmcli.active && Nmcli.active.ssid !== network.ssid) { + Nmcli.disconnectFromNetwork(); Qt.callLater(() => { connectToNetwork(network); }); @@ -228,29 +223,31 @@ ColumnLayout { function connectToNetwork(network): void { if (network.isSecure) { - // Check if we have a saved connection profile for this network (by SSID) - const hasSavedProfile = Network.hasSavedProfile(network.ssid); + const hasSavedProfile = Nmcli.hasSavedProfile(network.ssid); if (hasSavedProfile) { - // Try connecting with saved password - don't show dialog if it fails - // The saved password should work, but if connection fails for other reasons, - // we'll let the user try manually later - Network.connectToNetwork(network.ssid, "", network.bssid, null); + Nmcli.connectToNetwork(network.ssid, "", network.bssid, null); } else { - // No saved profile, try connecting without password first - Network.connectToNetworkWithPasswordCheck( + Nmcli.connectToNetworkWithPasswordCheck( network.ssid, network.isSecure, - () => { - // Callback: connection failed, show password dialog - root.session.network.showPasswordDialog = true; - root.session.network.pendingNetwork = network; + (result) => { + if (result.needsPassword) { + if (Nmcli.pendingConnection) { + Nmcli.connectionCheckTimer.stop(); + Nmcli.immediateCheckTimer.stop(); + Nmcli.immediateCheckTimer.checkCount = 0; + Nmcli.pendingConnection = null; + } + root.session.network.showPasswordDialog = true; + root.session.network.pendingNetwork = network; + } }, network.bssid ); } } else { - Network.connectToNetwork(network.ssid, "", network.bssid, null); + Nmcli.connectToNetwork(network.ssid, "", network.bssid, null); } } } \ No newline at end of file diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index 8a71fa8..e416016 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -108,26 +108,16 @@ Item { Layout.alignment: Qt.AlignHCenter Layout.topMargin: Appearance.spacing.small - visible: Network.connectionStatus.length > 0 || connectButton.connecting + visible: connectButton.connecting text: { - if (Network.connectionStatus.length > 0) { - return Network.connectionStatus; - } else if (connectButton.connecting) { + if (connectButton.connecting) { return qsTr("Connecting..."); } return ""; } - color: { - const status = Network.connectionStatus; - if (status.includes("Error") || status.includes("error") || status.includes("failed")) { - return Colours.palette.m3error; - } else if (status.includes("successful") || status.includes("Connected") || status.includes("success")) { - return Colours.palette.m3primary; - } - return Colours.palette.m3onSurfaceVariant; - } + color: Colours.palette.m3onSurfaceVariant font.pointSize: Appearance.font.size.small - font.weight: (Network.connectionStatus.includes("Error") || Network.connectionStatus.includes("error")) ? 500 : 400 + font.weight: 400 wrapMode: Text.WordWrap Layout.maximumWidth: parent.width - Appearance.padding.large * 2 } @@ -166,7 +156,6 @@ Item { if (root.visible) { passwordField.forceActiveFocus(); passwordField.text = ""; - Network.clearConnectionStatus(); } } } @@ -225,10 +214,9 @@ Item { connecting = true; enabled = false; text = qsTr("Connecting..."); - Network.clearConnectionStatus(); // Connect to network - Network.connectToNetwork( + Nmcli.connectToNetwork( root.network.ssid, password, root.network.bssid || "", @@ -248,27 +236,17 @@ Item { return; } - // Check connection status message for success indicators - const status = Network.connectionStatus; - const statusLower = status.toLowerCase(); - - // Check for success indicators in status message - const hasSuccessIndicator = statusLower.includes("connection activated") || - statusLower.includes("successfully") || - statusLower.includes("connected successfully") || - (statusLower.includes("connected") && !statusLower.includes("error") && !statusLower.includes("failed")); - // Check if we're connected to the target network (case-insensitive SSID comparison) - const isConnected = root.network && Network.active && Network.active.ssid && - Network.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); + const isConnected = root.network && Nmcli.active && Nmcli.active.ssid && + Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); - if (isConnected || hasSuccessIndicator) { + if (isConnected) { // Successfully connected - give it a moment for network list to update Qt.callLater(() => { // Double-check connection is still active - if (root.visible && Network.active && Network.active.ssid) { - const stillConnected = Network.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); - if (stillConnected || hasSuccessIndicator) { + if (root.visible && Nmcli.active && Nmcli.active.ssid) { + const stillConnected = Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); + if (stillConnected) { connectionMonitor.stop(); connectButton.connecting = false; connectButton.text = qsTr("Connect"); @@ -279,11 +257,10 @@ Item { return; } - // Check for connection errors (but not warnings about duplicate names) - if (status.includes("Error") || (status.includes("error") && !status.includes("Warning"))) { - // Only treat as error if it's not just a warning about duplicate names - if (!status.includes("another connection with the name") && !status.includes("Reference the connection by its uuid")) { - // Connection failed + // Check for connection failures - if pending connection was cleared but we're not connected + if (Nmcli.pendingConnection === null && connectButton.connecting) { + // Wait a bit more before giving up (allow time for connection to establish) + if (connectionMonitor.repeatCount > 10) { connectionMonitor.stop(); connectButton.connecting = false; connectButton.enabled = true; @@ -297,14 +274,22 @@ Item { interval: 1000 repeat: true triggeredOnStart: false + property int repeatCount: 0 onTriggered: { + repeatCount++; checkConnectionStatus(); } + + onRunningChanged: { + if (!running) { + repeatCount = 0; + } + } } Connections { - target: Network + target: Nmcli function onActiveChanged() { if (root.visible) { checkConnectionStatus(); @@ -318,6 +303,5 @@ Item { connectButton.connecting = false; connectButton.text = qsTr("Connect"); connectionMonitor.stop(); - Network.clearConnectionStatus(); } -} \ No newline at end of file +} diff --git a/modules/controlcenter/network/WirelessSettings.qml b/modules/controlcenter/network/WirelessSettings.qml index 073b09c..0eb1578 100644 --- a/modules/controlcenter/network/WirelessSettings.qml +++ b/modules/controlcenter/network/WirelessSettings.qml @@ -39,9 +39,9 @@ ColumnLayout { SectionContainer { ToggleRow { label: qsTr("WiFi enabled") - checked: Network.wifiEnabled + checked: Nmcli.wifiEnabled toggle.onToggled: { - Network.enableWifi(checked); + Nmcli.enableWifi(checked); } } } @@ -57,25 +57,25 @@ ColumnLayout { PropertyRow { label: qsTr("Connected network") - value: Network.active ? Network.active.ssid : qsTr("Not connected") + value: Nmcli.active ? Nmcli.active.ssid : qsTr("Not connected") } PropertyRow { showTopMargin: true label: qsTr("Signal strength") - value: Network.active ? qsTr("%1%").arg(Network.active.strength) : qsTr("N/A") + value: Nmcli.active ? qsTr("%1%").arg(Nmcli.active.strength) : qsTr("N/A") } PropertyRow { showTopMargin: true label: qsTr("Security") - value: Network.active ? (Network.active.isSecure ? qsTr("Secured") : qsTr("Open")) : qsTr("N/A") + value: Nmcli.active ? (Nmcli.active.isSecure ? qsTr("Secured") : qsTr("Open")) : qsTr("N/A") } PropertyRow { showTopMargin: true label: qsTr("Frequency") - value: Network.active ? qsTr("%1 MHz").arg(Network.active.frequency) : qsTr("N/A") + value: Nmcli.active ? qsTr("%1 MHz").arg(Nmcli.active.frequency) : qsTr("N/A") } } } \ No newline at end of file -- cgit v1.2.3-freya From 144d04b08b132c9ee662fbde79be7f31e18b3d5b Mon Sep 17 00:00:00 2001 From: ATMDA Date: Thu, 13 Nov 2025 22:42:46 -0500 Subject: controlcenter: password dialog matches lockscreen --- components/controls/StyledTextField.qml | 2 +- .../network/WirelessPasswordDialog.qml | 216 +++++++++++++++++---- 2 files changed, 180 insertions(+), 38 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/components/controls/StyledTextField.qml b/components/controls/StyledTextField.qml index 4db87e9..60bcff2 100644 --- a/components/controls/StyledTextField.qml +++ b/components/controls/StyledTextField.qml @@ -13,7 +13,7 @@ TextField { placeholderTextColor: Colours.palette.m3outline font.family: Appearance.font.family.sans font.pointSize: Appearance.font.size.smaller - renderType: TextField.NativeRendering + renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering cursorVisible: !readOnly background: null diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index e416016..1ea39c9 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -8,6 +8,7 @@ import qs.components.effects import qs.components.containers import qs.services import qs.config +import Quickshell import QtQuick import QtQuick.Layouts @@ -27,7 +28,8 @@ Item { return null; } - visible: session.network.showPasswordDialog + property bool isClosing: false + visible: session.network.showPasswordDialog && !isClosing enabled: visible focus: visible @@ -38,10 +40,14 @@ Item { Rectangle { anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.5) - opacity: root.visible ? 1 : 0 + opacity: root.visible && !root.isClosing ? 1 : 0 Behavior on opacity { - NumberAnimation { duration: 200 } + Anim { + property: "opacity" + duration: Appearance.anim.durations.normal + easing.bezierCurve: Appearance.anim.curves.standardDecel + } } MouseArea { @@ -60,15 +66,23 @@ Item { radius: Appearance.rounding.normal color: Colours.tPalette.m3surface - opacity: root.visible ? 1 : 0 - scale: root.visible ? 1 : 0.9 + opacity: root.visible && !root.isClosing ? 1 : 0 + scale: root.visible && !root.isClosing ? 1 : 0.9 Behavior on opacity { - NumberAnimation { duration: 200 } + Anim { + property: "opacity" + duration: Appearance.anim.durations.normal + easing.bezierCurve: Appearance.anim.curves.standardDecel + } } Behavior on scale { - NumberAnimation { duration: 200 } + Anim { + property: "scale" + duration: Appearance.anim.durations.normal + easing.bezierCurve: Appearance.anim.curves.standardDecel + } } Keys.onEscapePressed: closeDialog(); @@ -123,53 +137,151 @@ Item { } Item { + id: passwordContainer Layout.topMargin: Appearance.spacing.large Layout.fillWidth: true - implicitHeight: passwordField.implicitHeight + Appearance.padding.normal * 2 + implicitHeight: Math.max(48, charList.implicitHeight + Appearance.padding.normal * 2) + + focus: true + Keys.onPressed: event => { + if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { + if (connectButton.enabled) { + connectButton.clicked(); + } + } else if (event.key === Qt.Key_Backspace) { + if (event.modifiers & Qt.ControlModifier) { + passwordBuffer = ""; + } else { + passwordBuffer = passwordBuffer.slice(0, -1); + } + } else if (event.text && event.text.length > 0) { + passwordBuffer += event.text; + } + } + + property string passwordBuffer: "" + + Connections { + target: root + function onVisibleChanged(): void { + if (root.visible) { + passwordContainer.forceActiveFocus(); + passwordContainer.passwordBuffer = ""; + } + } + } StyledRect { anchors.fill: parent radius: Appearance.rounding.normal color: Colours.tPalette.m3surfaceContainer - border.width: passwordField.activeFocus ? 2 : 1 - border.color: passwordField.activeFocus ? Colours.palette.m3primary : Colours.palette.m3outline + border.width: passwordContainer.activeFocus ? 2 : 1 + border.color: passwordContainer.activeFocus ? Colours.palette.m3primary : Colours.palette.m3outline Behavior on border.color { CAnim {} } } - StyledTextField { - id: passwordField + StateLayer { + hoverEnabled: false + cursorShape: Qt.IBeamCursor + + function onClicked(): void { + passwordContainer.forceActiveFocus(); + } + } + + StyledText { + id: placeholder + anchors.centerIn: parent + text: qsTr("Password") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.normal + font.family: Appearance.font.family.mono + opacity: passwordContainer.passwordBuffer ? 0 : 1 + + Behavior on opacity { + Anim {} + } + } + + ListView { + id: charList + + readonly property int fullWidth: count * (implicitHeight + spacing) - spacing + + anchors.centerIn: parent + implicitWidth: fullWidth + implicitHeight: Appearance.font.size.normal + + orientation: Qt.Horizontal + spacing: Appearance.spacing.small / 2 + interactive: false + + model: ScriptModel { + values: passwordContainer.passwordBuffer.split("") + } + + delegate: StyledRect { + id: ch - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal + implicitWidth: implicitHeight + implicitHeight: charList.implicitHeight - echoMode: TextField.Password - placeholderText: qsTr("Password") + color: Colours.palette.m3onSurface + radius: Appearance.rounding.small / 2 + + opacity: 0 + scale: 0 + Component.onCompleted: { + opacity = 1; + scale = 1; + } + ListView.onRemove: removeAnim.start() - Connections { - target: root - function onVisibleChanged(): void { - if (root.visible) { - passwordField.forceActiveFocus(); - passwordField.text = ""; + SequentialAnimation { + id: removeAnim + + PropertyAction { + target: ch + property: "ListView.delayRemove" + value: true + } + ParallelAnimation { + Anim { + target: ch + property: "opacity" + to: 0 + } + Anim { + target: ch + property: "scale" + to: 0.5 + } + } + PropertyAction { + target: ch + property: "ListView.delayRemove" + value: false } } - } - Keys.onReturnPressed: { - if (connectButton.enabled) { - connectButton.clicked(); + Behavior on opacity { + Anim {} } - } - Keys.onEnterPressed: { - if (connectButton.enabled) { - connectButton.clicked(); + + Behavior on scale { + Anim { + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial + } } } + + Behavior on implicitWidth { + Anim {} + } } } @@ -196,7 +308,7 @@ Item { color: Colours.palette.m3primary onColor: Colours.palette.m3onPrimary text: qsTr("Connect") - enabled: passwordField.text.length > 0 && !connecting + enabled: passwordContainer.passwordBuffer.length > 0 && !connecting property bool connecting: false @@ -205,7 +317,7 @@ Item { return; } - const password = passwordField.text; + const password = passwordContainer.passwordBuffer; if (!password || password.length === 0) { return; } @@ -220,7 +332,19 @@ Item { root.network.ssid, password, root.network.bssid || "", - null + (result) => { + if (result && result.success) { + // Connection successful, monitor will handle the rest + } else if (result && result.needsPassword) { + // Shouldn't happen since we provided password + connectionMonitor.stop(); + connecting = false; + enabled = true; + text = qsTr("Connect"); + } else { + // Connection failed, monitor will handle timeout + } + } ); // Start monitoring connection @@ -295,13 +419,31 @@ Item { checkConnectionStatus(); } } + function onConnectionFailed(ssid: string) { + if (root.visible && root.network && root.network.ssid === ssid && connectButton.connecting) { + connectionMonitor.stop(); + connectButton.connecting = false; + connectButton.enabled = true; + connectButton.text = qsTr("Connect"); + } + } } function closeDialog(): void { - session.network.showPasswordDialog = false; - passwordField.text = ""; + if (isClosing) { + return; + } + + isClosing = true; + passwordContainer.passwordBuffer = ""; connectButton.connecting = false; connectButton.text = qsTr("Connect"); connectionMonitor.stop(); + + // Wait for fade-out animation to complete before actually hiding + Qt.callLater(() => { + session.network.showPasswordDialog = false; + isClosing = false; + }, Appearance.anim.durations.normal); } } -- cgit v1.2.3-freya From a004902bc6ff931342d7bc18ef4b448205f833d2 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 08:17:21 -0500 Subject: controlcenter: correcting cancel/connect sizing issues --- modules/controlcenter/network/WirelessPasswordDialog.qml | 2 ++ 1 file changed, 2 insertions(+) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index 1ea39c9..d1ce066 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -294,6 +294,7 @@ Item { id: cancelButton Layout.fillWidth: true + Layout.minimumHeight: Appearance.font.size.normal + Appearance.padding.normal * 2 color: Colours.palette.m3secondaryContainer onColor: Colours.palette.m3onSecondaryContainer text: qsTr("Cancel") @@ -305,6 +306,7 @@ Item { id: connectButton Layout.fillWidth: true + Layout.minimumHeight: Appearance.font.size.normal + Appearance.padding.normal * 2 color: Colours.palette.m3primary onColor: Colours.palette.m3onPrimary text: qsTr("Connect") -- cgit v1.2.3-freya From d9313ca42503127e6f5faccda7f8d7ad3d03f36d Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 08:19:22 -0500 Subject: controlcenter: correcting forget network button sizing issues --- modules/controlcenter/network/WirelessDetails.qml | 1 + 1 file changed, 1 insertion(+) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index 4f11589..ff86b8a 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -87,6 +87,7 @@ Item { SimpleButton { 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; -- cgit v1.2.3-freya From 540cebdbbdafe51e0f946e7a256320537a332612 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 11:59:36 -0500 Subject: controlcenter: merging wireless/ethernet panes (wip/stash) --- modules/controlcenter/NavRail.qml | 5 + modules/controlcenter/Panes.qml | 7 + modules/controlcenter/Session.qml | 2 +- modules/controlcenter/network/NetworkingPane.qml | 579 +++++++++++++++++++++ .../controlcenter/network/NetworkingSettings.qml | 106 ++++ 5 files changed, 698 insertions(+), 1 deletion(-) create mode 100644 modules/controlcenter/network/NetworkingPane.qml create mode 100644 modules/controlcenter/network/NetworkingSettings.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml index ca36048..abe2ea2 100644 --- a/modules/controlcenter/NavRail.qml +++ b/modules/controlcenter/NavRail.qml @@ -191,6 +191,11 @@ Item { icon: "apps" label: "launcher" } + + NavItem { + icon: "router" + label: "networking" + } } component NavItem: Item { diff --git a/modules/controlcenter/Panes.qml b/modules/controlcenter/Panes.qml index 11e60d3..0958902 100644 --- a/modules/controlcenter/Panes.qml +++ b/modules/controlcenter/Panes.qml @@ -76,6 +76,13 @@ ClippingRectangle { } } + Pane { + index: 7 + sourceComponent: NetworkingPane { + session: root.session + } + } + Behavior on y { Anim {} } diff --git a/modules/controlcenter/Session.qml b/modules/controlcenter/Session.qml index d44898e..1ff9688 100644 --- a/modules/controlcenter/Session.qml +++ b/modules/controlcenter/Session.qml @@ -2,7 +2,7 @@ import Quickshell.Bluetooth import QtQuick QtObject { - readonly property list panes: ["ethernet", "wireless", "bluetooth", "audio", "appearance", "taskbar", "launcher"] + readonly property list panes: ["ethernet", "wireless", "bluetooth", "audio", "appearance", "taskbar", "launcher", "networking"] required property var root property bool floating: false diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml new file mode 100644 index 0000000..f220960 --- /dev/null +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -0,0 +1,579 @@ +pragma ComponentBehavior: Bound + +import ".." +import "../ethernet" +import "." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.components.containers +import qs.services +import qs.config +import Quickshell.Widgets +import QtQuick +import QtQuick.Layouts + +RowLayout { + id: root + + required property Session session + + anchors.fill: parent + + spacing: 0 + + Item { + Layout.preferredWidth: Math.floor(parent.width * 0.4) + Layout.minimumWidth: 420 + Layout.fillHeight: true + + // Left pane - networking list with collapsible sections + StyledFlickable { + anchors.fill: parent + anchors.margins: Appearance.padding.large + Appearance.padding.normal + anchors.leftMargin: Appearance.padding.large + anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + flickableDirection: Flickable.VerticalFlick + contentHeight: leftContent.height + + ColumnLayout { + id: leftContent + + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.normal + + // Settings header above the collapsible sections + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.smaller + + StyledText { + text: qsTr("Settings") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + Item { + Layout.fillWidth: true + } + + ToggleButton { + toggled: Nmcli.wifiEnabled + icon: "wifi" + accent: "Tertiary" + + onClicked: { + Nmcli.toggleWifi(null); + } + } + + ToggleButton { + toggled: Nmcli.scanning + icon: "wifi_find" + accent: "Secondary" + + onClicked: { + Nmcli.rescanWifi(); + } + } + + ToggleButton { + toggled: !root.session.ethernet.active && !root.session.network.active + icon: "settings" + accent: "Primary" + + onClicked: { + if (root.session.ethernet.active || root.session.network.active) { + root.session.ethernet.active = null; + root.session.network.active = null; + } else { + // Toggle to show settings - prefer ethernet if available, otherwise wireless + if (Nmcli.ethernetDevices.length > 0) { + root.session.ethernet.active = ethernetListView.model.get(0)?.modelData ?? null; + } else if (Nmcli.networks.length > 0) { + root.session.network.active = wirelessListView.model.get(0)?.modelData ?? null; + } + } + } + } + } + + CollapsibleSection { + id: ethernetListSection + + Layout.fillWidth: true + title: qsTr("Ethernet") + expanded: true + + ColumnLayout { + 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.large + font.weight: 500 + } + } + + StyledText { + Layout.fillWidth: true + text: qsTr("All available ethernet devices") + color: Colours.palette.m3outline + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Nmcli.ethernetDevices.length > 0 ? Math.min(400, Math.max(200, Nmcli.ethernetDevices.length * 80)) : 200 + + StyledListView { + id: ethernetListView + + anchors.fill: parent + + model: Nmcli.ethernetDevices + + spacing: Appearance.spacing.small / 2 + clip: true + + StyledScrollBar.vertical: StyledScrollBar { + flickable: ethernetListView + } + + delegate: 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) + radius: Appearance.rounding.normal + border.width: root.session.ethernet.active === modelData ? 1 : 0 + border.color: Colours.palette.m3primary + + StateLayer { + function onClicked(): void { + 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 + } + } + + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 + + text: modelData.interface || qsTr("Unknown") + } + + StyledText { + 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 + } + + 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 + } + } + } + } + } + + CollapsibleSection { + id: wirelessListSection + + Layout.fillWidth: true + title: qsTr("Wireless") + expanded: true + + ColumnLayout { + 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.large + 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 + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Nmcli.networks.length > 0 ? Math.min(400, Math.max(200, Nmcli.networks.length * 80)) : 200 + + StyledListView { + id: wirelessListView + + anchors.fill: parent + + model: Nmcli.networks + + spacing: Appearance.spacing.small / 2 + clip: true + + StyledScrollBar.vertical: StyledScrollBar { + flickable: wirelessListView + } + + delegate: 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) + radius: Appearance.rounding.normal + border.width: root.session.network.active === modelData ? 1 : 0 + border.color: Colours.palette.m3primary + + StateLayer { + function onClicked(): void { + root.session.network.active = modelData; + // Check if we need to refresh saved connections when selecting a network + if (modelData && 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.active ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh + + MaterialIcon { + id: wirelessIcon + + anchors.centerIn: parent + text: modelData.isSecure ? "lock" : "wifi" + font.pointSize: Appearance.font.size.large + fill: modelData.active ? 1 : 0 + color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface + } + } + + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 + + text: modelData.ssid || qsTr("Unknown") + } + + StyledText { + text: modelData.active ? qsTr("Connected") : (modelData.isSecure ? qsTr("Secured") : qsTr("Open")) + color: modelData.active ? Colours.palette.m3primary : Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + font.weight: modelData.active ? 500 : 400 + } + + StyledText { + text: qsTr("%1%").arg(modelData.strength) + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledRect { + implicitWidth: implicitHeight + implicitHeight: wirelessConnectIcon.implicitHeight + Appearance.padding.smaller * 2 + + radius: Appearance.rounding.full + color: Qt.alpha(Colours.palette.m3primaryContainer, modelData.active ? 1 : 0) + + StateLayer { + function onClicked(): void { + if (modelData.active) { + Nmcli.disconnectFromNetwork(); + } else { + handleWirelessConnect(modelData); + } + } + } + + MaterialIcon { + id: wirelessConnectIcon + + anchors.centerIn: parent + text: modelData.active ? "link_off" : "link" + color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface + } + } + } + + implicitHeight: wirelessRowLayout.implicitHeight + Appearance.padding.normal * 2 + } + } + } + } + } + } + } + + InnerBorder { + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + ClippingRectangle { + anchors.fill: parent + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 + + radius: rightBorder.innerRadius + color: "transparent" + + // Right pane - networking details/settings + Loader { + id: loader + + property var ethernetPane: root.session.ethernet.active + property var wirelessPane: root.session.network.active + property var pane: ethernetPane || wirelessPane + property string paneId: ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : "") + + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + opacity: 1 + scale: 1 + transformOrigin: Item.Center + + clip: false + asynchronous: true + sourceComponent: pane ? (ethernetPane ? ethernetDetails : wirelessDetails) : settings + + Behavior on paneId { + SequentialAnimation { + ParallelAnimation { + Anim { + target: loader + property: "opacity" + to: 0 + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + Anim { + target: loader + property: "scale" + to: 0.8 + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + } + PropertyAction {} + ParallelAnimation { + Anim { + target: loader + property: "opacity" + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + Anim { + target: loader + property: "scale" + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + } + } + } + + onPaneChanged: { + paneId = ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : ""); + } + } + } + + InnerBorder { + id: rightBorder + + leftThickness: Appearance.padding.normal / 2 + } + + Component { + id: settings + + StyledFlickable { + flickableDirection: Flickable.VerticalFlick + contentHeight: settingsInner.height + + NetworkingSettings { + id: settingsInner + + anchors.left: parent.left + anchors.right: parent.right + session: root.session + } + } + } + + Component { + id: ethernetDetails + + EthernetDetails { + session: root.session + } + } + + Component { + id: wirelessDetails + + WirelessDetails { + session: root.session + } + } + } + + WirelessPasswordDialog { + anchors.fill: parent + session: root.session + z: 1000 + } + + component Anim: NumberAnimation { + target: loader + duration: Appearance.anim.durations.normal / 2 + easing.type: Easing.BezierSpline + } + + function checkSavedProfileForNetwork(ssid: string): void { + if (ssid && ssid.length > 0) { + Nmcli.loadSavedConnections(() => {}); + } + } + + function handleWirelessConnect(network): void { + if (Nmcli.active && Nmcli.active.ssid !== network.ssid) { + Nmcli.disconnectFromNetwork(); + Qt.callLater(() => { + connectToWirelessNetwork(network); + }); + } else { + connectToWirelessNetwork(network); + } + } + + function connectToWirelessNetwork(network): void { + if (network.isSecure) { + const hasSavedProfile = Nmcli.hasSavedProfile(network.ssid); + + if (hasSavedProfile) { + Nmcli.connectToNetwork(network.ssid, "", network.bssid, null); + } else { + Nmcli.connectToNetworkWithPasswordCheck( + network.ssid, + network.isSecure, + (result) => { + if (result.needsPassword) { + if (Nmcli.pendingConnection) { + Nmcli.connectionCheckTimer.stop(); + Nmcli.immediateCheckTimer.stop(); + Nmcli.immediateCheckTimer.checkCount = 0; + Nmcli.pendingConnection = null; + } + root.session.network.showPasswordDialog = true; + root.session.network.pendingNetwork = network; + } + }, + network.bssid + ); + } + } else { + Nmcli.connectToNetwork(network.ssid, "", network.bssid, null); + } + } +} + diff --git a/modules/controlcenter/network/NetworkingSettings.qml b/modules/controlcenter/network/NetworkingSettings.qml new file mode 100644 index 0000000..2475fed --- /dev/null +++ b/modules/controlcenter/network/NetworkingSettings.qml @@ -0,0 +1,106 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property Session session + + spacing: Appearance.spacing.normal + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + text: "router" + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Networking settings") + font.pointSize: Appearance.font.size.large + font.bold: true + } + + SectionHeader { + Layout.topMargin: Appearance.spacing.large + title: qsTr("Ethernet") + description: qsTr("Ethernet device information") + } + + SectionContainer { + contentSpacing: Appearance.spacing.small / 2 + + PropertyRow { + label: qsTr("Total devices") + value: qsTr("%1").arg(Nmcli.ethernetDevices.length) + } + + PropertyRow { + showTopMargin: true + label: qsTr("Connected devices") + value: qsTr("%1").arg(Nmcli.ethernetDevices.filter(d => d.connected).length) + } + } + + SectionHeader { + Layout.topMargin: Appearance.spacing.large + title: qsTr("Wireless") + description: qsTr("WiFi network settings") + } + + SectionContainer { + ToggleRow { + label: qsTr("WiFi enabled") + checked: Nmcli.wifiEnabled + toggle.onToggled: { + Nmcli.enableWifi(checked); + } + } + } + + SectionHeader { + Layout.topMargin: Appearance.spacing.large + title: qsTr("Current connection") + description: qsTr("Active network connection information") + } + + SectionContainer { + contentSpacing: Appearance.spacing.small / 2 + + PropertyRow { + label: qsTr("Network") + value: Nmcli.active ? Nmcli.active.ssid : (Nmcli.activeEthernet ? Nmcli.activeEthernet.interface : qsTr("Not connected")) + } + + PropertyRow { + showTopMargin: true + visible: Nmcli.active !== null + label: qsTr("Signal strength") + value: Nmcli.active ? qsTr("%1%").arg(Nmcli.active.strength) : qsTr("N/A") + } + + PropertyRow { + showTopMargin: true + visible: Nmcli.active !== null + label: qsTr("Security") + value: Nmcli.active ? (Nmcli.active.isSecure ? qsTr("Secured") : qsTr("Open")) : qsTr("N/A") + } + + PropertyRow { + showTopMargin: true + visible: Nmcli.active !== null + label: qsTr("Frequency") + value: Nmcli.active ? qsTr("%1 MHz").arg(Nmcli.active.frequency) : qsTr("N/A") + } + } +} + -- cgit v1.2.3-freya From 6043adf66615e3354a30f7efe24a1332bd10e3e5 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 12:05:07 -0500 Subject: controlcenter: merged wireless/ethernet panes --- modules/controlcenter/NavRail.qml | 14 +- modules/controlcenter/network/NetworkingPane.qml | 188 ++++++++++------------- 2 files changed, 82 insertions(+), 120 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml index abe2ea2..80a1ee3 100644 --- a/modules/controlcenter/NavRail.qml +++ b/modules/controlcenter/NavRail.qml @@ -158,13 +158,8 @@ Item { NavItem { Layout.topMargin: Appearance.spacing.large * 2 - icon: "cable" - label: "ethernet" - } - - NavItem { - icon: "wifi" - label: "wireless" + icon: "router" + label: "networking" } NavItem { @@ -191,11 +186,6 @@ Item { icon: "apps" label: "launcher" } - - NavItem { - icon: "router" - label: "networking" - } } component NavItem: Item { diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index f220960..8616c85 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -90,9 +90,9 @@ RowLayout { } else { // Toggle to show settings - prefer ethernet if available, otherwise wireless if (Nmcli.ethernetDevices.length > 0) { - root.session.ethernet.active = ethernetListView.model.get(0)?.modelData ?? null; + root.session.ethernet.active = Nmcli.ethernetDevices[0]; } else if (Nmcli.networks.length > 0) { - root.session.network.active = wirelessListView.model.get(0)?.modelData ?? null; + root.session.network.active = Nmcli.networks[0]; } } } @@ -127,29 +127,16 @@ RowLayout { color: Colours.palette.m3outline } - Item { - Layout.fillWidth: true - Layout.preferredHeight: Nmcli.ethernetDevices.length > 0 ? Math.min(400, Math.max(200, Nmcli.ethernetDevices.length * 80)) : 200 - - StyledListView { - id: ethernetListView - - anchors.fill: parent + Repeater { + id: ethernetRepeater + Layout.fillWidth: true model: Nmcli.ethernetDevices - spacing: Appearance.spacing.small / 2 - clip: true - - StyledScrollBar.vertical: StyledScrollBar { - flickable: ethernetListView - } - - delegate: StyledRect { + delegate: StyledRect { required property var modelData - anchors.left: parent.left - anchors.right: parent.right + Layout.fillWidth: true color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.ethernet.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal @@ -233,7 +220,6 @@ RowLayout { } implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 - } } } } @@ -274,123 +260,109 @@ RowLayout { color: Colours.palette.m3outline } - Item { - Layout.fillWidth: true - Layout.preferredHeight: Nmcli.networks.length > 0 ? Math.min(400, Math.max(200, Nmcli.networks.length * 80)) : 200 + Repeater { + id: wirelessRepeater - StyledListView { - id: wirelessListView + Layout.fillWidth: true + model: Nmcli.networks - anchors.fill: parent + delegate: StyledRect { + required property var modelData - model: Nmcli.networks + Layout.fillWidth: true - spacing: Appearance.spacing.small / 2 - clip: true + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.network.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: root.session.network.active === modelData ? 1 : 0 + border.color: Colours.palette.m3primary - StyledScrollBar.vertical: StyledScrollBar { - flickable: wirelessListView + StateLayer { + function onClicked(): void { + root.session.network.active = modelData; + // Check if we need to refresh saved connections when selecting a network + if (modelData && modelData.ssid) { + checkSavedProfileForNetwork(modelData.ssid); + } + } } - delegate: StyledRect { - required property var modelData + RowLayout { + id: wirelessRowLayout anchors.left: parent.left anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.network.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: root.session.network.active === modelData ? 1 : 0 - border.color: Colours.palette.m3primary - - StateLayer { - function onClicked(): void { - root.session.network.active = modelData; - // Check if we need to refresh saved connections when selecting a network - if (modelData && 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 + spacing: Appearance.spacing.normal - StyledRect { - implicitWidth: implicitHeight - implicitHeight: wirelessIcon.implicitHeight + Appearance.padding.normal * 2 + StyledRect { + implicitWidth: implicitHeight + implicitHeight: wirelessIcon.implicitHeight + Appearance.padding.normal * 2 - radius: Appearance.rounding.normal - color: modelData.active ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh + radius: Appearance.rounding.normal + color: modelData.active ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh - MaterialIcon { - id: wirelessIcon + MaterialIcon { + id: wirelessIcon - anchors.centerIn: parent - text: modelData.isSecure ? "lock" : "wifi" - font.pointSize: Appearance.font.size.large - fill: modelData.active ? 1 : 0 - color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface - } + anchors.centerIn: parent + text: modelData.isSecure ? "lock" : "wifi" + font.pointSize: Appearance.font.size.large + fill: modelData.active ? 1 : 0 + color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface } + } - StyledText { - Layout.fillWidth: true - elide: Text.ElideRight - maximumLineCount: 1 + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 - text: modelData.ssid || qsTr("Unknown") - } + text: modelData.ssid || qsTr("Unknown") + } - StyledText { - text: modelData.active ? qsTr("Connected") : (modelData.isSecure ? qsTr("Secured") : qsTr("Open")) - color: modelData.active ? Colours.palette.m3primary : Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - font.weight: modelData.active ? 500 : 400 - } + StyledText { + text: modelData.active ? qsTr("Connected") : (modelData.isSecure ? qsTr("Secured") : qsTr("Open")) + color: modelData.active ? Colours.palette.m3primary : Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + font.weight: modelData.active ? 500 : 400 + } - StyledText { - text: qsTr("%1%").arg(modelData.strength) - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } + StyledText { + text: qsTr("%1%").arg(modelData.strength) + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } - StyledRect { - implicitWidth: implicitHeight - implicitHeight: wirelessConnectIcon.implicitHeight + Appearance.padding.smaller * 2 + StyledRect { + implicitWidth: implicitHeight + implicitHeight: wirelessConnectIcon.implicitHeight + Appearance.padding.smaller * 2 - radius: Appearance.rounding.full - color: Qt.alpha(Colours.palette.m3primaryContainer, modelData.active ? 1 : 0) + radius: Appearance.rounding.full + color: Qt.alpha(Colours.palette.m3primaryContainer, modelData.active ? 1 : 0) - StateLayer { - function onClicked(): void { - if (modelData.active) { - Nmcli.disconnectFromNetwork(); - } else { - handleWirelessConnect(modelData); - } + StateLayer { + function onClicked(): void { + if (modelData.active) { + Nmcli.disconnectFromNetwork(); + } else { + handleWirelessConnect(modelData); } } + } - MaterialIcon { - id: wirelessConnectIcon + MaterialIcon { + id: wirelessConnectIcon - anchors.centerIn: parent - text: modelData.active ? "link_off" : "link" - color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface - } + anchors.centerIn: parent + text: modelData.active ? "link_off" : "link" + color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface } } - - implicitHeight: wirelessRowLayout.implicitHeight + Appearance.padding.normal * 2 } + + implicitHeight: wirelessRowLayout.implicitHeight + Appearance.padding.normal * 2 } } } -- cgit v1.2.3-freya From c30022b75e811488928c9cb6a0c6a3ebeeb6b0e3 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 13:48:32 -0500 Subject: controlcenter: fix bug, couldn't select wifi network while ethernet interface was selected --- modules/controlcenter/network/NetworkingPane.qml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 8616c85..5271c56 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -145,6 +145,7 @@ RowLayout { StateLayer { function onClicked(): void { + root.session.network.active = null; root.session.ethernet.active = modelData; } } @@ -276,15 +277,16 @@ RowLayout { border.width: root.session.network.active === modelData ? 1 : 0 border.color: Colours.palette.m3primary - StateLayer { - function onClicked(): void { - root.session.network.active = modelData; - // Check if we need to refresh saved connections when selecting a network - if (modelData && modelData.ssid) { - checkSavedProfileForNetwork(modelData.ssid); + StateLayer { + function onClicked(): void { + root.session.ethernet.active = null; + root.session.network.active = modelData; + // Check if we need to refresh saved connections when selecting a network + if (modelData && modelData.ssid) { + checkSavedProfileForNetwork(modelData.ssid); + } } } - } RowLayout { id: wirelessRowLayout -- cgit v1.2.3-freya From f39c4b6e6c446bf503acaf1cb5468c3e98b46d52 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 13:55:12 -0500 Subject: controlcenter: fix bug, connection information update event --- modules/controlcenter/network/WirelessDetails.qml | 54 ++++++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index ff86b8a..334c4b3 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -23,6 +23,11 @@ Item { } onNetworkChanged: { + // Restart timer when network changes + connectionUpdateTimer.stop(); + if (network && network.ssid) { + connectionUpdateTimer.start(); + } updateDeviceDetails(); checkSavedProfile(); } @@ -38,11 +43,56 @@ Item { function onActiveChanged() { updateDeviceDetails(); } + function onWirelessDeviceDetailsChanged() { + // When details are updated, check if we should stop the timer + if (network && network.ssid) { + const isActive = network.active || (Nmcli.active && Nmcli.active.ssid === network.ssid); + if (isActive && Nmcli.wirelessDeviceDetails && Nmcli.wirelessDeviceDetails !== null) { + // We have details for the active network, stop the timer + connectionUpdateTimer.stop(); + } + } + } + } + + Timer { + id: connectionUpdateTimer + interval: 500 + repeat: true + running: network && network.ssid + onTriggered: { + // Periodically check if network becomes active and update details + if (network) { + const isActive = network.active || (Nmcli.active && Nmcli.active.ssid === network.ssid); + if (isActive) { + // Network is active - check if we have details + if (!Nmcli.wirelessDeviceDetails || Nmcli.wirelessDeviceDetails === null) { + // Network is active but we don't have details yet, fetch them + Nmcli.getWirelessDeviceDetails("", () => { + // After fetching, check if we got details - if not, timer will try again + }); + } else { + // We have details, can stop the timer + connectionUpdateTimer.stop(); + } + } else { + // Network is not active, clear details + if (Nmcli.wirelessDeviceDetails !== null) { + Nmcli.wirelessDeviceDetails = null; + } + } + } + } } function updateDeviceDetails(): void { - if (network && Nmcli.active && Nmcli.active.ssid === network.ssid) { - Nmcli.getWirelessDeviceDetails("", () => {}); + if (network && network.ssid) { + const isActive = network.active || (Nmcli.active && Nmcli.active.ssid === network.ssid); + if (isActive) { + Nmcli.getWirelessDeviceDetails("", () => {}); + } else { + Nmcli.wirelessDeviceDetails = null; + } } else { Nmcli.wirelessDeviceDetails = null; } -- cgit v1.2.3-freya 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 --- components/controls/CollapsibleSection.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 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/components/controls/CollapsibleSection.qml b/components/controls/CollapsibleSection.qml index a22ad99..5bec5f8 100644 --- a/components/controls/CollapsibleSection.qml +++ b/components/controls/CollapsibleSection.qml @@ -34,7 +34,7 @@ ColumnLayout { StyledText { text: root.title - font.pointSize: Appearance.font.size.normal + font.pointSize: Appearance.font.size.larger font.weight: 500 } diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index 502134a..cf6e85a 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -48,7 +48,7 @@ RowLayout { StyledText { text: qsTr("Output devices (%1)").arg(Audio.sinks.length) - font.pointSize: Appearance.font.size.large + font.pointSize: Appearance.font.size.normal font.weight: 500 } @@ -119,7 +119,7 @@ RowLayout { StyledText { Layout.topMargin: Appearance.spacing.large text: qsTr("Input devices (%1)").arg(Audio.sources.length) - font.pointSize: Appearance.font.size.large + font.pointSize: Appearance.font.size.normal font.weight: 500 } 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 } diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index dd00877..7408101 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -139,7 +139,7 @@ RowLayout { StyledText { Layout.topMargin: Appearance.spacing.large text: qsTr("Applications (%1)").arg(allAppsDb.apps.length) - font.pointSize: Appearance.font.size.larger + font.pointSize: Appearance.font.size.normal font.weight: 500 } diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 5271c56..7db6f54 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -116,7 +116,7 @@ RowLayout { StyledText { text: qsTr("Devices (%1)").arg(Nmcli.ethernetDevices.length) - font.pointSize: Appearance.font.size.large + font.pointSize: Appearance.font.size.normal font.weight: 500 } } @@ -243,7 +243,7 @@ RowLayout { StyledText { text: qsTr("Networks (%1)").arg(Nmcli.networks.length) - font.pointSize: Appearance.font.size.large + font.pointSize: Appearance.font.size.normal font.weight: 500 } -- cgit v1.2.3-freya From 5994c77dea6eec5a70458872872997d3f014cfb6 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 14:11:40 -0500 Subject: controlcenter: minor adjustments of accordion menu --- modules/controlcenter/audio/AudioPane.qml | 255 +++++++++++++---------- modules/controlcenter/network/NetworkingPane.qml | 14 ++ 2 files changed, 156 insertions(+), 113 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index cf6e85a..616d479 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -24,166 +24,195 @@ RowLayout { Layout.minimumWidth: 420 Layout.fillHeight: true - ColumnLayout { + StyledFlickable { anchors.fill: parent anchors.margins: Appearance.padding.large + Appearance.padding.normal anchors.leftMargin: Appearance.padding.large anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + flickableDirection: Flickable.VerticalFlick + contentHeight: leftContent.height - spacing: Appearance.spacing.small + ColumnLayout { + id: leftContent - RowLayout { - spacing: Appearance.spacing.smaller + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.normal - StyledText { - text: qsTr("Settings") - font.pointSize: Appearance.font.size.large - font.weight: 500 + // Settings header above the collapsible sections + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.smaller + + StyledText { + text: qsTr("Settings") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + Item { + Layout.fillWidth: true + } } - Item { + CollapsibleSection { + id: outputDevicesSection + Layout.fillWidth: true - } - } + title: qsTr("Output devices") + expanded: true - StyledText { - text: qsTr("Output devices (%1)").arg(Audio.sinks.length) - font.pointSize: Appearance.font.size.normal - font.weight: 500 - } + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small - StyledText { - text: qsTr("All available output devices") - color: Colours.palette.m3outline - } + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small - StyledListView { - id: outputView + StyledText { + text: qsTr("Devices (%1)").arg(Audio.sinks.length) + font.pointSize: Appearance.font.size.normal + font.weight: 500 + } + } - Layout.fillWidth: true - Layout.fillHeight: true + StyledText { + Layout.fillWidth: true + text: qsTr("All available output devices") + color: Colours.palette.m3outline + } - model: Audio.sinks - spacing: Appearance.spacing.small / 2 - clip: true + Repeater { + Layout.fillWidth: true + model: Audio.sinks - StyledScrollBar.vertical: StyledScrollBar { - flickable: outputView - } + delegate: StyledRect { + required property var modelData - delegate: StyledRect { - required property var modelData + Layout.fillWidth: true - anchors.left: parent.left - anchors.right: parent.right + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, Audio.sink?.id === modelData.id ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: Audio.sink?.id === modelData.id ? 1 : 0 + border.color: Colours.palette.m3primary - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, Audio.sink?.id === modelData.id ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: Audio.sink?.id === modelData.id ? 1 : 0 - border.color: Colours.palette.m3primary + StateLayer { + function onClicked(): void { + Audio.setAudioSink(modelData); + } + } - StateLayer { - function onClicked(): void { - Audio.setAudioSink(modelData); - } - } + RowLayout { + id: outputRowLayout - RowLayout { - id: outputRowLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal + spacing: Appearance.spacing.normal - spacing: Appearance.spacing.normal + MaterialIcon { + text: Audio.sink?.id === modelData.id ? "speaker" : "speaker_group" + font.pointSize: Appearance.font.size.large + fill: Audio.sink?.id === modelData.id ? 1 : 0 + } - MaterialIcon { - text: Audio.sink?.id === modelData.id ? "speaker" : "speaker_group" - font.pointSize: Appearance.font.size.large - fill: Audio.sink?.id === modelData.id ? 1 : 0 - } + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 - StyledText { - Layout.fillWidth: true + text: modelData.description || qsTr("Unknown") + font.weight: Audio.sink?.id === modelData.id ? 500 : 400 + } + } - text: modelData.description || qsTr("Unknown") - font.weight: Audio.sink?.id === modelData.id ? 500 : 400 + implicitHeight: outputRowLayout.implicitHeight + Appearance.padding.normal * 2 + } } } - - implicitHeight: outputRowLayout.implicitHeight + Appearance.padding.normal * 2 } - } - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Input devices (%1)").arg(Audio.sources.length) - font.pointSize: Appearance.font.size.normal - font.weight: 500 - } + CollapsibleSection { + id: inputDevicesSection - StyledText { - text: qsTr("All available input devices") - color: Colours.palette.m3outline - } + Layout.fillWidth: true + title: qsTr("Input devices") + expanded: true - StyledListView { - id: inputView + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small - Layout.fillWidth: true - Layout.fillHeight: true + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small + + StyledText { + text: qsTr("Devices (%1)").arg(Audio.sources.length) + font.pointSize: Appearance.font.size.normal + font.weight: 500 + } + } - model: Audio.sources - spacing: Appearance.spacing.small / 2 - clip: true + StyledText { + Layout.fillWidth: true + text: qsTr("All available input devices") + color: Colours.palette.m3outline + } - StyledScrollBar.vertical: StyledScrollBar { - flickable: inputView - } + Repeater { + Layout.fillWidth: true + model: Audio.sources - delegate: StyledRect { - required property var modelData + delegate: StyledRect { + required property var modelData - anchors.left: parent.left - anchors.right: parent.right + Layout.fillWidth: true - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, Audio.source?.id === modelData.id ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: Audio.source?.id === modelData.id ? 1 : 0 - border.color: Colours.palette.m3primary + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, Audio.source?.id === modelData.id ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: Audio.source?.id === modelData.id ? 1 : 0 + border.color: Colours.palette.m3primary - StateLayer { - function onClicked(): void { - Audio.setAudioSource(modelData); - } - } + StateLayer { + function onClicked(): void { + Audio.setAudioSource(modelData); + } + } - RowLayout { - id: inputRowLayout + RowLayout { + id: inputRowLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal - spacing: Appearance.spacing.normal + spacing: Appearance.spacing.normal - MaterialIcon { - text: Audio.source?.id === modelData.id ? "mic" : "mic_external_on" - font.pointSize: Appearance.font.size.large - fill: Audio.source?.id === modelData.id ? 1 : 0 - } + MaterialIcon { + text: "mic" + font.pointSize: Appearance.font.size.large + fill: Audio.source?.id === modelData.id ? 1 : 0 + } - StyledText { - Layout.fillWidth: true + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 + + text: modelData.description || qsTr("Unknown") + font.weight: Audio.source?.id === modelData.id ? 500 : 400 + } + } - text: modelData.description || qsTr("Unknown") - font.weight: Audio.source?.id === modelData.id ? 500 : 400 + implicitHeight: inputRowLayout.implicitHeight + Appearance.padding.normal * 2 + } } } - - implicitHeight: inputRowLayout.implicitHeight + Appearance.padding.normal * 2 } } } diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 7db6f54..c5928de 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -106,6 +106,13 @@ RowLayout { title: qsTr("Ethernet") expanded: true + onToggleRequested: { + if (!expanded) { + // Opening ethernet, close wireless + wirelessListSection.expanded = false; + } + } + ColumnLayout { Layout.fillWidth: true spacing: Appearance.spacing.small @@ -233,6 +240,13 @@ RowLayout { title: qsTr("Wireless") expanded: true + onToggleRequested: { + if (!expanded) { + // Opening wireless, close ethernet + ethernetListSection.expanded = false; + } + } + ColumnLayout { Layout.fillWidth: true spacing: Appearance.spacing.small -- cgit v1.2.3-freya From f472be184a012031d490a3eeaf924d1c54b628a2 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 16:21:22 -0500 Subject: controlcenter: fade-out animation on password input dialog close --- .../network/WirelessPasswordDialog.qml | 51 ++++++++++++---------- 1 file changed, 27 insertions(+), 24 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index d1ce066..31372e0 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -29,9 +29,9 @@ Item { } property bool isClosing: false - visible: session.network.showPasswordDialog && !isClosing - enabled: visible - focus: visible + visible: session.network.showPasswordDialog || isClosing + enabled: session.network.showPasswordDialog && !isClosing + focus: enabled Keys.onEscapePressed: { closeDialog(); @@ -40,14 +40,10 @@ Item { Rectangle { anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.5) - opacity: root.visible && !root.isClosing ? 1 : 0 + opacity: root.session.network.showPasswordDialog && !root.isClosing ? 1 : 0 Behavior on opacity { - Anim { - property: "opacity" - duration: Appearance.anim.durations.normal - easing.bezierCurve: Appearance.anim.curves.standardDecel - } + Anim {} } MouseArea { @@ -66,22 +62,35 @@ Item { radius: Appearance.rounding.normal color: Colours.tPalette.m3surface - opacity: root.visible && !root.isClosing ? 1 : 0 - scale: root.visible && !root.isClosing ? 1 : 0.9 + opacity: root.session.network.showPasswordDialog && !root.isClosing ? 1 : 0 + scale: root.session.network.showPasswordDialog && !root.isClosing ? 1 : 0.7 Behavior on opacity { - Anim { - property: "opacity" - duration: Appearance.anim.durations.normal - easing.bezierCurve: Appearance.anim.curves.standardDecel - } + Anim {} } Behavior on scale { + Anim {} + } + + ParallelAnimation { + running: root.isClosing + onFinished: { + if (root.isClosing) { + root.session.network.showPasswordDialog = false; + root.isClosing = false; + } + } + Anim { + target: dialog + property: "opacity" + to: 0 + } + Anim { + target: dialog property: "scale" - duration: Appearance.anim.durations.normal - easing.bezierCurve: Appearance.anim.curves.standardDecel + to: 0.7 } } @@ -441,11 +450,5 @@ Item { connectButton.connecting = false; connectButton.text = qsTr("Connect"); connectionMonitor.stop(); - - // Wait for fade-out animation to complete before actually hiding - Qt.callLater(() => { - session.network.showPasswordDialog = false; - isClosing = false; - }, Appearance.anim.durations.normal); } } -- cgit v1.2.3-freya From 9de5e790a075d2f1d74113147bfad72b357d1215 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 16:42:23 -0500 Subject: controlcenter: minor tidying (capitalization and filename) --- .../controlcenter/appearance/AppearancePane.qml | 2 +- modules/controlcenter/audio/AudioPane.qml | 2 +- modules/controlcenter/bluetooth/Settings.qml | 2 +- modules/controlcenter/network/NetworkSettings.qml | 106 +++++++++++++++++++++ modules/controlcenter/network/NetworkingPane.qml | 2 +- .../controlcenter/network/NetworkingSettings.qml | 106 --------------------- modules/controlcenter/taskbar/TaskbarPane.qml | 2 +- 7 files changed, 111 insertions(+), 111 deletions(-) create mode 100644 modules/controlcenter/network/NetworkSettings.qml delete mode 100644 modules/controlcenter/network/NetworkingSettings.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index efd67e9..719e9b3 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -992,7 +992,7 @@ RowLayout { StyledText { Layout.alignment: Qt.AlignHCenter - text: qsTr("Appearance settings") + text: qsTr("Appearance Settings") font.pointSize: Appearance.font.size.large font.bold: true } diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index 732bb89..6f5a1f4 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -256,7 +256,7 @@ RowLayout { StyledText { Layout.alignment: Qt.AlignHCenter - text: qsTr("Audio settings") + text: qsTr("Audio Settings") font.pointSize: Appearance.font.size.large font.bold: true } diff --git a/modules/controlcenter/bluetooth/Settings.qml b/modules/controlcenter/bluetooth/Settings.qml index fb493ff..fd33af9 100644 --- a/modules/controlcenter/bluetooth/Settings.qml +++ b/modules/controlcenter/bluetooth/Settings.qml @@ -26,7 +26,7 @@ ColumnLayout { StyledText { Layout.alignment: Qt.AlignHCenter - text: qsTr("Bluetooth settings") + text: qsTr("Bluetooth Settings") font.pointSize: Appearance.font.size.large font.bold: true } diff --git a/modules/controlcenter/network/NetworkSettings.qml b/modules/controlcenter/network/NetworkSettings.qml new file mode 100644 index 0000000..75a7660 --- /dev/null +++ b/modules/controlcenter/network/NetworkSettings.qml @@ -0,0 +1,106 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property Session session + + spacing: Appearance.spacing.normal + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + text: "router" + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Network Settings") + font.pointSize: Appearance.font.size.large + font.bold: true + } + + SectionHeader { + Layout.topMargin: Appearance.spacing.large + title: qsTr("Ethernet") + description: qsTr("Ethernet device information") + } + + SectionContainer { + contentSpacing: Appearance.spacing.small / 2 + + PropertyRow { + label: qsTr("Total devices") + value: qsTr("%1").arg(Nmcli.ethernetDevices.length) + } + + PropertyRow { + showTopMargin: true + label: qsTr("Connected devices") + value: qsTr("%1").arg(Nmcli.ethernetDevices.filter(d => d.connected).length) + } + } + + SectionHeader { + Layout.topMargin: Appearance.spacing.large + title: qsTr("Wireless") + description: qsTr("WiFi network settings") + } + + SectionContainer { + ToggleRow { + label: qsTr("WiFi enabled") + checked: Nmcli.wifiEnabled + toggle.onToggled: { + Nmcli.enableWifi(checked); + } + } + } + + SectionHeader { + Layout.topMargin: Appearance.spacing.large + title: qsTr("Current connection") + description: qsTr("Active network connection information") + } + + SectionContainer { + contentSpacing: Appearance.spacing.small / 2 + + PropertyRow { + label: qsTr("Network") + value: Nmcli.active ? Nmcli.active.ssid : (Nmcli.activeEthernet ? Nmcli.activeEthernet.interface : qsTr("Not connected")) + } + + PropertyRow { + showTopMargin: true + visible: Nmcli.active !== null + label: qsTr("Signal strength") + value: Nmcli.active ? qsTr("%1%").arg(Nmcli.active.strength) : qsTr("N/A") + } + + PropertyRow { + showTopMargin: true + visible: Nmcli.active !== null + label: qsTr("Security") + value: Nmcli.active ? (Nmcli.active.isSecure ? qsTr("Secured") : qsTr("Open")) : qsTr("N/A") + } + + PropertyRow { + showTopMargin: true + visible: Nmcli.active !== null + label: qsTr("Frequency") + value: Nmcli.active ? qsTr("%1 MHz").arg(Nmcli.active.frequency) : qsTr("N/A") + } + } +} + diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index c5928de..823c19a 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -478,7 +478,7 @@ RowLayout { flickableDirection: Flickable.VerticalFlick contentHeight: settingsInner.height - NetworkingSettings { + NetworkSettings { id: settingsInner anchors.left: parent.left diff --git a/modules/controlcenter/network/NetworkingSettings.qml b/modules/controlcenter/network/NetworkingSettings.qml deleted file mode 100644 index 2475fed..0000000 --- a/modules/controlcenter/network/NetworkingSettings.qml +++ /dev/null @@ -1,106 +0,0 @@ -pragma ComponentBehavior: Bound - -import ".." -import qs.components -import qs.components.controls -import qs.components.effects -import qs.services -import qs.config -import QtQuick -import QtQuick.Layouts - -ColumnLayout { - id: root - - required property Session session - - spacing: Appearance.spacing.normal - - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - text: "router" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Networking settings") - font.pointSize: Appearance.font.size.large - font.bold: true - } - - SectionHeader { - Layout.topMargin: Appearance.spacing.large - title: qsTr("Ethernet") - description: qsTr("Ethernet device information") - } - - SectionContainer { - contentSpacing: Appearance.spacing.small / 2 - - PropertyRow { - label: qsTr("Total devices") - value: qsTr("%1").arg(Nmcli.ethernetDevices.length) - } - - PropertyRow { - showTopMargin: true - label: qsTr("Connected devices") - value: qsTr("%1").arg(Nmcli.ethernetDevices.filter(d => d.connected).length) - } - } - - SectionHeader { - Layout.topMargin: Appearance.spacing.large - title: qsTr("Wireless") - description: qsTr("WiFi network settings") - } - - SectionContainer { - ToggleRow { - label: qsTr("WiFi enabled") - checked: Nmcli.wifiEnabled - toggle.onToggled: { - Nmcli.enableWifi(checked); - } - } - } - - SectionHeader { - Layout.topMargin: Appearance.spacing.large - title: qsTr("Current connection") - description: qsTr("Active network connection information") - } - - SectionContainer { - contentSpacing: Appearance.spacing.small / 2 - - PropertyRow { - label: qsTr("Network") - value: Nmcli.active ? Nmcli.active.ssid : (Nmcli.activeEthernet ? Nmcli.activeEthernet.interface : qsTr("Not connected")) - } - - PropertyRow { - showTopMargin: true - visible: Nmcli.active !== null - label: qsTr("Signal strength") - value: Nmcli.active ? qsTr("%1%").arg(Nmcli.active.strength) : qsTr("N/A") - } - - PropertyRow { - showTopMargin: true - visible: Nmcli.active !== null - label: qsTr("Security") - value: Nmcli.active ? (Nmcli.active.isSecure ? qsTr("Secured") : qsTr("Open")) : qsTr("N/A") - } - - PropertyRow { - showTopMargin: true - visible: Nmcli.active !== null - label: qsTr("Frequency") - value: Nmcli.active ? qsTr("%1 MHz").arg(Nmcli.active.frequency) : qsTr("N/A") - } - } -} - diff --git a/modules/controlcenter/taskbar/TaskbarPane.qml b/modules/controlcenter/taskbar/TaskbarPane.qml index cf52fd3..0dcc152 100644 --- a/modules/controlcenter/taskbar/TaskbarPane.qml +++ b/modules/controlcenter/taskbar/TaskbarPane.qml @@ -631,7 +631,7 @@ RowLayout { StyledText { Layout.alignment: Qt.AlignHCenter - text: qsTr("Taskbar settings") + text: qsTr("Taskbar Settings") font.pointSize: Appearance.font.size.large font.bold: true } -- cgit v1.2.3-freya From b40b797caf7fb1c2aa66502791aedd4ef61cbed2 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 17:44:41 -0500 Subject: controlcenter: connected wireless network at top of list --- modules/controlcenter/network/WirelessList.qml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml index f4e76b4..00af47a 100644 --- a/modules/controlcenter/network/WirelessList.qml +++ b/modules/controlcenter/network/WirelessList.qml @@ -94,7 +94,15 @@ ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true - model: Nmcli.networks + 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; + }) + } spacing: Appearance.spacing.small / 2 clip: true -- cgit v1.2.3-freya From 9a08d6666eeef12608a834bdd54147d5075c10cd Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 18:00:24 -0500 Subject: controlcenter: auto-focus reliably for wireless password input --- .../controlcenter/network/WirelessPasswordDialog.qml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index 31372e0..d0c023f 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -170,12 +170,27 @@ Item { property string passwordBuffer: "" + Connections { + target: root.session.network + function onShowPasswordDialogChanged(): void { + if (root.session.network.showPasswordDialog) { + // Use callLater to ensure focus happens after dialog is fully rendered + Qt.callLater(() => { + passwordContainer.forceActiveFocus(); + passwordContainer.passwordBuffer = ""; + }); + } + } + } + Connections { target: root function onVisibleChanged(): void { if (root.visible) { - passwordContainer.forceActiveFocus(); - passwordContainer.passwordBuffer = ""; + // Use callLater to ensure focus happens after dialog is fully rendered + Qt.callLater(() => { + passwordContainer.forceActiveFocus(); + }); } } } -- cgit v1.2.3-freya From bd6122eaef4efd74865000bcf94fd9cb8ac84b44 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 22:43:09 -0500 Subject: controlcenter connected network top of list fix --- modules/controlcenter/network/NetworkingPane.qml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 823c19a..a44a54c 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -9,6 +9,7 @@ import qs.components.effects import qs.components.containers import qs.services import qs.config +import Quickshell import Quickshell.Widgets import QtQuick import QtQuick.Layouts @@ -279,7 +280,15 @@ RowLayout { id: wirelessRepeater Layout.fillWidth: true - model: Nmcli.networks + 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 -- cgit v1.2.3-freya From b1e5660c568a59af115acc2da1bca86c36a59efc Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 00:15:50 -0500 Subject: tray: open settings button matches other popouts --- modules/bar/popouts/Audio.qml | 2 +- modules/bar/popouts/Bluetooth.qml | 2 +- modules/controlcenter/network/SimpleButton.qml | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/bar/popouts/Audio.qml b/modules/bar/popouts/Audio.qml index 20e98e5..40c09d3 100644 --- a/modules/bar/popouts/Audio.qml +++ b/modules/bar/popouts/Audio.qml @@ -110,7 +110,7 @@ Item { Layout.topMargin: Appearance.spacing.normal color: Colours.palette.m3primaryContainer onColor: Colours.palette.m3onPrimaryContainer - text: qsTr("Open Settings") + text: qsTr("Open settings") icon: "settings" onClicked: { diff --git a/modules/bar/popouts/Bluetooth.qml b/modules/bar/popouts/Bluetooth.qml index 1415f89..63621c2 100644 --- a/modules/bar/popouts/Bluetooth.qml +++ b/modules/bar/popouts/Bluetooth.qml @@ -170,7 +170,7 @@ ColumnLayout { Layout.topMargin: Appearance.spacing.normal color: Colours.palette.m3primaryContainer onColor: Colours.palette.m3onPrimaryContainer - text: qsTr("Open Settings") + text: qsTr("Open settings") icon: "settings" onClicked: { diff --git a/modules/controlcenter/network/SimpleButton.qml b/modules/controlcenter/network/SimpleButton.qml index 7d85e4f..a73f751 100644 --- a/modules/controlcenter/network/SimpleButton.qml +++ b/modules/controlcenter/network/SimpleButton.qml @@ -15,7 +15,7 @@ StyledRect { property string icon: "" implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2 - implicitHeight: rowLayout.implicitHeight + Appearance.padding.small + implicitHeight: rowLayout.implicitHeight + Appearance.padding.small * 2 radius: Appearance.rounding.normal StateLayer { @@ -38,12 +38,10 @@ StyledRect { visible: root.icon.length > 0 text: root.icon color: root.onColor - font.pointSize: Appearance.font.size.large } StyledText { id: label - Layout.leftMargin: root.icon.length > 0 ? Appearance.padding.smaller : 0 color: parent.parent.onColor } } -- cgit v1.2.3-freya From 7d1c89a7a85f45b229d341013e1001cce30b642e Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 00:18:20 -0500 Subject: controlcenter: undo accordion collapse on network and audio pane sections --- modules/controlcenter/audio/AudioPane.qml | 14 -------------- modules/controlcenter/network/NetworkingPane.qml | 14 -------------- 2 files changed, 28 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index 6f5a1f4..8c990a6 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -62,13 +62,6 @@ RowLayout { title: qsTr("Output devices") expanded: true - onToggleRequested: { - if (!expanded) { - // Opening output devices, close input devices - inputDevicesSection.expanded = false; - } - } - ColumnLayout { Layout.fillWidth: true spacing: Appearance.spacing.small @@ -149,13 +142,6 @@ RowLayout { title: qsTr("Input devices") expanded: true - onToggleRequested: { - if (!expanded) { - // Opening input devices, close output devices - outputDevicesSection.expanded = false; - } - } - ColumnLayout { Layout.fillWidth: true spacing: Appearance.spacing.small diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index a44a54c..0560581 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -107,13 +107,6 @@ RowLayout { title: qsTr("Ethernet") expanded: true - onToggleRequested: { - if (!expanded) { - // Opening ethernet, close wireless - wirelessListSection.expanded = false; - } - } - ColumnLayout { Layout.fillWidth: true spacing: Appearance.spacing.small @@ -241,13 +234,6 @@ RowLayout { title: qsTr("Wireless") expanded: true - onToggleRequested: { - if (!expanded) { - // Opening wireless, close ethernet - ethernetListSection.expanded = false; - } - } - ColumnLayout { Layout.fillWidth: true spacing: Appearance.spacing.small -- cgit v1.2.3-freya From 90e920bf2e002b4e6e0aa1896687c4800d770605 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 00:25:31 -0500 Subject: controlcenter: fixed anchors vs parent logs --- components/controls/CollapsibleSection.qml | 2 +- modules/controlcenter/appearance/AppearancePane.qml | 12 ++++-------- modules/controlcenter/launcher/LauncherPane.qml | 3 +-- modules/controlcenter/network/NetworkingPane.qml | 3 ++- modules/controlcenter/taskbar/TaskbarPane.qml | 8 ++++---- 5 files changed, 12 insertions(+), 16 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/components/controls/CollapsibleSection.qml b/components/controls/CollapsibleSection.qml index 5bec5f8..3fba1c3 100644 --- a/components/controls/CollapsibleSection.qml +++ b/components/controls/CollapsibleSection.qml @@ -49,7 +49,7 @@ ColumnLayout { font.pointSize: Appearance.font.size.normal Behavior on rotation { Anim { - duration: Appearance.anim.durations.short + duration: Appearance.anim.durations.small easing.bezierCurve: Appearance.anim.curves.standard } } diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index 719e9b3..5ddcb06 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -295,8 +295,7 @@ RowLayout { delegate: StyledRect { required property var modelData - anchors.left: parent.left - anchors.right: parent.right + width: parent ? parent.width : 0 color: Qt.alpha(Colours.tPalette.m3surfaceContainer, modelData.variant === Schemes.currentVariant ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal @@ -386,8 +385,7 @@ RowLayout { delegate: StyledRect { required property var modelData - anchors.left: parent.left - anchors.right: parent.right + width: parent ? parent.width : 0 readonly property string schemeKey: `${modelData.name} ${modelData.flavour}` readonly property bool isCurrent: schemeKey === Schemes.currentScheme @@ -427,9 +425,7 @@ RowLayout { RowLayout { id: schemeRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter + anchors.fill: parent anchors.margins: Appearance.padding.normal spacing: Appearance.spacing.normal @@ -437,7 +433,7 @@ RowLayout { StyledRect { id: preview - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter border.width: 1 border.color: Qt.alpha(`#${modelData.colours?.outline}`, 0.5) diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index d0bd406..0645860 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -310,8 +310,7 @@ RowLayout { delegate: StyledRect { required property var modelData - anchors.left: parent.left - anchors.right: parent.right + width: parent ? parent.width : 0 readonly property bool isSelected: root.selectedApp === modelData diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 0560581..620e6f7 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -501,7 +501,8 @@ RowLayout { } WirelessPasswordDialog { - anchors.fill: parent + Layout.fillWidth: true + Layout.fillHeight: true session: root.session z: 1000 } diff --git a/modules/controlcenter/taskbar/TaskbarPane.qml b/modules/controlcenter/taskbar/TaskbarPane.qml index 0dcc152..ee47683 100644 --- a/modules/controlcenter/taskbar/TaskbarPane.qml +++ b/modules/controlcenter/taskbar/TaskbarPane.qml @@ -250,10 +250,10 @@ RowLayout { RowLayout { id: clockRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large + Layout.fillWidth: true + Layout.leftMargin: Appearance.padding.large + Layout.rightMargin: Appearance.padding.large + Layout.alignment: Qt.AlignVCenter spacing: Appearance.spacing.normal -- cgit v1.2.3-freya From 20e07b2a13036335c9890fe67708d2ab2292a4b4 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 00:29:42 -0500 Subject: controlcenter: clip to all styledflickable --- modules/controlcenter/Panes.qml | 2 ++ modules/controlcenter/appearance/AppearancePane.qml | 2 ++ modules/controlcenter/audio/AudioPane.qml | 1 + modules/controlcenter/bluetooth/BtPane.qml | 3 +++ modules/controlcenter/bluetooth/Details.qml | 1 + modules/controlcenter/ethernet/EthernetDetails.qml | 1 + modules/controlcenter/ethernet/EthernetPane.qml | 4 +++- modules/controlcenter/launcher/LauncherPane.qml | 1 + modules/controlcenter/network/NetworkingPane.qml | 5 ++++- modules/controlcenter/network/WirelessDetails.qml | 1 + modules/controlcenter/network/WirelessPane.qml | 1 + modules/controlcenter/taskbar/TaskbarPane.qml | 2 ++ 12 files changed, 22 insertions(+), 2 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/Panes.qml b/modules/controlcenter/Panes.qml index 8a46088..52ad7f2 100644 --- a/modules/controlcenter/Panes.qml +++ b/modules/controlcenter/Panes.qml @@ -19,12 +19,14 @@ ClippingRectangle { required property Session session color: "transparent" + clip: true ColumnLayout { id: layout spacing: 0 y: -root.session.activeIndex * root.height + clip: true Pane { index: 0 diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index 5ddcb06..fd75ff4 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -225,6 +225,7 @@ RowLayout { anchors.fill: parent flickableDirection: Flickable.VerticalFlick contentHeight: sidebarLayout.implicitHeight + Appearance.padding.large * 2 + clip: true StyledScrollBar.vertical: StyledScrollBar { flickable: sidebarFlickable @@ -965,6 +966,7 @@ RowLayout { flickableDirection: Flickable.VerticalFlick contentHeight: contentLayout.implicitHeight + clip: true StyledScrollBar.vertical: StyledScrollBar { flickable: parent diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index 8c990a6..0a7f602 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -31,6 +31,7 @@ RowLayout { anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 flickableDirection: Flickable.VerticalFlick contentHeight: leftContent.height + clip: true ColumnLayout { id: leftContent diff --git a/modules/controlcenter/bluetooth/BtPane.qml b/modules/controlcenter/bluetooth/BtPane.qml index 96dc002..40cf356 100644 --- a/modules/controlcenter/bluetooth/BtPane.qml +++ b/modules/controlcenter/bluetooth/BtPane.qml @@ -50,6 +50,7 @@ RowLayout { radius: rightBorder.innerRadius color: "transparent" + clip: true Loader { id: loader @@ -59,6 +60,7 @@ RowLayout { anchors.fill: parent anchors.margins: Appearance.padding.large * 2 + clip: true asynchronous: true sourceComponent: pane ? details : settings @@ -106,6 +108,7 @@ RowLayout { StyledFlickable { flickableDirection: Flickable.VerticalFlick contentHeight: settingsInner.height + clip: true Settings { id: settingsInner diff --git a/modules/controlcenter/bluetooth/Details.qml b/modules/controlcenter/bluetooth/Details.qml index 104f673..cbccd9b 100644 --- a/modules/controlcenter/bluetooth/Details.qml +++ b/modules/controlcenter/bluetooth/Details.qml @@ -22,6 +22,7 @@ Item { anchors.fill: parent flickableDirection: Flickable.VerticalFlick + clip: true contentHeight: layout.height ColumnLayout { diff --git a/modules/controlcenter/ethernet/EthernetDetails.qml b/modules/controlcenter/ethernet/EthernetDetails.qml index 68510da..6a01a8b 100644 --- a/modules/controlcenter/ethernet/EthernetDetails.qml +++ b/modules/controlcenter/ethernet/EthernetDetails.qml @@ -34,6 +34,7 @@ Item { anchors.fill: parent flickableDirection: Flickable.VerticalFlick + clip: true contentHeight: layout.height ColumnLayout { diff --git a/modules/controlcenter/ethernet/EthernetPane.qml b/modules/controlcenter/ethernet/EthernetPane.qml index b3ff317..05d0b1b 100644 --- a/modules/controlcenter/ethernet/EthernetPane.qml +++ b/modules/controlcenter/ethernet/EthernetPane.qml @@ -50,6 +50,7 @@ RowLayout { radius: rightBorder.innerRadius color: "transparent" + clip: true Loader { id: loader @@ -64,7 +65,7 @@ RowLayout { scale: 1 transformOrigin: Item.Center - clip: false + clip: true asynchronous: true sourceComponent: pane ? details : settings @@ -120,6 +121,7 @@ RowLayout { StyledFlickable { flickableDirection: Flickable.VerticalFlick contentHeight: settingsInner.height + clip: true EthernetSettings { id: settingsInner diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index 0645860..01ed72e 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -421,6 +421,7 @@ RowLayout { anchors.fill: parent flickableDirection: Flickable.VerticalFlick contentHeight: debugLayout.implicitHeight + clip: true StyledScrollBar.vertical: StyledScrollBar { flickable: parent diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 620e6f7..2faa40a 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -36,6 +36,7 @@ RowLayout { anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 flickableDirection: Flickable.VerticalFlick contentHeight: leftContent.height + clip: true ColumnLayout { id: leftContent @@ -399,6 +400,7 @@ RowLayout { radius: rightBorder.innerRadius color: "transparent" + clip: true // Right pane - networking details/settings Loader { @@ -416,7 +418,7 @@ RowLayout { scale: 1 transformOrigin: Item.Center - clip: false + clip: true asynchronous: true sourceComponent: pane ? (ethernetPane ? ethernetDetails : wirelessDetails) : settings @@ -472,6 +474,7 @@ RowLayout { StyledFlickable { flickableDirection: Flickable.VerticalFlick contentHeight: settingsInner.height + clip: true NetworkSettings { id: settingsInner diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index 334c4b3..f41451a 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -102,6 +102,7 @@ Item { anchors.fill: parent flickableDirection: Flickable.VerticalFlick + clip: true contentHeight: layout.height ColumnLayout { diff --git a/modules/controlcenter/network/WirelessPane.qml b/modules/controlcenter/network/WirelessPane.qml index 9d48729..67a00f7 100644 --- a/modules/controlcenter/network/WirelessPane.qml +++ b/modules/controlcenter/network/WirelessPane.qml @@ -120,6 +120,7 @@ RowLayout { StyledFlickable { flickableDirection: Flickable.VerticalFlick contentHeight: settingsInner.height + clip: true WirelessSettings { id: settingsInner diff --git a/modules/controlcenter/taskbar/TaskbarPane.qml b/modules/controlcenter/taskbar/TaskbarPane.qml index ee47683..72e9a80 100644 --- a/modules/controlcenter/taskbar/TaskbarPane.qml +++ b/modules/controlcenter/taskbar/TaskbarPane.qml @@ -209,6 +209,7 @@ RowLayout { anchors.fill: parent flickableDirection: Flickable.VerticalFlick contentHeight: sidebarLayout.implicitHeight + Appearance.padding.large * 2 + clip: true StyledScrollBar.vertical: StyledScrollBar { flickable: sidebarFlickable @@ -608,6 +609,7 @@ RowLayout { flickableDirection: Flickable.VerticalFlick contentHeight: contentLayout.implicitHeight + clip: true StyledScrollBar.vertical: StyledScrollBar { flickable: parent -- cgit v1.2.3-freya From e339439ee885345b44bff6e2f9db9dbffacfcfbd Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 00:31:19 -0500 Subject: controlcenter: network pane added scrollbar --- modules/controlcenter/ethernet/EthernetDetails.qml | 6 ++++++ modules/controlcenter/network/NetworkingPane.qml | 12 ++++++++++++ modules/controlcenter/network/WirelessDetails.qml | 6 ++++++ 3 files changed, 24 insertions(+) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/ethernet/EthernetDetails.qml b/modules/controlcenter/ethernet/EthernetDetails.qml index 6a01a8b..f715970 100644 --- a/modules/controlcenter/ethernet/EthernetDetails.qml +++ b/modules/controlcenter/ethernet/EthernetDetails.qml @@ -31,12 +31,18 @@ Item { } StyledFlickable { + id: flickable + anchors.fill: parent flickableDirection: Flickable.VerticalFlick clip: true contentHeight: layout.height + StyledScrollBar.vertical: StyledScrollBar { + flickable: flickable + } + ColumnLayout { id: layout diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 2faa40a..5d93ff9 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -30,6 +30,8 @@ RowLayout { // Left pane - networking list with collapsible sections StyledFlickable { + id: leftFlickable + anchors.fill: parent anchors.margins: Appearance.padding.large + Appearance.padding.normal anchors.leftMargin: Appearance.padding.large @@ -38,6 +40,10 @@ RowLayout { contentHeight: leftContent.height clip: true + StyledScrollBar.vertical: StyledScrollBar { + flickable: leftFlickable + } + ColumnLayout { id: leftContent @@ -472,10 +478,16 @@ RowLayout { id: settings StyledFlickable { + id: settingsFlickable + flickableDirection: Flickable.VerticalFlick contentHeight: settingsInner.height clip: true + StyledScrollBar.vertical: StyledScrollBar { + flickable: settingsFlickable + } + NetworkSettings { id: settingsInner diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index f41451a..5b4f541 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -99,12 +99,18 @@ Item { } StyledFlickable { + id: flickable + anchors.fill: parent flickableDirection: Flickable.VerticalFlick clip: true contentHeight: layout.height + StyledScrollBar.vertical: StyledScrollBar { + flickable: flickable + } + ColumnLayout { id: layout -- cgit v1.2.3-freya From a40624839bba5cb429c34b9461b817a4a3434d02 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 00:36:26 -0500 Subject: controlcenter: selectables now match bluetooth pane --- modules/controlcenter/audio/AudioPane.qml | 4 ---- modules/controlcenter/launcher/LauncherPane.qml | 2 -- modules/controlcenter/network/NetworkingPane.qml | 4 ---- 3 files changed, 10 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index d6989cd..ca54f77 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -95,8 +95,6 @@ RowLayout { color: Qt.alpha(Colours.tPalette.m3surfaceContainer, Audio.sink?.id === modelData.id ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal - border.width: Audio.sink?.id === modelData.id ? 1 : 0 - border.color: Colours.palette.m3primary StateLayer { function onClicked(): void { @@ -175,8 +173,6 @@ RowLayout { color: Qt.alpha(Colours.tPalette.m3surfaceContainer, Audio.source?.id === modelData.id ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal - border.width: Audio.source?.id === modelData.id ? 1 : 0 - border.color: Colours.palette.m3primary StateLayer { function onClicked(): void { diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index 01ed72e..92e17ee 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -316,8 +316,6 @@ RowLayout { color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isSelected ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal - border.width: isSelected ? 1 : 0 - border.color: Colours.palette.m3primary StateLayer { function onClicked(): void { diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 5d93ff9..c605a36 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -148,8 +148,6 @@ RowLayout { color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.ethernet.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal - border.width: root.session.ethernet.active === modelData ? 1 : 0 - border.color: Colours.palette.m3primary StateLayer { function onClicked(): void { @@ -290,8 +288,6 @@ RowLayout { color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.network.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal - border.width: root.session.network.active === modelData ? 1 : 0 - border.color: Colours.palette.m3primary StateLayer { function onClicked(): void { -- cgit v1.2.3-freya From 6cc779d6f3e7067ed38b8c7b1001827f26314cf1 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 00:50:17 -0500 Subject: controlcenter: wireless networks display icons of signal strength --- modules/controlcenter/network/NetworkingPane.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index c605a36..c834aaa 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -9,6 +9,7 @@ import qs.components.effects import qs.components.containers import qs.services import qs.config +import qs.utils import Quickshell import Quickshell.Widgets import QtQuick @@ -321,7 +322,7 @@ RowLayout { id: wirelessIcon anchors.centerIn: parent - text: modelData.isSecure ? "lock" : "wifi" + text: Icons.getNetworkIcon(modelData.strength) font.pointSize: Appearance.font.size.large fill: modelData.active ? 1 : 0 color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface -- cgit v1.2.3-freya From 19fb58d249a0036bf454a07beb7793798ac50926 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 00:59:19 -0500 Subject: controlcenter: removed signal strength percentage from network list --- modules/controlcenter/network/NetworkingPane.qml | 6 ------ 1 file changed, 6 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index c834aaa..56ab7f1 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -344,12 +344,6 @@ RowLayout { font.weight: modelData.active ? 500 : 400 } - StyledText { - text: qsTr("%1%").arg(modelData.strength) - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - StyledRect { implicitWidth: implicitHeight implicitHeight: wirelessConnectIcon.implicitHeight + Appearance.padding.smaller * 2 -- cgit v1.2.3-freya From 6a92f985d8739ef2c397714f79e18b74f48fb705 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Sat, 15 Nov 2025 17:15:02 +1100 Subject: internal: use existing button controls Instead of SimpleButton --- components/controls/IconTextButton.qml | 9 +- modules/bar/popouts/Audio.qml | 11 +- modules/bar/popouts/Bluetooth.qml | 11 +- modules/bar/popouts/Network.qml | 15 +- modules/bar/popouts/WirelessPasswordPopout.qml | 165 +++++++-------------- modules/controlcenter/network/SimpleButton.qml | 50 ------- modules/controlcenter/network/WirelessDetails.qml | 44 +++--- .../network/WirelessPasswordDialog.qml | 54 +++---- utils/Icons.qml | 1 + 9 files changed, 120 insertions(+), 240 deletions(-) delete mode 100644 modules/controlcenter/network/SimpleButton.qml (limited to 'modules/controlcenter/network') diff --git a/components/controls/IconTextButton.qml b/components/controls/IconTextButton.qml index 78e7c5b..0badd7a 100644 --- a/components/controls/IconTextButton.qml +++ b/components/controls/IconTextButton.qml @@ -2,6 +2,7 @@ import ".." import qs.services import qs.config import QtQuick +import QtQuick.Layouts StyledRect { id: root @@ -53,7 +54,7 @@ StyledRect { } } - Row { + RowLayout { id: row anchors.centerIn: parent @@ -62,7 +63,8 @@ StyledRect { MaterialIcon { id: iconLabel - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter + Layout.topMargin: Math.round(fontInfo.pointSize * 0.0575) color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour fill: root.internalChecked ? 1 : 0 @@ -74,7 +76,8 @@ StyledRect { StyledText { id: label - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter + Layout.topMargin: -Math.round(iconLabel.fontInfo.pointSize * 0.0575) color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour } } diff --git a/modules/bar/popouts/Audio.qml b/modules/bar/popouts/Audio.qml index 40c09d3..58b29ba 100644 --- a/modules/bar/popouts/Audio.qml +++ b/modules/bar/popouts/Audio.qml @@ -105,17 +105,16 @@ Item { } } - SimpleButton { + IconTextButton { Layout.fillWidth: true Layout.topMargin: Appearance.spacing.normal - color: Colours.palette.m3primaryContainer - onColor: Colours.palette.m3onPrimaryContainer + inactiveColour: Colours.palette.m3primaryContainer + inactiveOnColour: Colours.palette.m3onPrimaryContainer + verticalPadding: Appearance.padding.small text: qsTr("Open settings") icon: "settings" - onClicked: { - root.wrapper.detach("audio"); - } + onClicked: root.wrapper.detach("audio") } } } diff --git a/modules/bar/popouts/Bluetooth.qml b/modules/bar/popouts/Bluetooth.qml index 63621c2..4674905 100644 --- a/modules/bar/popouts/Bluetooth.qml +++ b/modules/bar/popouts/Bluetooth.qml @@ -165,17 +165,16 @@ ColumnLayout { } } - SimpleButton { + IconTextButton { Layout.fillWidth: true Layout.topMargin: Appearance.spacing.normal - color: Colours.palette.m3primaryContainer - onColor: Colours.palette.m3onPrimaryContainer + inactiveColour: Colours.palette.m3primaryContainer + inactiveOnColour: Colours.palette.m3onPrimaryContainer + verticalPadding: Appearance.padding.small text: qsTr("Open settings") icon: "settings" - onClicked: { - root.wrapper.detach("bluetooth"); - } + onClicked: root.wrapper.detach("bluetooth") } component Toggle: RowLayout { diff --git a/modules/bar/popouts/Network.qml b/modules/bar/popouts/Network.qml index 93ff867..b9f66c4 100644 --- a/modules/bar/popouts/Network.qml +++ b/modules/bar/popouts/Network.qml @@ -111,10 +111,8 @@ ColumnLayout { } StyledRect { - id: connectBtn - implicitWidth: implicitHeight - implicitHeight: connectIcon.implicitHeight + Appearance.padding.small + implicitHeight: wirelessConnectIcon.implicitHeight + Appearance.padding.small radius: Appearance.rounding.full color: Qt.alpha(Colours.palette.m3primary, networkItem.modelData.active ? 1 : 0) @@ -136,7 +134,7 @@ ColumnLayout { // Check if network is secure if (networkItem.modelData.isSecure) { // Try to connect first - will show password dialog if password is needed - Nmcli.connectToNetwork(networkItem.modelData.ssid, "", networkItem.modelData.bssid, (result) => { + Nmcli.connectToNetwork(networkItem.modelData.ssid, "", networkItem.modelData.bssid, result => { if (result && result.needsPassword) { // Password is required - show password dialog root.passwordNetwork = networkItem.modelData; @@ -159,7 +157,7 @@ ColumnLayout { } MaterialIcon { - id: connectIcon + id: wirelessConnectIcon anchors.centerIn: parent animate: true @@ -205,12 +203,14 @@ ColumnLayout { MaterialIcon { id: scanIcon + Layout.topMargin: Math.round(fontInfo.pointSize * 0.0575) animate: true text: "wifi_find" color: Colours.palette.m3onPrimaryContainer } StyledText { + Layout.topMargin: -Math.round(scanIcon.fontInfo.pointSize * 0.0575) text: qsTr("Rescan networks") color: Colours.palette.m3onPrimaryContainer } @@ -303,8 +303,6 @@ ColumnLayout { } StyledRect { - id: connectBtn - implicitWidth: implicitHeight implicitHeight: connectIcon.implicitHeight + Appearance.padding.small @@ -354,8 +352,7 @@ ColumnLayout { if (Nmcli.active && root.connectingToSsid === Nmcli.active.ssid) { root.connectingToSsid = ""; // Close password dialog if we successfully connected - if (root.showPasswordDialog && root.passwordNetwork && - Nmcli.active.ssid === root.passwordNetwork.ssid) { + if (root.showPasswordDialog && root.passwordNetwork && Nmcli.active.ssid === root.passwordNetwork.ssid) { root.showPasswordDialog = false; root.passwordNetwork = null; if (root.wrapper.currentName === "wirelesspassword") { diff --git a/modules/bar/popouts/WirelessPasswordPopout.qml b/modules/bar/popouts/WirelessPasswordPopout.qml index 2fd063f..aa7f40f 100644 --- a/modules/bar/popouts/WirelessPasswordPopout.qml +++ b/modules/bar/popouts/WirelessPasswordPopout.qml @@ -41,14 +41,14 @@ ColumnLayout { } spacing: Appearance.spacing.normal - + implicitWidth: 400 implicitHeight: content.implicitHeight + Appearance.padding.large * 2 visible: shouldBeVisible || isClosing enabled: shouldBeVisible && !isClosing focus: enabled - + Component.onCompleted: { if (shouldBeVisible) { Qt.callLater(() => { @@ -57,7 +57,7 @@ ColumnLayout { }, 150); } } - + onShouldBeVisibleChanged: { if (shouldBeVisible) { Qt.callLater(() => { @@ -67,9 +67,7 @@ ColumnLayout { } } - Keys.onEscapePressed: { - closeDialog(); - } + Keys.onEscapePressed: closeDialog() StyledRect { Layout.fillWidth: true @@ -109,7 +107,7 @@ ColumnLayout { } } - Keys.onEscapePressed: closeDialog(); + Keys.onEscapePressed: root.closeDialog() ColumnLayout { id: content @@ -149,7 +147,7 @@ ColumnLayout { color: Colours.palette.m3outline font.pointSize: Appearance.font.size.small } - + Timer { interval: 50 running: root.shouldBeVisible && (!root.network || !root.network.ssid) @@ -217,12 +215,12 @@ ColumnLayout { if (!activeFocus) { forceActiveFocus(); } - + // Clear error when user starts typing if (connectButton.hasError && event.text && event.text.length > 0) { connectButton.hasError = false; } - + if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { if (connectButton.enabled) { connectButton.clicked(); @@ -253,7 +251,7 @@ ColumnLayout { } } } - + Component.onCompleted: { if (root.shouldBeVisible) { Qt.callLater(() => { @@ -265,9 +263,7 @@ ColumnLayout { StyledRect { anchors.fill: parent radius: Appearance.rounding.normal - color: passwordContainer.activeFocus - ? Qt.lighter(Colours.tPalette.m3surfaceContainer, 1.05) - : Colours.tPalette.m3surfaceContainer + color: passwordContainer.activeFocus ? Qt.lighter(Colours.tPalette.m3surfaceContainer, 1.05) : Colours.tPalette.m3surfaceContainer border.width: passwordContainer.activeFocus || connectButton.hasError ? 4 : (root.shouldBeVisible ? 1 : 0) border.color: { if (connectButton.hasError) { @@ -401,31 +397,31 @@ ColumnLayout { Layout.fillWidth: true spacing: Appearance.spacing.normal - SimpleButton { + TextButton { id: cancelButton Layout.fillWidth: true Layout.minimumHeight: Appearance.font.size.normal + Appearance.padding.normal * 2 - color: Colours.palette.m3secondaryContainer - onColor: Colours.palette.m3onSecondaryContainer + inactiveColour: Colours.palette.m3secondaryContainer + inactiveOnColour: Colours.palette.m3onSecondaryContainer text: qsTr("Cancel") - onClicked: closeDialog(); + onClicked: root.closeDialog() } - SimpleButton { + TextButton { id: connectButton + property bool connecting: false + property bool hasError: false + Layout.fillWidth: true Layout.minimumHeight: Appearance.font.size.normal + Appearance.padding.normal * 2 - color: Colours.palette.m3primary - onColor: Colours.palette.m3onPrimary + inactiveColour: Colours.palette.m3primary + inactiveOnColour: Colours.palette.m3onPrimary text: qsTr("Connect") enabled: passwordContainer.passwordBuffer.length > 0 && !connecting - property bool connecting: false - property bool hasError: false - onClicked: { if (!root.network || connecting) { return; @@ -445,40 +441,35 @@ ColumnLayout { text = qsTr("Connecting..."); // Connect to network - Nmcli.connectToNetwork( - root.network.ssid, - password, - root.network.bssid || "", - (result) => { - if (result && result.success) { - // Connection successful, monitor will handle the rest - } else if (result && result.needsPassword) { - // Shouldn't happen since we provided password - connectionMonitor.stop(); - connecting = false; - hasError = true; - enabled = true; - text = qsTr("Connect"); - passwordContainer.passwordBuffer = ""; - // Delete the failed connection - if (root.network && root.network.ssid) { - Nmcli.forgetNetwork(root.network.ssid, () => {}); - } - } else { - // Connection failed immediately - show error - connectionMonitor.stop(); - connecting = false; - hasError = true; - enabled = true; - text = qsTr("Connect"); - passwordContainer.passwordBuffer = ""; - // Delete the failed connection - if (root.network && root.network.ssid) { - Nmcli.forgetNetwork(root.network.ssid, () => {}); - } + Nmcli.connectToNetwork(root.network.ssid, password, root.network.bssid || "", result => { + if (result && result.success) + // Connection successful, monitor will handle the rest + {} else if (result && result.needsPassword) { + // Shouldn't happen since we provided password + connectionMonitor.stop(); + connecting = false; + hasError = true; + enabled = true; + text = qsTr("Connect"); + passwordContainer.passwordBuffer = ""; + // Delete the failed connection + if (root.network && root.network.ssid) { + Nmcli.forgetNetwork(root.network.ssid); + } + } else { + // Connection failed immediately - show error + connectionMonitor.stop(); + connecting = false; + hasError = true; + enabled = true; + text = qsTr("Connect"); + passwordContainer.passwordBuffer = ""; + // Delete the failed connection + if (root.network && root.network.ssid) { + Nmcli.forgetNetwork(root.network.ssid); } } - ); + }); // Start monitoring connection connectionMonitor.start(); @@ -494,8 +485,7 @@ ColumnLayout { } // Check if we're connected to the target network (case-insensitive SSID comparison) - const isConnected = root.network && Nmcli.active && Nmcli.active.ssid && - Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); + const isConnected = root.network && Nmcli.active && Nmcli.active.ssid && Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); if (isConnected) { // Successfully connected - give it a moment for network list to update @@ -530,7 +520,7 @@ ColumnLayout { passwordContainer.passwordBuffer = ""; // Delete the failed connection if (root.network && root.network.ssid) { - Nmcli.forgetNetwork(root.network.ssid, () => {}); + Nmcli.forgetNetwork(root.network.ssid); } } } @@ -545,7 +535,7 @@ ColumnLayout { onTriggered: { repeatCount++; - checkConnectionStatus(); + root.checkConnectionStatus(); } onRunningChanged: { @@ -559,7 +549,7 @@ ColumnLayout { target: Nmcli function onActiveChanged() { if (root.shouldBeVisible) { - checkConnectionStatus(); + root.checkConnectionStatus(); } } function onConnectionFailed(ssid: string) { @@ -571,7 +561,7 @@ ColumnLayout { connectButton.text = qsTr("Connect"); passwordContainer.passwordBuffer = ""; // Delete the failed connection - Nmcli.forgetNetwork(ssid, () => {}); + Nmcli.forgetNetwork(ssid); } } } @@ -580,64 +570,17 @@ ColumnLayout { if (isClosing) { return; } - + isClosing = true; passwordContainer.passwordBuffer = ""; connectButton.connecting = false; connectButton.hasError = false; connectButton.text = qsTr("Connect"); connectionMonitor.stop(); - + // Return to network popout if (root.wrapper.currentName === "wirelesspassword") { root.wrapper.currentName = "network"; } } - - component SimpleButton: StyledRect { - id: button - - property color onColor: Colours.palette.m3onSurface - property alias disabled: stateLayer.disabled - property alias text: label.text - property alias enabled: stateLayer.enabled - property string icon: "" - - implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2 - implicitHeight: rowLayout.implicitHeight + Appearance.padding.small - radius: Appearance.rounding.small - - StateLayer { - id: stateLayer - color: parent.onColor - function onClicked(): void { - if (parent.enabled !== false) { - parent.clicked(); - } - } - } - - RowLayout { - id: rowLayout - anchors.centerIn: parent - spacing: Appearance.spacing.small - - MaterialIcon { - id: iconItem - visible: button.icon.length > 0 - text: button.icon - color: button.onColor - font.pointSize: Appearance.font.size.large - } - - StyledText { - id: label - Layout.leftMargin: button.icon.length > 0 ? Appearance.padding.smaller : 0 - color: parent.parent.onColor - } - } - - signal clicked - } } - diff --git a/modules/controlcenter/network/SimpleButton.qml b/modules/controlcenter/network/SimpleButton.qml deleted file mode 100644 index a73f751..0000000 --- a/modules/controlcenter/network/SimpleButton.qml +++ /dev/null @@ -1,50 +0,0 @@ -import qs.components -import qs.components.controls -import qs.components.effects -import qs.config -import QtQuick -import QtQuick.Layouts - -StyledRect { - id: root - - property color onColor: Colours.palette.m3onSurface - property alias disabled: stateLayer.disabled - property alias text: label.text - property alias enabled: stateLayer.enabled - property string icon: "" - - implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2 - implicitHeight: rowLayout.implicitHeight + Appearance.padding.small * 2 - radius: Appearance.rounding.normal - - StateLayer { - id: stateLayer - color: parent.onColor - function onClicked(): void { - if (parent.enabled !== false) { - parent.clicked(); - } - } - } - - RowLayout { - id: rowLayout - anchors.centerIn: parent - spacing: Appearance.spacing.small - - MaterialIcon { - id: iconItem - visible: root.icon.length > 0 - text: root.icon - color: root.onColor - } - - StyledText { - id: label - color: parent.parent.onColor - } - } - - signal clicked -} \ No newline at end of file diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index 5b4f541..651f1fb 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -69,7 +69,7 @@ Item { if (!Nmcli.wirelessDeviceDetails || Nmcli.wirelessDeviceDetails === null) { // Network is active but we don't have details yet, fetch them Nmcli.getWirelessDeviceDetails("", () => { - // After fetching, check if we got details - if not, timer will try again + // After fetching, check if we got details - if not, timer will try again }); } else { // We have details, can stop the timer @@ -89,7 +89,7 @@ Item { if (network && network.ssid) { const isActive = network.active || (Nmcli.active && Nmcli.active.ssid === network.ssid); if (isActive) { - Nmcli.getWirelessDeviceDetails("", () => {}); + Nmcli.getWirelessDeviceDetails(""); } else { Nmcli.wirelessDeviceDetails = null; } @@ -134,14 +134,14 @@ Item { checked: root.network?.active ?? false toggle.onToggled: { if (checked) { - handleConnect(); + root.handleConnect(); } else { Nmcli.disconnectFromNetwork(); } } } - SimpleButton { + TextButton { Layout.fillWidth: true Layout.topMargin: Appearance.spacing.normal Layout.minimumHeight: Appearance.font.size.normal + Appearance.padding.normal * 2 @@ -151,8 +151,8 @@ Item { } return Nmcli.hasSavedProfile(root.network.ssid); } - color: Colours.palette.m3errorContainer - onColor: Colours.palette.m3onErrorContainer + inactiveColour: Colours.palette.m3errorContainer + inactiveOnColour: Colours.palette.m3onErrorContainer text: qsTr("Forget Network") onClicked: { @@ -160,7 +160,7 @@ Item { if (root.network.active) { Nmcli.disconnectFromNetwork(); } - Nmcli.forgetNetwork(root.network.ssid, () => {}); + Nmcli.forgetNetwork(root.network.ssid); } } } @@ -214,7 +214,6 @@ Item { deviceDetails: Nmcli.wirelessDeviceDetails } } - } } @@ -236,26 +235,21 @@ Item { if (hasSavedProfile) { Nmcli.connectToNetwork(root.network.ssid, "", root.network.bssid, null); } else { - Nmcli.connectToNetworkWithPasswordCheck( - root.network.ssid, - root.network.isSecure, - (result) => { - if (result.needsPassword) { - if (Nmcli.pendingConnection) { - Nmcli.connectionCheckTimer.stop(); - Nmcli.immediateCheckTimer.stop(); - Nmcli.immediateCheckTimer.checkCount = 0; - Nmcli.pendingConnection = null; - } - root.session.network.showPasswordDialog = true; - root.session.network.pendingNetwork = root.network; + Nmcli.connectToNetworkWithPasswordCheck(root.network.ssid, root.network.isSecure, result => { + if (result.needsPassword) { + if (Nmcli.pendingConnection) { + Nmcli.connectionCheckTimer.stop(); + Nmcli.immediateCheckTimer.stop(); + Nmcli.immediateCheckTimer.checkCount = 0; + Nmcli.pendingConnection = null; } - }, - root.network.bssid - ); + root.session.network.showPasswordDialog = true; + root.session.network.pendingNetwork = root.network; + } + }, root.network.bssid); } } else { Nmcli.connectToNetwork(root.network.ssid, "", root.network.bssid, null); } } -} \ No newline at end of file +} diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index d0c023f..f3381b7 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -48,7 +48,7 @@ Item { MouseArea { anchors.fill: parent - onClicked: closeDialog(); + onClicked: closeDialog() } } @@ -94,7 +94,7 @@ Item { } } - Keys.onEscapePressed: closeDialog(); + Keys.onEscapePressed: closeDialog() ColumnLayout { id: content @@ -314,25 +314,25 @@ Item { Layout.fillWidth: true spacing: Appearance.spacing.normal - SimpleButton { + TextButton { id: cancelButton Layout.fillWidth: true Layout.minimumHeight: Appearance.font.size.normal + Appearance.padding.normal * 2 - color: Colours.palette.m3secondaryContainer - onColor: Colours.palette.m3onSecondaryContainer + inactiveColour: Colours.palette.m3secondaryContainer + inactiveOnColour: Colours.palette.m3onSecondaryContainer text: qsTr("Cancel") - onClicked: closeDialog(); + onClicked: root.closeDialog() } - SimpleButton { + TextButton { id: connectButton Layout.fillWidth: true Layout.minimumHeight: Appearance.font.size.normal + Appearance.padding.normal * 2 - color: Colours.palette.m3primary - onColor: Colours.palette.m3onPrimary + inactiveColour: Colours.palette.m3primary + inactiveOnColour: Colours.palette.m3onPrimary text: qsTr("Connect") enabled: passwordContainer.passwordBuffer.length > 0 && !connecting @@ -354,24 +354,19 @@ Item { text = qsTr("Connecting..."); // Connect to network - Nmcli.connectToNetwork( - root.network.ssid, - password, - root.network.bssid || "", - (result) => { - if (result && result.success) { - // Connection successful, monitor will handle the rest - } else if (result && result.needsPassword) { - // Shouldn't happen since we provided password - connectionMonitor.stop(); - connecting = false; - enabled = true; - text = qsTr("Connect"); - } else { - // Connection failed, monitor will handle timeout - } - } - ); + Nmcli.connectToNetwork(root.network.ssid, password, root.network.bssid || "", result => { + if (result && result.success) + // Connection successful, monitor will handle the rest + {} else if (result && result.needsPassword) { + // Shouldn't happen since we provided password + connectionMonitor.stop(); + connecting = false; + enabled = true; + text = qsTr("Connect"); + } else + // Connection failed, monitor will handle timeout + {} + }); // Start monitoring connection connectionMonitor.start(); @@ -387,8 +382,7 @@ Item { } // Check if we're connected to the target network (case-insensitive SSID comparison) - const isConnected = root.network && Nmcli.active && Nmcli.active.ssid && - Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); + const isConnected = root.network && Nmcli.active && Nmcli.active.ssid && Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); if (isConnected) { // Successfully connected - give it a moment for network list to update @@ -459,7 +453,7 @@ Item { if (isClosing) { return; } - + isClosing = true; passwordContainer.passwordBuffer = ""; connectButton.connecting = false; diff --git a/utils/Icons.qml b/utils/Icons.qml index 389eca3..b3fe8ab 100644 --- a/utils/Icons.qml +++ b/utils/Icons.qml @@ -3,6 +3,7 @@ pragma Singleton import qs.config import Quickshell import Quickshell.Services.Notifications +import QtQuick Singleton { id: root -- cgit v1.2.3-freya From 05b0660627586dc7624380e82b818b53004771f5 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 01:31:53 -0500 Subject: controlcenter: password input errors/wrong pass --- modules/bar/popouts/WirelessPasswordPopout.qml | 85 ++- modules/controlcenter/network/NetworkingPane.qml | 691 +++++++++++---------- .../network/WirelessPasswordDialog.qml | 115 +++- services/Nmcli.qml | 10 +- 4 files changed, 502 insertions(+), 399 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/bar/popouts/WirelessPasswordPopout.qml b/modules/bar/popouts/WirelessPasswordPopout.qml index aa7f40f..59a15b9 100644 --- a/modules/bar/popouts/WirelessPasswordPopout.qml +++ b/modules/bar/popouts/WirelessPasswordPopout.qml @@ -32,14 +32,22 @@ ColumnLayout { } } // Force focus to password container when popout becomes active - Qt.callLater(() => { - passwordContainer.forceActiveFocus(); - }, 100); - }, 100); + // Use Timer for actual delay to ensure dialog is fully rendered + focusTimer.start(); + }); } } } + Timer { + id: focusTimer + interval: 150 + onTriggered: { + root.forceActiveFocus(); + passwordContainer.forceActiveFocus(); + } + } + spacing: Appearance.spacing.normal implicitWidth: 400 @@ -51,19 +59,15 @@ ColumnLayout { Component.onCompleted: { if (shouldBeVisible) { - Qt.callLater(() => { - root.forceActiveFocus(); - passwordContainer.forceActiveFocus(); - }, 150); + // Use Timer for actual delay to ensure dialog is fully rendered + focusTimer.start(); } } onShouldBeVisibleChanged: { if (shouldBeVisible) { - Qt.callLater(() => { - root.forceActiveFocus(); - passwordContainer.forceActiveFocus(); - }, 150); + // Use Timer for actual delay to ensure dialog is fully rendered + focusTimer.start(); } } @@ -243,20 +247,26 @@ ColumnLayout { target: root function onShouldBeVisibleChanged(): void { if (root.shouldBeVisible) { - Qt.callLater(() => { - passwordContainer.forceActiveFocus(); - }, 50); + // Use Timer for actual delay to ensure focus works correctly + passwordFocusTimer.start(); passwordContainer.passwordBuffer = ""; connectButton.hasError = false; } } } + Timer { + id: passwordFocusTimer + interval: 50 + onTriggered: { + passwordContainer.forceActiveFocus(); + } + } + Component.onCompleted: { if (root.shouldBeVisible) { - Qt.callLater(() => { - passwordContainer.forceActiveFocus(); - }, 100); + // Use Timer for actual delay to ensure focus works correctly + passwordFocusTimer.start(); } } @@ -489,22 +499,8 @@ ColumnLayout { if (isConnected) { // Successfully connected - give it a moment for network list to update - Qt.callLater(() => { - // Double-check connection is still active - if (root.shouldBeVisible && Nmcli.active && Nmcli.active.ssid) { - const stillConnected = Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); - if (stillConnected) { - connectionMonitor.stop(); - connectButton.connecting = false; - connectButton.text = qsTr("Connect"); - // Return to network popout on successful connection - if (root.wrapper.currentName === "wirelesspassword") { - root.wrapper.currentName = "network"; - } - closeDialog(); - } - } - }, 500); + // Use Timer for actual delay + connectionSuccessTimer.start(); return; } @@ -545,6 +541,27 @@ ColumnLayout { } } + Timer { + id: connectionSuccessTimer + interval: 500 + onTriggered: { + // Double-check connection is still active + if (root.shouldBeVisible && Nmcli.active && Nmcli.active.ssid) { + const stillConnected = Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); + if (stillConnected) { + connectionMonitor.stop(); + connectButton.connecting = false; + connectButton.text = qsTr("Connect"); + // Return to network popout on successful connection + if (root.wrapper.currentName === "wirelesspassword") { + root.wrapper.currentName = "network"; + } + closeDialog(); + } + } + } + } + Connections { target: Nmcli function onActiveChanged() { diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 56ab7f1..74e0034 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -15,500 +15,507 @@ import Quickshell.Widgets import QtQuick import QtQuick.Layouts -RowLayout { +Item { id: root required property Session session anchors.fill: parent - spacing: 0 + RowLayout { + id: contentLayout - Item { - Layout.preferredWidth: Math.floor(parent.width * 0.4) - Layout.minimumWidth: 420 - Layout.fillHeight: true + anchors.fill: parent + spacing: 0 - // Left pane - networking list with collapsible sections - StyledFlickable { - id: leftFlickable + Item { + Layout.preferredWidth: Math.floor(parent.width * 0.4) + Layout.minimumWidth: 420 + Layout.fillHeight: true - anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - flickableDirection: Flickable.VerticalFlick - contentHeight: leftContent.height - clip: true - - StyledScrollBar.vertical: StyledScrollBar { - flickable: leftFlickable - } + // Left pane - networking list with collapsible sections + StyledFlickable { + id: leftFlickable - ColumnLayout { - id: leftContent + anchors.fill: parent + anchors.margins: Appearance.padding.large + Appearance.padding.normal + anchors.leftMargin: Appearance.padding.large + anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + flickableDirection: Flickable.VerticalFlick + contentHeight: leftContent.height + clip: true - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.normal + StyledScrollBar.vertical: StyledScrollBar { + flickable: leftFlickable + } - // Settings header above the collapsible sections - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.smaller + ColumnLayout { + id: leftContent - StyledText { - text: qsTr("Settings") - font.pointSize: Appearance.font.size.large - font.weight: 500 - } + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.normal - Item { + // Settings header above the collapsible sections + RowLayout { Layout.fillWidth: true - } + spacing: Appearance.spacing.smaller + + StyledText { + text: qsTr("Settings") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } - ToggleButton { - toggled: Nmcli.wifiEnabled - icon: "wifi" - accent: "Tertiary" + Item { + Layout.fillWidth: true + } - onClicked: { - Nmcli.toggleWifi(null); + ToggleButton { + toggled: Nmcli.wifiEnabled + icon: "wifi" + accent: "Tertiary" + + onClicked: { + Nmcli.toggleWifi(null); + } } - } - ToggleButton { - toggled: Nmcli.scanning - icon: "wifi_find" - accent: "Secondary" + ToggleButton { + toggled: Nmcli.scanning + icon: "wifi_find" + accent: "Secondary" - onClicked: { - Nmcli.rescanWifi(); + onClicked: { + Nmcli.rescanWifi(); + } } - } - ToggleButton { - toggled: !root.session.ethernet.active && !root.session.network.active - icon: "settings" - accent: "Primary" - - onClicked: { - if (root.session.ethernet.active || root.session.network.active) { - root.session.ethernet.active = null; - root.session.network.active = null; - } else { - // Toggle to show settings - prefer ethernet if available, otherwise wireless - if (Nmcli.ethernetDevices.length > 0) { - root.session.ethernet.active = Nmcli.ethernetDevices[0]; - } else if (Nmcli.networks.length > 0) { - root.session.network.active = Nmcli.networks[0]; + ToggleButton { + toggled: !root.session.ethernet.active && !root.session.network.active + icon: "settings" + accent: "Primary" + + onClicked: { + if (root.session.ethernet.active || root.session.network.active) { + root.session.ethernet.active = null; + root.session.network.active = null; + } else { + // Toggle to show settings - prefer ethernet if available, otherwise wireless + if (Nmcli.ethernetDevices.length > 0) { + root.session.ethernet.active = Nmcli.ethernetDevices[0]; + } else if (Nmcli.networks.length > 0) { + root.session.network.active = Nmcli.networks[0]; + } } } } } - } - - CollapsibleSection { - id: ethernetListSection - Layout.fillWidth: true - title: qsTr("Ethernet") - expanded: true + CollapsibleSection { + id: ethernetListSection - ColumnLayout { Layout.fillWidth: true - spacing: Appearance.spacing.small + title: qsTr("Ethernet") + expanded: true - RowLayout { + ColumnLayout { 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 + 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 - } + StyledText { + Layout.fillWidth: true + text: qsTr("All available ethernet devices") + color: Colours.palette.m3outline + } - Repeater { - id: ethernetRepeater + Repeater { + id: ethernetRepeater - Layout.fillWidth: true - model: Nmcli.ethernetDevices + Layout.fillWidth: true + model: Nmcli.ethernetDevices - delegate: StyledRect { - required property var modelData + delegate: StyledRect { + required property var modelData - Layout.fillWidth: true + Layout.fillWidth: true - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.ethernet.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal + 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; + StateLayer { + function onClicked(): void { + root.session.network.active = null; + root.session.ethernet.active = modelData; + } } - } - RowLayout { - id: rowLayout + RowLayout { + id: rowLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal - spacing: Appearance.spacing.normal + spacing: Appearance.spacing.normal - StyledRect { - implicitWidth: implicitHeight - implicitHeight: icon.implicitHeight + Appearance.padding.normal * 2 + StyledRect { + implicitWidth: implicitHeight + implicitHeight: icon.implicitHeight + Appearance.padding.normal * 2 - radius: Appearance.rounding.normal - color: modelData.connected ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh + radius: Appearance.rounding.normal + color: modelData.connected ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh - MaterialIcon { - id: icon + 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 + 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 + } } - } - StyledText { - Layout.fillWidth: true - elide: Text.ElideRight - maximumLineCount: 1 + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 - text: modelData.interface || qsTr("Unknown") - } + text: modelData.interface || qsTr("Unknown") + } - StyledText { - 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 - } + StyledText { + 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 + } - StyledRect { - implicitWidth: implicitHeight - implicitHeight: connectIcon.implicitHeight + Appearance.padding.smaller * 2 + StyledRect { + implicitWidth: implicitHeight + implicitHeight: connectIcon.implicitHeight + Appearance.padding.smaller * 2 - radius: Appearance.rounding.full - color: Qt.alpha(Colours.palette.m3primaryContainer, modelData.connected ? 1 : 0) + 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 || "", () => {}); + StateLayer { + function onClicked(): void { + if (modelData.connected && modelData.connection) { + Nmcli.disconnectEthernet(modelData.connection, () => {}); + } else { + Nmcli.connectEthernet(modelData.connection || "", modelData.interface || "", () => {}); + } } } - } - MaterialIcon { - id: connectIcon + MaterialIcon { + id: connectIcon - anchors.centerIn: parent - text: modelData.connected ? "link_off" : "link" - color: modelData.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface + 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 + implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 + } } } } - } - - CollapsibleSection { - id: wirelessListSection - Layout.fillWidth: true - title: qsTr("Wireless") - expanded: true + CollapsibleSection { + id: wirelessListSection - ColumnLayout { Layout.fillWidth: true - spacing: Appearance.spacing.small + title: qsTr("Wireless") + expanded: true - RowLayout { + ColumnLayout { 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 + 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 { - visible: Nmcli.scanning - text: qsTr("Scanning...") - color: Colours.palette.m3primary - font.pointSize: Appearance.font.size.small + Layout.fillWidth: true + text: qsTr("All available WiFi networks") + color: Colours.palette.m3outline } - } - - StyledText { - Layout.fillWidth: true - text: qsTr("All available WiFi networks") - color: Colours.palette.m3outline - } - Repeater { - id: wirelessRepeater + 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; - }) - } + 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 + delegate: StyledRect { + required property var modelData - Layout.fillWidth: true + Layout.fillWidth: true - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.session.network.active === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal + 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 && modelData.ssid) { + if (modelData.ssid) { checkSavedProfileForNetwork(modelData.ssid); } } } - RowLayout { - id: wirelessRowLayout + RowLayout { + id: wirelessRowLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal - spacing: Appearance.spacing.normal + spacing: Appearance.spacing.normal - StyledRect { - implicitWidth: implicitHeight - implicitHeight: wirelessIcon.implicitHeight + Appearance.padding.normal * 2 + StyledRect { + implicitWidth: implicitHeight + implicitHeight: wirelessIcon.implicitHeight + Appearance.padding.normal * 2 - radius: Appearance.rounding.normal - color: modelData.active ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh + radius: Appearance.rounding.normal + color: (modelData && modelData.active) ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh - MaterialIcon { - id: wirelessIcon + MaterialIcon { + id: wirelessIcon - anchors.centerIn: parent - text: Icons.getNetworkIcon(modelData.strength) - font.pointSize: Appearance.font.size.large - fill: modelData.active ? 1 : 0 - color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface + 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 + } } - } - StyledText { - Layout.fillWidth: true - elide: Text.ElideRight - maximumLineCount: 1 + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 - text: modelData.ssid || qsTr("Unknown") - } + text: (modelData && modelData.ssid) ? modelData.ssid : qsTr("Unknown") + } - StyledText { - text: modelData.active ? qsTr("Connected") : (modelData.isSecure ? qsTr("Secured") : qsTr("Open")) - color: modelData.active ? Colours.palette.m3primary : Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - font.weight: modelData.active ? 500 : 400 - } + StyledText { + text: (modelData && modelData.active) ? qsTr("Connected") : ((modelData && modelData.isSecure) ? qsTr("Secured") : 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 + } - StyledRect { - implicitWidth: implicitHeight - implicitHeight: wirelessConnectIcon.implicitHeight + Appearance.padding.smaller * 2 + StyledRect { + implicitWidth: implicitHeight + implicitHeight: wirelessConnectIcon.implicitHeight + Appearance.padding.smaller * 2 - radius: Appearance.rounding.full - color: Qt.alpha(Colours.palette.m3primaryContainer, modelData.active ? 1 : 0) + radius: Appearance.rounding.full + color: Qt.alpha(Colours.palette.m3primaryContainer, (modelData && modelData.active) ? 1 : 0) - StateLayer { - function onClicked(): void { - if (modelData.active) { - Nmcli.disconnectFromNetwork(); - } else { - handleWirelessConnect(modelData); + StateLayer { + function onClicked(): void { + if (modelData && modelData.active) { + Nmcli.disconnectFromNetwork(); + } else if (modelData) { + handleWirelessConnect(modelData); + } } } - } - MaterialIcon { - id: wirelessConnectIcon + MaterialIcon { + id: wirelessConnectIcon - anchors.centerIn: parent - text: modelData.active ? "link_off" : "link" - color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface + 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 + implicitHeight: wirelessRowLayout.implicitHeight + Appearance.padding.normal * 2 + } } } } } } - } - InnerBorder { - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 + InnerBorder { + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } } - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - ClippingRectangle { - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: rightBorder.innerRadius - color: "transparent" - clip: true - - // Right pane - networking details/settings - Loader { - id: loader - property var ethernetPane: root.session.ethernet.active - property var wirelessPane: root.session.network.active - property var pane: ethernetPane || wirelessPane - property string paneId: ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : "") + Item { + Layout.fillWidth: true + Layout.fillHeight: true + ClippingRectangle { anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 - - opacity: 1 - scale: 1 - transformOrigin: Item.Center + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 + radius: rightBorder.innerRadius + color: "transparent" clip: true - asynchronous: true - sourceComponent: pane ? (ethernetPane ? ethernetDetails : wirelessDetails) : settings - - Behavior on paneId { - SequentialAnimation { - ParallelAnimation { - Anim { - target: loader - property: "opacity" - to: 0 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - Anim { - target: loader - property: "scale" - to: 0.8 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - } - PropertyAction {} - ParallelAnimation { - Anim { - target: loader - property: "opacity" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel + + // Right pane - networking details/settings + Loader { + id: loader + + property var ethernetPane: root.session.ethernet.active + property var wirelessPane: root.session.network.active + property var pane: ethernetPane || wirelessPane + property string paneId: ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : "") + + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + opacity: 1 + scale: 1 + transformOrigin: Item.Center + + clip: true + asynchronous: true + sourceComponent: pane ? (ethernetPane ? ethernetDetails : wirelessDetails) : settings + + Behavior on paneId { + SequentialAnimation { + ParallelAnimation { + Anim { + target: loader + property: "opacity" + to: 0 + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + Anim { + target: loader + property: "scale" + to: 0.8 + easing.bezierCurve: Appearance.anim.curves.standardAccel + } } - Anim { - target: loader - property: "scale" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel + PropertyAction {} + ParallelAnimation { + Anim { + target: loader + property: "opacity" + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + Anim { + target: loader + property: "scale" + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } } } } - } - onPaneChanged: { - paneId = ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : ""); + onPaneChanged: { + paneId = ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : ""); + } } } - } - InnerBorder { - id: rightBorder + InnerBorder { + id: rightBorder - leftThickness: Appearance.padding.normal / 2 - } + leftThickness: Appearance.padding.normal / 2 + } - Component { - id: settings + Component { + id: settings - StyledFlickable { - id: settingsFlickable + StyledFlickable { + id: settingsFlickable - flickableDirection: Flickable.VerticalFlick - contentHeight: settingsInner.height - clip: true + flickableDirection: Flickable.VerticalFlick + contentHeight: settingsInner.height + clip: true - StyledScrollBar.vertical: StyledScrollBar { - flickable: settingsFlickable - } + StyledScrollBar.vertical: StyledScrollBar { + flickable: settingsFlickable + } - NetworkSettings { - id: settingsInner + NetworkSettings { + id: settingsInner - anchors.left: parent.left - anchors.right: parent.right - session: root.session + anchors.left: parent.left + anchors.right: parent.right + session: root.session + } } } - } - Component { - id: ethernetDetails + Component { + id: ethernetDetails - EthernetDetails { - session: root.session + EthernetDetails { + session: root.session + } } - } - Component { - id: wirelessDetails + Component { + id: wirelessDetails - WirelessDetails { - session: root.session + WirelessDetails { + session: root.session + } } } } WirelessPasswordDialog { - Layout.fillWidth: true - Layout.fillHeight: true + anchors.fill: parent session: root.session z: 1000 } diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index f3381b7..4b350be 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -131,14 +131,17 @@ Item { Layout.alignment: Qt.AlignHCenter Layout.topMargin: Appearance.spacing.small - visible: connectButton.connecting + visible: connectButton.connecting || connectButton.hasError text: { + if (connectButton.hasError) { + return qsTr("Connection failed. Please check your password and try again."); + } if (connectButton.connecting) { return qsTr("Connecting..."); } return ""; } - color: Colours.palette.m3onSurfaceVariant + color: connectButton.hasError ? Colours.palette.m3error : Colours.palette.m3onSurfaceVariant font.pointSize: Appearance.font.size.small font.weight: 400 wrapMode: Text.WordWrap @@ -153,18 +156,31 @@ Item { focus: true Keys.onPressed: event => { + // Ensure we have focus when receiving keyboard input + if (!activeFocus) { + forceActiveFocus(); + } + + // Clear error when user starts typing + if (connectButton.hasError && event.text && event.text.length > 0) { + connectButton.hasError = false; + } + if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { if (connectButton.enabled) { connectButton.clicked(); } + event.accepted = true; } else if (event.key === Qt.Key_Backspace) { if (event.modifiers & Qt.ControlModifier) { passwordBuffer = ""; } else { passwordBuffer = passwordBuffer.slice(0, -1); } + event.accepted = true; } else if (event.text && event.text.length > 0) { passwordBuffer += event.text; + event.accepted = true; } } @@ -178,6 +194,7 @@ Item { Qt.callLater(() => { passwordContainer.forceActiveFocus(); passwordContainer.passwordBuffer = ""; + connectButton.hasError = false; }); } } @@ -198,13 +215,29 @@ Item { StyledRect { anchors.fill: parent radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - border.width: passwordContainer.activeFocus ? 2 : 1 - border.color: passwordContainer.activeFocus ? Colours.palette.m3primary : Colours.palette.m3outline + color: passwordContainer.activeFocus ? Qt.lighter(Colours.tPalette.m3surfaceContainer, 1.05) : Colours.tPalette.m3surfaceContainer + border.width: passwordContainer.activeFocus || connectButton.hasError ? 4 : (root.visible ? 1 : 0) + border.color: { + if (connectButton.hasError) { + return Colours.palette.m3error; + } + if (passwordContainer.activeFocus) { + return Colours.palette.m3primary; + } + return root.visible ? Colours.palette.m3outline : "transparent"; + } Behavior on border.color { CAnim {} } + + Behavior on border.width { + CAnim {} + } + + Behavior on color { + CAnim {} + } } StateLayer { @@ -329,6 +362,9 @@ Item { TextButton { id: connectButton + property bool connecting: false + property bool hasError: false + Layout.fillWidth: true Layout.minimumHeight: Appearance.font.size.normal + Appearance.padding.normal * 2 inactiveColour: Colours.palette.m3primary @@ -336,8 +372,6 @@ Item { text: qsTr("Connect") enabled: passwordContainer.passwordBuffer.length > 0 && !connecting - property bool connecting: false - onClicked: { if (!root.network || connecting) { return; @@ -348,6 +382,9 @@ Item { return; } + // Clear any previous error + hasError = false; + // Set connecting state connecting = true; enabled = false; @@ -361,11 +398,27 @@ Item { // Shouldn't happen since we provided password connectionMonitor.stop(); connecting = false; + hasError = true; enabled = true; text = qsTr("Connect"); - } else - // Connection failed, monitor will handle timeout - {} + passwordContainer.passwordBuffer = ""; + // Delete the failed connection + if (root.network && root.network.ssid) { + Nmcli.forgetNetwork(root.network.ssid); + } + } else { + // Connection failed immediately - show error + connectionMonitor.stop(); + connecting = false; + hasError = true; + enabled = true; + text = qsTr("Connect"); + passwordContainer.passwordBuffer = ""; + // Delete the failed connection + if (root.network && root.network.ssid) { + Nmcli.forgetNetwork(root.network.ssid); + } + } }); // Start monitoring connection @@ -386,18 +439,8 @@ Item { if (isConnected) { // Successfully connected - give it a moment for network list to update - Qt.callLater(() => { - // Double-check connection is still active - if (root.visible && Nmcli.active && Nmcli.active.ssid) { - const stillConnected = Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); - if (stillConnected) { - connectionMonitor.stop(); - connectButton.connecting = false; - connectButton.text = qsTr("Connect"); - closeDialog(); - } - } - }, 500); + // Use Timer for actual delay + connectionSuccessTimer.start(); return; } @@ -407,8 +450,14 @@ Item { if (connectionMonitor.repeatCount > 10) { connectionMonitor.stop(); connectButton.connecting = false; + connectButton.hasError = true; connectButton.enabled = true; connectButton.text = qsTr("Connect"); + passwordContainer.passwordBuffer = ""; + // Delete the failed connection + if (root.network && root.network.ssid) { + Nmcli.forgetNetwork(root.network.ssid); + } } } } @@ -432,6 +481,23 @@ Item { } } + Timer { + id: connectionSuccessTimer + interval: 500 + onTriggered: { + // Double-check connection is still active + if (root.visible && Nmcli.active && Nmcli.active.ssid) { + const stillConnected = Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); + if (stillConnected) { + connectionMonitor.stop(); + connectButton.connecting = false; + connectButton.text = qsTr("Connect"); + closeDialog(); + } + } + } + } + Connections { target: Nmcli function onActiveChanged() { @@ -443,8 +509,12 @@ Item { if (root.visible && root.network && root.network.ssid === ssid && connectButton.connecting) { connectionMonitor.stop(); connectButton.connecting = false; + connectButton.hasError = true; connectButton.enabled = true; connectButton.text = qsTr("Connect"); + passwordContainer.passwordBuffer = ""; + // Delete the failed connection + Nmcli.forgetNetwork(ssid); } } } @@ -457,6 +527,7 @@ Item { isClosing = true; passwordContainer.passwordBuffer = ""; connectButton.connecting = false; + connectButton.hasError = false; connectButton.text = qsTr("Connect"); connectionMonitor.stop(); } diff --git a/services/Nmcli.qml b/services/Nmcli.qml index 24a93da..36bd3e6 100644 --- a/services/Nmcli.qml +++ b/services/Nmcli.qml @@ -1272,7 +1272,15 @@ Singleton { stdout: SplitParser { onRead: root.refreshOnConnectionChange() } - onExited: Qt.callLater(() => monitorProc.running = true, 2000) + onExited: monitorRestartTimer.start() + } + + Timer { + id: monitorRestartTimer + interval: 2000 + onTriggered: { + monitorProc.running = true; + } } function refreshOnConnectionChange(): void { -- cgit v1.2.3-freya From f79fd9328ce01bde921775a0301a3a6969deaa34 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 10:25:45 -0500 Subject: controlcenter: fix edge to edge in panels --- .../controlcenter/appearance/AppearancePane.qml | 12 ++++++----- modules/controlcenter/audio/AudioPane.qml | 18 ++++++++++------ modules/controlcenter/bluetooth/BtPane.qml | 3 --- modules/controlcenter/launcher/LauncherPane.qml | 14 +++++++++--- modules/controlcenter/network/NetworkingPane.qml | 25 +++++++++------------- modules/controlcenter/taskbar/TaskbarPane.qml | 12 ++++++----- 6 files changed, 46 insertions(+), 38 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index 1e81205..35b479a 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -114,8 +114,7 @@ RowLayout { id: sidebarFlickable anchors.fill: parent flickableDirection: Flickable.VerticalFlick - contentHeight: sidebarLayout.implicitHeight + Appearance.padding.large * 2 - clip: true + contentHeight: sidebarLayout.height StyledScrollBar.vertical: StyledScrollBar { flickable: sidebarFlickable @@ -852,11 +851,12 @@ RowLayout { StyledFlickable { anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 flickableDirection: Flickable.VerticalFlick - contentHeight: contentLayout.implicitHeight - clip: true + contentHeight: contentLayout.height StyledScrollBar.vertical: StyledScrollBar { flickable: parent @@ -868,6 +868,8 @@ RowLayout { anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top + anchors.leftMargin: Appearance.padding.large * 2 + anchors.rightMargin: Appearance.padding.large * 2 spacing: Appearance.spacing.normal diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index b4a0bf1..1f8677e 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -31,7 +31,10 @@ RowLayout { anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 flickableDirection: Flickable.VerticalFlick contentHeight: leftContent.height - clip: true + + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent + } ColumnLayout { id: leftContent @@ -225,17 +228,16 @@ RowLayout { Layout.fillHeight: true StyledFlickable { - id: rightFlickable - anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 flickableDirection: Flickable.VerticalFlick - contentHeight: contentLayout.implicitHeight - clip: true + contentHeight: contentLayout.height StyledScrollBar.vertical: StyledScrollBar { - flickable: rightFlickable + flickable: parent } ColumnLayout { @@ -244,6 +246,8 @@ RowLayout { anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top + anchors.leftMargin: Appearance.padding.large * 2 + anchors.rightMargin: Appearance.padding.large * 2 spacing: Appearance.spacing.normal diff --git a/modules/controlcenter/bluetooth/BtPane.qml b/modules/controlcenter/bluetooth/BtPane.qml index 40cf356..96dc002 100644 --- a/modules/controlcenter/bluetooth/BtPane.qml +++ b/modules/controlcenter/bluetooth/BtPane.qml @@ -50,7 +50,6 @@ RowLayout { radius: rightBorder.innerRadius color: "transparent" - clip: true Loader { id: loader @@ -60,7 +59,6 @@ RowLayout { anchors.fill: parent anchors.margins: Appearance.padding.large * 2 - clip: true asynchronous: true sourceComponent: pane ? details : settings @@ -108,7 +106,6 @@ RowLayout { StyledFlickable { flickableDirection: Flickable.VerticalFlick contentHeight: settingsInner.height - clip: true Settings { id: settingsInner diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index d585c32..1869e18 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -153,6 +153,7 @@ RowLayout { anchors.margins: Appearance.padding.large + Appearance.padding.normal anchors.leftMargin: Appearance.padding.large anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + anchors.bottomMargin: 0 spacing: Appearance.spacing.small @@ -335,12 +336,16 @@ RowLayout { ColumnLayout { anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 spacing: Appearance.spacing.normal Item { Layout.alignment: Qt.AlignHCenter + Layout.leftMargin: Appearance.padding.large * 2 + Layout.rightMargin: Appearance.padding.large * 2 implicitWidth: iconLoader.implicitWidth implicitHeight: iconLoader.implicitHeight @@ -376,6 +381,8 @@ RowLayout { StyledText { Layout.alignment: Qt.AlignHCenter + Layout.leftMargin: Appearance.padding.large * 2 + Layout.rightMargin: Appearance.padding.large * 2 text: root.selectedApp ? (root.selectedApp.name || root.selectedApp.entry?.name || qsTr("Application Details")) : qsTr("Launcher Applications") font.pointSize: Appearance.font.size.large font.bold: true @@ -385,12 +392,13 @@ RowLayout { Layout.fillWidth: true Layout.fillHeight: true Layout.topMargin: Appearance.spacing.large + Layout.leftMargin: Appearance.padding.large * 2 + Layout.rightMargin: Appearance.padding.large * 2 StyledFlickable { anchors.fill: parent flickableDirection: Flickable.VerticalFlick - contentHeight: debugLayout.implicitHeight - clip: true + contentHeight: debugLayout.height StyledScrollBar.vertical: StyledScrollBar { flickable: parent diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 74e0034..c404af0 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -38,12 +38,8 @@ Item { id: leftFlickable anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 flickableDirection: Flickable.VerticalFlick contentHeight: leftContent.height - clip: true StyledScrollBar.vertical: StyledScrollBar { flickable: leftFlickable @@ -54,6 +50,10 @@ Item { anchors.left: parent.left anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Appearance.padding.large + Appearance.padding.normal + anchors.leftMargin: Appearance.padding.large + anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 spacing: Appearance.spacing.normal // Settings header above the collapsible sections @@ -404,7 +404,6 @@ Item { radius: rightBorder.innerRadius color: "transparent" - clip: true // Right pane - networking details/settings Loader { @@ -415,14 +414,17 @@ Item { property var pane: ethernetPane || wirelessPane property string paneId: ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : "") - anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.leftMargin: Appearance.padding.large * 2 + anchors.rightMargin: Appearance.padding.large * 2 opacity: 1 scale: 1 transformOrigin: Item.Center - clip: true asynchronous: true sourceComponent: pane ? (ethernetPane ? ethernetDetails : wirelessDetails) : settings @@ -476,15 +478,8 @@ Item { id: settings StyledFlickable { - id: settingsFlickable - flickableDirection: Flickable.VerticalFlick contentHeight: settingsInner.height - clip: true - - StyledScrollBar.vertical: StyledScrollBar { - flickable: settingsFlickable - } NetworkSettings { id: settingsInner diff --git a/modules/controlcenter/taskbar/TaskbarPane.qml b/modules/controlcenter/taskbar/TaskbarPane.qml index 5385ab7..9ec9e2a 100644 --- a/modules/controlcenter/taskbar/TaskbarPane.qml +++ b/modules/controlcenter/taskbar/TaskbarPane.qml @@ -134,8 +134,7 @@ RowLayout { id: sidebarFlickable anchors.fill: parent flickableDirection: Flickable.VerticalFlick - contentHeight: sidebarLayout.implicitHeight + Appearance.padding.large * 2 - clip: true + contentHeight: sidebarLayout.height StyledScrollBar.vertical: StyledScrollBar { flickable: sidebarFlickable @@ -531,11 +530,12 @@ RowLayout { StyledFlickable { anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 flickableDirection: Flickable.VerticalFlick - contentHeight: contentLayout.implicitHeight - clip: true + contentHeight: contentLayout.height StyledScrollBar.vertical: StyledScrollBar { flickable: parent @@ -547,6 +547,8 @@ RowLayout { anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top + anchors.leftMargin: Appearance.padding.large * 2 + anchors.rightMargin: Appearance.padding.large * 2 spacing: Appearance.spacing.normal -- cgit v1.2.3-freya From bfe0905d4760ad14eaeaf6beb0a75fc2ca77a332 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 10:58:04 -0500 Subject: controlcenter: correcting margins (networking details pane) --- modules/controlcenter/network/NetworkingPane.qml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index c404af0..ada29dc 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -414,12 +414,8 @@ Item { property var pane: ethernetPane || wirelessPane property string paneId: ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : "") - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.leftMargin: Appearance.padding.large * 2 - anchors.rightMargin: Appearance.padding.large * 2 + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 opacity: 1 scale: 1 -- cgit v1.2.3-freya From b0006f2f1146c14f4a8d719d6a268ffce1fed0de Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 16:44:11 -0500 Subject: controlcenter: corrected all panels edge-to-edge containers --- .../controlcenter/appearance/AppearancePane.qml | 295 ++++++++++++--------- modules/controlcenter/audio/AudioPane.qml | 124 ++++++--- modules/controlcenter/bluetooth/BtPane.qml | 37 ++- modules/controlcenter/launcher/LauncherPane.qml | 87 ++++-- modules/controlcenter/network/NetworkingPane.qml | 63 +++-- modules/controlcenter/taskbar/TaskbarPane.qml | 155 ++++++----- 6 files changed, 491 insertions(+), 270 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index 6781cf0..ba95977 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -12,6 +12,7 @@ import qs.config import qs.utils import Caelestia.Models import Quickshell +import Quickshell.Widgets import QtQuick import QtQuick.Layouts @@ -47,26 +48,6 @@ RowLayout { spacing: 0 - function collapseAllSections(exceptSection) { - if (exceptSection !== themeModeSection) - themeModeSection.expanded = false; - if (exceptSection !== colorVariantSection) - colorVariantSection.expanded = false; - if (exceptSection !== colorSchemeSection) - colorSchemeSection.expanded = false; - if (exceptSection !== animationsSection) - animationsSection.expanded = false; - if (exceptSection !== fontsSection) - fontsSection.expanded = false; - if (exceptSection !== scalesSection) - scalesSection.expanded = false; - if (exceptSection !== transparencySection) - transparencySection.expanded = false; - if (exceptSection !== borderSection) - borderSection.expanded = false; - if (exceptSection !== backgroundSection) - backgroundSection.expanded = false; - } function saveConfig() { // Update animations @@ -110,26 +91,72 @@ RowLayout { Layout.minimumWidth: 420 Layout.fillHeight: true - StyledFlickable { - id: sidebarFlickable + ClippingRectangle { + id: leftAppearanceClippingRect anchors.fill: parent - flickableDirection: Flickable.VerticalFlick - contentHeight: sidebarLayout.height - - StyledScrollBar.vertical: StyledScrollBar { - flickable: sidebarFlickable - } + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 + radius: leftAppearanceBorder.innerRadius + color: "transparent" - ColumnLayout { - id: sidebarLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top + Loader { + id: leftAppearanceLoader + anchors.fill: parent anchors.margins: Appearance.padding.large + Appearance.padding.normal anchors.leftMargin: Appearance.padding.large anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + asynchronous: true + sourceComponent: appearanceLeftContentComponent + property var rootPane: root + } + } + + InnerBorder { + id: leftAppearanceBorder + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + + Component { + id: appearanceLeftContentComponent + + StyledFlickable { + id: sidebarFlickable + readonly property var rootPane: leftAppearanceLoader.rootPane + flickableDirection: Flickable.VerticalFlick + contentHeight: sidebarLayout.height + + function collapseAllSections(exceptSection) { + if (exceptSection !== themeModeSection) + themeModeSection.expanded = false; + if (exceptSection !== colorVariantSection) + colorVariantSection.expanded = false; + if (exceptSection !== colorSchemeSection) + colorSchemeSection.expanded = false; + if (exceptSection !== animationsSection) + animationsSection.expanded = false; + if (exceptSection !== fontsSection) + fontsSection.expanded = false; + if (exceptSection !== scalesSection) + scalesSection.expanded = false; + if (exceptSection !== transparencySection) + transparencySection.expanded = false; + if (exceptSection !== borderSection) + borderSection.expanded = false; + if (exceptSection !== backgroundSection) + backgroundSection.expanded = false; + } - spacing: Appearance.spacing.small + StyledScrollBar.vertical: StyledScrollBar { + flickable: sidebarFlickable + } + + ColumnLayout { + id: sidebarLayout + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.small RowLayout { spacing: Appearance.spacing.smaller @@ -150,7 +177,7 @@ RowLayout { title: qsTr("Theme mode") description: qsTr("Light or dark theme") onToggleRequested: { - root.collapseAllSections(themeModeSection); + sidebarFlickable.collapseAllSections(themeModeSection); } SwitchRow { @@ -167,7 +194,7 @@ RowLayout { title: qsTr("Color variant") description: qsTr("Material theme variant") onToggleRequested: { - root.collapseAllSections(colorVariantSection); + sidebarFlickable.collapseAllSections(colorVariantSection); } StyledListView { @@ -257,7 +284,7 @@ RowLayout { title: qsTr("Color scheme") description: qsTr("Available color schemes") onToggleRequested: { - root.collapseAllSections(colorSchemeSection); + sidebarFlickable.collapseAllSections(colorSchemeSection); } StyledListView { @@ -401,7 +428,7 @@ RowLayout { id: animationsSection title: qsTr("Animations") onToggleRequested: { - root.collapseAllSections(animationsSection); + sidebarFlickable.collapseAllSections(animationsSection); } SpinBoxRow { @@ -409,10 +436,10 @@ RowLayout { min: 0.1 max: 5 step: 0.1 - value: root.animDurationsScale + value: rootPane.animDurationsScale onValueModified: value => { - root.animDurationsScale = value; - root.saveConfig(); + rootPane.animDurationsScale = value; + rootPane.saveConfig(); } } } @@ -421,7 +448,7 @@ RowLayout { id: fontsSection title: qsTr("Fonts") onToggleRequested: { - root.collapseAllSections(fontsSection); + sidebarFlickable.collapseAllSections(fontsSection); } StyledText { @@ -449,7 +476,7 @@ RowLayout { anchors.left: parent.left anchors.right: parent.right - readonly property bool isCurrent: modelData === root.fontFamilyMaterial + readonly property bool isCurrent: modelData === rootPane.fontFamilyMaterial color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal border.width: isCurrent ? 1 : 0 @@ -457,8 +484,8 @@ RowLayout { StateLayer { function onClicked(): void { - root.fontFamilyMaterial = modelData; - root.saveConfig(); + rootPane.fontFamilyMaterial = modelData; + rootPane.saveConfig(); } } @@ -522,7 +549,7 @@ RowLayout { anchors.left: parent.left anchors.right: parent.right - readonly property bool isCurrent: modelData === root.fontFamilyMono + readonly property bool isCurrent: modelData === rootPane.fontFamilyMono color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal border.width: isCurrent ? 1 : 0 @@ -530,8 +557,8 @@ RowLayout { StateLayer { function onClicked(): void { - root.fontFamilyMono = modelData; - root.saveConfig(); + rootPane.fontFamilyMono = modelData; + rootPane.saveConfig(); } } @@ -595,7 +622,7 @@ RowLayout { anchors.left: parent.left anchors.right: parent.right - readonly property bool isCurrent: modelData === root.fontFamilySans + readonly property bool isCurrent: modelData === rootPane.fontFamilySans color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal border.width: isCurrent ? 1 : 0 @@ -603,8 +630,8 @@ RowLayout { StateLayer { function onClicked(): void { - root.fontFamilySans = modelData; - root.saveConfig(); + rootPane.fontFamilySans = modelData; + rootPane.saveConfig(); } } @@ -648,10 +675,10 @@ RowLayout { min: 0.1 max: 5 step: 0.1 - value: root.fontSizeScale + value: rootPane.fontSizeScale onValueModified: value => { - root.fontSizeScale = value; - root.saveConfig(); + rootPane.fontSizeScale = value; + rootPane.saveConfig(); } } } @@ -660,7 +687,7 @@ RowLayout { id: scalesSection title: qsTr("Scales") onToggleRequested: { - root.collapseAllSections(scalesSection); + sidebarFlickable.collapseAllSections(scalesSection); } SpinBoxRow { @@ -668,10 +695,10 @@ RowLayout { min: 0.1 max: 5 step: 0.1 - value: root.paddingScale + value: rootPane.paddingScale onValueModified: value => { - root.paddingScale = value; - root.saveConfig(); + rootPane.paddingScale = value; + rootPane.saveConfig(); } } @@ -680,10 +707,10 @@ RowLayout { min: 0.1 max: 5 step: 0.1 - value: root.roundingScale + value: rootPane.roundingScale onValueModified: value => { - root.roundingScale = value; - root.saveConfig(); + rootPane.roundingScale = value; + rootPane.saveConfig(); } } @@ -692,10 +719,10 @@ RowLayout { min: 0.1 max: 5 step: 0.1 - value: root.spacingScale + value: rootPane.spacingScale onValueModified: value => { - root.spacingScale = value; - root.saveConfig(); + rootPane.spacingScale = value; + rootPane.saveConfig(); } } } @@ -704,15 +731,15 @@ RowLayout { id: transparencySection title: qsTr("Transparency") onToggleRequested: { - root.collapseAllSections(transparencySection); + sidebarFlickable.collapseAllSections(transparencySection); } SwitchRow { label: qsTr("Transparency enabled") - checked: root.transparencyEnabled + checked: rootPane.transparencyEnabled onToggled: checked => { - root.transparencyEnabled = checked; - root.saveConfig(); + rootPane.transparencyEnabled = checked; + rootPane.saveConfig(); } } @@ -737,7 +764,7 @@ RowLayout { } StyledText { - text: qsTr("%1%").arg(Math.round(root.transparencyBase * 100)) + text: qsTr("%1%").arg(Math.round(rootPane.transparencyBase * 100)) color: Colours.palette.m3outline font.pointSize: Appearance.font.size.normal } @@ -751,10 +778,10 @@ RowLayout { from: 0 to: 100 - value: root.transparencyBase * 100 + value: rootPane.transparencyBase * 100 onMoved: { - root.transparencyBase = baseSlider.value / 100; - root.saveConfig(); + rootPane.transparencyBase = baseSlider.value / 100; + rootPane.saveConfig(); } } } @@ -781,7 +808,7 @@ RowLayout { } StyledText { - text: qsTr("%1%").arg(Math.round(root.transparencyLayers * 100)) + text: qsTr("%1%").arg(Math.round(rootPane.transparencyLayers * 100)) color: Colours.palette.m3outline font.pointSize: Appearance.font.size.normal } @@ -795,10 +822,10 @@ RowLayout { from: 0 to: 100 - value: root.transparencyLayers * 100 + value: rootPane.transparencyLayers * 100 onMoved: { - root.transparencyLayers = layersSlider.value / 100; - root.saveConfig(); + rootPane.transparencyLayers = layersSlider.value / 100; + rootPane.saveConfig(); } } } @@ -809,7 +836,7 @@ RowLayout { id: borderSection title: qsTr("Border") onToggleRequested: { - root.collapseAllSections(borderSection); + sidebarFlickable.collapseAllSections(borderSection); } SpinBoxRow { @@ -817,10 +844,10 @@ RowLayout { min: 0.1 max: 100 step: 0.1 - value: root.borderRounding + value: rootPane.borderRounding onValueModified: value => { - root.borderRounding = value; - root.saveConfig(); + rootPane.borderRounding = value; + rootPane.saveConfig(); } } @@ -829,10 +856,10 @@ RowLayout { min: 0.1 max: 100 step: 0.1 - value: root.borderThickness + value: rootPane.borderThickness onValueModified: value => { - root.borderThickness = value; - root.saveConfig(); + rootPane.borderThickness = value; + rootPane.saveConfig(); } } } @@ -841,24 +868,24 @@ RowLayout { id: backgroundSection title: qsTr("Background") onToggleRequested: { - root.collapseAllSections(backgroundSection); + sidebarFlickable.collapseAllSections(backgroundSection); } SwitchRow { label: qsTr("Desktop clock") - checked: root.desktopClockEnabled + checked: rootPane.desktopClockEnabled onToggled: checked => { - root.desktopClockEnabled = checked; - root.saveConfig(); + rootPane.desktopClockEnabled = checked; + rootPane.saveConfig(); } } SwitchRow { label: qsTr("Background enabled") - checked: root.backgroundEnabled + checked: rootPane.backgroundEnabled onToggled: checked => { - root.backgroundEnabled = checked; - root.saveConfig(); + rootPane.backgroundEnabled = checked; + rootPane.saveConfig(); } } @@ -871,19 +898,19 @@ RowLayout { SwitchRow { label: qsTr("Visualiser enabled") - checked: root.visualiserEnabled + checked: rootPane.visualiserEnabled onToggled: checked => { - root.visualiserEnabled = checked; - root.saveConfig(); + rootPane.visualiserEnabled = checked; + rootPane.saveConfig(); } } SwitchRow { label: qsTr("Visualiser auto hide") - checked: root.visualiserAutoHide + checked: rootPane.visualiserAutoHide onToggled: checked => { - root.visualiserAutoHide = checked; - root.saveConfig(); + rootPane.visualiserAutoHide = checked; + rootPane.saveConfig(); } } @@ -891,10 +918,10 @@ RowLayout { label: qsTr("Visualiser rounding") min: 0 max: 10 - value: Math.round(root.visualiserRounding) + value: Math.round(rootPane.visualiserRounding) onValueModified: value => { - root.visualiserRounding = value; - root.saveConfig(); + rootPane.visualiserRounding = value; + rootPane.saveConfig(); } } @@ -902,19 +929,15 @@ RowLayout { label: qsTr("Visualiser spacing") min: 0 max: 10 - value: Math.round(root.visualiserSpacing) + value: Math.round(rootPane.visualiserSpacing) onValueModified: value => { - root.visualiserSpacing = value; - root.saveConfig(); + rootPane.visualiserSpacing = value; + rootPane.saveConfig(); } } } } - } - - InnerBorder { - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 + } } } @@ -922,30 +945,53 @@ RowLayout { Layout.fillWidth: true Layout.fillHeight: true - StyledFlickable { + ClippingRectangle { + id: rightAppearanceClippingRect anchors.fill: parent anchors.margins: Appearance.padding.normal anchors.leftMargin: 0 anchors.rightMargin: Appearance.padding.normal / 2 + radius: rightAppearanceBorder.innerRadius + color: "transparent" - flickableDirection: Flickable.VerticalFlick - contentHeight: contentLayout.height - - StyledScrollBar.vertical: StyledScrollBar { - flickable: parent + Loader { + id: rightAppearanceLoader + anchors.fill: parent + anchors.topMargin: Appearance.padding.large * 2 + anchors.bottomMargin: Appearance.padding.large * 2 + anchors.leftMargin: 0 + anchors.rightMargin: 0 + asynchronous: true + sourceComponent: appearanceRightContentComponent + property var rootPane: root } + } - ColumnLayout { - id: contentLayout + InnerBorder { + id: rightAppearanceBorder + leftThickness: Appearance.padding.normal / 2 + } - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.leftMargin: Appearance.padding.large * 2 - anchors.rightMargin: Appearance.padding.large * 2 - anchors.topMargin: Appearance.padding.large * 2 + Component { + id: appearanceRightContentComponent - spacing: Appearance.spacing.normal + StyledFlickable { + id: rightAppearanceFlickable + flickableDirection: Flickable.VerticalFlick + contentHeight: contentLayout.height + + StyledScrollBar.vertical: StyledScrollBar { + flickable: rightAppearanceFlickable + } + + ColumnLayout { + id: contentLayout + + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: Appearance.padding.large * 2 + anchors.rightMargin: Appearance.padding.large * 2 + spacing: Appearance.spacing.normal MaterialIcon { Layout.alignment: Qt.AlignHCenter @@ -1182,10 +1228,7 @@ RowLayout { } } } - } - - InnerBorder { - leftThickness: Appearance.padding.normal / 2 + } } } } diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index add7078..005de3a 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -7,6 +7,7 @@ import qs.components.effects import qs.components.containers import qs.services import qs.config +import Quickshell.Widgets import QtQuick import QtQuick.Layouts @@ -20,28 +21,58 @@ RowLayout { spacing: 0 Item { + id: leftAudioItem Layout.preferredWidth: Math.floor(parent.width * 0.4) Layout.minimumWidth: 420 Layout.fillHeight: true - StyledFlickable { + ClippingRectangle { + id: leftAudioClippingRect anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - flickableDirection: Flickable.VerticalFlick - contentHeight: leftContent.height - - StyledScrollBar.vertical: StyledScrollBar { - flickable: parent + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 + + radius: leftAudioBorder.innerRadius + color: "transparent" + + Loader { + id: leftAudioLoader + + anchors.fill: parent + anchors.margins: Appearance.padding.large + Appearance.padding.normal + anchors.leftMargin: Appearance.padding.large + anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + + asynchronous: true + sourceComponent: audioLeftContentComponent } + } + + InnerBorder { + id: leftAudioBorder + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + + Component { + id: audioLeftContentComponent + + StyledFlickable { + id: leftAudioFlickable + flickableDirection: Flickable.VerticalFlick + contentHeight: leftContent.height - ColumnLayout { - id: leftContent + StyledScrollBar.vertical: StyledScrollBar { + flickable: leftAudioFlickable + } + + 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 // Settings header above the collapsible sections RowLayout { @@ -215,42 +246,64 @@ RowLayout { } } } - } - - InnerBorder { - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 + } } } Item { + id: rightAudioItem Layout.fillWidth: true Layout.fillHeight: true - StyledFlickable { + ClippingRectangle { + id: rightAudioClippingRect anchors.fill: parent anchors.margins: Appearance.padding.normal anchors.leftMargin: 0 anchors.rightMargin: Appearance.padding.normal / 2 - flickableDirection: Flickable.VerticalFlick - contentHeight: contentLayout.height + radius: rightAudioBorder.innerRadius + color: "transparent" + + Loader { + id: rightAudioLoader + + anchors.fill: parent + anchors.topMargin: Appearance.padding.large * 2 + anchors.bottomMargin: Appearance.padding.large * 2 + anchors.leftMargin: 0 + anchors.rightMargin: 0 - StyledScrollBar.vertical: StyledScrollBar { - flickable: parent + asynchronous: true + sourceComponent: audioRightContentComponent } + } + + InnerBorder { + id: rightAudioBorder + leftThickness: Appearance.padding.normal / 2 + } - ColumnLayout { - id: contentLayout + Component { + id: audioRightContentComponent - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.leftMargin: Appearance.padding.large * 2 - anchors.rightMargin: Appearance.padding.large * 2 - anchors.topMargin: Appearance.padding.large * 2 + StyledFlickable { + id: rightAudioFlickable + flickableDirection: Flickable.VerticalFlick + contentHeight: contentLayout.height + + StyledScrollBar.vertical: StyledScrollBar { + flickable: rightAudioFlickable + } + + ColumnLayout { + id: contentLayout - spacing: Appearance.spacing.normal + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: Appearance.padding.large * 2 + anchors.rightMargin: Appearance.padding.large * 2 + spacing: Appearance.spacing.normal ConnectionHeader { icon: "volume_up" @@ -397,10 +450,7 @@ RowLayout { } } } - } - - InnerBorder { - leftThickness: Appearance.padding.normal / 2 + } } } } \ No newline at end of file diff --git a/modules/controlcenter/bluetooth/BtPane.qml b/modules/controlcenter/bluetooth/BtPane.qml index 96dc002..32d2c0d 100644 --- a/modules/controlcenter/bluetooth/BtPane.qml +++ b/modules/controlcenter/bluetooth/BtPane.qml @@ -19,30 +19,57 @@ RowLayout { spacing: 0 Item { + id: leftBtItem Layout.preferredWidth: Math.floor(parent.width * 0.4) Layout.minimumWidth: 420 Layout.fillHeight: true - DeviceList { + ClippingRectangle { + id: leftBtClippingRect anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 + + radius: leftBtBorder.innerRadius + color: "transparent" + + Loader { + id: leftBtLoader + + anchors.fill: parent + anchors.margins: Appearance.padding.large + Appearance.padding.normal + anchors.leftMargin: Appearance.padding.large + anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - session: root.session + asynchronous: true + sourceComponent: btDeviceListComponent + } } InnerBorder { + id: leftBtBorder leftThickness: 0 rightThickness: Appearance.padding.normal / 2 } + + Component { + id: btDeviceListComponent + + DeviceList { + anchors.fill: parent + session: root.session + } + } } Item { + id: rightBtItem Layout.fillWidth: true Layout.fillHeight: true ClippingRectangle { + id: btClippingRect anchors.fill: parent anchors.margins: Appearance.padding.normal anchors.leftMargin: 0 diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index 8ddccb4..12abc1e 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -144,20 +144,50 @@ RowLayout { } Item { + id: leftLauncherItem Layout.preferredWidth: Math.floor(parent.width * 0.4) Layout.minimumWidth: 420 Layout.fillHeight: true - ColumnLayout { + ClippingRectangle { + id: leftLauncherClippingRect anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - anchors.bottomMargin: 0 + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 + + radius: leftLauncherBorder.innerRadius + color: "transparent" + + Loader { + id: leftLauncherLoader - spacing: Appearance.spacing.small + anchors.fill: parent + anchors.margins: Appearance.padding.large + Appearance.padding.normal + anchors.leftMargin: Appearance.padding.large + anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + anchors.bottomMargin: 0 - RowLayout { + asynchronous: true + sourceComponent: leftContentComponent + } + } + + InnerBorder { + id: leftLauncherBorder + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + + Component { + id: leftContentComponent + + ColumnLayout { + anchors.fill: parent + + spacing: Appearance.spacing.small + + RowLayout { spacing: Appearance.spacing.smaller StyledText { @@ -268,6 +298,7 @@ RowLayout { } StyledListView { + id: appsListView Layout.fillWidth: true Layout.fillHeight: true @@ -323,26 +354,50 @@ RowLayout { } } } - - InnerBorder { - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 } } Item { + id: rightLauncherItem Layout.fillWidth: true Layout.fillHeight: true - ColumnLayout { + ClippingRectangle { + id: rightLauncherClippingRect anchors.fill: parent anchors.margins: Appearance.padding.normal anchors.leftMargin: 0 anchors.rightMargin: Appearance.padding.normal / 2 - spacing: Appearance.spacing.normal + radius: rightLauncherBorder.innerRadius + color: "transparent" - Item { + Loader { + id: rightLauncherLoader + + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + asynchronous: true + sourceComponent: rightContentComponent + } + } + + InnerBorder { + id: rightLauncherBorder + + leftThickness: Appearance.padding.normal / 2 + } + + Component { + id: rightContentComponent + + ColumnLayout { + anchors.fill: parent + + spacing: Appearance.spacing.normal + + Item { Layout.alignment: Qt.AlignHCenter Layout.leftMargin: Appearance.padding.large * 2 Layout.rightMargin: Appearance.padding.large * 2 @@ -397,6 +452,7 @@ RowLayout { Layout.rightMargin: Appearance.padding.large * 2 StyledFlickable { + id: detailsFlickable anchors.fill: parent flickableDirection: Flickable.VerticalFlick contentHeight: debugLayout.height @@ -428,9 +484,6 @@ RowLayout { } } } - - InnerBorder { - leftThickness: Appearance.padding.normal / 2 } } } diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index ada29dc..c3621bb 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -29,32 +29,59 @@ Item { spacing: 0 Item { + id: leftNetworkItem Layout.preferredWidth: Math.floor(parent.width * 0.4) Layout.minimumWidth: 420 Layout.fillHeight: true - // Left pane - networking list with collapsible sections - StyledFlickable { - id: leftFlickable - + ClippingRectangle { + id: leftNetworkClippingRect anchors.fill: parent - flickableDirection: Flickable.VerticalFlick - contentHeight: leftContent.height + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 - StyledScrollBar.vertical: StyledScrollBar { - flickable: leftFlickable - } + radius: leftNetworkBorder.innerRadius + color: "transparent" - ColumnLayout { - id: leftContent + Loader { + id: leftNetworkLoader - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top + anchors.fill: parent anchors.margins: Appearance.padding.large + Appearance.padding.normal anchors.leftMargin: Appearance.padding.large anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - spacing: Appearance.spacing.normal + + asynchronous: true + sourceComponent: networkListComponent + } + } + + InnerBorder { + id: leftNetworkBorder + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + + Component { + id: networkListComponent + + StyledFlickable { + id: leftFlickable + + flickableDirection: Flickable.VerticalFlick + contentHeight: leftContent.height + + StyledScrollBar.vertical: StyledScrollBar { + flickable: leftFlickable + } + + ColumnLayout { + id: leftContent + + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.normal // Settings header above the collapsible sections RowLayout { @@ -385,18 +412,16 @@ Item { } } } - - InnerBorder { - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 } } Item { + id: rightNetworkItem Layout.fillWidth: true Layout.fillHeight: true ClippingRectangle { + id: networkClippingRect anchors.fill: parent anchors.margins: Appearance.padding.normal anchors.leftMargin: 0 diff --git a/modules/controlcenter/taskbar/TaskbarPane.qml b/modules/controlcenter/taskbar/TaskbarPane.qml index 107091e..85e5275 100644 --- a/modules/controlcenter/taskbar/TaskbarPane.qml +++ b/modules/controlcenter/taskbar/TaskbarPane.qml @@ -9,6 +9,7 @@ import qs.services import qs.config import qs.utils import Quickshell +import Quickshell.Widgets import QtQuick import QtQuick.Layouts @@ -17,6 +18,9 @@ RowLayout { required property Session session + // Clock + property bool clockShowIcon: Config.bar.clock.showIcon ?? true + // Bar Behavior property bool persistent: Config.bar.persistent ?? true property bool showOnHover: Config.bar.showOnHover ?? true @@ -48,9 +52,6 @@ RowLayout { spacing: 0 Component.onCompleted: { - // Update clock toggle - clockShowIconSwitch.checked = Config.bar.clock.showIcon ?? true; - // Update entries if (Config.bar.entries) { entriesModel.clear(); @@ -66,7 +67,7 @@ RowLayout { function saveConfig(entryIndex, entryEnabled) { // Update clock setting - Config.bar.clock.showIcon = clockShowIconSwitch.checked; + Config.bar.clock.showIcon = root.clockShowIcon; // Update bar behavior Config.bar.persistent = root.persistent; @@ -116,42 +117,62 @@ RowLayout { id: entriesModel } - - function collapseAllSections(exceptSection) { - if (exceptSection !== clockSection) clockSection.expanded = false; - if (exceptSection !== barBehaviorSection) barBehaviorSection.expanded = false; - if (exceptSection !== statusIconsSection) statusIconsSection.expanded = false; - if (exceptSection !== traySettingsSection) traySettingsSection.expanded = false; - if (exceptSection !== workspacesSection) workspacesSection.expanded = false; - } - Item { + id: leftTaskbarItem Layout.preferredWidth: Math.floor(parent.width * 0.4) Layout.minimumWidth: 420 Layout.fillHeight: true - StyledFlickable { - id: sidebarFlickable + ClippingRectangle { + id: leftTaskbarClippingRect anchors.fill: parent - flickableDirection: Flickable.VerticalFlick - contentHeight: sidebarLayout.height + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 - StyledScrollBar.vertical: StyledScrollBar { - flickable: sidebarFlickable - } + radius: leftTaskbarBorder.innerRadius + color: "transparent" + + Loader { + id: leftTaskbarLoader - ColumnLayout { - id: sidebarLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top + anchors.fill: parent anchors.margins: Appearance.padding.large + Appearance.padding.normal anchors.leftMargin: Appearance.padding.large anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - spacing: Appearance.spacing.small + asynchronous: true + sourceComponent: leftTaskbarContentComponent + } + } - RowLayout { + InnerBorder { + id: leftTaskbarBorder + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + + Component { + id: leftTaskbarContentComponent + + StyledFlickable { + id: sidebarFlickable + flickableDirection: Flickable.VerticalFlick + contentHeight: sidebarLayout.height + + StyledScrollBar.vertical: StyledScrollBar { + flickable: sidebarFlickable + } + + ColumnLayout { + id: sidebarLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + + spacing: Appearance.spacing.small + + RowLayout { spacing: Appearance.spacing.smaller StyledText { @@ -169,9 +190,6 @@ RowLayout { id: clockSection title: qsTr("Clock") description: qsTr("Clock display settings") - onToggleRequested: { - root.collapseAllSections(clockSection); - } RowLayout { id: clockRow @@ -190,8 +208,9 @@ RowLayout { StyledSwitch { id: clockShowIconSwitch - checked: true + checked: root.clockShowIcon onToggled: { + root.clockShowIcon = checked; root.saveConfig(); } } @@ -201,9 +220,6 @@ RowLayout { CollapsibleSection { id: barBehaviorSection title: qsTr("Bar Behavior") - onToggleRequested: { - root.collapseAllSections(barBehaviorSection); - } SwitchRow { label: qsTr("Persistent") @@ -238,9 +254,6 @@ RowLayout { CollapsibleSection { id: statusIconsSection title: qsTr("Status Icons") - onToggleRequested: { - root.collapseAllSections(statusIconsSection); - } SwitchRow { label: qsTr("Show audio") @@ -309,9 +322,6 @@ RowLayout { CollapsibleSection { id: traySettingsSection title: qsTr("Tray Settings") - onToggleRequested: { - root.collapseAllSections(traySettingsSection); - } SwitchRow { label: qsTr("Background") @@ -344,9 +354,6 @@ RowLayout { CollapsibleSection { id: workspacesSection title: qsTr("Workspaces") - onToggleRequested: { - root.collapseAllSections(workspacesSection); - } StyledRect { Layout.fillWidth: true @@ -517,43 +524,62 @@ RowLayout { } } } - - InnerBorder { - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 } } Item { + id: rightTaskbarItem Layout.fillWidth: true Layout.fillHeight: true - StyledFlickable { + ClippingRectangle { + id: rightTaskbarClippingRect anchors.fill: parent anchors.margins: Appearance.padding.normal anchors.leftMargin: 0 anchors.rightMargin: Appearance.padding.normal / 2 - flickableDirection: Flickable.VerticalFlick - contentHeight: contentLayout.height + radius: rightTaskbarBorder.innerRadius + color: "transparent" + + Loader { + id: rightTaskbarLoader - StyledScrollBar.vertical: StyledScrollBar { - flickable: parent + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + asynchronous: true + sourceComponent: rightTaskbarContentComponent } + } + + InnerBorder { + id: rightTaskbarBorder + + leftThickness: Appearance.padding.normal / 2 + } + + Component { + id: rightTaskbarContentComponent + + StyledFlickable { + flickableDirection: Flickable.VerticalFlick + contentHeight: contentLayout.height - ColumnLayout { - id: contentLayout + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent + } + + ColumnLayout { + id: contentLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.leftMargin: Appearance.padding.large * 2 - anchors.rightMargin: Appearance.padding.large * 2 - anchors.topMargin: Appearance.padding.large * 2 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top - spacing: Appearance.spacing.normal + spacing: Appearance.spacing.normal - MaterialIcon { + MaterialIcon { Layout.alignment: Qt.AlignHCenter text: "task_alt" font.pointSize: Appearance.font.size.extraLarge * 3 @@ -577,7 +603,7 @@ RowLayout { StyledText { Layout.alignment: Qt.AlignHCenter - text: clockShowIconSwitch.checked ? qsTr("Clock icon enabled") : qsTr("Clock icon disabled") + text: root.clockShowIcon ? qsTr("Clock icon enabled") : qsTr("Clock icon disabled") color: Colours.palette.m3outline } @@ -597,9 +623,6 @@ RowLayout { } } - - InnerBorder { - leftThickness: Appearance.padding.normal / 2 } } } -- cgit v1.2.3-freya From 7db0348ff97ecff2e258ace65aeb95f0eafe508b Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sat, 15 Nov 2025 19:20:01 -0500 Subject: controlcenter: forget network button color --- modules/controlcenter/network/WirelessDetails.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index 651f1fb..82ac975 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -151,8 +151,8 @@ Item { } return Nmcli.hasSavedProfile(root.network.ssid); } - inactiveColour: Colours.palette.m3errorContainer - inactiveOnColour: Colours.palette.m3onErrorContainer + inactiveColour: Colours.palette.m3secondaryContainer + inactiveOnColour: Colours.palette.m3onSecondaryContainer text: qsTr("Forget Network") onClicked: { -- cgit v1.2.3-freya From b05650b45171cb50797122d9afaca3e084a0bb4a Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sun, 16 Nov 2025 12:59:49 -0500 Subject: controlcenter: lock icon + security type --- modules/controlcenter/network/NetworkingPane.qml | 29 ++++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index c3621bb..1dd798d 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -371,11 +371,30 @@ Item { text: (modelData && modelData.ssid) ? modelData.ssid : qsTr("Unknown") } - StyledText { - text: (modelData && modelData.active) ? qsTr("Connected") : ((modelData && modelData.isSecure) ? qsTr("Secured") : 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 + RowLayout { + spacing: Appearance.spacing.smaller + + MaterialIcon { + visible: (modelData && modelData.isSecure) + text: "lock" + font.pointSize: Appearance.font.size.small + color: (modelData && modelData.active) ? Colours.palette.m3primary : Colours.palette.m3outline + } + + StyledText { + 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 + } } StyledRect { -- cgit v1.2.3-freya From d0c51d4c51e91614cf14b9b32de518ea0f81d228 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sun, 16 Nov 2025 19:22:46 -0500 Subject: hierarchy: moved ethernet to network --- modules/controlcenter/ethernet/EthernetDetails.qml | 119 --------------- modules/controlcenter/ethernet/EthernetList.qml | 168 --------------------- modules/controlcenter/ethernet/EthernetPane.qml | 150 ------------------ .../controlcenter/ethernet/EthernetSettings.qml | 84 ----------- modules/controlcenter/network/EthernetDetails.qml | 119 +++++++++++++++ modules/controlcenter/network/EthernetList.qml | 168 +++++++++++++++++++++ modules/controlcenter/network/EthernetPane.qml | 150 ++++++++++++++++++ modules/controlcenter/network/EthernetSettings.qml | 84 +++++++++++ modules/controlcenter/network/NetworkingPane.qml | 1 - 9 files changed, 521 insertions(+), 522 deletions(-) delete mode 100644 modules/controlcenter/ethernet/EthernetDetails.qml delete mode 100644 modules/controlcenter/ethernet/EthernetList.qml delete mode 100644 modules/controlcenter/ethernet/EthernetPane.qml delete mode 100644 modules/controlcenter/ethernet/EthernetSettings.qml create mode 100644 modules/controlcenter/network/EthernetDetails.qml create mode 100644 modules/controlcenter/network/EthernetList.qml create mode 100644 modules/controlcenter/network/EthernetPane.qml create mode 100644 modules/controlcenter/network/EthernetSettings.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/ethernet/EthernetDetails.qml b/modules/controlcenter/ethernet/EthernetDetails.qml deleted file mode 100644 index f715970..0000000 --- a/modules/controlcenter/ethernet/EthernetDetails.qml +++ /dev/null @@ -1,119 +0,0 @@ -pragma ComponentBehavior: Bound - -import ".." -import qs.components -import qs.components.controls -import qs.components.effects -import qs.components.containers -import qs.services -import qs.config -import QtQuick -import QtQuick.Layouts - -Item { - id: root - - required property Session session - readonly property var device: session.ethernet.active - - Component.onCompleted: { - if (device && device.interface) { - Nmcli.getEthernetDeviceDetails(device.interface, () => {}); - } - } - - onDeviceChanged: { - if (device && device.interface) { - Nmcli.getEthernetDeviceDetails(device.interface, () => {}); - } else { - Nmcli.ethernetDeviceDetails = null; - } - } - - StyledFlickable { - id: flickable - - anchors.fill: parent - - flickableDirection: Flickable.VerticalFlick - clip: true - contentHeight: layout.height - - StyledScrollBar.vertical: StyledScrollBar { - flickable: flickable - } - - ColumnLayout { - id: layout - - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.normal - - ConnectionHeader { - icon: "cable" - title: root.device?.interface ?? qsTr("Unknown") - } - - SectionHeader { - title: qsTr("Connection status") - description: qsTr("Connection settings for this device") - } - - 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, () => {}); - } - } - } - } - } - - SectionHeader { - title: qsTr("Device properties") - description: qsTr("Additional information") - } - - SectionContainer { - contentSpacing: Appearance.spacing.small / 2 - - PropertyRow { - label: qsTr("Interface") - value: root.device?.interface ?? qsTr("Unknown") - } - - PropertyRow { - showTopMargin: true - label: qsTr("Connection") - value: root.device?.connection || qsTr("Not connected") - } - - PropertyRow { - showTopMargin: true - label: qsTr("State") - value: root.device?.state ?? qsTr("Unknown") - } - } - - SectionHeader { - title: qsTr("Connection information") - description: qsTr("Network connection details") - } - - SectionContainer { - ConnectionInfoSection { - deviceDetails: Nmcli.ethernetDeviceDetails - } - } - - } - } - -} \ No newline at end of file diff --git a/modules/controlcenter/ethernet/EthernetList.qml b/modules/controlcenter/ethernet/EthernetList.qml deleted file mode 100644 index 45c9481..0000000 --- a/modules/controlcenter/ethernet/EthernetList.qml +++ /dev/null @@ -1,168 +0,0 @@ -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 - -ColumnLayout { - id: root - - required property Session session - - spacing: Appearance.spacing.small - - RowLayout { - spacing: Appearance.spacing.smaller - - StyledText { - text: qsTr("Settings") - font.pointSize: Appearance.font.size.large - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } - - ToggleButton { - toggled: !root.session.ethernet.active - icon: "settings" - accent: "Primary" - - onClicked: { - if (root.session.ethernet.active) - root.session.ethernet.active = null; - else { - root.session.ethernet.active = view.model.get(0)?.modelData ?? null; - } - } - } - } - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small - - StyledText { - text: qsTr("Devices (%1)").arg(Nmcli.ethernetDevices.length) - font.pointSize: Appearance.font.size.large - font.weight: 500 - } - } - - 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 { - 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) - radius: Appearance.rounding.normal - border.width: root.session.ethernet.active === modelData ? 1 : 0 - border.color: Colours.palette.m3primary - - StateLayer { - function onClicked(): void { - 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 - } - } - - StyledText { - Layout.fillWidth: true - elide: Text.ElideRight - maximumLineCount: 1 - - text: modelData.interface || qsTr("Unknown") - } - - StyledText { - 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 - } - - 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 - } - } -} \ No newline at end of file diff --git a/modules/controlcenter/ethernet/EthernetPane.qml b/modules/controlcenter/ethernet/EthernetPane.qml deleted file mode 100644 index 05d0b1b..0000000 --- a/modules/controlcenter/ethernet/EthernetPane.qml +++ /dev/null @@ -1,150 +0,0 @@ -pragma ComponentBehavior: Bound - -import ".." -import qs.components -import qs.components.effects -import qs.components.containers -import qs.config -import Quickshell.Widgets -import QtQuick -import QtQuick.Layouts - -RowLayout { - id: root - - required property Session session - - anchors.fill: parent - - spacing: 0 - - Item { - Layout.preferredWidth: Math.floor(parent.width * 0.4) - Layout.minimumWidth: 420 - Layout.fillHeight: true - - EthernetList { - anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - - session: root.session - } - - InnerBorder { - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 - } - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - ClippingRectangle { - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: rightBorder.innerRadius - color: "transparent" - clip: true - - Loader { - id: loader - - property var pane: root.session.ethernet.active - property string paneId: pane ? (pane.interface || "") : "" - - anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 - - opacity: 1 - scale: 1 - transformOrigin: Item.Center - - clip: true - asynchronous: true - sourceComponent: pane ? details : settings - - Behavior on paneId { - SequentialAnimation { - ParallelAnimation { - Anim { - target: loader - property: "opacity" - to: 0 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - Anim { - target: loader - property: "scale" - to: 0.8 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - } - PropertyAction {} - ParallelAnimation { - Anim { - target: loader - property: "opacity" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - Anim { - target: loader - property: "scale" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - } - } - } - - onPaneChanged: { - paneId = pane ? (pane.interface || "") : ""; - } - } - } - - InnerBorder { - id: rightBorder - - leftThickness: Appearance.padding.normal / 2 - } - - Component { - id: settings - - StyledFlickable { - flickableDirection: Flickable.VerticalFlick - contentHeight: settingsInner.height - clip: true - - EthernetSettings { - id: settingsInner - - anchors.left: parent.left - anchors.right: parent.right - session: root.session - } - } - } - - Component { - id: details - - EthernetDetails { - session: root.session - } - } - } - - component Anim: NumberAnimation { - target: loader - duration: Appearance.anim.durations.normal / 2 - easing.type: Easing.BezierSpline - } -} \ No newline at end of file diff --git a/modules/controlcenter/ethernet/EthernetSettings.qml b/modules/controlcenter/ethernet/EthernetSettings.qml deleted file mode 100644 index 161492c..0000000 --- a/modules/controlcenter/ethernet/EthernetSettings.qml +++ /dev/null @@ -1,84 +0,0 @@ -pragma ComponentBehavior: Bound - -import ".." -import qs.components -import qs.components.controls -import qs.components.effects -import qs.services -import qs.config -import QtQuick -import QtQuick.Layouts - -ColumnLayout { - id: root - - required property Session session - - spacing: Appearance.spacing.normal - - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - text: "cable" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Ethernet settings") - font.pointSize: Appearance.font.size.large - font.bold: true - } - - StyledText { - Layout.topMargin: Appearance.spacing.large - text: qsTr("Ethernet devices") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - StyledText { - text: qsTr("Available ethernet devices") - color: Colours.palette.m3outline - } - - StyledRect { - Layout.fillWidth: true - implicitHeight: ethernetInfo.implicitHeight + Appearance.padding.large * 2 - - radius: Appearance.rounding.normal - color: Colours.tPalette.m3surfaceContainer - - ColumnLayout { - id: ethernetInfo - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.large - - spacing: Appearance.spacing.small / 2 - - StyledText { - text: qsTr("Total devices") - } - - StyledText { - text: qsTr("%1").arg(Nmcli.ethernetDevices.length) - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Connected devices") - } - - StyledText { - text: qsTr("%1").arg(Nmcli.ethernetDevices.filter(d => d.connected).length) - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - } - } - } -} \ No newline at end of file diff --git a/modules/controlcenter/network/EthernetDetails.qml b/modules/controlcenter/network/EthernetDetails.qml new file mode 100644 index 0000000..f715970 --- /dev/null +++ b/modules/controlcenter/network/EthernetDetails.qml @@ -0,0 +1,119 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.components.containers +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +Item { + id: root + + required property Session session + readonly property var device: session.ethernet.active + + Component.onCompleted: { + if (device && device.interface) { + Nmcli.getEthernetDeviceDetails(device.interface, () => {}); + } + } + + onDeviceChanged: { + if (device && device.interface) { + Nmcli.getEthernetDeviceDetails(device.interface, () => {}); + } else { + Nmcli.ethernetDeviceDetails = null; + } + } + + StyledFlickable { + id: flickable + + anchors.fill: parent + + flickableDirection: Flickable.VerticalFlick + clip: true + contentHeight: layout.height + + StyledScrollBar.vertical: StyledScrollBar { + flickable: flickable + } + + ColumnLayout { + id: layout + + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.normal + + ConnectionHeader { + icon: "cable" + title: root.device?.interface ?? qsTr("Unknown") + } + + SectionHeader { + title: qsTr("Connection status") + description: qsTr("Connection settings for this device") + } + + 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, () => {}); + } + } + } + } + } + + SectionHeader { + title: qsTr("Device properties") + description: qsTr("Additional information") + } + + SectionContainer { + contentSpacing: Appearance.spacing.small / 2 + + PropertyRow { + label: qsTr("Interface") + value: root.device?.interface ?? qsTr("Unknown") + } + + PropertyRow { + showTopMargin: true + label: qsTr("Connection") + value: root.device?.connection || qsTr("Not connected") + } + + PropertyRow { + showTopMargin: true + label: qsTr("State") + value: root.device?.state ?? qsTr("Unknown") + } + } + + SectionHeader { + title: qsTr("Connection information") + description: qsTr("Network connection details") + } + + 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 new file mode 100644 index 0000000..45c9481 --- /dev/null +++ b/modules/controlcenter/network/EthernetList.qml @@ -0,0 +1,168 @@ +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 + +ColumnLayout { + id: root + + required property Session session + + spacing: Appearance.spacing.small + + RowLayout { + spacing: Appearance.spacing.smaller + + StyledText { + text: qsTr("Settings") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + Item { + Layout.fillWidth: true + } + + ToggleButton { + toggled: !root.session.ethernet.active + icon: "settings" + accent: "Primary" + + onClicked: { + if (root.session.ethernet.active) + root.session.ethernet.active = null; + else { + root.session.ethernet.active = view.model.get(0)?.modelData ?? null; + } + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small + + StyledText { + text: qsTr("Devices (%1)").arg(Nmcli.ethernetDevices.length) + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + } + + 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 { + 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) + radius: Appearance.rounding.normal + border.width: root.session.ethernet.active === modelData ? 1 : 0 + border.color: Colours.palette.m3primary + + StateLayer { + function onClicked(): void { + 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 + } + } + + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 + + text: modelData.interface || qsTr("Unknown") + } + + StyledText { + 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 + } + + 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 + } + } +} \ No newline at end of file diff --git a/modules/controlcenter/network/EthernetPane.qml b/modules/controlcenter/network/EthernetPane.qml new file mode 100644 index 0000000..05d0b1b --- /dev/null +++ b/modules/controlcenter/network/EthernetPane.qml @@ -0,0 +1,150 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.effects +import qs.components.containers +import qs.config +import Quickshell.Widgets +import QtQuick +import QtQuick.Layouts + +RowLayout { + id: root + + required property Session session + + anchors.fill: parent + + spacing: 0 + + Item { + Layout.preferredWidth: Math.floor(parent.width * 0.4) + Layout.minimumWidth: 420 + Layout.fillHeight: true + + EthernetList { + anchors.fill: parent + anchors.margins: Appearance.padding.large + Appearance.padding.normal + anchors.leftMargin: Appearance.padding.large + anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + + session: root.session + } + + InnerBorder { + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + ClippingRectangle { + anchors.fill: parent + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 + + radius: rightBorder.innerRadius + color: "transparent" + clip: true + + Loader { + id: loader + + property var pane: root.session.ethernet.active + property string paneId: pane ? (pane.interface || "") : "" + + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + opacity: 1 + scale: 1 + transformOrigin: Item.Center + + clip: true + asynchronous: true + sourceComponent: pane ? details : settings + + Behavior on paneId { + SequentialAnimation { + ParallelAnimation { + Anim { + target: loader + property: "opacity" + to: 0 + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + Anim { + target: loader + property: "scale" + to: 0.8 + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + } + PropertyAction {} + ParallelAnimation { + Anim { + target: loader + property: "opacity" + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + Anim { + target: loader + property: "scale" + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + } + } + } + + onPaneChanged: { + paneId = pane ? (pane.interface || "") : ""; + } + } + } + + InnerBorder { + id: rightBorder + + leftThickness: Appearance.padding.normal / 2 + } + + Component { + id: settings + + StyledFlickable { + flickableDirection: Flickable.VerticalFlick + contentHeight: settingsInner.height + clip: true + + EthernetSettings { + id: settingsInner + + anchors.left: parent.left + anchors.right: parent.right + session: root.session + } + } + } + + Component { + id: details + + EthernetDetails { + session: root.session + } + } + } + + component Anim: NumberAnimation { + target: loader + duration: Appearance.anim.durations.normal / 2 + easing.type: Easing.BezierSpline + } +} \ No newline at end of file diff --git a/modules/controlcenter/network/EthernetSettings.qml b/modules/controlcenter/network/EthernetSettings.qml new file mode 100644 index 0000000..161492c --- /dev/null +++ b/modules/controlcenter/network/EthernetSettings.qml @@ -0,0 +1,84 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property Session session + + spacing: Appearance.spacing.normal + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + text: "cable" + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Ethernet settings") + font.pointSize: Appearance.font.size.large + font.bold: true + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Ethernet devices") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("Available ethernet devices") + color: Colours.palette.m3outline + } + + StyledRect { + Layout.fillWidth: true + implicitHeight: ethernetInfo.implicitHeight + Appearance.padding.large * 2 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + + ColumnLayout { + id: ethernetInfo + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.large + + spacing: Appearance.spacing.small / 2 + + StyledText { + text: qsTr("Total devices") + } + + StyledText { + text: qsTr("%1").arg(Nmcli.ethernetDevices.length) + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Connected devices") + } + + StyledText { + text: qsTr("%1").arg(Nmcli.ethernetDevices.filter(d => d.connected).length) + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + } + } +} \ No newline at end of file diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 1dd798d..3601b8b 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -1,7 +1,6 @@ pragma ComponentBehavior: Bound import ".." -import "../ethernet" import "." import qs.components import qs.components.controls -- cgit v1.2.3-freya From 9f8f8ac73145aacfa9aeaa6455210aac39579908 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sun, 16 Nov 2025 23:01:51 -0500 Subject: controlcenter: correcting containers (network details) --- modules/controlcenter/network/EthernetDetails.qml | 27 ++++++----------------- modules/controlcenter/network/NetworkingPane.qml | 27 +++++++++++++++++++---- modules/controlcenter/network/WirelessDetails.qml | 26 ++++++---------------- 3 files changed, 37 insertions(+), 43 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/EthernetDetails.qml b/modules/controlcenter/network/EthernetDetails.qml index f715970..c16eb9d 100644 --- a/modules/controlcenter/network/EthernetDetails.qml +++ b/modules/controlcenter/network/EthernetDetails.qml @@ -16,6 +16,8 @@ Item { required property Session session readonly property var device: session.ethernet.active + anchors.fill: parent + Component.onCompleted: { if (device && device.interface) { Nmcli.getEthernetDeviceDetails(device.interface, () => {}); @@ -30,25 +32,12 @@ Item { } } - StyledFlickable { - id: flickable - - anchors.fill: parent - - flickableDirection: Flickable.VerticalFlick - clip: true - contentHeight: layout.height - - StyledScrollBar.vertical: StyledScrollBar { - flickable: flickable - } - - ColumnLayout { - id: layout + ColumnLayout { + id: layout - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.normal + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.normal ConnectionHeader { icon: "cable" @@ -112,8 +101,6 @@ Item { deviceDetails: Nmcli.ethernetDeviceDetails } } - - } } } \ No newline at end of file diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 3601b8b..680e495 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -463,6 +463,7 @@ Item { opacity: 1 scale: 1 transformOrigin: Item.Center + clip: false asynchronous: true sourceComponent: pane ? (ethernetPane ? ethernetDetails : wirelessDetails) : settings @@ -533,16 +534,34 @@ Item { Component { id: ethernetDetails - EthernetDetails { - session: root.session + StyledFlickable { + flickableDirection: Flickable.VerticalFlick + contentHeight: ethernetDetailsInner.height + + EthernetDetails { + id: ethernetDetailsInner + + anchors.left: parent.left + anchors.right: parent.right + session: root.session + } } } Component { id: wirelessDetails - WirelessDetails { - session: root.session + StyledFlickable { + flickableDirection: Flickable.VerticalFlick + contentHeight: wirelessDetailsInner.height + + WirelessDetails { + id: wirelessDetailsInner + + anchors.left: parent.left + anchors.right: parent.right + session: root.session + } } } } diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index 82ac975..6b7cb15 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -17,6 +17,8 @@ Item { required property Session session readonly property var network: session.network.active + anchors.fill: parent + Component.onCompleted: { updateDeviceDetails(); checkSavedProfile(); @@ -98,25 +100,12 @@ Item { } } - StyledFlickable { - id: flickable - - anchors.fill: parent + ColumnLayout { + id: layout - flickableDirection: Flickable.VerticalFlick - clip: true - contentHeight: layout.height - - StyledScrollBar.vertical: StyledScrollBar { - flickable: flickable - } - - ColumnLayout { - id: layout - - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.normal + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.normal ConnectionHeader { icon: root.network?.isSecure ? "lock" : "wifi" @@ -214,7 +203,6 @@ Item { deviceDetails: Nmcli.wirelessDeviceDetails } } - } } function handleConnect(): void { -- cgit v1.2.3-freya From c2c04b86379c222b5d0e0f123b12713e7a82366e Mon Sep 17 00:00:00 2001 From: ATMDA Date: Mon, 17 Nov 2025 08:09:32 -0500 Subject: controlcenter: corrections on scrolling in panes and missing scrollbars --- modules/controlcenter/bluetooth/BtPane.qml | 7 +++++++ modules/controlcenter/bluetooth/Details.qml | 6 ++++-- modules/controlcenter/bluetooth/Settings.qml | 12 ++++++++---- modules/controlcenter/network/EthernetDetails.qml | 4 +++- modules/controlcenter/network/NetworkingPane.qml | 18 ++++++++++++++++++ modules/controlcenter/network/WirelessDetails.qml | 4 +++- 6 files changed, 43 insertions(+), 8 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/bluetooth/BtPane.qml b/modules/controlcenter/bluetooth/BtPane.qml index 32d2c0d..8ad4b1f 100644 --- a/modules/controlcenter/bluetooth/BtPane.qml +++ b/modules/controlcenter/bluetooth/BtPane.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import qs.components.controls import qs.components.effects import qs.components.containers import qs.config @@ -131,14 +132,20 @@ RowLayout { id: settings StyledFlickable { + id: settingsFlickable flickableDirection: Flickable.VerticalFlick contentHeight: settingsInner.height + StyledScrollBar.vertical: StyledScrollBar { + flickable: settingsFlickable + } + Settings { id: settingsInner anchors.left: parent.left anchors.right: parent.right + anchors.top: parent.top session: root.session } } diff --git a/modules/controlcenter/bluetooth/Details.qml b/modules/controlcenter/bluetooth/Details.qml index 7ac5286..883b408 100644 --- a/modules/controlcenter/bluetooth/Details.qml +++ b/modules/controlcenter/bluetooth/Details.qml @@ -18,11 +18,13 @@ StyledFlickable { required property Session session readonly property BluetoothDevice device: session.bt.active - anchors.fill: parent - flickableDirection: Flickable.VerticalFlick contentHeight: layoutWrapper.height + StyledScrollBar.vertical: StyledScrollBar { + flickable: root + } + Item { id: layoutWrapper diff --git a/modules/controlcenter/bluetooth/Settings.qml b/modules/controlcenter/bluetooth/Settings.qml index fd33af9..c8453b6 100644 --- a/modules/controlcenter/bluetooth/Settings.qml +++ b/modules/controlcenter/bluetooth/Settings.qml @@ -284,8 +284,12 @@ ColumnLayout { CustomSpinBox { min: 0 - value: root.session.bt.currentAdapter.discoverableTimeout - onValueModified: value => root.session.bt.currentAdapter.discoverableTimeout = value + value: root.session.bt.currentAdapter?.discoverableTimeout ?? 0 + onValueModified: value => { + if (root.session.bt.currentAdapter) { + root.session.bt.currentAdapter.discoverableTimeout = value; + } + } } } @@ -345,7 +349,7 @@ ColumnLayout { anchors.top: renameLabel.bottom anchors.leftMargin: root.session.bt.editingAdapterName ? 0 : -Appearance.padding.normal - text: root.session.bt.currentAdapter.name + text: root.session.bt.currentAdapter?.name ?? "" readOnly: !root.session.bt.editingAdapterName onAccepted: { root.session.bt.editingAdapterName = false; @@ -392,7 +396,7 @@ ColumnLayout { function onClicked(): void { root.session.bt.editingAdapterName = false; - adapterNameEdit.text = Qt.binding(() => root.session.bt.currentAdapter.name); + adapterNameEdit.text = Qt.binding(() => root.session.bt.currentAdapter?.name ?? ""); } } diff --git a/modules/controlcenter/network/EthernetDetails.qml b/modules/controlcenter/network/EthernetDetails.qml index c16eb9d..7c2534a 100644 --- a/modules/controlcenter/network/EthernetDetails.qml +++ b/modules/controlcenter/network/EthernetDetails.qml @@ -16,7 +16,8 @@ Item { required property Session session readonly property var device: session.ethernet.active - anchors.fill: parent + implicitWidth: layout.implicitWidth + implicitHeight: layout.implicitHeight Component.onCompleted: { if (device && device.interface) { @@ -37,6 +38,7 @@ Item { anchors.left: parent.left anchors.right: parent.right + anchors.top: parent.top spacing: Appearance.spacing.normal ConnectionHeader { diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 680e495..d00916c 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -518,14 +518,20 @@ Item { id: settings StyledFlickable { + id: settingsFlickable flickableDirection: Flickable.VerticalFlick contentHeight: settingsInner.height + StyledScrollBar.vertical: StyledScrollBar { + flickable: settingsFlickable + } + NetworkSettings { id: settingsInner anchors.left: parent.left anchors.right: parent.right + anchors.top: parent.top session: root.session } } @@ -535,14 +541,20 @@ Item { id: ethernetDetails StyledFlickable { + id: ethernetFlickable flickableDirection: Flickable.VerticalFlick contentHeight: ethernetDetailsInner.height + StyledScrollBar.vertical: StyledScrollBar { + flickable: ethernetFlickable + } + EthernetDetails { id: ethernetDetailsInner anchors.left: parent.left anchors.right: parent.right + anchors.top: parent.top session: root.session } } @@ -552,14 +564,20 @@ Item { id: wirelessDetails StyledFlickable { + id: wirelessFlickable flickableDirection: Flickable.VerticalFlick contentHeight: wirelessDetailsInner.height + StyledScrollBar.vertical: StyledScrollBar { + flickable: wirelessFlickable + } + WirelessDetails { id: wirelessDetailsInner anchors.left: parent.left anchors.right: parent.right + anchors.top: parent.top session: root.session } } diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index 6b7cb15..09abff3 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -17,7 +17,8 @@ Item { required property Session session readonly property var network: session.network.active - anchors.fill: parent + implicitWidth: layout.implicitWidth + implicitHeight: layout.implicitHeight Component.onCompleted: { updateDeviceDetails(); @@ -105,6 +106,7 @@ Item { anchors.left: parent.left anchors.right: parent.right + anchors.top: parent.top spacing: Appearance.spacing.normal ConnectionHeader { -- cgit v1.2.3-freya From 4a7c2dbdd5667f923677b3100f850ec19f117486 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Mon, 17 Nov 2025 08:22:36 -0500 Subject: controlcenter: moved connection status/security type in networking --- modules/controlcenter/network/NetworkingPane.qml | 83 ++++++++++++++---------- 1 file changed, 50 insertions(+), 33 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index d00916c..93d3f93 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -215,19 +215,27 @@ Item { } } - StyledText { + ColumnLayout { Layout.fillWidth: true - elide: Text.ElideRight - maximumLineCount: 1 - text: modelData.interface || qsTr("Unknown") - } + spacing: 0 + + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 + + text: modelData.interface || qsTr("Unknown") + } - StyledText { - 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 + 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 { @@ -362,37 +370,46 @@ Item { } } - StyledText { + ColumnLayout { Layout.fillWidth: true - elide: Text.ElideRight - maximumLineCount: 1 - text: (modelData && modelData.ssid) ? modelData.ssid : qsTr("Unknown") - } + spacing: 0 - RowLayout { - spacing: Appearance.spacing.smaller + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 - MaterialIcon { - visible: (modelData && modelData.isSecure) - text: "lock" - font.pointSize: Appearance.font.size.small - color: (modelData && modelData.active) ? Colours.palette.m3primary : Colours.palette.m3outline + text: (modelData && modelData.ssid) ? modelData.ssid : qsTr("Unknown") } - StyledText { - text: { - if (!modelData) return qsTr("Open"); - if (modelData.active) return qsTr("Connected"); - if (modelData.isSecure && modelData.security && modelData.security.length > 0) { - return modelData.security; + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.smaller + + MaterialIcon { + visible: (modelData && modelData.isSecure) + text: "lock" + font.pointSize: Appearance.font.size.small + color: (modelData && modelData.active) ? Colours.palette.m3primary : Colours.palette.m3outline + } + + 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"); } - 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 } - color: (modelData && modelData.active) ? Colours.palette.m3primary : Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - font.weight: (modelData && modelData.active) ? 500 : 400 } } -- cgit v1.2.3-freya From eb0286a282ff40b53aa9b43dd563f4c267c60274 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Mon, 17 Nov 2025 08:36:14 -0500 Subject: controlcenter: lock badge on signal strength for secure networks --- modules/controlcenter/network/NetworkingPane.qml | 33 +++++++++++++++++++----- 1 file changed, 26 insertions(+), 7 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 93d3f93..dc568e2 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -368,6 +368,32 @@ Item { 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: (modelData && modelData.active) ? Colours.palette.m3secondaryContainer : Colours.palette.m3surfaceContainerHighest + + readonly property real lockIconSize: lockIcon.implicitWidth + + MaterialIcon { + id: lockIcon + + anchors.centerIn: parent + text: "lock" + font.pointSize: Appearance.font.size.small + fill: 1 + color: (modelData && modelData.active) ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface + } + } } ColumnLayout { @@ -387,13 +413,6 @@ Item { Layout.fillWidth: true spacing: Appearance.spacing.smaller - MaterialIcon { - visible: (modelData && modelData.isSecure) - text: "lock" - font.pointSize: Appearance.font.size.small - color: (modelData && modelData.active) ? Colours.palette.m3primary : Colours.palette.m3outline - } - StyledText { Layout.fillWidth: true text: { -- cgit v1.2.3-freya From 70c1e5f257b9a3d78173bb4ae89ce49e75fad2ab Mon Sep 17 00:00:00 2001 From: ATMDA Date: Mon, 17 Nov 2025 13:46:12 -0500 Subject: controlcenter: updated lock badge colors on networking --- modules/controlcenter/network/NetworkingPane.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index dc568e2..a632c2b 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -380,7 +380,7 @@ Item { implicitWidth: lockIconSize + Appearance.padding.smaller implicitHeight: lockIconSize + Appearance.padding.smaller radius: Appearance.rounding.full - color: (modelData && modelData.active) ? Colours.palette.m3secondaryContainer : Colours.palette.m3surfaceContainerHighest + color: Colours.palette.m3secondaryContainer readonly property real lockIconSize: lockIcon.implicitWidth @@ -391,7 +391,7 @@ Item { text: "lock" font.pointSize: Appearance.font.size.small fill: 1 - color: (modelData && modelData.active) ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface + color: Colours.palette.m3onSurface } } } -- 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/network') 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 ee33f8f07bade777e84b3727647a7d5eb89a29e5 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Mon, 17 Nov 2025 19:44:31 -0500 Subject: controlcenter: corrected timing of network panel animations --- modules/controlcenter/network/EthernetPane.qml | 16 ++++++++++++++-- modules/controlcenter/network/NetworkingPane.qml | 20 ++++++++++++++++++-- modules/controlcenter/network/WirelessPane.qml | 16 ++++++++++++++-- 3 files changed, 46 insertions(+), 6 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/EthernetPane.qml b/modules/controlcenter/network/EthernetPane.qml index 05d0b1b..6a50cde 100644 --- a/modules/controlcenter/network/EthernetPane.qml +++ b/modules/controlcenter/network/EthernetPane.qml @@ -57,6 +57,8 @@ RowLayout { property var pane: root.session.ethernet.active property string paneId: pane ? (pane.interface || "") : "" + property Component targetComponent: settings + property Component nextComponent: settings anchors.fill: parent anchors.margins: Appearance.padding.large * 2 @@ -67,7 +69,12 @@ RowLayout { clip: true asynchronous: true - sourceComponent: pane ? details : settings + sourceComponent: loader.targetComponent + + Component.onCompleted: { + targetComponent = pane ? details : settings; + nextComponent = targetComponent; + } Behavior on paneId { SequentialAnimation { @@ -85,7 +92,11 @@ RowLayout { easing.bezierCurve: Appearance.anim.curves.standardAccel } } - PropertyAction {} + PropertyAction { + target: loader + property: "targetComponent" + value: loader.nextComponent + } ParallelAnimation { Anim { target: loader @@ -104,6 +115,7 @@ RowLayout { } onPaneChanged: { + nextComponent = pane ? details : settings; paneId = pane ? (pane.interface || "") : ""; } } diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 52499d8..47bb7fb 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -492,6 +492,12 @@ Item { property var wirelessPane: root.session.network.active property var pane: ethernetPane || wirelessPane property string paneId: ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : "") + property Component targetComponent: settings + property Component nextComponent: settings + + function getComponentForPane() { + return pane ? (ethernetPane ? ethernetDetails : wirelessDetails) : settings; + } anchors.fill: parent anchors.margins: Appearance.padding.large * 2 @@ -502,7 +508,12 @@ Item { clip: false asynchronous: true - sourceComponent: pane ? (ethernetPane ? ethernetDetails : wirelessDetails) : settings + sourceComponent: loader.targetComponent + + Component.onCompleted: { + targetComponent = getComponentForPane(); + nextComponent = targetComponent; + } Behavior on paneId { SequentialAnimation { @@ -520,7 +531,11 @@ Item { easing.bezierCurve: Appearance.anim.curves.standardAccel } } - PropertyAction {} + PropertyAction { + target: loader + property: "targetComponent" + value: loader.nextComponent + } ParallelAnimation { Anim { target: loader @@ -539,6 +554,7 @@ Item { } onPaneChanged: { + nextComponent = getComponentForPane(); paneId = ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : ""); } } diff --git a/modules/controlcenter/network/WirelessPane.qml b/modules/controlcenter/network/WirelessPane.qml index 67a00f7..22364a1 100644 --- a/modules/controlcenter/network/WirelessPane.qml +++ b/modules/controlcenter/network/WirelessPane.qml @@ -56,6 +56,8 @@ RowLayout { property var pane: root.session.network.active property string paneId: pane ? (pane.ssid || pane.bssid || "") : "" + property Component targetComponent: settings + property Component nextComponent: settings anchors.fill: parent anchors.margins: Appearance.padding.large * 2 @@ -66,7 +68,12 @@ RowLayout { clip: false asynchronous: true - sourceComponent: pane ? details : settings + sourceComponent: loader.targetComponent + + Component.onCompleted: { + targetComponent = pane ? details : settings; + nextComponent = targetComponent; + } Behavior on paneId { SequentialAnimation { @@ -84,7 +91,11 @@ RowLayout { easing.bezierCurve: Appearance.anim.curves.standardAccel } } - PropertyAction {} + PropertyAction { + target: loader + property: "targetComponent" + value: loader.nextComponent + } ParallelAnimation { Anim { target: loader @@ -103,6 +114,7 @@ RowLayout { } onPaneChanged: { + nextComponent = pane ? details : settings; paneId = pane ? (pane.ssid || pane.bssid || "") : ""; } } -- cgit v1.2.3-freya From 52b1b79abda0c089e782e06e6e4ce3b50a7f73b4 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Mon, 17 Nov 2025 20:03:31 -0500 Subject: controlcenter: added elevation effect to networking pane for improved visual hierarchy --- modules/controlcenter/network/NetworkingPane.qml | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 47bb7fb..d0ea852 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -384,6 +384,13 @@ Item { readonly property real lockIconSize: lockIcon.implicitWidth + Elevation { + anchors.fill: parent + radius: parent.radius + z: -1 + level: 2 + } + MaterialIcon { id: lockIcon -- cgit v1.2.3-freya From ad4213d45ccf227e3528dd2bcb992ec75ab8d0c1 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 19 Nov 2025 12:39:45 -0500 Subject: refactor: SplitPaneLayout now component --- .../controlcenter/appearance/AppearancePane.qml | 3205 ++++++++++---------- modules/controlcenter/audio/AudioPane.qml | 570 ++-- modules/controlcenter/bluetooth/BtPane.qml | 138 +- .../controlcenter/components/SplitPaneLayout.qml | 120 + modules/controlcenter/launcher/LauncherPane.qml | 288 +- modules/controlcenter/network/NetworkingPane.qml | 296 +- 6 files changed, 2249 insertions(+), 2368 deletions(-) create mode 100644 modules/controlcenter/components/SplitPaneLayout.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index 61cdcaa..2041bf8 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import "../../launcher/services" import qs.components import qs.components.controls @@ -16,7 +17,7 @@ import Quickshell.Widgets import QtQuick import QtQuick.Layouts -RowLayout { +Item { id: root required property Session session @@ -46,9 +47,6 @@ RowLayout { anchors.fill: parent - spacing: 0 - - function saveConfig() { Config.appearance.anim.durations.scale = root.animDurationsScale; @@ -79,794 +77,920 @@ RowLayout { Config.save(); } - Item { - id: leftAppearanceItem - Layout.preferredWidth: Math.floor(parent.width * 0.4) - Layout.minimumWidth: 420 - Layout.fillHeight: true - - ClippingRectangle { - id: leftAppearanceClippingRect - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - radius: leftAppearanceBorder.innerRadius - color: "transparent" - - Loader { - id: leftAppearanceLoader - anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - asynchronous: true - sourceComponent: appearanceLeftContentComponent - property var rootPane: root - } - } + Component { + id: appearanceRightContentComponent - InnerBorder { - id: leftAppearanceBorder - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 - } + StyledFlickable { + id: rightAppearanceFlickable + flickableDirection: Flickable.VerticalFlick + contentHeight: contentLayout.height - Component { - id: appearanceLeftContentComponent + StyledScrollBar.vertical: StyledScrollBar { + flickable: rightAppearanceFlickable + } - StyledFlickable { - id: sidebarFlickable - readonly property var rootPane: leftAppearanceLoader.rootPane - flickableDirection: Flickable.VerticalFlick - contentHeight: sidebarLayout.height + ColumnLayout { + id: contentLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: Appearance.spacing.normal - StyledScrollBar.vertical: StyledScrollBar { - flickable: sidebarFlickable + MaterialIcon { + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.topMargin: 0 + text: "palette" + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true } - ColumnLayout { - id: sidebarLayout - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small - - readonly property bool allSectionsExpanded: - themeModeSection.expanded && - colorVariantSection.expanded && - colorSchemeSection.expanded && - animationsSection.expanded && - fontsSection.expanded && - scalesSection.expanded && - transparencySection.expanded && - borderSection.expanded && - backgroundSection.expanded - - RowLayout { - spacing: Appearance.spacing.smaller - - StyledText { - text: qsTr("Appearance") - font.pointSize: Appearance.font.size.large - font.weight: 500 - } - - Item { - Layout.fillWidth: true - } - - IconButton { - icon: sidebarLayout.allSectionsExpanded ? "unfold_less" : "unfold_more" - type: IconButton.Text - label.animate: true - onClicked: { - const shouldExpand = !sidebarLayout.allSectionsExpanded; - themeModeSection.expanded = shouldExpand; - colorVariantSection.expanded = shouldExpand; - colorSchemeSection.expanded = shouldExpand; - animationsSection.expanded = shouldExpand; - fontsSection.expanded = shouldExpand; - scalesSection.expanded = shouldExpand; - transparencySection.expanded = shouldExpand; - borderSection.expanded = shouldExpand; - backgroundSection.expanded = shouldExpand; - } - } + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Appearance Settings") + font.pointSize: Appearance.font.size.large + font.bold: true } - CollapsibleSection { - id: themeModeSection - title: qsTr("Theme mode") - description: qsTr("Light or dark theme") - showBackground: true - - SwitchRow { - label: qsTr("Dark mode") - checked: !Colours.currentLight - onToggled: checked => { - Colours.setMode(checked ? "dark" : "light"); - } - } + StyledText { + Layout.topMargin: Appearance.spacing.large + Layout.alignment: Qt.AlignHCenter + text: qsTr("Wallpaper") + font.pointSize: Appearance.font.size.extraLarge + font.weight: 600 } - CollapsibleSection { - id: colorVariantSection - title: qsTr("Color variant") - description: qsTr("Material theme variant") - showBackground: true - - ColumnLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small / 2 - - Repeater { - model: M3Variants.list - - delegate: StyledRect { - required property var modelData - - Layout.fillWidth: true - - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, modelData.variant === Schemes.currentVariant ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: modelData.variant === Schemes.currentVariant ? 1 : 0 - border.color: Colours.palette.m3primary - - StateLayer { - function onClicked(): void { - const variant = modelData.variant; - - // Optimistic update - set immediately for responsive UI - Schemes.currentVariant = variant; - Quickshell.execDetached(["caelestia", "scheme", "set", "-v", variant]); + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Select a wallpaper") + font.pointSize: Appearance.font.size.normal + color: Colours.palette.m3onSurfaceVariant + } - // Reload after a delay to confirm changes - Qt.callLater(() => { - reloadTimer.restart(); - }); + Item { + Layout.fillWidth: true + Layout.topMargin: Appearance.spacing.large + Layout.preferredHeight: wallpaperLoader.item ? wallpaperLoader.item.layoutPreferredHeight : 0 + + Loader { + id: wallpaperLoader + anchors.fill: parent + asynchronous: true + active: { + // Lazy load: only activate when: + // 1. Right pane is loaded AND + // 2. Appearance pane is active (index 3) or adjacent (for smooth transitions) + // This prevents loading all wallpapers when control center opens but appearance pane isn't visible + const isActive = root.session.activeIndex === 3; + const isAdjacent = Math.abs(root.session.activeIndex - 3) === 1; + // Access loader through SplitPaneLayout's rightLoader + const splitLayout = root.children[0]; + const loader = splitLayout && splitLayout.rightLoader ? splitLayout.rightLoader : null; + const shouldActivate = loader && loader.item !== null && (isActive || isAdjacent); + return shouldActivate; + } + + onStatusChanged: { + if (status === Loader.Error) { + console.error("[AppearancePane] Wallpaper loader error!"); + } + } + + // Stop lazy loading when loader becomes inactive + onActiveChanged: { + if (!active && wallpaperLoader.item) { + const container = wallpaperLoader.item; + // Access timer through wallpaperGrid + if (container && container.wallpaperGrid) { + const grid = container.wallpaperGrid; + if (grid.imageUpdateTimer) { + grid.imageUpdateTimer.stop(); } } - - Timer { - id: reloadTimer - interval: 300 - onTriggered: { - Schemes.reload(); + } + } + + sourceComponent: Item { + id: wallpaperGridContainer + property alias layoutPreferredHeight: wallpaperGrid.layoutPreferredHeight + + // Find and store reference to parent Flickable for scroll monitoring + property var parentFlickable: { + let item = parent; + while (item) { + if (item.flickableDirection !== undefined) { + return item; } + item = item.parent; } - - RowLayout { - id: variantRow - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal - - spacing: Appearance.spacing.normal - - MaterialIcon { - text: modelData.icon - font.pointSize: Appearance.font.size.large - fill: modelData.variant === Schemes.currentVariant ? 1 : 0 + return null; + } + + // Cleanup when component is destroyed + Component.onDestruction: { + if (wallpaperGrid) { + if (wallpaperGrid.scrollCheckTimer) { + wallpaperGrid.scrollCheckTimer.stop(); } - - StyledText { - Layout.fillWidth: true - text: modelData.name - font.weight: modelData.variant === Schemes.currentVariant ? 500 : 400 + wallpaperGrid._expansionInProgress = false; + } + } + + // Lazy loading model: loads one image at a time, only when touching bottom + // This prevents GridView from creating all delegates at once + QtObject { + id: lazyModel + + property var sourceList: null + property int loadedCount: 0 // Total items available to load + property int visibleCount: 0 // Items actually exposed to GridView (only visible + buffer) + property int totalCount: 0 + + function initialize(list) { + sourceList = list; + totalCount = list ? list.length : 0; + // Start with enough items to fill the initial viewport (~3 rows) + const initialRows = 3; + const cols = wallpaperGrid.columnsCount > 0 ? wallpaperGrid.columnsCount : 3; + const initialCount = Math.min(initialRows * cols, totalCount); + loadedCount = initialCount; + visibleCount = initialCount; + } + + function loadOneRow() { + if (loadedCount < totalCount) { + const cols = wallpaperGrid.columnsCount > 0 ? wallpaperGrid.columnsCount : 1; + const itemsToLoad = Math.min(cols, totalCount - loadedCount); + loadedCount += itemsToLoad; } - - MaterialIcon { - visible: modelData.variant === Schemes.currentVariant - text: "check" - color: Colours.palette.m3primary - font.pointSize: Appearance.font.size.large + } + + function updateVisibleCount(neededCount) { + // Always round up to complete rows to avoid incomplete rows in the grid + const cols = wallpaperGrid.columnsCount > 0 ? wallpaperGrid.columnsCount : 1; + const maxVisible = Math.min(neededCount, loadedCount); + const rows = Math.ceil(maxVisible / cols); + const newVisibleCount = Math.min(rows * cols, loadedCount); + + if (newVisibleCount > visibleCount) { + visibleCount = newVisibleCount; } } - - implicitHeight: variantRow.implicitHeight + Appearance.padding.normal * 2 } - } - } - } - - CollapsibleSection { - id: colorSchemeSection - title: qsTr("Color scheme") - description: qsTr("Available color schemes") - showBackground: true - - ColumnLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small / 2 - - Repeater { - model: Schemes.list - - delegate: StyledRect { - required property var modelData - - Layout.fillWidth: true - - readonly property string schemeKey: `${modelData.name} ${modelData.flavour}` - readonly property bool isCurrent: schemeKey === Schemes.currentScheme - - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: isCurrent ? 1 : 0 - border.color: Colours.palette.m3primary - - StateLayer { - function onClicked(): void { - const name = modelData.name; - const flavour = modelData.flavour; - const schemeKey = `${name} ${flavour}`; - - // Optimistic update - set immediately for responsive UI - Schemes.currentScheme = schemeKey; - Quickshell.execDetached(["caelestia", "scheme", "set", "-n", name, "-f", flavour]); - - // Reload after a delay to confirm changes - Qt.callLater(() => { - reloadTimer.restart(); - }); + + GridView { + id: wallpaperGrid + anchors.fill: parent + + property int _delegateCount: 0 + + readonly property int minCellWidth: 200 + Appearance.spacing.normal + readonly property int columnsCount: Math.max(1, Math.floor(parent.width / minCellWidth)) + + // Height based on visible items only - prevents GridView from creating all delegates + readonly property int layoutPreferredHeight: { + if (!lazyModel || lazyModel.visibleCount === 0 || columnsCount === 0) { + return 0; } + const calculated = Math.ceil(lazyModel.visibleCount / columnsCount) * cellHeight; + return calculated; } + + height: layoutPreferredHeight + cellWidth: width / columnsCount + cellHeight: 140 + Appearance.spacing.normal + + leftMargin: 0 + rightMargin: 0 + topMargin: 0 + bottomMargin: 0 + // Use ListModel for incremental updates to prevent flashing when new items are added + ListModel { + id: wallpaperListModel + } + + model: wallpaperListModel + + Connections { + target: lazyModel + function onVisibleCountChanged(): void { + if (!lazyModel || !lazyModel.sourceList) return; + + const newCount = lazyModel.visibleCount; + const currentCount = wallpaperListModel.count; + + // Only append new items - never remove or replace existing ones + if (newCount > currentCount) { + const flickable = wallpaperGridContainer.parentFlickable; + const oldScrollY = flickable ? flickable.contentY : 0; + + for (let i = currentCount; i < newCount; i++) { + wallpaperListModel.append({modelData: lazyModel.sourceList[i]}); + } + + // Preserve scroll position after model update + if (flickable) { + Qt.callLater(function() { + if (Math.abs(flickable.contentY - oldScrollY) < 1) { + flickable.contentY = oldScrollY; + } + }); + } + } + } + } + + Component.onCompleted: { + Qt.callLater(function() { + const isActive = root.session.activeIndex === 3; + if (width > 0 && parent && parent.visible && isActive && Wallpapers.list) { + lazyModel.initialize(Wallpapers.list); + wallpaperListModel.clear(); + for (let i = 0; i < lazyModel.visibleCount; i++) { + wallpaperListModel.append({modelData: lazyModel.sourceList[i]}); + } + } + }); + } + + Connections { + target: root.session + function onActiveIndexChanged(): void { + const isActive = root.session.activeIndex === 3; + + // Stop lazy loading when switching away from appearance pane + if (!isActive) { + if (scrollCheckTimer) { + scrollCheckTimer.stop(); + } + if (wallpaperGrid) { + wallpaperGrid._expansionInProgress = false; + } + return; + } + + // Initialize if needed when switching to appearance pane + if (isActive && width > 0 && !lazyModel.sourceList && parent && parent.visible && Wallpapers.list) { + lazyModel.initialize(Wallpapers.list); + wallpaperListModel.clear(); + for (let i = 0; i < lazyModel.visibleCount; i++) { + wallpaperListModel.append({modelData: lazyModel.sourceList[i]}); + } + } + } + } + + onWidthChanged: { + const isActive = root.session.activeIndex === 3; + if (width > 0 && !lazyModel.sourceList && parent && parent.visible && isActive && Wallpapers.list) { + lazyModel.initialize(Wallpapers.list); + wallpaperListModel.clear(); + for (let i = 0; i < lazyModel.visibleCount; i++) { + wallpaperListModel.append({modelData: lazyModel.sourceList[i]}); + } + } + } + + // Force true lazy loading: only create delegates for visible items + displayMarginBeginning: 0 + displayMarginEnd: 0 + cacheBuffer: 0 + + // Debounce expansion to avoid too frequent checks + property bool _expansionInProgress: false + + Connections { + target: wallpaperGridContainer.parentFlickable + function onContentYChanged(): void { + // Don't process scroll events if appearance pane is not active + const isActive = root.session.activeIndex === 3; + if (!isActive) return; + + if (!lazyModel || !lazyModel.sourceList || lazyModel.loadedCount >= lazyModel.totalCount || wallpaperGrid._expansionInProgress) { + return; + } + + const flickable = wallpaperGridContainer.parentFlickable; + if (!flickable) return; + + const gridY = wallpaperGridContainer.y; + const scrollY = flickable.contentY; + const viewportHeight = flickable.height; + + const topY = scrollY - gridY; + const bottomY = scrollY + viewportHeight - gridY; + + if (bottomY < 0) return; + + const topRow = Math.max(0, Math.floor(topY / wallpaperGrid.cellHeight)); + const bottomRow = Math.floor(bottomY / wallpaperGrid.cellHeight); + + // Update visible count with 1 row buffer ahead + const bufferRows = 1; + const neededBottomRow = bottomRow + bufferRows; + const neededCount = Math.min((neededBottomRow + 1) * wallpaperGrid.columnsCount, lazyModel.loadedCount); + lazyModel.updateVisibleCount(neededCount); + + // Load more when we're within 1 row of running out of loaded items + const loadedRows = Math.ceil(lazyModel.loadedCount / wallpaperGrid.columnsCount); + const rowsRemaining = loadedRows - (bottomRow + 1); + + if (rowsRemaining <= 1 && lazyModel.loadedCount < lazyModel.totalCount) { + if (!wallpaperGrid._expansionInProgress) { + wallpaperGrid._expansionInProgress = true; + lazyModel.loadOneRow(); + Qt.callLater(function() { + wallpaperGrid._expansionInProgress = false; + }); + } + } + } + } + + // Fallback timer to check scroll position periodically Timer { - id: reloadTimer - interval: 300 + id: scrollCheckTimer + interval: 100 + running: { + const isActive = root.session.activeIndex === 3; + return isActive && lazyModel && lazyModel.sourceList && lazyModel.loadedCount < lazyModel.totalCount; + } + repeat: true onTriggered: { - Schemes.reload(); + // Double-check that appearance pane is still active + const isActive = root.session.activeIndex === 3; + if (!isActive) { + stop(); + return; + } + + const flickable = wallpaperGridContainer.parentFlickable; + if (!flickable || !lazyModel || !lazyModel.sourceList) return; + + const gridY = wallpaperGridContainer.y; + const scrollY = flickable.contentY; + const viewportHeight = flickable.height; + + const topY = scrollY - gridY; + const bottomY = scrollY + viewportHeight - gridY; + if (bottomY < 0) return; + + const topRow = Math.max(0, Math.floor(topY / wallpaperGrid.cellHeight)); + const bottomRow = Math.floor(bottomY / wallpaperGrid.cellHeight); + + const bufferRows = 1; + const neededBottomRow = bottomRow + bufferRows; + const neededCount = Math.min((neededBottomRow + 1) * wallpaperGrid.columnsCount, lazyModel.loadedCount); + lazyModel.updateVisibleCount(neededCount); + + // Load more when we're within 1 row of running out of loaded items + const loadedRows = Math.ceil(lazyModel.loadedCount / wallpaperGrid.columnsCount); + const rowsRemaining = loadedRows - (bottomRow + 1); + + if (rowsRemaining <= 1 && lazyModel.loadedCount < lazyModel.totalCount) { + if (!wallpaperGrid._expansionInProgress) { + wallpaperGrid._expansionInProgress = true; + lazyModel.loadOneRow(); + Qt.callLater(function() { + wallpaperGrid._expansionInProgress = false; + }); + } + } } } + + + // Parent Flickable handles scrolling + interactive: false - RowLayout { - id: schemeRow - - anchors.fill: parent - anchors.margins: Appearance.padding.normal - - spacing: Appearance.spacing.normal - StyledRect { - id: preview + delegate: Item { + required property var modelData - Layout.alignment: Qt.AlignVCenter + width: wallpaperGrid.cellWidth + height: wallpaperGrid.cellHeight - border.width: 1 - border.color: Qt.alpha(`#${modelData.colours?.outline}`, 0.5) + readonly property bool isCurrent: modelData.path === Wallpapers.actualCurrent + readonly property real itemMargin: Appearance.spacing.normal / 2 + readonly property real itemRadius: Appearance.rounding.normal + + Component.onCompleted: { + wallpaperGrid._delegateCount++; + } - color: `#${modelData.colours?.surface}` - radius: Appearance.rounding.full - implicitWidth: iconPlaceholder.implicitWidth - implicitHeight: iconPlaceholder.implicitWidth + StateLayer { + anchors.fill: parent + anchors.leftMargin: itemMargin + anchors.rightMargin: itemMargin + anchors.topMargin: itemMargin + anchors.bottomMargin: itemMargin + radius: itemRadius - MaterialIcon { - id: iconPlaceholder - visible: false - text: "circle" - font.pointSize: Appearance.font.size.large - } + function onClicked(): void { + Wallpapers.setWallpaper(modelData.path); + } + } - Item { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right + StyledClippingRect { + id: image - implicitWidth: parent.implicitWidth / 2 - clip: true + anchors.fill: parent + anchors.leftMargin: itemMargin + anchors.rightMargin: itemMargin + anchors.topMargin: itemMargin + anchors.bottomMargin: itemMargin + color: Colours.tPalette.m3surfaceContainer + radius: itemRadius + antialiasing: true + layer.enabled: true + layer.smooth: true - StyledRect { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right + CachingImage { + id: cachingImage - implicitWidth: preview.implicitWidth - color: `#${modelData.colours?.primary}` - radius: Appearance.rounding.full - } - } - } + path: modelData.path + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + cache: true + visible: opacity > 0 + antialiasing: true + smooth: true - Column { - Layout.fillWidth: true - spacing: 0 + opacity: status === Image.Ready ? 1 : 0 - StyledText { - text: modelData.flavour ?? "" - font.pointSize: Appearance.font.size.normal - } + Behavior on opacity { + NumberAnimation { + duration: 1000 + easing.type: Easing.OutQuad + } + } + } - StyledText { - text: modelData.name ?? "" - font.pointSize: Appearance.font.size.small - color: Colours.palette.m3outline + // Fallback if CachingImage fails to load + Image { + id: fallbackImage - elide: Text.ElideRight - anchors.left: parent.left - anchors.right: parent.right - } - } + anchors.fill: parent + source: fallbackTimer.triggered && cachingImage.status !== Image.Ready ? modelData.path : "" + asynchronous: true + fillMode: Image.PreserveAspectCrop + cache: true + visible: opacity > 0 + antialiasing: true + smooth: true - Loader { - active: isCurrent - asynchronous: true + opacity: status === Image.Ready && cachingImage.status !== Image.Ready ? 1 : 0 - sourceComponent: MaterialIcon { - text: "check" - color: Colours.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.large - } + Behavior on opacity { + NumberAnimation { + duration: 1000 + easing.type: Easing.OutQuad } } + } - implicitHeight: schemeRow.implicitHeight + Appearance.padding.normal * 2 + Timer { + id: fallbackTimer + + property bool triggered: false + interval: 800 + running: cachingImage.status === Image.Loading || cachingImage.status === Image.Null + onTriggered: triggered = true } - } - } - } - CollapsibleSection { - id: animationsSection - title: qsTr("Animations") - showBackground: true + // Gradient overlay for filename + Rectangle { + id: filenameOverlay - SectionContainer { - contentSpacing: Appearance.spacing.normal + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom - ColumnLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + implicitHeight: filenameText.implicitHeight + Appearance.padding.normal * 1.5 + radius: 0 + + gradient: Gradient { + GradientStop { + position: 0.0 + color: Qt.rgba(Colours.palette.m3surfaceContainer.r, + Colours.palette.m3surfaceContainer.g, + Colours.palette.m3surfaceContainer.b, 0) + } + GradientStop { + position: 0.3 + color: Qt.rgba(Colours.palette.m3surfaceContainer.r, + Colours.palette.m3surfaceContainer.g, + Colours.palette.m3surfaceContainer.b, 0.7) + } + GradientStop { + position: 0.6 + color: Qt.rgba(Colours.palette.m3surfaceContainer.r, + Colours.palette.m3surfaceContainer.g, + Colours.palette.m3surfaceContainer.b, 0.9) + } + GradientStop { + position: 1.0 + color: Qt.rgba(Colours.palette.m3surfaceContainer.r, + Colours.palette.m3surfaceContainer.g, + Colours.palette.m3surfaceContainer.b, 0.95) + } + } - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal + opacity: 0 - StyledText { - text: qsTr("Animation duration scale") - font.pointSize: Appearance.font.size.normal + Behavior on opacity { + NumberAnimation { + duration: 1000 + easing.type: Easing.OutCubic + } } - Item { - Layout.fillWidth: true + Component.onCompleted: { + opacity = 1; } + } + } - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: animDurationsInput.implicitHeight + Appearance.padding.small * 2 - color: animDurationsInputHover.containsMouse || animDurationsInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: animDurationsInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) + Rectangle { + anchors.fill: parent + anchors.leftMargin: itemMargin + anchors.rightMargin: itemMargin + anchors.topMargin: itemMargin + anchors.bottomMargin: itemMargin + color: "transparent" + radius: itemRadius + border.width + border.width: isCurrent ? 2 : 0 + border.color: Colours.palette.m3primary + antialiasing: true + smooth: true - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } + Behavior on border.width { + NumberAnimation { + duration: 150 + easing.type: Easing.OutQuad + } + } - MouseArea { - id: animDurationsInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } + MaterialIcon { + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Appearance.padding.small - StyledTextField { - id: animDurationsInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.1; top: 5.0 } - - Component.onCompleted: { - text = (rootPane.animDurationsScale).toFixed(1); - } - - onTextChanged: { - if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0.1 && val <= 5.0) { - rootPane.animDurationsScale = val; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0.1 || val > 5.0) { - text = (rootPane.animDurationsScale).toFixed(1); - } - } - } - } + visible: isCurrent + text: "check_circle" + color: Colours.palette.m3primary + font.pointSize: Appearance.font.size.large + } + } - StyledText { - text: "×" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - } + StyledText { + id: filenameText + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.leftMargin: Appearance.padding.normal + Appearance.spacing.normal / 2 + anchors.rightMargin: Appearance.padding.normal + Appearance.spacing.normal / 2 + anchors.bottomMargin: Appearance.padding.normal + + readonly property string fileName: { + const path = modelData.relativePath || ""; + const parts = path.split("/"); + return parts.length > 0 ? parts[parts.length - 1] : path; } - StyledSlider { - id: animDurationsSlider + text: fileName + font.pointSize: Appearance.font.size.smaller + font.weight: 500 + color: isCurrent ? Colours.palette.m3primary : Colours.palette.m3onSurface + elide: Text.ElideMiddle + maximumLineCount: 1 + horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 + opacity: 0 - from: 0.1 - to: 5.0 - value: rootPane.animDurationsScale - onMoved: { - rootPane.animDurationsScale = animDurationsSlider.value; - if (!animDurationsInput.activeFocus) { - animDurationsInput.text = (animDurationsSlider.value).toFixed(1); - } - rootPane.saveConfig(); + Behavior on opacity { + NumberAnimation { + duration: 1000 + easing.type: Easing.OutCubic } } + + Component.onCompleted: { + opacity = 1; + } + } + } + } } } } + } + } + } - CollapsibleSection { - id: fontsSection - title: qsTr("Fonts") - showBackground: true - - CollapsibleSection { - id: materialFontSection - title: qsTr("Material font family") - expanded: true - showBackground: true - nested: true - - Loader { - id: materialFontLoader - Layout.fillWidth: true - Layout.preferredHeight: item ? Math.min(item.contentHeight, 300) : 0 - asynchronous: true - active: materialFontSection.expanded - - sourceComponent: StyledListView { - id: materialFontList - property alias contentHeight: materialFontList.contentHeight - - clip: true - spacing: Appearance.spacing.small / 2 - model: Qt.fontFamilies() - - StyledScrollBar.vertical: StyledScrollBar { - flickable: materialFontList - } - - delegate: StyledRect { - required property string modelData - required property int index + SplitPaneLayout { + anchors.fill: parent - width: ListView.view.width + leftContent: Component { - readonly property bool isCurrent: modelData === rootPane.fontFamilyMaterial - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: isCurrent ? 1 : 0 - border.color: Colours.palette.m3primary + StyledFlickable { + id: sidebarFlickable + readonly property var rootPane: root + flickableDirection: Flickable.VerticalFlick + contentHeight: sidebarLayout.height - StateLayer { - function onClicked(): void { - rootPane.fontFamilyMaterial = modelData; - rootPane.saveConfig(); - } - } - RowLayout { - id: fontFamilyMaterialRow + StyledScrollBar.vertical: StyledScrollBar { + flickable: sidebarFlickable + } - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal + ColumnLayout { + id: sidebarLayout + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.small - spacing: Appearance.spacing.normal + readonly property bool allSectionsExpanded: + themeModeSection.expanded && + colorVariantSection.expanded && + colorSchemeSection.expanded && + animationsSection.expanded && + fontsSection.expanded && + scalesSection.expanded && + transparencySection.expanded && + borderSection.expanded && + backgroundSection.expanded - StyledText { - text: modelData - font.pointSize: Appearance.font.size.normal - } + RowLayout { + spacing: Appearance.spacing.smaller - Item { - Layout.fillWidth: true - } + StyledText { + text: qsTr("Appearance") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } - Loader { - active: isCurrent - asynchronous: true + Item { + Layout.fillWidth: true + } - sourceComponent: MaterialIcon { - text: "check" - color: Colours.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.large - } - } - } + IconButton { + icon: sidebarLayout.allSectionsExpanded ? "unfold_less" : "unfold_more" + type: IconButton.Text + label.animate: true + onClicked: { + const shouldExpand = !sidebarLayout.allSectionsExpanded; + themeModeSection.expanded = shouldExpand; + colorVariantSection.expanded = shouldExpand; + colorSchemeSection.expanded = shouldExpand; + animationsSection.expanded = shouldExpand; + fontsSection.expanded = shouldExpand; + scalesSection.expanded = shouldExpand; + transparencySection.expanded = shouldExpand; + borderSection.expanded = shouldExpand; + backgroundSection.expanded = shouldExpand; + } + } + } - implicitHeight: fontFamilyMaterialRow.implicitHeight + Appearance.padding.normal * 2 - } - } + CollapsibleSection { + id: themeModeSection + title: qsTr("Theme mode") + description: qsTr("Light or dark theme") + showBackground: true + SwitchRow { + label: qsTr("Dark mode") + checked: !Colours.currentLight + onToggled: checked => { + Colours.setMode(checked ? "dark" : "light"); } } + } - CollapsibleSection { - id: monoFontSection - title: qsTr("Monospace font family") - expanded: false - showBackground: true - nested: true + CollapsibleSection { + id: colorVariantSection + title: qsTr("Color variant") + description: qsTr("Material theme variant") + showBackground: true - Loader { - Layout.fillWidth: true - Layout.preferredHeight: item ? Math.min(item.contentHeight, 300) : 0 - asynchronous: true - active: monoFontSection.expanded + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small / 2 - sourceComponent: StyledListView { - id: monoFontList - property alias contentHeight: monoFontList.contentHeight + Repeater { + model: M3Variants.list - clip: true - spacing: Appearance.spacing.small / 2 - model: Qt.fontFamilies() + delegate: StyledRect { + required property var modelData - StyledScrollBar.vertical: StyledScrollBar { - flickable: monoFontList - } + Layout.fillWidth: true - delegate: StyledRect { - required property string modelData - required property int index + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, modelData.variant === Schemes.currentVariant ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: modelData.variant === Schemes.currentVariant ? 1 : 0 + border.color: Colours.palette.m3primary - width: ListView.view.width + StateLayer { + function onClicked(): void { + const variant = modelData.variant; - readonly property bool isCurrent: modelData === rootPane.fontFamilyMono - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: isCurrent ? 1 : 0 - border.color: Colours.palette.m3primary + // Optimistic update - set immediately for responsive UI + Schemes.currentVariant = variant; + Quickshell.execDetached(["caelestia", "scheme", "set", "-v", variant]); - StateLayer { - function onClicked(): void { - rootPane.fontFamilyMono = modelData; - rootPane.saveConfig(); - } + // Reload after a delay to confirm changes + Qt.callLater(() => { + reloadTimer.restart(); + }); + } + } + + Timer { + id: reloadTimer + interval: 300 + onTriggered: { + Schemes.reload(); } + } - RowLayout { - id: fontFamilyMonoRow - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal - - spacing: Appearance.spacing.normal + RowLayout { + id: variantRow - StyledText { - text: modelData - font.pointSize: Appearance.font.size.normal - } + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal - Item { - Layout.fillWidth: true - } + spacing: Appearance.spacing.normal - Loader { - active: isCurrent - asynchronous: true + MaterialIcon { + text: modelData.icon + font.pointSize: Appearance.font.size.large + fill: modelData.variant === Schemes.currentVariant ? 1 : 0 + } - sourceComponent: MaterialIcon { - text: "check" - color: Colours.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.large - } - } + StyledText { + Layout.fillWidth: true + text: modelData.name + font.weight: modelData.variant === Schemes.currentVariant ? 500 : 400 } - implicitHeight: fontFamilyMonoRow.implicitHeight + Appearance.padding.normal * 2 + MaterialIcon { + visible: modelData.variant === Schemes.currentVariant + text: "check" + color: Colours.palette.m3primary + font.pointSize: Appearance.font.size.large + } } + + implicitHeight: variantRow.implicitHeight + Appearance.padding.normal * 2 } } } + } - CollapsibleSection { - id: sansFontSection - title: qsTr("Sans-serif font family") - expanded: false - showBackground: true - nested: true - - Loader { - Layout.fillWidth: true - Layout.preferredHeight: item ? Math.min(item.contentHeight, 300) : 0 - asynchronous: true - active: sansFontSection.expanded - - sourceComponent: StyledListView { - id: sansFontList - property alias contentHeight: sansFontList.contentHeight - - clip: true - spacing: Appearance.spacing.small / 2 - model: Qt.fontFamilies() + CollapsibleSection { + id: colorSchemeSection + title: qsTr("Color scheme") + description: qsTr("Available color schemes") + showBackground: true - StyledScrollBar.vertical: StyledScrollBar { - flickable: sansFontList - } + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small / 2 - delegate: StyledRect { - required property string modelData - required property int index + Repeater { + model: Schemes.list - width: ListView.view.width + delegate: StyledRect { + required property var modelData - readonly property bool isCurrent: modelData === rootPane.fontFamilySans - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: isCurrent ? 1 : 0 - border.color: Colours.palette.m3primary + Layout.fillWidth: true - StateLayer { - function onClicked(): void { - rootPane.fontFamilySans = modelData; - rootPane.saveConfig(); - } - } + readonly property string schemeKey: `${modelData.name} ${modelData.flavour}` + readonly property bool isCurrent: schemeKey === Schemes.currentScheme - RowLayout { - id: fontFamilySansRow + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: isCurrent ? 1 : 0 + border.color: Colours.palette.m3primary - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal + StateLayer { + function onClicked(): void { + const name = modelData.name; + const flavour = modelData.flavour; + const schemeKey = `${name} ${flavour}`; - spacing: Appearance.spacing.normal + // Optimistic update - set immediately for responsive UI + Schemes.currentScheme = schemeKey; + Quickshell.execDetached(["caelestia", "scheme", "set", "-n", name, "-f", flavour]); - StyledText { - text: modelData - font.pointSize: Appearance.font.size.normal - } + // Reload after a delay to confirm changes + Qt.callLater(() => { + reloadTimer.restart(); + }); + } + } - Item { - Layout.fillWidth: true - } + Timer { + id: reloadTimer + interval: 300 + onTriggered: { + Schemes.reload(); + } + } - Loader { - active: isCurrent - asynchronous: true + RowLayout { + id: schemeRow - sourceComponent: MaterialIcon { - text: "check" - color: Colours.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.large - } - } - } + anchors.fill: parent + anchors.margins: Appearance.padding.normal - implicitHeight: fontFamilySansRow.implicitHeight + Appearance.padding.normal * 2 - } - } - } - } + spacing: Appearance.spacing.normal - SectionContainer { - contentSpacing: Appearance.spacing.normal + StyledRect { + id: preview - ColumnLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + Layout.alignment: Qt.AlignVCenter - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal + border.width: 1 + border.color: Qt.alpha(`#${modelData.colours?.outline}`, 0.5) - StyledText { - text: qsTr("Font size scale") - font.pointSize: Appearance.font.size.normal - } + color: `#${modelData.colours?.surface}` + radius: Appearance.rounding.full + implicitWidth: iconPlaceholder.implicitWidth + implicitHeight: iconPlaceholder.implicitWidth - Item { - Layout.fillWidth: true - } + MaterialIcon { + id: iconPlaceholder + visible: false + text: "circle" + font.pointSize: Appearance.font.size.large + } - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: fontSizeInput.implicitHeight + Appearance.padding.small * 2 - color: fontSizeInputHover.containsMouse || fontSizeInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: fontSizeInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) + Item { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } + implicitWidth: parent.implicitWidth / 2 + clip: true - MouseArea { - id: fontSizeInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } + StyledRect { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right - StyledTextField { - id: fontSizeInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.7; top: 1.5 } - - Component.onCompleted: { - text = (rootPane.fontSizeScale).toFixed(1); - } - - onTextChanged: { - if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0.7 && val <= 1.5) { - rootPane.fontSizeScale = val; - rootPane.saveConfig(); - } + implicitWidth: preview.implicitWidth + color: `#${modelData.colours?.primary}` + radius: Appearance.rounding.full } } - onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0.7 || val > 1.5) { - text = (rootPane.fontSizeScale).toFixed(1); - } + } + + Column { + Layout.fillWidth: true + spacing: 0 + + StyledText { + text: modelData.flavour ?? "" + font.pointSize: Appearance.font.size.normal } - } - } - StyledText { - text: "×" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - } - } + StyledText { + text: modelData.name ?? "" + font.pointSize: Appearance.font.size.small + color: Colours.palette.m3outline - StyledSlider { - id: fontSizeSlider + elide: Text.ElideRight + anchors.left: parent.left + anchors.right: parent.right + } + } - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 + Loader { + active: isCurrent + asynchronous: true - from: 0.7 - to: 1.5 - value: rootPane.fontSizeScale - onMoved: { - rootPane.fontSizeScale = fontSizeSlider.value; - if (!fontSizeInput.activeFocus) { - fontSizeInput.text = (fontSizeSlider.value).toFixed(1); + sourceComponent: MaterialIcon { + text: "check" + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.large + } } - rootPane.saveConfig(); } + + implicitHeight: schemeRow.implicitHeight + Appearance.padding.normal * 2 } } } } CollapsibleSection { - id: scalesSection - title: qsTr("Scales") + id: animationsSection + title: qsTr("Animations") showBackground: true SectionContainer { @@ -881,7 +1005,7 @@ RowLayout { spacing: Appearance.spacing.normal StyledText { - text: qsTr("Padding scale") + text: qsTr("Animation duration scale") font.pointSize: Appearance.font.size.normal } @@ -891,13 +1015,13 @@ RowLayout { StyledRect { Layout.preferredWidth: 70 - implicitHeight: paddingInput.implicitHeight + Appearance.padding.small * 2 - color: paddingInputHover.containsMouse || paddingInput.activeFocus + implicitHeight: animDurationsInput.implicitHeight + Appearance.padding.small * 2 + color: animDurationsInputHover.containsMouse || animDurationsInput.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2) radius: Appearance.rounding.small border.width: 1 - border.color: paddingInput.activeFocus + border.color: animDurationsInput.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3) @@ -905,7 +1029,7 @@ RowLayout { Behavior on border.color { CAnim {} } MouseArea { - id: paddingInputHover + id: animDurationsInputHover anchors.fill: parent hoverEnabled: true cursorShape: Qt.IBeamCursor @@ -913,29 +1037,29 @@ RowLayout { } StyledTextField { - id: paddingInput + id: animDurationsInput anchors.centerIn: parent width: parent.width - Appearance.padding.normal horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.5; top: 2.0 } + validator: DoubleValidator { bottom: 0.1; top: 5.0 } Component.onCompleted: { - text = (rootPane.paddingScale).toFixed(1); + text = (rootPane.animDurationsScale).toFixed(1); } onTextChanged: { if (activeFocus) { const val = parseFloat(text); - if (!isNaN(val) && val >= 0.5 && val <= 2.0) { - rootPane.paddingScale = val; + if (!isNaN(val) && val >= 0.1 && val <= 5.0) { + rootPane.animDurationsScale = val; rootPane.saveConfig(); } } } onEditingFinished: { const val = parseFloat(text); - if (isNaN(val) || val < 0.5 || val > 2.0) { - text = (rootPane.paddingScale).toFixed(1); + if (isNaN(val) || val < 0.1 || val > 5.0) { + text = (rootPane.animDurationsScale).toFixed(1); } } } @@ -949,332 +1073,271 @@ RowLayout { } StyledSlider { - id: paddingSlider + id: animDurationsSlider Layout.fillWidth: true implicitHeight: Appearance.padding.normal * 3 - from: 0.5 - to: 2.0 - value: rootPane.paddingScale + from: 0.1 + to: 5.0 + value: rootPane.animDurationsScale onMoved: { - rootPane.paddingScale = paddingSlider.value; - if (!paddingInput.activeFocus) { - paddingInput.text = (paddingSlider.value).toFixed(1); + rootPane.animDurationsScale = animDurationsSlider.value; + if (!animDurationsInput.activeFocus) { + animDurationsInput.text = (animDurationsSlider.value).toFixed(1); } rootPane.saveConfig(); } } } } + } - SectionContainer { - contentSpacing: Appearance.spacing.normal + CollapsibleSection { + id: fontsSection + title: qsTr("Fonts") + showBackground: true - ColumnLayout { + CollapsibleSection { + id: materialFontSection + title: qsTr("Material font family") + expanded: true + showBackground: true + nested: true + + Loader { + id: materialFontLoader Layout.fillWidth: true - spacing: Appearance.spacing.small + Layout.preferredHeight: item ? Math.min(item.contentHeight, 300) : 0 + asynchronous: true + active: materialFontSection.expanded - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal + sourceComponent: StyledListView { + id: materialFontList + property alias contentHeight: materialFontList.contentHeight - StyledText { - text: qsTr("Rounding scale") - font.pointSize: Appearance.font.size.normal - } + clip: true + spacing: Appearance.spacing.small / 2 + model: Qt.fontFamilies() - Item { - Layout.fillWidth: true + StyledScrollBar.vertical: StyledScrollBar { + flickable: materialFontList } - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: roundingInput.implicitHeight + Appearance.padding.small * 2 - color: roundingInputHover.containsMouse || roundingInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: roundingInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) + delegate: StyledRect { + required property string modelData + required property int index - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } + width: ListView.view.width - MouseArea { - id: roundingInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton + readonly property bool isCurrent: modelData === rootPane.fontFamilyMaterial + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: isCurrent ? 1 : 0 + border.color: Colours.palette.m3primary + + StateLayer { + function onClicked(): void { + rootPane.fontFamilyMaterial = modelData; + rootPane.saveConfig(); + } } - StyledTextField { - id: roundingInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.1; top: 5.0 } - - Component.onCompleted: { - text = (rootPane.roundingScale).toFixed(1); + RowLayout { + id: fontFamilyMaterialRow + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal + + spacing: Appearance.spacing.normal + + StyledText { + text: modelData + font.pointSize: Appearance.font.size.normal } - - onTextChanged: { - if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0.1 && val <= 5.0) { - rootPane.roundingScale = val; - rootPane.saveConfig(); - } - } + + Item { + Layout.fillWidth: true } - onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0.1 || val > 5.0) { - text = (rootPane.roundingScale).toFixed(1); + + Loader { + active: isCurrent + asynchronous: true + + sourceComponent: MaterialIcon { + text: "check" + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.large } } } - } - StyledText { - text: "×" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal + implicitHeight: fontFamilyMaterialRow.implicitHeight + Appearance.padding.normal * 2 } } - StyledSlider { - id: roundingSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0.1 - to: 5.0 - value: rootPane.roundingScale - onMoved: { - rootPane.roundingScale = roundingSlider.value; - if (!roundingInput.activeFocus) { - roundingInput.text = (roundingSlider.value).toFixed(1); - } - rootPane.saveConfig(); - } - } } } - SectionContainer { - contentSpacing: Appearance.spacing.normal + CollapsibleSection { + id: monoFontSection + title: qsTr("Monospace font family") + expanded: false + showBackground: true + nested: true - ColumnLayout { + Loader { Layout.fillWidth: true - spacing: Appearance.spacing.small + Layout.preferredHeight: item ? Math.min(item.contentHeight, 300) : 0 + asynchronous: true + active: monoFontSection.expanded - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal + sourceComponent: StyledListView { + id: monoFontList + property alias contentHeight: monoFontList.contentHeight - StyledText { - text: qsTr("Spacing scale") - font.pointSize: Appearance.font.size.normal + clip: true + spacing: Appearance.spacing.small / 2 + model: Qt.fontFamilies() + + StyledScrollBar.vertical: StyledScrollBar { + flickable: monoFontList } - Item { - Layout.fillWidth: true - } + delegate: StyledRect { + required property string modelData + required property int index - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: spacingInput.implicitHeight + Appearance.padding.small * 2 - color: spacingInputHover.containsMouse || spacingInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: spacingInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) + width: ListView.view.width - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } + readonly property bool isCurrent: modelData === rootPane.fontFamilyMono + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: isCurrent ? 1 : 0 + border.color: Colours.palette.m3primary - MouseArea { - id: spacingInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton + StateLayer { + function onClicked(): void { + rootPane.fontFamilyMono = modelData; + rootPane.saveConfig(); + } } - StyledTextField { - id: spacingInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.1; top: 2.0 } - - Component.onCompleted: { - text = (rootPane.spacingScale).toFixed(1); + RowLayout { + id: fontFamilyMonoRow + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal + + spacing: Appearance.spacing.normal + + StyledText { + text: modelData + font.pointSize: Appearance.font.size.normal } - - onTextChanged: { - if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0.1 && val <= 2.0) { - rootPane.spacingScale = val; - rootPane.saveConfig(); - } - } + + Item { + Layout.fillWidth: true } - onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0.1 || val > 2.0) { - text = (rootPane.spacingScale).toFixed(1); + + Loader { + active: isCurrent + asynchronous: true + + sourceComponent: MaterialIcon { + text: "check" + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.large } } } - } - - StyledText { - text: "×" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - } - } - - StyledSlider { - id: spacingSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - from: 0.1 - to: 2.0 - value: rootPane.spacingScale - onMoved: { - rootPane.spacingScale = spacingSlider.value; - if (!spacingInput.activeFocus) { - spacingInput.text = (spacingSlider.value).toFixed(1); - } - rootPane.saveConfig(); + implicitHeight: fontFamilyMonoRow.implicitHeight + Appearance.padding.normal * 2 } } } } - } - - CollapsibleSection { - id: transparencySection - title: qsTr("Transparency") - showBackground: true - - SwitchRow { - label: qsTr("Transparency enabled") - checked: rootPane.transparencyEnabled - onToggled: checked => { - rootPane.transparencyEnabled = checked; - rootPane.saveConfig(); - } - } - SectionContainer { - contentSpacing: Appearance.spacing.normal + CollapsibleSection { + id: sansFontSection + title: qsTr("Sans-serif font family") + expanded: false + showBackground: true + nested: true - ColumnLayout { + Loader { Layout.fillWidth: true - spacing: Appearance.spacing.small + Layout.preferredHeight: item ? Math.min(item.contentHeight, 300) : 0 + asynchronous: true + active: sansFontSection.expanded - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal + sourceComponent: StyledListView { + id: sansFontList + property alias contentHeight: sansFontList.contentHeight - StyledText { - text: qsTr("Transparency base") - font.pointSize: Appearance.font.size.normal - } + clip: true + spacing: Appearance.spacing.small / 2 + model: Qt.fontFamilies() - Item { - Layout.fillWidth: true + StyledScrollBar.vertical: StyledScrollBar { + flickable: sansFontList } - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: transparencyBaseInput.implicitHeight + Appearance.padding.small * 2 - color: transparencyBaseInputHover.containsMouse || transparencyBaseInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: transparencyBaseInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) + delegate: StyledRect { + required property string modelData + required property int index - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } + width: ListView.view.width - MouseArea { - id: transparencyBaseInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } + readonly property bool isCurrent: modelData === rootPane.fontFamilySans + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: isCurrent ? 1 : 0 + border.color: Colours.palette.m3primary - StyledTextField { - id: transparencyBaseInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: IntValidator { bottom: 0; top: 100 } - - Component.onCompleted: { - text = Math.round(rootPane.transparencyBase * 100).toString(); - } - - onTextChanged: { - if (activeFocus) { - const val = parseInt(text); - if (!isNaN(val) && val >= 0 && val <= 100) { - rootPane.transparencyBase = val / 100; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseInt(text); - if (isNaN(val) || val < 0 || val > 100) { - text = Math.round(rootPane.transparencyBase * 100).toString(); - } + StateLayer { + function onClicked(): void { + rootPane.fontFamilySans = modelData; + rootPane.saveConfig(); } } - } - StyledText { - text: "%" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - } - } + RowLayout { + id: fontFamilySansRow - StyledSlider { - id: baseSlider + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 + spacing: Appearance.spacing.normal - from: 0 - to: 100 - value: rootPane.transparencyBase * 100 - onMoved: { - rootPane.transparencyBase = baseSlider.value / 100; - if (!transparencyBaseInput.activeFocus) { - transparencyBaseInput.text = Math.round(baseSlider.value).toString(); + StyledText { + text: modelData + font.pointSize: Appearance.font.size.normal + } + + Item { + Layout.fillWidth: true + } + + Loader { + active: isCurrent + asynchronous: true + + sourceComponent: MaterialIcon { + text: "check" + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.large + } + } } - rootPane.saveConfig(); + + implicitHeight: fontFamilySansRow.implicitHeight + Appearance.padding.normal * 2 } } } @@ -1292,7 +1355,7 @@ RowLayout { spacing: Appearance.spacing.normal StyledText { - text: qsTr("Transparency layers") + text: qsTr("Font size scale") font.pointSize: Appearance.font.size.normal } @@ -1302,13 +1365,13 @@ RowLayout { StyledRect { Layout.preferredWidth: 70 - implicitHeight: transparencyLayersInput.implicitHeight + Appearance.padding.small * 2 - color: transparencyLayersInputHover.containsMouse || transparencyLayersInput.activeFocus + implicitHeight: fontSizeInput.implicitHeight + Appearance.padding.small * 2 + color: fontSizeInputHover.containsMouse || fontSizeInput.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2) radius: Appearance.rounding.small border.width: 1 - border.color: transparencyLayersInput.activeFocus + border.color: fontSizeInput.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3) @@ -1316,7 +1379,7 @@ RowLayout { Behavior on border.color { CAnim {} } MouseArea { - id: transparencyLayersInputHover + id: fontSizeInputHover anchors.fill: parent hoverEnabled: true cursorShape: Qt.IBeamCursor @@ -1324,56 +1387,56 @@ RowLayout { } StyledTextField { - id: transparencyLayersInput + id: fontSizeInput anchors.centerIn: parent width: parent.width - Appearance.padding.normal horizontalAlignment: TextInput.AlignHCenter - validator: IntValidator { bottom: 0; top: 100 } + validator: DoubleValidator { bottom: 0.7; top: 1.5 } Component.onCompleted: { - text = Math.round(rootPane.transparencyLayers * 100).toString(); + text = (rootPane.fontSizeScale).toFixed(1); } onTextChanged: { if (activeFocus) { - const val = parseInt(text); - if (!isNaN(val) && val >= 0 && val <= 100) { - rootPane.transparencyLayers = val / 100; + const val = parseFloat(text); + if (!isNaN(val) && val >= 0.7 && val <= 1.5) { + rootPane.fontSizeScale = val; rootPane.saveConfig(); } } } onEditingFinished: { - const val = parseInt(text); - if (isNaN(val) || val < 0 || val > 100) { - text = Math.round(rootPane.transparencyLayers * 100).toString(); + const val = parseFloat(text); + if (isNaN(val) || val < 0.7 || val > 1.5) { + text = (rootPane.fontSizeScale).toFixed(1); } } } } StyledText { - text: "%" + text: "×" color: Colours.palette.m3outline font.pointSize: Appearance.font.size.normal } } StyledSlider { - id: layersSlider + id: fontSizeSlider Layout.fillWidth: true implicitHeight: Appearance.padding.normal * 3 - from: 0 - to: 100 - value: rootPane.transparencyLayers * 100 + from: 0.7 + to: 1.5 + value: rootPane.fontSizeScale onMoved: { - rootPane.transparencyLayers = layersSlider.value / 100; - if (!transparencyLayersInput.activeFocus) { - transparencyLayersInput.text = Math.round(layersSlider.value).toString(); + rootPane.fontSizeScale = fontSizeSlider.value; + if (!fontSizeInput.activeFocus) { + fontSizeInput.text = (fontSizeSlider.value).toFixed(1); } - rootPane.saveConfig(); + rootPane.saveConfig(); } } } @@ -1381,8 +1444,8 @@ RowLayout { } CollapsibleSection { - id: borderSection - title: qsTr("Border") + id: scalesSection + title: qsTr("Scales") showBackground: true SectionContainer { @@ -1397,7 +1460,7 @@ RowLayout { spacing: Appearance.spacing.normal StyledText { - text: qsTr("Border rounding") + text: qsTr("Padding scale") font.pointSize: Appearance.font.size.normal } @@ -1407,13 +1470,13 @@ RowLayout { StyledRect { Layout.preferredWidth: 70 - implicitHeight: borderRoundingInput.implicitHeight + Appearance.padding.small * 2 - color: borderRoundingInputHover.containsMouse || borderRoundingInput.activeFocus + implicitHeight: paddingInput.implicitHeight + Appearance.padding.small * 2 + color: paddingInputHover.containsMouse || paddingInput.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2) radius: Appearance.rounding.small border.width: 1 - border.color: borderRoundingInput.activeFocus + border.color: paddingInput.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3) @@ -1421,7 +1484,7 @@ RowLayout { Behavior on border.color { CAnim {} } MouseArea { - id: borderRoundingInputHover + id: paddingInputHover anchors.fill: parent hoverEnabled: true cursorShape: Qt.IBeamCursor @@ -1429,48 +1492,54 @@ RowLayout { } StyledTextField { - id: borderRoundingInput + id: paddingInput anchors.centerIn: parent width: parent.width - Appearance.padding.normal horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.1; top: 100 } + validator: DoubleValidator { bottom: 0.5; top: 2.0 } Component.onCompleted: { - text = (rootPane.borderRounding).toFixed(1); + text = (rootPane.paddingScale).toFixed(1); } onTextChanged: { if (activeFocus) { const val = parseFloat(text); - if (!isNaN(val) && val >= 0.1 && val <= 100) { - rootPane.borderRounding = val; + if (!isNaN(val) && val >= 0.5 && val <= 2.0) { + rootPane.paddingScale = val; rootPane.saveConfig(); } } } onEditingFinished: { const val = parseFloat(text); - if (isNaN(val) || val < 0.1 || val > 100) { - text = (rootPane.borderRounding).toFixed(1); + if (isNaN(val) || val < 0.5 || val > 2.0) { + text = (rootPane.paddingScale).toFixed(1); } } } } + + StyledText { + text: "×" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.normal + } } StyledSlider { - id: borderRoundingSlider + id: paddingSlider Layout.fillWidth: true implicitHeight: Appearance.padding.normal * 3 - from: 0.1 - to: 100 - value: rootPane.borderRounding + from: 0.5 + to: 2.0 + value: rootPane.paddingScale onMoved: { - rootPane.borderRounding = borderRoundingSlider.value; - if (!borderRoundingInput.activeFocus) { - borderRoundingInput.text = (borderRoundingSlider.value).toFixed(1); + rootPane.paddingScale = paddingSlider.value; + if (!paddingInput.activeFocus) { + paddingInput.text = (paddingSlider.value).toFixed(1); } rootPane.saveConfig(); } @@ -1490,7 +1559,7 @@ RowLayout { spacing: Appearance.spacing.normal StyledText { - text: qsTr("Border thickness") + text: qsTr("Rounding scale") font.pointSize: Appearance.font.size.normal } @@ -1500,13 +1569,13 @@ RowLayout { StyledRect { Layout.preferredWidth: 70 - implicitHeight: borderThicknessInput.implicitHeight + Appearance.padding.small * 2 - color: borderThicknessInputHover.containsMouse || borderThicknessInput.activeFocus + implicitHeight: roundingInput.implicitHeight + Appearance.padding.small * 2 + color: roundingInputHover.containsMouse || roundingInput.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2) radius: Appearance.rounding.small border.width: 1 - border.color: borderThicknessInput.activeFocus + border.color: roundingInput.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3) @@ -1514,7 +1583,7 @@ RowLayout { Behavior on border.color { CAnim {} } MouseArea { - id: borderThicknessInputHover + id: roundingInputHover anchors.fill: parent hoverEnabled: true cursorShape: Qt.IBeamCursor @@ -1522,103 +1591,60 @@ RowLayout { } StyledTextField { - id: borderThicknessInput + id: roundingInput anchors.centerIn: parent width: parent.width - Appearance.padding.normal horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.1; top: 100 } + validator: DoubleValidator { bottom: 0.1; top: 5.0 } Component.onCompleted: { - text = (rootPane.borderThickness).toFixed(1); + text = (rootPane.roundingScale).toFixed(1); } onTextChanged: { if (activeFocus) { const val = parseFloat(text); - if (!isNaN(val) && val >= 0.1 && val <= 100) { - rootPane.borderThickness = val; + if (!isNaN(val) && val >= 0.1 && val <= 5.0) { + rootPane.roundingScale = val; rootPane.saveConfig(); } } } onEditingFinished: { const val = parseFloat(text); - if (isNaN(val) || val < 0.1 || val > 100) { - text = (rootPane.borderThickness).toFixed(1); + if (isNaN(val) || val < 0.1 || val > 5.0) { + text = (rootPane.roundingScale).toFixed(1); } } } } + + StyledText { + text: "×" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.normal + } } StyledSlider { - id: borderThicknessSlider + id: roundingSlider Layout.fillWidth: true implicitHeight: Appearance.padding.normal * 3 from: 0.1 - to: 100 - value: rootPane.borderThickness + to: 5.0 + value: rootPane.roundingScale onMoved: { - rootPane.borderThickness = borderThicknessSlider.value; - if (!borderThicknessInput.activeFocus) { - borderThicknessInput.text = (borderThicknessSlider.value).toFixed(1); + rootPane.roundingScale = roundingSlider.value; + if (!roundingInput.activeFocus) { + roundingInput.text = (roundingSlider.value).toFixed(1); } rootPane.saveConfig(); } } } } - } - - CollapsibleSection { - id: backgroundSection - title: qsTr("Background") - showBackground: true - - SwitchRow { - label: qsTr("Desktop clock") - checked: rootPane.desktopClockEnabled - onToggled: checked => { - rootPane.desktopClockEnabled = checked; - rootPane.saveConfig(); - } - } - - SwitchRow { - label: qsTr("Background enabled") - checked: rootPane.backgroundEnabled - onToggled: checked => { - rootPane.backgroundEnabled = checked; - rootPane.saveConfig(); - } - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Visualiser") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - SwitchRow { - label: qsTr("Visualiser enabled") - checked: rootPane.visualiserEnabled - onToggled: checked => { - rootPane.visualiserEnabled = checked; - rootPane.saveConfig(); - } - } - - SwitchRow { - label: qsTr("Visualiser auto hide") - checked: rootPane.visualiserAutoHide - onToggled: checked => { - rootPane.visualiserAutoHide = checked; - rootPane.saveConfig(); - } - } SectionContainer { contentSpacing: Appearance.spacing.normal @@ -1632,7 +1658,7 @@ RowLayout { spacing: Appearance.spacing.normal StyledText { - text: qsTr("Visualiser rounding") + text: qsTr("Spacing scale") font.pointSize: Appearance.font.size.normal } @@ -1642,13 +1668,13 @@ RowLayout { StyledRect { Layout.preferredWidth: 70 - implicitHeight: visualiserRoundingInput.implicitHeight + Appearance.padding.small * 2 - color: visualiserRoundingInputHover.containsMouse || visualiserRoundingInput.activeFocus + implicitHeight: spacingInput.implicitHeight + Appearance.padding.small * 2 + color: spacingInputHover.containsMouse || spacingInput.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2) radius: Appearance.rounding.small border.width: 1 - border.color: visualiserRoundingInput.activeFocus + border.color: spacingInput.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3) @@ -1656,7 +1682,7 @@ RowLayout { Behavior on border.color { CAnim {} } MouseArea { - id: visualiserRoundingInputHover + id: spacingInputHover anchors.fill: parent hoverEnabled: true cursorShape: Qt.IBeamCursor @@ -1664,55 +1690,75 @@ RowLayout { } StyledTextField { - id: visualiserRoundingInput + id: spacingInput anchors.centerIn: parent width: parent.width - Appearance.padding.normal horizontalAlignment: TextInput.AlignHCenter - validator: IntValidator { bottom: 0; top: 10 } + validator: DoubleValidator { bottom: 0.1; top: 2.0 } Component.onCompleted: { - text = Math.round(rootPane.visualiserRounding).toString(); + text = (rootPane.spacingScale).toFixed(1); } onTextChanged: { if (activeFocus) { - const val = parseInt(text); - if (!isNaN(val) && val >= 0 && val <= 10) { - rootPane.visualiserRounding = val; - rootPane.saveConfig(); + const val = parseFloat(text); + if (!isNaN(val) && val >= 0.1 && val <= 2.0) { + rootPane.spacingScale = val; + rootPane.saveConfig(); } } } onEditingFinished: { - const val = parseInt(text); - if (isNaN(val) || val < 0 || val > 10) { - text = Math.round(rootPane.visualiserRounding).toString(); + const val = parseFloat(text); + if (isNaN(val) || val < 0.1 || val > 2.0) { + text = (rootPane.spacingScale).toFixed(1); } } } } + + StyledText { + text: "×" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.normal + } } StyledSlider { - id: visualiserRoundingSlider + id: spacingSlider Layout.fillWidth: true implicitHeight: Appearance.padding.normal * 3 - from: 0 - to: 10 - stepSize: 1 - value: rootPane.visualiserRounding + from: 0.1 + to: 2.0 + value: rootPane.spacingScale onMoved: { - rootPane.visualiserRounding = Math.round(visualiserRoundingSlider.value); - if (!visualiserRoundingInput.activeFocus) { - visualiserRoundingInput.text = Math.round(visualiserRoundingSlider.value).toString(); + rootPane.spacingScale = spacingSlider.value; + if (!spacingInput.activeFocus) { + spacingInput.text = (spacingSlider.value).toFixed(1); } rootPane.saveConfig(); + } + } } } } - } + + CollapsibleSection { + id: transparencySection + title: qsTr("Transparency") + showBackground: true + + SwitchRow { + label: qsTr("Transparency enabled") + checked: rootPane.transparencyEnabled + onToggled: checked => { + rootPane.transparencyEnabled = checked; + rootPane.saveConfig(); + } + } SectionContainer { contentSpacing: Appearance.spacing.normal @@ -1726,7 +1772,7 @@ RowLayout { spacing: Appearance.spacing.normal StyledText { - text: qsTr("Visualiser spacing") + text: qsTr("Transparency base") font.pointSize: Appearance.font.size.normal } @@ -1736,13 +1782,13 @@ RowLayout { StyledRect { Layout.preferredWidth: 70 - implicitHeight: visualiserSpacingInput.implicitHeight + Appearance.padding.small * 2 - color: visualiserSpacingInputHover.containsMouse || visualiserSpacingInput.activeFocus + implicitHeight: transparencyBaseInput.implicitHeight + Appearance.padding.small * 2 + color: transparencyBaseInputHover.containsMouse || transparencyBaseInput.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2) radius: Appearance.rounding.small border.width: 1 - border.color: visualiserSpacingInput.activeFocus + border.color: transparencyBaseInput.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3) @@ -1750,7 +1796,7 @@ RowLayout { Behavior on border.color { CAnim {} } MouseArea { - id: visualiserSpacingInputHover + id: transparencyBaseInputHover anchors.fill: parent hoverEnabled: true cursorShape: Qt.IBeamCursor @@ -1758,701 +1804,592 @@ RowLayout { } StyledTextField { - id: visualiserSpacingInput + id: transparencyBaseInput anchors.centerIn: parent width: parent.width - Appearance.padding.normal horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0; top: 2 } + validator: IntValidator { bottom: 0; top: 100 } Component.onCompleted: { - text = (rootPane.visualiserSpacing).toFixed(1); + text = Math.round(rootPane.transparencyBase * 100).toString(); } onTextChanged: { if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0 && val <= 2) { - rootPane.visualiserSpacing = val; + const val = parseInt(text); + if (!isNaN(val) && val >= 0 && val <= 100) { + rootPane.transparencyBase = val / 100; rootPane.saveConfig(); } } } onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0 || val > 2) { - text = (rootPane.visualiserSpacing).toFixed(1); + const val = parseInt(text); + if (isNaN(val) || val < 0 || val > 100) { + text = Math.round(rootPane.transparencyBase * 100).toString(); } } } } + + StyledText { + text: "%" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.normal + } } StyledSlider { - id: visualiserSpacingSlider + id: baseSlider Layout.fillWidth: true implicitHeight: Appearance.padding.normal * 3 from: 0 - to: 2 - value: rootPane.visualiserSpacing + to: 100 + value: rootPane.transparencyBase * 100 onMoved: { - rootPane.visualiserSpacing = visualiserSpacingSlider.value; - if (!visualiserSpacingInput.activeFocus) { - visualiserSpacingInput.text = (visualiserSpacingSlider.value).toFixed(1); + rootPane.transparencyBase = baseSlider.value / 100; + if (!transparencyBaseInput.activeFocus) { + transparencyBaseInput.text = Math.round(baseSlider.value).toString(); } rootPane.saveConfig(); } } } } - } - } - } - } - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - ClippingRectangle { - id: rightAppearanceClippingRect - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - radius: rightAppearanceBorder.innerRadius - color: "transparent" - - Loader { - id: rightAppearanceLoader - anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 - asynchronous: true - sourceComponent: appearanceRightContentComponent - property var rootPane: root - - onStatusChanged: { - if (status === Loader.Error) { - console.error("[AppearancePane] Right appearance loader error!"); - } - } - } - } - - InnerBorder { - id: rightAppearanceBorder - leftThickness: Appearance.padding.normal / 2 - } - - Component { - id: appearanceRightContentComponent - - StyledFlickable { - id: rightAppearanceFlickable - flickableDirection: Flickable.VerticalFlick - contentHeight: contentLayout.height - - StyledScrollBar.vertical: StyledScrollBar { - flickable: rightAppearanceFlickable - } - ColumnLayout { - id: contentLayout + SectionContainer { + contentSpacing: Appearance.spacing.normal - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - spacing: Appearance.spacing.normal + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small - MaterialIcon { - Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - Layout.topMargin: 0 - text: "palette" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.normal - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Appearance Settings") - font.pointSize: Appearance.font.size.large - font.bold: true - } + StyledText { + text: qsTr("Transparency layers") + font.pointSize: Appearance.font.size.normal + } - StyledText { - Layout.topMargin: Appearance.spacing.large - Layout.alignment: Qt.AlignHCenter - text: qsTr("Wallpaper") - font.pointSize: Appearance.font.size.extraLarge - font.weight: 600 - } + Item { + Layout.fillWidth: true + } - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Select a wallpaper") - font.pointSize: Appearance.font.size.normal - color: Colours.palette.m3onSurfaceVariant - } + StyledRect { + Layout.preferredWidth: 70 + implicitHeight: transparencyLayersInput.implicitHeight + Appearance.padding.small * 2 + color: transparencyLayersInputHover.containsMouse || transparencyLayersInput.activeFocus + ? Colours.layer(Colours.palette.m3surfaceContainer, 3) + : Colours.layer(Colours.palette.m3surfaceContainer, 2) + radius: Appearance.rounding.small + border.width: 1 + border.color: transparencyLayersInput.activeFocus + ? Colours.palette.m3primary + : Qt.alpha(Colours.palette.m3outline, 0.3) - Item { - Layout.fillWidth: true - Layout.topMargin: Appearance.spacing.large - Layout.preferredHeight: wallpaperLoader.item ? wallpaperLoader.item.layoutPreferredHeight : 0 - - Loader { - id: wallpaperLoader - anchors.fill: parent - asynchronous: true - active: { - // Lazy load: only activate when: - // 1. Right pane is loaded AND - // 2. Appearance pane is active (index 3) or adjacent (for smooth transitions) - // This prevents loading all wallpapers when control center opens but appearance pane isn't visible - const isActive = root.session.activeIndex === 3; - const isAdjacent = Math.abs(root.session.activeIndex - 3) === 1; - const shouldActivate = rightAppearanceLoader.item !== null && (isActive || isAdjacent); - return shouldActivate; - } - - onStatusChanged: { - if (status === Loader.Error) { - console.error("[AppearancePane] Wallpaper loader error!"); - } - } - - // Stop lazy loading when loader becomes inactive - onActiveChanged: { - if (!active && wallpaperLoader.item) { - const container = wallpaperLoader.item; - // Access timer through wallpaperGrid - if (container && container.wallpaperGrid) { - if (container.wallpaperGrid.scrollCheckTimer) { - container.wallpaperGrid.scrollCheckTimer.stop(); - } - container.wallpaperGrid._expansionInProgress = false; - } - } - } + Behavior on color { CAnim {} } + Behavior on border.color { CAnim {} } - sourceComponent: Item { - id: wallpaperGridContainer - property alias layoutPreferredHeight: wallpaperGrid.layoutPreferredHeight - - // Find and store reference to parent Flickable for scroll monitoring - property var parentFlickable: { - let item = parent; - while (item) { - if (item.flickableDirection !== undefined) { - return item; - } - item = item.parent; - } - return null; - } - - // Cleanup when component is destroyed - Component.onDestruction: { - if (wallpaperGrid) { - if (wallpaperGrid.scrollCheckTimer) { - wallpaperGrid.scrollCheckTimer.stop(); - } - wallpaperGrid._expansionInProgress = false; - } - } - - // Lazy loading model: loads one image at a time, only when touching bottom - // This prevents GridView from creating all delegates at once - QtObject { - id: lazyModel - - property var sourceList: null - property int loadedCount: 0 // Total items available to load - property int visibleCount: 0 // Items actually exposed to GridView (only visible + buffer) - property int totalCount: 0 - - function initialize(list) { - sourceList = list; - totalCount = list ? list.length : 0; - // Start with enough items to fill the initial viewport (~3 rows) - const initialRows = 3; - const cols = wallpaperGrid.columnsCount > 0 ? wallpaperGrid.columnsCount : 3; - const initialCount = Math.min(initialRows * cols, totalCount); - loadedCount = initialCount; - visibleCount = initialCount; - } - - function loadOneRow() { - if (loadedCount < totalCount) { - const cols = wallpaperGrid.columnsCount > 0 ? wallpaperGrid.columnsCount : 1; - const itemsToLoad = Math.min(cols, totalCount - loadedCount); - loadedCount += itemsToLoad; - } - } - - function updateVisibleCount(neededCount) { - // Always round up to complete rows to avoid incomplete rows in the grid - const cols = wallpaperGrid.columnsCount > 0 ? wallpaperGrid.columnsCount : 1; - const maxVisible = Math.min(neededCount, loadedCount); - const rows = Math.ceil(maxVisible / cols); - const newVisibleCount = Math.min(rows * cols, loadedCount); - - if (newVisibleCount > visibleCount) { - visibleCount = newVisibleCount; - } - } - } - - GridView { - id: wallpaperGrid - anchors.fill: parent - - property int _delegateCount: 0 - - readonly property int minCellWidth: 200 + Appearance.spacing.normal - readonly property int columnsCount: Math.max(1, Math.floor(parent.width / minCellWidth)) - - // Height based on visible items only - prevents GridView from creating all delegates - readonly property int layoutPreferredHeight: { - if (!lazyModel || lazyModel.visibleCount === 0 || columnsCount === 0) { - return 0; - } - const calculated = Math.ceil(lazyModel.visibleCount / columnsCount) * cellHeight; - return calculated; - } - - height: layoutPreferredHeight - cellWidth: width / columnsCount - cellHeight: 140 + Appearance.spacing.normal - - leftMargin: 0 - rightMargin: 0 - topMargin: 0 - bottomMargin: 0 + MouseArea { + id: transparencyLayersInputHover + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.NoButton + } - // Use ListModel for incremental updates to prevent flashing when new items are added - ListModel { - id: wallpaperListModel - } - - model: wallpaperListModel - - Connections { - target: lazyModel - function onVisibleCountChanged(): void { - if (!lazyModel || !lazyModel.sourceList) return; - - const newCount = lazyModel.visibleCount; - const currentCount = wallpaperListModel.count; + StyledTextField { + id: transparencyLayersInput + anchors.centerIn: parent + width: parent.width - Appearance.padding.normal + horizontalAlignment: TextInput.AlignHCenter + validator: IntValidator { bottom: 0; top: 100 } - // Only append new items - never remove or replace existing ones - if (newCount > currentCount) { - const flickable = wallpaperGridContainer.parentFlickable; - const oldScrollY = flickable ? flickable.contentY : 0; - - for (let i = currentCount; i < newCount; i++) { - wallpaperListModel.append({modelData: lazyModel.sourceList[i]}); - } - - // Preserve scroll position after model update - if (flickable) { - Qt.callLater(function() { - if (Math.abs(flickable.contentY - oldScrollY) < 1) { - flickable.contentY = oldScrollY; - } - }); - } - } - } - } - - Component.onCompleted: { - Qt.callLater(function() { - const isActive = root.session.activeIndex === 3; - if (width > 0 && parent && parent.visible && isActive && Wallpapers.list) { - lazyModel.initialize(Wallpapers.list); - wallpaperListModel.clear(); - for (let i = 0; i < lazyModel.visibleCount; i++) { - wallpaperListModel.append({modelData: lazyModel.sourceList[i]}); - } + Component.onCompleted: { + text = Math.round(rootPane.transparencyLayers * 100).toString(); } - }); - } - - Connections { - target: root.session - function onActiveIndexChanged(): void { - const isActive = root.session.activeIndex === 3; - // Stop lazy loading when switching away from appearance pane - if (!isActive) { - if (scrollCheckTimer) { - scrollCheckTimer.stop(); - } - if (wallpaperGrid) { - wallpaperGrid._expansionInProgress = false; + onTextChanged: { + if (activeFocus) { + const val = parseInt(text); + if (!isNaN(val) && val >= 0 && val <= 100) { + rootPane.transparencyLayers = val / 100; + rootPane.saveConfig(); + } } - return; } - - // Initialize if needed when switching to appearance pane - if (isActive && width > 0 && !lazyModel.sourceList && parent && parent.visible && Wallpapers.list) { - lazyModel.initialize(Wallpapers.list); - wallpaperListModel.clear(); - for (let i = 0; i < lazyModel.visibleCount; i++) { - wallpaperListModel.append({modelData: lazyModel.sourceList[i]}); + onEditingFinished: { + const val = parseInt(text); + if (isNaN(val) || val < 0 || val > 100) { + text = Math.round(rootPane.transparencyLayers * 100).toString(); } } } } - - onWidthChanged: { - const isActive = root.session.activeIndex === 3; - if (width > 0 && !lazyModel.sourceList && parent && parent.visible && isActive && Wallpapers.list) { - lazyModel.initialize(Wallpapers.list); - wallpaperListModel.clear(); - for (let i = 0; i < lazyModel.visibleCount; i++) { - wallpaperListModel.append({modelData: lazyModel.sourceList[i]}); - } + + StyledText { + text: "%" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.normal + } + } + + StyledSlider { + id: layersSlider + + Layout.fillWidth: true + implicitHeight: Appearance.padding.normal * 3 + + from: 0 + to: 100 + value: rootPane.transparencyLayers * 100 + onMoved: { + rootPane.transparencyLayers = layersSlider.value / 100; + if (!transparencyLayersInput.activeFocus) { + transparencyLayersInput.text = Math.round(layersSlider.value).toString(); } + rootPane.saveConfig(); } - - // Force true lazy loading: only create delegates for visible items - displayMarginBeginning: 0 - displayMarginEnd: 0 - cacheBuffer: 0 - - // Debounce expansion to avoid too frequent checks - property bool _expansionInProgress: false - - Connections { - target: wallpaperGridContainer.parentFlickable - function onContentYChanged(): void { - // Don't process scroll events if appearance pane is not active - const isActive = root.session.activeIndex === 3; - if (!isActive) return; + } + } + } + } + + CollapsibleSection { + id: borderSection + title: qsTr("Border") + showBackground: true + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small + + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + StyledText { + text: qsTr("Border rounding") + font.pointSize: Appearance.font.size.normal + } + + Item { + Layout.fillWidth: true + } + + StyledRect { + Layout.preferredWidth: 70 + implicitHeight: borderRoundingInput.implicitHeight + Appearance.padding.small * 2 + color: borderRoundingInputHover.containsMouse || borderRoundingInput.activeFocus + ? Colours.layer(Colours.palette.m3surfaceContainer, 3) + : Colours.layer(Colours.palette.m3surfaceContainer, 2) + radius: Appearance.rounding.small + border.width: 1 + border.color: borderRoundingInput.activeFocus + ? Colours.palette.m3primary + : Qt.alpha(Colours.palette.m3outline, 0.3) + + Behavior on color { CAnim {} } + Behavior on border.color { CAnim {} } + + MouseArea { + id: borderRoundingInputHover + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.NoButton + } + + StyledTextField { + id: borderRoundingInput + anchors.centerIn: parent + width: parent.width - Appearance.padding.normal + horizontalAlignment: TextInput.AlignHCenter + validator: DoubleValidator { bottom: 0.1; top: 100 } - if (!lazyModel || !lazyModel.sourceList || lazyModel.loadedCount >= lazyModel.totalCount || wallpaperGrid._expansionInProgress) { - return; + Component.onCompleted: { + text = (rootPane.borderRounding).toFixed(1); } - const flickable = wallpaperGridContainer.parentFlickable; - if (!flickable) return; - - const gridY = wallpaperGridContainer.y; - const scrollY = flickable.contentY; - const viewportHeight = flickable.height; - - const topY = scrollY - gridY; - const bottomY = scrollY + viewportHeight - gridY; - - if (bottomY < 0) return; - - const topRow = Math.max(0, Math.floor(topY / wallpaperGrid.cellHeight)); - const bottomRow = Math.floor(bottomY / wallpaperGrid.cellHeight); - - // Update visible count with 1 row buffer ahead - const bufferRows = 1; - const neededBottomRow = bottomRow + bufferRows; - const neededCount = Math.min((neededBottomRow + 1) * wallpaperGrid.columnsCount, lazyModel.loadedCount); - lazyModel.updateVisibleCount(neededCount); - - // Load more when we're within 1 row of running out of loaded items - const loadedRows = Math.ceil(lazyModel.loadedCount / wallpaperGrid.columnsCount); - const rowsRemaining = loadedRows - (bottomRow + 1); - - if (rowsRemaining <= 1 && lazyModel.loadedCount < lazyModel.totalCount) { - if (!wallpaperGrid._expansionInProgress) { - wallpaperGrid._expansionInProgress = true; - lazyModel.loadOneRow(); - Qt.callLater(function() { - wallpaperGrid._expansionInProgress = false; - }); + onTextChanged: { + if (activeFocus) { + const val = parseFloat(text); + if (!isNaN(val) && val >= 0.1 && val <= 100) { + rootPane.borderRounding = val; + rootPane.saveConfig(); + } + } + } + onEditingFinished: { + const val = parseFloat(text); + if (isNaN(val) || val < 0.1 || val > 100) { + text = (rootPane.borderRounding).toFixed(1); } } } } - - // Fallback timer to check scroll position periodically - Timer { - id: scrollCheckTimer - interval: 100 - running: { - const isActive = root.session.activeIndex === 3; - return isActive && lazyModel && lazyModel.sourceList && lazyModel.loadedCount < lazyModel.totalCount; + } + + StyledSlider { + id: borderRoundingSlider + + Layout.fillWidth: true + implicitHeight: Appearance.padding.normal * 3 + + from: 0.1 + to: 100 + value: rootPane.borderRounding + onMoved: { + rootPane.borderRounding = borderRoundingSlider.value; + if (!borderRoundingInput.activeFocus) { + borderRoundingInput.text = (borderRoundingSlider.value).toFixed(1); + } + rootPane.saveConfig(); + } + } + } + } + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small + + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + StyledText { + text: qsTr("Border thickness") + font.pointSize: Appearance.font.size.normal + } + + Item { + Layout.fillWidth: true + } + + StyledRect { + Layout.preferredWidth: 70 + implicitHeight: borderThicknessInput.implicitHeight + Appearance.padding.small * 2 + color: borderThicknessInputHover.containsMouse || borderThicknessInput.activeFocus + ? Colours.layer(Colours.palette.m3surfaceContainer, 3) + : Colours.layer(Colours.palette.m3surfaceContainer, 2) + radius: Appearance.rounding.small + border.width: 1 + border.color: borderThicknessInput.activeFocus + ? Colours.palette.m3primary + : Qt.alpha(Colours.palette.m3outline, 0.3) + + Behavior on color { CAnim {} } + Behavior on border.color { CAnim {} } + + MouseArea { + id: borderThicknessInputHover + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.NoButton } - repeat: true - onTriggered: { - // Double-check that appearance pane is still active - const isActive = root.session.activeIndex === 3; - if (!isActive) { - stop(); - return; - } - - const flickable = wallpaperGridContainer.parentFlickable; - if (!flickable || !lazyModel || !lazyModel.sourceList) return; - - const gridY = wallpaperGridContainer.y; - const scrollY = flickable.contentY; - const viewportHeight = flickable.height; - - const topY = scrollY - gridY; - const bottomY = scrollY + viewportHeight - gridY; - if (bottomY < 0) return; - - const topRow = Math.max(0, Math.floor(topY / wallpaperGrid.cellHeight)); - const bottomRow = Math.floor(bottomY / wallpaperGrid.cellHeight); - - const bufferRows = 1; - const neededBottomRow = bottomRow + bufferRows; - const neededCount = Math.min((neededBottomRow + 1) * wallpaperGrid.columnsCount, lazyModel.loadedCount); - lazyModel.updateVisibleCount(neededCount); + + StyledTextField { + id: borderThicknessInput + anchors.centerIn: parent + width: parent.width - Appearance.padding.normal + horizontalAlignment: TextInput.AlignHCenter + validator: DoubleValidator { bottom: 0.1; top: 100 } - // Load more when we're within 1 row of running out of loaded items - const loadedRows = Math.ceil(lazyModel.loadedCount / wallpaperGrid.columnsCount); - const rowsRemaining = loadedRows - (bottomRow + 1); + Component.onCompleted: { + text = (rootPane.borderThickness).toFixed(1); + } - if (rowsRemaining <= 1 && lazyModel.loadedCount < lazyModel.totalCount) { - if (!wallpaperGrid._expansionInProgress) { - wallpaperGrid._expansionInProgress = true; - lazyModel.loadOneRow(); - Qt.callLater(function() { - wallpaperGrid._expansionInProgress = false; - }); + onTextChanged: { + if (activeFocus) { + const val = parseFloat(text); + if (!isNaN(val) && val >= 0.1 && val <= 100) { + rootPane.borderThickness = val; + rootPane.saveConfig(); + } + } + } + onEditingFinished: { + const val = parseFloat(text); + if (isNaN(val) || val < 0.1 || val > 100) { + text = (rootPane.borderThickness).toFixed(1); } } } } - - - // Parent Flickable handles scrolling - interactive: false + } + StyledSlider { + id: borderThicknessSlider - delegate: Item { - required property var modelData + Layout.fillWidth: true + implicitHeight: Appearance.padding.normal * 3 - width: wallpaperGrid.cellWidth - height: wallpaperGrid.cellHeight + from: 0.1 + to: 100 + value: rootPane.borderThickness + onMoved: { + rootPane.borderThickness = borderThicknessSlider.value; + if (!borderThicknessInput.activeFocus) { + borderThicknessInput.text = (borderThicknessSlider.value).toFixed(1); + } + rootPane.saveConfig(); + } + } + } + } + } - readonly property bool isCurrent: modelData.path === Wallpapers.actualCurrent - readonly property real itemMargin: Appearance.spacing.normal / 2 - readonly property real itemRadius: Appearance.rounding.normal - - Component.onCompleted: { - wallpaperGrid._delegateCount++; + CollapsibleSection { + id: backgroundSection + title: qsTr("Background") + showBackground: true + + SwitchRow { + label: qsTr("Desktop clock") + checked: rootPane.desktopClockEnabled + onToggled: checked => { + rootPane.desktopClockEnabled = checked; + rootPane.saveConfig(); + } + } + + SwitchRow { + label: qsTr("Background enabled") + checked: rootPane.backgroundEnabled + onToggled: checked => { + rootPane.backgroundEnabled = checked; + rootPane.saveConfig(); } + } - StateLayer { - anchors.fill: parent - anchors.leftMargin: itemMargin - anchors.rightMargin: itemMargin - anchors.topMargin: itemMargin - anchors.bottomMargin: itemMargin - radius: itemRadius + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Visualiser") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } - function onClicked(): void { - Wallpapers.setWallpaper(modelData.path); - } + SwitchRow { + label: qsTr("Visualiser enabled") + checked: rootPane.visualiserEnabled + onToggled: checked => { + rootPane.visualiserEnabled = checked; + rootPane.saveConfig(); } + } - StyledClippingRect { - id: image + SwitchRow { + label: qsTr("Visualiser auto hide") + checked: rootPane.visualiserAutoHide + onToggled: checked => { + rootPane.visualiserAutoHide = checked; + rootPane.saveConfig(); + } + } - anchors.fill: parent - anchors.leftMargin: itemMargin - anchors.rightMargin: itemMargin - anchors.topMargin: itemMargin - anchors.bottomMargin: itemMargin - color: Colours.tPalette.m3surfaceContainer - radius: itemRadius - antialiasing: true - layer.enabled: true - layer.smooth: true + SectionContainer { + contentSpacing: Appearance.spacing.normal - CachingImage { - id: cachingImage + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small - path: modelData.path - anchors.fill: parent - fillMode: Image.PreserveAspectCrop - cache: true - visible: opacity > 0 - antialiasing: true - smooth: true + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.normal - opacity: status === Image.Ready ? 1 : 0 + StyledText { + text: qsTr("Visualiser rounding") + font.pointSize: Appearance.font.size.normal + } - Behavior on opacity { - NumberAnimation { - duration: 1000 - easing.type: Easing.OutQuad - } + Item { + Layout.fillWidth: true } - } - // Fallback if CachingImage fails to load - Image { - id: fallbackImage + StyledRect { + Layout.preferredWidth: 70 + implicitHeight: visualiserRoundingInput.implicitHeight + Appearance.padding.small * 2 + color: visualiserRoundingInputHover.containsMouse || visualiserRoundingInput.activeFocus + ? Colours.layer(Colours.palette.m3surfaceContainer, 3) + : Colours.layer(Colours.palette.m3surfaceContainer, 2) + radius: Appearance.rounding.small + border.width: 1 + border.color: visualiserRoundingInput.activeFocus + ? Colours.palette.m3primary + : Qt.alpha(Colours.palette.m3outline, 0.3) - anchors.fill: parent - source: fallbackTimer.triggered && cachingImage.status !== Image.Ready ? modelData.path : "" - asynchronous: true - fillMode: Image.PreserveAspectCrop - cache: true - visible: opacity > 0 - antialiasing: true - smooth: true + Behavior on color { CAnim {} } + Behavior on border.color { CAnim {} } - opacity: status === Image.Ready && cachingImage.status !== Image.Ready ? 1 : 0 + MouseArea { + id: visualiserRoundingInputHover + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.NoButton + } - Behavior on opacity { - NumberAnimation { - duration: 1000 - easing.type: Easing.OutQuad + StyledTextField { + id: visualiserRoundingInput + anchors.centerIn: parent + width: parent.width - Appearance.padding.normal + horizontalAlignment: TextInput.AlignHCenter + validator: IntValidator { bottom: 0; top: 10 } + + Component.onCompleted: { + text = Math.round(rootPane.visualiserRounding).toString(); + } + + onTextChanged: { + if (activeFocus) { + const val = parseInt(text); + if (!isNaN(val) && val >= 0 && val <= 10) { + rootPane.visualiserRounding = val; + rootPane.saveConfig(); + } + } + } + onEditingFinished: { + const val = parseInt(text); + if (isNaN(val) || val < 0 || val > 10) { + text = Math.round(rootPane.visualiserRounding).toString(); + } + } } } } - Timer { - id: fallbackTimer - - property bool triggered: false - interval: 800 - running: cachingImage.status === Image.Loading || cachingImage.status === Image.Null - onTriggered: triggered = true - } - - // Gradient overlay for filename - Rectangle { - id: filenameOverlay + StyledSlider { + id: visualiserRoundingSlider - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom + Layout.fillWidth: true + implicitHeight: Appearance.padding.normal * 3 - implicitHeight: filenameText.implicitHeight + Appearance.padding.normal * 1.5 - radius: 0 - - gradient: Gradient { - GradientStop { - position: 0.0 - color: Qt.rgba(Colours.palette.m3surfaceContainer.r, - Colours.palette.m3surfaceContainer.g, - Colours.palette.m3surfaceContainer.b, 0) - } - GradientStop { - position: 0.3 - color: Qt.rgba(Colours.palette.m3surfaceContainer.r, - Colours.palette.m3surfaceContainer.g, - Colours.palette.m3surfaceContainer.b, 0.7) - } - GradientStop { - position: 0.6 - color: Qt.rgba(Colours.palette.m3surfaceContainer.r, - Colours.palette.m3surfaceContainer.g, - Colours.palette.m3surfaceContainer.b, 0.9) - } - GradientStop { - position: 1.0 - color: Qt.rgba(Colours.palette.m3surfaceContainer.r, - Colours.palette.m3surfaceContainer.g, - Colours.palette.m3surfaceContainer.b, 0.95) + from: 0 + to: 10 + stepSize: 1 + value: rootPane.visualiserRounding + onMoved: { + rootPane.visualiserRounding = Math.round(visualiserRoundingSlider.value); + if (!visualiserRoundingInput.activeFocus) { + visualiserRoundingInput.text = Math.round(visualiserRoundingSlider.value).toString(); } - } + rootPane.saveConfig(); + } + } + } + } - opacity: 0 + SectionContainer { + contentSpacing: Appearance.spacing.normal - Behavior on opacity { - NumberAnimation { - duration: 1000 - easing.type: Easing.OutCubic - } - } + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small - Component.onCompleted: { - opacity = 1; - } - } - } + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.normal - Rectangle { - anchors.fill: parent - anchors.leftMargin: itemMargin - anchors.rightMargin: itemMargin - anchors.topMargin: itemMargin - anchors.bottomMargin: itemMargin - color: "transparent" - radius: itemRadius + border.width - border.width: isCurrent ? 2 : 0 - border.color: Colours.palette.m3primary - antialiasing: true - smooth: true + StyledText { + text: qsTr("Visualiser spacing") + font.pointSize: Appearance.font.size.normal + } - Behavior on border.width { - NumberAnimation { - duration: 150 - easing.type: Easing.OutQuad + Item { + Layout.fillWidth: true } - } - MaterialIcon { - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: Appearance.padding.small + StyledRect { + Layout.preferredWidth: 70 + implicitHeight: visualiserSpacingInput.implicitHeight + Appearance.padding.small * 2 + color: visualiserSpacingInputHover.containsMouse || visualiserSpacingInput.activeFocus + ? Colours.layer(Colours.palette.m3surfaceContainer, 3) + : Colours.layer(Colours.palette.m3surfaceContainer, 2) + radius: Appearance.rounding.small + border.width: 1 + border.color: visualiserSpacingInput.activeFocus + ? Colours.palette.m3primary + : Qt.alpha(Colours.palette.m3outline, 0.3) - visible: isCurrent - text: "check_circle" - color: Colours.palette.m3primary - font.pointSize: Appearance.font.size.large - } - } + Behavior on color { CAnim {} } + Behavior on border.color { CAnim {} } - StyledText { - id: filenameText - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.leftMargin: Appearance.padding.normal + Appearance.spacing.normal / 2 - anchors.rightMargin: Appearance.padding.normal + Appearance.spacing.normal / 2 - anchors.bottomMargin: Appearance.padding.normal + MouseArea { + id: visualiserSpacingInputHover + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.NoButton + } - readonly property string fileName: { - const path = modelData.relativePath || ""; - const parts = path.split("/"); - return parts.length > 0 ? parts[parts.length - 1] : path; + StyledTextField { + id: visualiserSpacingInput + anchors.centerIn: parent + width: parent.width - Appearance.padding.normal + horizontalAlignment: TextInput.AlignHCenter + validator: DoubleValidator { bottom: 0; top: 2 } + + Component.onCompleted: { + text = (rootPane.visualiserSpacing).toFixed(1); + } + + onTextChanged: { + if (activeFocus) { + const val = parseFloat(text); + if (!isNaN(val) && val >= 0 && val <= 2) { + rootPane.visualiserSpacing = val; + rootPane.saveConfig(); + } + } + } + onEditingFinished: { + const val = parseFloat(text); + if (isNaN(val) || val < 0 || val > 2) { + text = (rootPane.visualiserSpacing).toFixed(1); + } + } + } + } } - text: fileName - font.pointSize: Appearance.font.size.smaller - font.weight: 500 - color: isCurrent ? Colours.palette.m3primary : Colours.palette.m3onSurface - elide: Text.ElideMiddle - maximumLineCount: 1 - horizontalAlignment: Text.AlignHCenter + StyledSlider { + id: visualiserSpacingSlider - opacity: 0 + Layout.fillWidth: true + implicitHeight: Appearance.padding.normal * 3 - Behavior on opacity { - NumberAnimation { - duration: 1000 - easing.type: Easing.OutCubic + from: 0 + to: 2 + value: rootPane.visualiserSpacing + onMoved: { + rootPane.visualiserSpacing = visualiserSpacingSlider.value; + if (!visualiserSpacingInput.activeFocus) { + visualiserSpacingInput.text = (visualiserSpacingSlider.value).toFixed(1); + } + rootPane.saveConfig(); } } - - Component.onCompleted: { - opacity = 1; - } - } - } - } } } } } } - } + } + + rightContent: appearanceRightContentComponent } } diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index c2d60d8..dc3ba56 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.effects @@ -11,52 +12,17 @@ import Quickshell.Widgets import QtQuick import QtQuick.Layouts -RowLayout { +Item { id: root required property Session session anchors.fill: parent - spacing: 0 + SplitPaneLayout { + anchors.fill: parent - Item { - id: leftAudioItem - Layout.preferredWidth: Math.floor(parent.width * 0.4) - Layout.minimumWidth: 420 - Layout.fillHeight: true - - ClippingRectangle { - id: leftAudioClippingRect - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: leftAudioBorder.innerRadius - color: "transparent" - - Loader { - id: leftAudioLoader - - anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - - asynchronous: true - sourceComponent: audioLeftContentComponent - } - } - - InnerBorder { - id: leftAudioBorder - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 - } - - Component { - id: audioLeftContentComponent + leftContent: Component { StyledFlickable { id: leftAudioFlickable @@ -246,349 +212,321 @@ RowLayout { } } } - } } - } - - Item { - id: rightAudioItem - Layout.fillWidth: true - Layout.fillHeight: true - - ClippingRectangle { - id: rightAudioClippingRect - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: rightAudioBorder.innerRadius - color: "transparent" - - Loader { - id: rightAudioLoader + } + rightContent: Component { + Item { anchors.fill: parent anchors.topMargin: Appearance.padding.large * 2 anchors.bottomMargin: Appearance.padding.large * 2 anchors.leftMargin: 0 anchors.rightMargin: 0 - asynchronous: true - sourceComponent: audioRightContentComponent - } - } - - InnerBorder { - id: rightAudioBorder - leftThickness: Appearance.padding.normal / 2 - } - - Component { - id: audioRightContentComponent + StyledFlickable { + id: rightAudioFlickable + anchors.fill: parent + flickableDirection: Flickable.VerticalFlick + contentHeight: contentLayout.height - StyledFlickable { - id: rightAudioFlickable - flickableDirection: Flickable.VerticalFlick - contentHeight: contentLayout.height - - StyledScrollBar.vertical: StyledScrollBar { - flickable: rightAudioFlickable - } + StyledScrollBar.vertical: StyledScrollBar { + flickable: rightAudioFlickable + } - ColumnLayout { - id: contentLayout + ColumnLayout { + id: contentLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: Appearance.padding.large * 2 - anchors.rightMargin: Appearance.padding.large * 2 - spacing: Appearance.spacing.normal + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: Appearance.padding.large * 2 + anchors.rightMargin: Appearance.padding.large * 2 + spacing: Appearance.spacing.normal - ConnectionHeader { - icon: "volume_up" - title: qsTr("Audio Settings") - } + ConnectionHeader { + icon: "volume_up" + title: qsTr("Audio Settings") + } - SectionHeader { - title: qsTr("Output volume") - description: qsTr("Control the volume of your output device") - } + SectionHeader { + title: qsTr("Output volume") + description: qsTr("Control the volume of your output device") + } - SectionContainer { - contentSpacing: Appearance.spacing.normal + SectionContainer { + contentSpacing: Appearance.spacing.normal - ColumnLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.normal - StyledText { - text: qsTr("Volume") - font.pointSize: Appearance.font.size.normal - font.weight: 500 - } + StyledText { + text: qsTr("Volume") + font.pointSize: Appearance.font.size.normal + font.weight: 500 + } - Item { - Layout.fillWidth: true - } + Item { + Layout.fillWidth: true + } - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: outputVolumeInput.implicitHeight + Appearance.padding.small * 2 - color: outputVolumeInputHover.containsMouse || outputVolumeInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: outputVolumeInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - enabled: !Audio.muted - opacity: enabled ? 1 : 0.5 - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: outputVolumeInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } + StyledRect { + Layout.preferredWidth: 70 + implicitHeight: outputVolumeInput.implicitHeight + Appearance.padding.small * 2 + color: outputVolumeInputHover.containsMouse || outputVolumeInput.activeFocus + ? Colours.layer(Colours.palette.m3surfaceContainer, 3) + : Colours.layer(Colours.palette.m3surfaceContainer, 2) + radius: Appearance.rounding.small + border.width: 1 + border.color: outputVolumeInput.activeFocus + ? Colours.palette.m3primary + : Qt.alpha(Colours.palette.m3outline, 0.3) + enabled: !Audio.muted + opacity: enabled ? 1 : 0.5 + + Behavior on color { CAnim {} } + Behavior on border.color { CAnim {} } + + MouseArea { + id: outputVolumeInputHover + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.NoButton + } - StyledTextField { - id: outputVolumeInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: IntValidator { bottom: 0; top: 100 } - enabled: !Audio.muted - - Component.onCompleted: { - text = Math.round(Audio.volume * 100).toString(); - } - - Connections { - target: Audio - function onVolumeChanged() { - if (!outputVolumeInput.activeFocus) { - outputVolumeInput.text = Math.round(Audio.volume * 100).toString(); + StyledTextField { + id: outputVolumeInput + anchors.centerIn: parent + width: parent.width - Appearance.padding.normal + horizontalAlignment: TextInput.AlignHCenter + validator: IntValidator { bottom: 0; top: 100 } + enabled: !Audio.muted + + Component.onCompleted: { + text = Math.round(Audio.volume * 100).toString(); } - } - } - - onTextChanged: { - if (activeFocus) { - const val = parseInt(text); - if (!isNaN(val) && val >= 0 && val <= 100) { - Audio.setVolume(val / 100); + + Connections { + target: Audio + function onVolumeChanged() { + if (!outputVolumeInput.activeFocus) { + outputVolumeInput.text = Math.round(Audio.volume * 100).toString(); + } + } + } + + onTextChanged: { + if (activeFocus) { + const val = parseInt(text); + if (!isNaN(val) && val >= 0 && val <= 100) { + Audio.setVolume(val / 100); + } + } + } + onEditingFinished: { + const val = parseInt(text); + if (isNaN(val) || val < 0 || val > 100) { + text = Math.round(Audio.volume * 100).toString(); + } } } } - onEditingFinished: { - const val = parseInt(text); - if (isNaN(val) || val < 0 || val > 100) { - text = Math.round(Audio.volume * 100).toString(); - } + + StyledText { + text: "%" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.normal + opacity: Audio.muted ? 0.5 : 1 } - } - } - StyledText { - text: "%" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - opacity: Audio.muted ? 0.5 : 1 - } + StyledRect { + implicitWidth: implicitHeight + implicitHeight: muteIcon.implicitHeight + Appearance.padding.normal * 2 - StyledRect { - implicitWidth: implicitHeight - implicitHeight: muteIcon.implicitHeight + Appearance.padding.normal * 2 + radius: Appearance.rounding.normal + color: Audio.muted ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer - radius: Appearance.rounding.normal - color: Audio.muted ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer + StateLayer { + function onClicked(): void { + if (Audio.sink?.audio) { + Audio.sink.audio.muted = !Audio.sink.audio.muted; + } + } + } - StateLayer { - function onClicked(): void { - if (Audio.sink?.audio) { - Audio.sink.audio.muted = !Audio.sink.audio.muted; + MaterialIcon { + id: muteIcon + + anchors.centerIn: parent + text: Audio.muted ? "volume_off" : "volume_up" + color: Audio.muted ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer } } } - MaterialIcon { - id: muteIcon + StyledSlider { + id: outputVolumeSlider + Layout.fillWidth: true + implicitHeight: Appearance.padding.normal * 3 - anchors.centerIn: parent - text: Audio.muted ? "volume_off" : "volume_up" - color: Audio.muted ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer + value: Audio.volume + enabled: !Audio.muted + opacity: enabled ? 1 : 0.5 + onMoved: { + Audio.setVolume(value); + if (!outputVolumeInput.activeFocus) { + outputVolumeInput.text = Math.round(value * 100).toString(); + } + } } } } - StyledSlider { - id: outputVolumeSlider - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - value: Audio.volume - enabled: !Audio.muted - opacity: enabled ? 1 : 0.5 - onMoved: { - Audio.setVolume(value); - if (!outputVolumeInput.activeFocus) { - outputVolumeInput.text = Math.round(value * 100).toString(); - } - } + SectionHeader { + title: qsTr("Input volume") + description: qsTr("Control the volume of your input device") } - } - } - SectionHeader { - title: qsTr("Input volume") - description: qsTr("Control the volume of your input device") - } - - SectionContainer { - contentSpacing: Appearance.spacing.normal + SectionContainer { + contentSpacing: Appearance.spacing.normal - ColumnLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.normal - StyledText { - text: qsTr("Volume") - font.pointSize: Appearance.font.size.normal - font.weight: 500 - } + StyledText { + text: qsTr("Volume") + font.pointSize: Appearance.font.size.normal + font.weight: 500 + } - Item { - Layout.fillWidth: true - } + Item { + Layout.fillWidth: true + } - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: inputVolumeInput.implicitHeight + Appearance.padding.small * 2 - color: inputVolumeInputHover.containsMouse || inputVolumeInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: inputVolumeInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - enabled: !Audio.sourceMuted - opacity: enabled ? 1 : 0.5 - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: inputVolumeInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } + StyledRect { + Layout.preferredWidth: 70 + implicitHeight: inputVolumeInput.implicitHeight + Appearance.padding.small * 2 + color: inputVolumeInputHover.containsMouse || inputVolumeInput.activeFocus + ? Colours.layer(Colours.palette.m3surfaceContainer, 3) + : Colours.layer(Colours.palette.m3surfaceContainer, 2) + radius: Appearance.rounding.small + border.width: 1 + border.color: inputVolumeInput.activeFocus + ? Colours.palette.m3primary + : Qt.alpha(Colours.palette.m3outline, 0.3) + enabled: !Audio.sourceMuted + opacity: enabled ? 1 : 0.5 + + Behavior on color { CAnim {} } + Behavior on border.color { CAnim {} } + + MouseArea { + id: inputVolumeInputHover + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.NoButton + } - StyledTextField { - id: inputVolumeInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: IntValidator { bottom: 0; top: 100 } - enabled: !Audio.sourceMuted - - Component.onCompleted: { - text = Math.round(Audio.sourceVolume * 100).toString(); - } - - Connections { - target: Audio - function onSourceVolumeChanged() { - if (!inputVolumeInput.activeFocus) { - inputVolumeInput.text = Math.round(Audio.sourceVolume * 100).toString(); + StyledTextField { + id: inputVolumeInput + anchors.centerIn: parent + width: parent.width - Appearance.padding.normal + horizontalAlignment: TextInput.AlignHCenter + validator: IntValidator { bottom: 0; top: 100 } + enabled: !Audio.sourceMuted + + Component.onCompleted: { + text = Math.round(Audio.sourceVolume * 100).toString(); } - } - } - - onTextChanged: { - if (activeFocus) { - const val = parseInt(text); - if (!isNaN(val) && val >= 0 && val <= 100) { - Audio.setSourceVolume(val / 100); + + Connections { + target: Audio + function onSourceVolumeChanged() { + if (!inputVolumeInput.activeFocus) { + inputVolumeInput.text = Math.round(Audio.sourceVolume * 100).toString(); + } + } + } + + onTextChanged: { + if (activeFocus) { + const val = parseInt(text); + if (!isNaN(val) && val >= 0 && val <= 100) { + Audio.setSourceVolume(val / 100); + } + } + } + onEditingFinished: { + const val = parseInt(text); + if (isNaN(val) || val < 0 || val > 100) { + text = Math.round(Audio.sourceVolume * 100).toString(); + } } } } - onEditingFinished: { - const val = parseInt(text); - if (isNaN(val) || val < 0 || val > 100) { - text = Math.round(Audio.sourceVolume * 100).toString(); - } + + StyledText { + text: "%" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.normal + opacity: Audio.sourceMuted ? 0.5 : 1 } - } - } - StyledText { - text: "%" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - opacity: Audio.sourceMuted ? 0.5 : 1 - } + StyledRect { + implicitWidth: implicitHeight + implicitHeight: muteInputIcon.implicitHeight + Appearance.padding.normal * 2 - StyledRect { - implicitWidth: implicitHeight - implicitHeight: muteInputIcon.implicitHeight + Appearance.padding.normal * 2 + radius: Appearance.rounding.normal + color: Audio.sourceMuted ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer - radius: Appearance.rounding.normal - color: Audio.sourceMuted ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer + StateLayer { + function onClicked(): void { + if (Audio.source?.audio) { + Audio.source.audio.muted = !Audio.source.audio.muted; + } + } + } - StateLayer { - function onClicked(): void { - if (Audio.source?.audio) { - Audio.source.audio.muted = !Audio.source.audio.muted; + MaterialIcon { + id: muteInputIcon + + anchors.centerIn: parent + text: "mic_off" + color: Audio.sourceMuted ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer } } } - MaterialIcon { - id: muteInputIcon + StyledSlider { + id: inputVolumeSlider + Layout.fillWidth: true + implicitHeight: Appearance.padding.normal * 3 - anchors.centerIn: parent - text: "mic_off" - color: Audio.sourceMuted ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer - } - } - } - - StyledSlider { - id: inputVolumeSlider - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - value: Audio.sourceVolume - enabled: !Audio.sourceMuted - opacity: enabled ? 1 : 0.5 - onMoved: { - Audio.setSourceVolume(value); - if (!inputVolumeInput.activeFocus) { - inputVolumeInput.text = Math.round(value * 100).toString(); + value: Audio.sourceVolume + enabled: !Audio.sourceMuted + opacity: enabled ? 1 : 0.5 + onMoved: { + Audio.setSourceVolume(value); + if (!inputVolumeInput.activeFocus) { + inputVolumeInput.text = Math.round(value * 100).toString(); + } + } } } } } } } - } } } } \ No newline at end of file diff --git a/modules/controlcenter/bluetooth/BtPane.qml b/modules/controlcenter/bluetooth/BtPane.qml index 8ad4b1f..cacb611 100644 --- a/modules/controlcenter/bluetooth/BtPane.qml +++ b/modules/controlcenter/bluetooth/BtPane.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components.controls import qs.components.effects import qs.components.containers @@ -10,95 +11,50 @@ import Quickshell.Bluetooth import QtQuick import QtQuick.Layouts -RowLayout { +Item { id: root required property Session session anchors.fill: parent - spacing: 0 - - Item { - id: leftBtItem - Layout.preferredWidth: Math.floor(parent.width * 0.4) - Layout.minimumWidth: 420 - Layout.fillHeight: true - - ClippingRectangle { - id: leftBtClippingRect - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: leftBtBorder.innerRadius - color: "transparent" - - Loader { - id: leftBtLoader - - anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - - asynchronous: true - sourceComponent: btDeviceListComponent - } - } - - InnerBorder { - id: leftBtBorder - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 - } - - Component { - id: btDeviceListComponent + SplitPaneLayout { + anchors.fill: parent + leftContent: Component { DeviceList { anchors.fill: parent session: root.session } } - } - Item { - id: rightBtItem - Layout.fillWidth: true - Layout.fillHeight: true - - ClippingRectangle { - id: btClippingRect - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: rightBorder.innerRadius - color: "transparent" - - Loader { - id: loader + rightContent: Component { + Item { + id: rightBtPane property BluetoothDevice pane: root.session.bt.active - anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 + Loader { + id: rightLoader - asynchronous: true - sourceComponent: pane ? details : settings + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + asynchronous: true + sourceComponent: rightBtPane.pane ? details : settings + } Behavior on pane { SequentialAnimation { ParallelAnimation { Anim { + target: rightLoader property: "opacity" to: 0 easing.bezierCurve: Appearance.anim.curves.standardAccel } Anim { + target: rightLoader property: "scale" to: 0.8 easing.bezierCurve: Appearance.anim.curves.standardAccel @@ -107,11 +63,13 @@ RowLayout { PropertyAction {} ParallelAnimation { Anim { + target: rightLoader property: "opacity" to: 1 easing.bezierCurve: Appearance.anim.curves.standardDecel } Anim { + target: rightLoader property: "scale" to: 1 easing.bezierCurve: Appearance.anim.curves.standardDecel @@ -119,49 +77,49 @@ RowLayout { } } } - } - } - InnerBorder { - id: rightBorder - - leftThickness: Appearance.padding.normal / 2 + Connections { + target: root.session.bt + function onActiveChanged() { + rightBtPane.pane = root.session.bt.active; + } + } + } } + } - Component { - id: settings + Component { + id: settings - StyledFlickable { - id: settingsFlickable - flickableDirection: Flickable.VerticalFlick - contentHeight: settingsInner.height + StyledFlickable { + id: settingsFlickable + flickableDirection: Flickable.VerticalFlick + contentHeight: settingsInner.height - StyledScrollBar.vertical: StyledScrollBar { - flickable: settingsFlickable - } + StyledScrollBar.vertical: StyledScrollBar { + flickable: settingsFlickable + } - Settings { - id: settingsInner + Settings { + id: settingsInner - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - session: root.session - } + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + session: root.session } } + } - Component { - id: details + Component { + id: details - Details { - session: root.session - } + Details { + session: root.session } } component Anim: NumberAnimation { - target: loader duration: Appearance.anim.durations.normal / 2 easing.type: Easing.BezierSpline } diff --git a/modules/controlcenter/components/SplitPaneLayout.qml b/modules/controlcenter/components/SplitPaneLayout.qml new file mode 100644 index 0000000..7bd7db0 --- /dev/null +++ b/modules/controlcenter/components/SplitPaneLayout.qml @@ -0,0 +1,120 @@ +pragma ComponentBehavior: Bound + +import qs.components +import qs.components.effects +import qs.config +import Quickshell.Widgets +import QtQuick +import QtQuick.Layouts + +RowLayout { + id: root + + spacing: 0 + + property Component leftContent: null + property Component rightContent: null + + // Left pane configuration + property real leftWidthRatio: 0.4 + property int leftMinimumWidth: 420 + property var leftLoaderProperties: ({}) + + // Right pane configuration + property var rightLoaderProperties: ({}) + + // Expose loaders for customization (access via splitLayout.leftLoader or splitLayout.rightLoader) + property alias leftLoader: leftLoader + property alias rightLoader: rightLoader + + // Left pane + Item { + id: leftPane + + Layout.preferredWidth: Math.floor(parent.width * root.leftWidthRatio) + Layout.minimumWidth: root.leftMinimumWidth + Layout.fillHeight: true + + ClippingRectangle { + id: leftClippingRect + + anchors.fill: parent + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 + + radius: leftBorder.innerRadius + color: "transparent" + + Loader { + id: leftLoader + + anchors.fill: parent + anchors.margins: Appearance.padding.large + Appearance.padding.normal + anchors.leftMargin: Appearance.padding.large + anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + + asynchronous: true + sourceComponent: root.leftContent + + // Apply any additional properties from leftLoaderProperties + Component.onCompleted: { + for (const key in root.leftLoaderProperties) { + leftLoader[key] = root.leftLoaderProperties[key]; + } + } + } + } + + InnerBorder { + id: leftBorder + + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + } + + // Right pane + Item { + id: rightPane + + Layout.fillWidth: true + Layout.fillHeight: true + + ClippingRectangle { + id: rightClippingRect + + anchors.fill: parent + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 + + radius: rightBorder.innerRadius + color: "transparent" + + Loader { + id: rightLoader + + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + asynchronous: true + sourceComponent: root.rightContent + + // Apply any additional properties from rightLoaderProperties + Component.onCompleted: { + for (const key in root.rightLoaderProperties) { + rightLoader[key] = root.rightLoaderProperties[key]; + } + } + } + } + + InnerBorder { + id: rightBorder + + leftThickness: Appearance.padding.normal / 2 + } + } +} + diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index f2247a7..30e2953 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import "../../launcher/services" import qs.components import qs.components.controls @@ -16,7 +17,7 @@ import QtQuick import QtQuick.Layouts import "../../../utils/scripts/fuzzysort.js" as Fuzzy -RowLayout { +Item { id: root required property Session session @@ -26,8 +27,6 @@ RowLayout { anchors.fill: parent - spacing: 0 - onSelectedAppChanged: { root.session.launcher.active = root.selectedApp; updateToggleState(); @@ -156,43 +155,10 @@ RowLayout { } } - Item { - id: leftLauncherItem - Layout.preferredWidth: Math.floor(parent.width * 0.4) - Layout.minimumWidth: 420 - Layout.fillHeight: true - - ClippingRectangle { - id: leftLauncherClippingRect - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: leftLauncherBorder.innerRadius - color: "transparent" - - Loader { - id: leftLauncherLoader - - anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - - asynchronous: true - sourceComponent: leftContentComponent - } - } - - InnerBorder { - id: leftLauncherBorder - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 - } + SplitPaneLayout { + anchors.fill: parent - Component { - id: leftContentComponent + leftContent: Component { ColumnLayout { id: leftLauncherLayout @@ -336,7 +302,8 @@ RowLayout { // Lazy load: activate when left pane is loaded // The ListView will load asynchronously, and search will work because filteredApps // is updated regardless of whether the ListView is loaded - return leftLauncherLoader.item !== null; + // Access loader through parent - this will be set when component loads + return true; } sourceComponent: StyledListView { @@ -412,25 +379,10 @@ RowLayout { } } } - } - - Item { - id: rightLauncherItem - Layout.fillWidth: true - Layout.fillHeight: true - - ClippingRectangle { - id: rightLauncherClippingRect - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - radius: rightLauncherBorder.innerRadius - color: "transparent" - - Loader { - id: rightLauncherLoader + rightContent: Component { + Item { + id: rightLauncherPane property var pane: root.session.launcher.active property string paneId: pane ? (pane.id || pane.entry?.id || "") : "" @@ -442,28 +394,34 @@ RowLayout { return pane ? appDetails : settings; } - anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 - - opacity: 1 - scale: 1 - transformOrigin: Item.Center - clip: false - - asynchronous: true - sourceComponent: rightLauncherLoader.targetComponent - active: true - Component.onCompleted: { displayedApp = pane; targetComponent = getComponentForPane(); nextComponent = targetComponent; } - onItemChanged: { - // Ensure displayedApp is set when item is created (for async loading) - if (item && pane && displayedApp !== pane) { - displayedApp = pane; + Loader { + id: rightLauncherLoader + + anchors.fill: parent + + opacity: 1 + scale: 1 + transformOrigin: Item.Center + clip: false + + asynchronous: true + sourceComponent: rightLauncherPane.targetComponent + active: true + + // Expose displayedApp to loaded components + property var displayedApp: rightLauncherPane.displayedApp + + onItemChanged: { + // Ensure displayedApp is set when item is created (for async loading) + if (item && rightLauncherPane.pane && rightLauncherPane.displayedApp !== rightLauncherPane.pane) { + rightLauncherPane.displayedApp = rightLauncherPane.pane; + } } } @@ -484,9 +442,9 @@ RowLayout { } } PropertyAction { - target: rightLauncherLoader + target: rightLauncherPane property: "displayedApp" - value: rightLauncherLoader.pane + value: rightLauncherPane.pane } PropertyAction { target: rightLauncherLoader @@ -494,9 +452,9 @@ RowLayout { value: false } PropertyAction { - target: rightLauncherLoader + target: rightLauncherPane property: "targetComponent" - value: rightLauncherLoader.nextComponent + value: rightLauncherPane.nextComponent } PropertyAction { target: rightLauncherLoader @@ -539,90 +497,90 @@ RowLayout { } } } + } - InnerBorder { - id: rightLauncherBorder - - leftThickness: Appearance.padding.normal / 2 - } - - Component { - id: settings + Component { + id: settings - StyledFlickable { - id: settingsFlickable - flickableDirection: Flickable.VerticalFlick - contentHeight: settingsInner.height + StyledFlickable { + id: settingsFlickable + flickableDirection: Flickable.VerticalFlick + contentHeight: settingsInner.height - StyledScrollBar.vertical: StyledScrollBar { - flickable: settingsFlickable - } + StyledScrollBar.vertical: StyledScrollBar { + flickable: settingsFlickable + } - Settings { - id: settingsInner + Settings { + id: settingsInner - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - session: root.session - } + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + session: root.session } } + } - Component { - id: appDetails + Component { + id: appDetails - ColumnLayout { - anchors.fill: parent + ColumnLayout { + id: appDetailsLayout + anchors.fill: parent - spacing: Appearance.spacing.normal + // Get displayedApp from parent Loader (the Loader has displayedApp property we set) + readonly property var displayedApp: parent && parent.displayedApp !== undefined ? parent.displayedApp : null - Item { - Layout.alignment: Qt.AlignHCenter - Layout.leftMargin: Appearance.padding.large * 2 - Layout.rightMargin: Appearance.padding.large * 2 - Layout.topMargin: Appearance.padding.large * 2 - implicitWidth: iconLoader.implicitWidth - implicitHeight: iconLoader.implicitHeight + spacing: Appearance.spacing.normal - Loader { - id: iconLoader - sourceComponent: rightLauncherLoader.displayedApp ? appIconComponent : defaultIconComponent - } + Item { + Layout.alignment: Qt.AlignHCenter + Layout.leftMargin: Appearance.padding.large * 2 + Layout.rightMargin: Appearance.padding.large * 2 + Layout.topMargin: Appearance.padding.large * 2 + implicitWidth: iconLoader.implicitWidth + implicitHeight: iconLoader.implicitHeight - Component { - id: appIconComponent - IconImage { - implicitSize: Appearance.font.size.extraLarge * 3 * 2 - source: { - if (!rightLauncherLoader.displayedApp) return "image-missing"; - const entry = rightLauncherLoader.displayedApp.entry; - if (entry && entry.icon) { - return Quickshell.iconPath(entry.icon, "image-missing"); - } - return "image-missing"; + Loader { + id: iconLoader + sourceComponent: parent.parent.displayedApp ? appIconComponent : defaultIconComponent + } + + Component { + id: appIconComponent + IconImage { + implicitSize: Appearance.font.size.extraLarge * 3 * 2 + source: { + const app = iconLoader.parent.parent.displayedApp; + if (!app) return "image-missing"; + const entry = app.entry; + if (entry && entry.icon) { + return Quickshell.iconPath(entry.icon, "image-missing"); } + return "image-missing"; } } + } - Component { - id: defaultIconComponent - MaterialIcon { - text: "apps" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } + Component { + id: defaultIconComponent + MaterialIcon { + text: "apps" + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true } } + } - StyledText { - Layout.alignment: Qt.AlignHCenter - Layout.leftMargin: Appearance.padding.large * 2 - Layout.rightMargin: Appearance.padding.large * 2 - text: rightLauncherLoader.displayedApp ? (rightLauncherLoader.displayedApp.name || rightLauncherLoader.displayedApp.entry?.name || qsTr("Application Details")) : qsTr("Launcher Applications") - font.pointSize: Appearance.font.size.large - font.bold: true - } + StyledText { + Layout.alignment: Qt.AlignHCenter + Layout.leftMargin: Appearance.padding.large * 2 + Layout.rightMargin: Appearance.padding.large * 2 + text: displayedApp ? (displayedApp.name || displayedApp.entry?.name || qsTr("Application Details")) : qsTr("Launcher Applications") + font.pointSize: Appearance.font.size.large + font.bold: true + } Item { Layout.fillWidth: true @@ -648,38 +606,38 @@ RowLayout { anchors.top: parent.top spacing: Appearance.spacing.normal - SwitchRow { - Layout.topMargin: Appearance.spacing.normal - visible: rightLauncherLoader.displayedApp !== null - label: qsTr("Hide from launcher") - checked: root.hideFromLauncherChecked - enabled: rightLauncherLoader.displayedApp !== null - onToggled: checked => { - root.hideFromLauncherChecked = checked; - if (rightLauncherLoader.displayedApp) { - const appId = rightLauncherLoader.displayedApp.id || rightLauncherLoader.displayedApp.entry?.id; - const hiddenApps = Config.launcher.hiddenApps ? [...Config.launcher.hiddenApps] : []; - if (checked) { - if (!hiddenApps.includes(appId)) { - hiddenApps.push(appId); - } - } else { - const index = hiddenApps.indexOf(appId); - if (index !== -1) { - hiddenApps.splice(index, 1); - } + SwitchRow { + Layout.topMargin: Appearance.spacing.normal + visible: appDetailsLayout.displayedApp !== null + label: qsTr("Hide from launcher") + checked: root.hideFromLauncherChecked + enabled: appDetailsLayout.displayedApp !== null + onToggled: checked => { + root.hideFromLauncherChecked = checked; + const app = appDetailsLayout.displayedApp; + if (app) { + const appId = app.id || app.entry?.id; + const hiddenApps = Config.launcher.hiddenApps ? [...Config.launcher.hiddenApps] : []; + if (checked) { + if (!hiddenApps.includes(appId)) { + hiddenApps.push(appId); + } + } else { + const index = hiddenApps.indexOf(appId); + if (index !== -1) { + hiddenApps.splice(index, 1); } - Config.launcher.hiddenApps = hiddenApps; - Config.save(); } + Config.launcher.hiddenApps = hiddenApps; + Config.save(); } } + } } } } } - } } component Anim: NumberAnimation { diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index d0ea852..55c70d2 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import "." import qs.components import qs.components.controls @@ -21,49 +22,12 @@ Item { anchors.fill: parent - RowLayout { - id: contentLayout + SplitPaneLayout { + id: splitLayout anchors.fill: parent - spacing: 0 - Item { - id: leftNetworkItem - Layout.preferredWidth: Math.floor(parent.width * 0.4) - Layout.minimumWidth: 420 - Layout.fillHeight: true - - ClippingRectangle { - id: leftNetworkClippingRect - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: leftNetworkBorder.innerRadius - color: "transparent" - - Loader { - id: leftNetworkLoader - - anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - - asynchronous: true - sourceComponent: networkListComponent - } - } - - InnerBorder { - id: leftNetworkBorder - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 - } - - Component { - id: networkListComponent + leftContent: Component { StyledFlickable { id: leftFlickable @@ -473,38 +437,47 @@ Item { } } } - } } - Item { - id: rightNetworkItem - Layout.fillWidth: true - Layout.fillHeight: true - - ClippingRectangle { - id: networkClippingRect - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: rightBorder.innerRadius - color: "transparent" - + rightContent: Component { + Item { + id: rightPaneItem + // Right pane - networking details/settings - Loader { - id: loader + property var ethernetPane: root.session.ethernet.active + property var wirelessPane: root.session.network.active + property var pane: ethernetPane || wirelessPane + property string paneId: ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : "") + property Component targetComponent: settings + property Component nextComponent: settings + + function getComponentForPane() { + return pane ? (ethernetPane ? ethernetDetails : wirelessDetails) : settings; + } - property var ethernetPane: root.session.ethernet.active - property var wirelessPane: root.session.network.active - property var pane: ethernetPane || wirelessPane - property string paneId: ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : "") - property Component targetComponent: settings - property Component nextComponent: settings + Component.onCompleted: { + targetComponent = getComponentForPane(); + nextComponent = targetComponent; + } - function getComponentForPane() { - return pane ? (ethernetPane ? ethernetDetails : wirelessDetails) : settings; + Connections { + target: root.session.ethernet + function onActiveChanged() { + nextComponent = getComponentForPane(); + paneId = ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : ""); + } + } + + Connections { + target: root.session.network + function onActiveChanged() { + nextComponent = getComponentForPane(); + paneId = ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : ""); } + } + + Loader { + id: rightLoader anchors.fill: parent anchors.margins: Appearance.padding.large * 2 @@ -515,131 +488,129 @@ Item { clip: false asynchronous: true - sourceComponent: loader.targetComponent + sourceComponent: rightPaneItem.targetComponent - Component.onCompleted: { - targetComponent = getComponentForPane(); - nextComponent = targetComponent; + Connections { + target: rightPaneItem + function onPaneIdChanged() { + rightPaneItem.targetComponent = rightPaneItem.nextComponent; + } } + } - Behavior on paneId { - SequentialAnimation { - ParallelAnimation { - Anim { - target: loader - property: "opacity" - to: 0 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - Anim { - target: loader - property: "scale" - to: 0.8 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } + Behavior on paneId { + SequentialAnimation { + ParallelAnimation { + Anim { + target: rightLoader + property: "opacity" + to: 0 + easing.bezierCurve: Appearance.anim.curves.standardAccel } - PropertyAction { - target: loader - property: "targetComponent" - value: loader.nextComponent + Anim { + target: rightLoader + property: "scale" + to: 0.8 + easing.bezierCurve: Appearance.anim.curves.standardAccel } - ParallelAnimation { - Anim { - target: loader - property: "opacity" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - Anim { - target: loader - property: "scale" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } + } + PropertyAction { + target: rightPaneItem + property: "targetComponent" + value: rightPaneItem.nextComponent + } + ParallelAnimation { + Anim { + target: rightLoader + property: "opacity" + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + Anim { + target: rightLoader + property: "scale" + to: 1 + easing.bezierCurve: Appearance.anim.curves.standardDecel } } } + } - onPaneChanged: { - nextComponent = getComponentForPane(); - paneId = ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : ""); + Connections { + target: rightPaneItem + function onPaneIdChanged() { + rightPaneItem.targetComponent = rightPaneItem.nextComponent; } } } + } + } - InnerBorder { - id: rightBorder - - leftThickness: Appearance.padding.normal / 2 - } - - Component { - id: settings + Component { + id: settings - StyledFlickable { - id: settingsFlickable - flickableDirection: Flickable.VerticalFlick - contentHeight: settingsInner.height + StyledFlickable { + id: settingsFlickable + flickableDirection: Flickable.VerticalFlick + contentHeight: settingsInner.height - StyledScrollBar.vertical: StyledScrollBar { - flickable: settingsFlickable - } + StyledScrollBar.vertical: StyledScrollBar { + flickable: settingsFlickable + } - NetworkSettings { - id: settingsInner + NetworkSettings { + id: settingsInner - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - session: root.session - } - } + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + session: root.session } + } + } - Component { - id: ethernetDetails + Component { + id: ethernetDetails - StyledFlickable { - id: ethernetFlickable - flickableDirection: Flickable.VerticalFlick - contentHeight: ethernetDetailsInner.height + StyledFlickable { + id: ethernetFlickable + flickableDirection: Flickable.VerticalFlick + contentHeight: ethernetDetailsInner.height - StyledScrollBar.vertical: StyledScrollBar { - flickable: ethernetFlickable - } + StyledScrollBar.vertical: StyledScrollBar { + flickable: ethernetFlickable + } - EthernetDetails { - id: ethernetDetailsInner + EthernetDetails { + id: ethernetDetailsInner - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - session: root.session - } - } + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + session: root.session } + } + } - Component { - id: wirelessDetails + Component { + id: wirelessDetails - StyledFlickable { - id: wirelessFlickable - flickableDirection: Flickable.VerticalFlick - contentHeight: wirelessDetailsInner.height + StyledFlickable { + id: wirelessFlickable + flickableDirection: Flickable.VerticalFlick + contentHeight: wirelessDetailsInner.height - StyledScrollBar.vertical: StyledScrollBar { - flickable: wirelessFlickable - } + StyledScrollBar.vertical: StyledScrollBar { + flickable: wirelessFlickable + } - WirelessDetails { - id: wirelessDetailsInner + WirelessDetails { + id: wirelessDetailsInner - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - session: root.session - } - } + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + session: root.session } } } @@ -651,7 +622,6 @@ Item { } component Anim: NumberAnimation { - target: loader duration: Appearance.anim.durations.normal / 2 easing.type: Easing.BezierSpline } -- cgit v1.2.3-freya From aff01f01a859f0d959c76d67f346b34a714cb971 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 19 Nov 2025 13:10:09 -0500 Subject: refactor: PaneTransition now component --- modules/controlcenter/bluetooth/BtPane.qml | 60 +++++++--------- .../controlcenter/components/PaneTransition.qml | 84 ++++++++++++++++++++++ modules/controlcenter/launcher/LauncherPane.qml | 73 ++++++------------- modules/controlcenter/network/EthernetPane.qml | 45 ++---------- modules/controlcenter/network/NetworkingPane.qml | 70 ++++-------------- modules/controlcenter/network/WirelessPane.qml | 45 ++---------- 6 files changed, 156 insertions(+), 221 deletions(-) create mode 100644 modules/controlcenter/components/PaneTransition.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/bluetooth/BtPane.qml b/modules/controlcenter/bluetooth/BtPane.qml index cacb611..9d0a4c1 100644 --- a/modules/controlcenter/bluetooth/BtPane.qml +++ b/modules/controlcenter/bluetooth/BtPane.qml @@ -33,6 +33,18 @@ Item { id: rightBtPane property BluetoothDevice pane: root.session.bt.active + property string paneId: pane ? (pane.address || "") : "" + property Component targetComponent: settings + property Component nextComponent: settings + + function getComponentForPane() { + return pane ? details : settings; + } + + Component.onCompleted: { + targetComponent = getComponentForPane(); + nextComponent = targetComponent; + } Loader { id: rightLoader @@ -41,40 +53,19 @@ Item { anchors.margins: Appearance.padding.large * 2 asynchronous: true - sourceComponent: rightBtPane.pane ? details : settings + sourceComponent: rightBtPane.targetComponent } - Behavior on pane { - SequentialAnimation { - ParallelAnimation { - Anim { - target: rightLoader - property: "opacity" - to: 0 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - Anim { - target: rightLoader - property: "scale" - to: 0.8 - easing.bezierCurve: Appearance.anim.curves.standardAccel + Behavior on paneId { + PaneTransition { + target: rightLoader + propertyActions: [ + PropertyAction { + target: rightBtPane + property: "targetComponent" + value: rightBtPane.nextComponent } - } - PropertyAction {} - ParallelAnimation { - Anim { - target: rightLoader - property: "opacity" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - Anim { - target: rightLoader - property: "scale" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - } + ] } } @@ -82,6 +73,8 @@ Item { target: root.session.bt function onActiveChanged() { rightBtPane.pane = root.session.bt.active; + rightBtPane.nextComponent = rightBtPane.getComponentForPane(); + rightBtPane.paneId = pane ? (pane.address || "") : ""; } } } @@ -118,9 +111,4 @@ Item { session: root.session } } - - component Anim: NumberAnimation { - duration: Appearance.anim.durations.normal / 2 - easing.type: Easing.BezierSpline - } } diff --git a/modules/controlcenter/components/PaneTransition.qml b/modules/controlcenter/components/PaneTransition.qml new file mode 100644 index 0000000..1da4afb --- /dev/null +++ b/modules/controlcenter/components/PaneTransition.qml @@ -0,0 +1,84 @@ +pragma ComponentBehavior: Bound + +import qs.config +import QtQuick + +// Reusable pane transition animation component +// Provides standard fade-out/scale-down → update → fade-in/scale-up animation +// Used when switching between detail/settings views in panes +SequentialAnimation { + id: root + + // The Loader element to animate + required property Item target + + // Optional list of PropertyActions to execute during the transition + // These typically update the component being displayed + property list propertyActions + + // Animation parameters (with sensible defaults) + property real scaleFrom: 1.0 + property real scaleTo: 0.8 + property real opacityFrom: 1.0 + property real opacityTo: 0.0 + + // Fade out and scale down + ParallelAnimation { + NumberAnimation { + target: root.target + property: "opacity" + from: root.opacityFrom + to: root.opacityTo + duration: Appearance.anim.durations.normal / 2 + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + + NumberAnimation { + target: root.target + property: "scale" + from: root.scaleFrom + to: root.scaleTo + duration: Appearance.anim.durations.normal / 2 + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + } + + // Execute property actions (component switching, state updates, etc.) + // This is where the component change happens while invisible + ScriptAction { + script: { + for (let i = 0; i < root.propertyActions.length; i++) { + const action = root.propertyActions[i]; + if (action.target && action.property !== undefined) { + action.target[action.property] = action.value; + } + } + } + } + + // Fade in and scale up + ParallelAnimation { + NumberAnimation { + target: root.target + property: "opacity" + from: root.opacityTo + to: root.opacityFrom + duration: Appearance.anim.durations.normal / 2 + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + + NumberAnimation { + target: root.target + property: "scale" + from: root.scaleTo + to: root.scaleFrom + duration: Appearance.anim.durations.normal / 2 + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + } +} + diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index 30e2953..a8c5c76 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -426,55 +426,30 @@ Item { } Behavior on paneId { - SequentialAnimation { - ParallelAnimation { - Anim { + PaneTransition { + target: rightLauncherLoader + propertyActions: [ + PropertyAction { + target: rightLauncherPane + property: "displayedApp" + value: rightLauncherPane.pane + }, + PropertyAction { target: rightLauncherLoader - property: "opacity" - to: 0 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - Anim { + property: "active" + value: false + }, + PropertyAction { + target: rightLauncherPane + property: "targetComponent" + value: rightLauncherPane.nextComponent + }, + PropertyAction { target: rightLauncherLoader - property: "scale" - to: 0.8 - easing.bezierCurve: Appearance.anim.curves.standardAccel + property: "active" + value: true } - } - PropertyAction { - target: rightLauncherPane - property: "displayedApp" - value: rightLauncherPane.pane - } - PropertyAction { - target: rightLauncherLoader - property: "active" - value: false - } - PropertyAction { - target: rightLauncherPane - property: "targetComponent" - value: rightLauncherPane.nextComponent - } - PropertyAction { - target: rightLauncherLoader - property: "active" - value: true - } - ParallelAnimation { - Anim { - target: rightLauncherLoader - property: "opacity" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - Anim { - target: rightLauncherLoader - property: "scale" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - } + ] } } @@ -639,10 +614,4 @@ Item { } } } - - component Anim: NumberAnimation { - target: rightLauncherLoader - duration: Appearance.anim.durations.normal / 2 - easing.type: Easing.BezierSpline - } } diff --git a/modules/controlcenter/network/EthernetPane.qml b/modules/controlcenter/network/EthernetPane.qml index 6a50cde..fc979c3 100644 --- a/modules/controlcenter/network/EthernetPane.qml +++ b/modules/controlcenter/network/EthernetPane.qml @@ -77,40 +77,15 @@ RowLayout { } Behavior on paneId { - SequentialAnimation { - ParallelAnimation { - Anim { + PaneTransition { + target: loader + propertyActions: [ + PropertyAction { target: loader - property: "opacity" - to: 0 - easing.bezierCurve: Appearance.anim.curves.standardAccel + property: "targetComponent" + value: loader.nextComponent } - Anim { - target: loader - property: "scale" - to: 0.8 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - } - PropertyAction { - target: loader - property: "targetComponent" - value: loader.nextComponent - } - ParallelAnimation { - Anim { - target: loader - property: "opacity" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - Anim { - target: loader - property: "scale" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - } + ] } } @@ -153,10 +128,4 @@ RowLayout { } } } - - component Anim: NumberAnimation { - target: loader - duration: Appearance.anim.durations.normal / 2 - easing.type: Easing.BezierSpline - } } \ No newline at end of file diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 55c70d2..127a42a 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -447,7 +447,7 @@ Item { property var ethernetPane: root.session.ethernet.active property var wirelessPane: root.session.network.active property var pane: ethernetPane || wirelessPane - property string paneId: ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : "") + property string paneId: ethernetPane ? ("eth:" + (ethernetPane.interface || "")) : (wirelessPane ? ("wifi:" + (wirelessPane.ssid || wirelessPane.bssid || "")) : "settings") property Component targetComponent: settings property Component nextComponent: settings @@ -463,16 +463,16 @@ Item { Connections { target: root.session.ethernet function onActiveChanged() { - nextComponent = getComponentForPane(); - paneId = ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : ""); + rightPaneItem.nextComponent = rightPaneItem.getComponentForPane(); + rightPaneItem.paneId = rightPaneItem.ethernetPane ? ("eth:" + (rightPaneItem.ethernetPane.interface || "")) : (rightPaneItem.wirelessPane ? ("wifi:" + (rightPaneItem.wirelessPane.ssid || rightPaneItem.wirelessPane.bssid || "")) : "settings"); } } Connections { target: root.session.network function onActiveChanged() { - nextComponent = getComponentForPane(); - paneId = ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : ""); + rightPaneItem.nextComponent = rightPaneItem.getComponentForPane(); + rightPaneItem.paneId = rightPaneItem.ethernetPane ? ("eth:" + (rightPaneItem.ethernetPane.interface || "")) : (rightPaneItem.wirelessPane ? ("wifi:" + (rightPaneItem.wirelessPane.ssid || rightPaneItem.wirelessPane.bssid || "")) : "settings"); } } @@ -489,57 +489,18 @@ Item { asynchronous: true sourceComponent: rightPaneItem.targetComponent - - Connections { - target: rightPaneItem - function onPaneIdChanged() { - rightPaneItem.targetComponent = rightPaneItem.nextComponent; - } - } } Behavior on paneId { - SequentialAnimation { - ParallelAnimation { - Anim { - target: rightLoader - property: "opacity" - to: 0 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - Anim { - target: rightLoader - property: "scale" - to: 0.8 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - } - PropertyAction { - target: rightPaneItem - property: "targetComponent" - value: rightPaneItem.nextComponent - } - ParallelAnimation { - Anim { - target: rightLoader - property: "opacity" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - Anim { - target: rightLoader - property: "scale" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel + PaneTransition { + target: rightLoader + propertyActions: [ + PropertyAction { + target: rightPaneItem + property: "targetComponent" + value: rightPaneItem.nextComponent } - } - } - } - - Connections { - target: rightPaneItem - function onPaneIdChanged() { - rightPaneItem.targetComponent = rightPaneItem.nextComponent; + ] } } } @@ -621,11 +582,6 @@ Item { z: 1000 } - component Anim: NumberAnimation { - duration: Appearance.anim.durations.normal / 2 - easing.type: Easing.BezierSpline - } - function checkSavedProfileForNetwork(ssid: string): void { if (ssid && ssid.length > 0) { Nmcli.loadSavedConnections(() => {}); diff --git a/modules/controlcenter/network/WirelessPane.qml b/modules/controlcenter/network/WirelessPane.qml index 22364a1..0c8f0ae 100644 --- a/modules/controlcenter/network/WirelessPane.qml +++ b/modules/controlcenter/network/WirelessPane.qml @@ -76,40 +76,15 @@ RowLayout { } Behavior on paneId { - SequentialAnimation { - ParallelAnimation { - Anim { + PaneTransition { + target: loader + propertyActions: [ + PropertyAction { target: loader - property: "opacity" - to: 0 - easing.bezierCurve: Appearance.anim.curves.standardAccel + property: "targetComponent" + value: loader.nextComponent } - Anim { - target: loader - property: "scale" - to: 0.8 - easing.bezierCurve: Appearance.anim.curves.standardAccel - } - } - PropertyAction { - target: loader - property: "targetComponent" - value: loader.nextComponent - } - ParallelAnimation { - Anim { - target: loader - property: "opacity" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - Anim { - target: loader - property: "scale" - to: 1 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } - } + ] } } @@ -158,10 +133,4 @@ RowLayout { session: root.session z: 1000 } - - component Anim: NumberAnimation { - target: loader - duration: Appearance.anim.durations.normal / 2 - easing.type: Easing.BezierSpline - } } \ No newline at end of file -- cgit v1.2.3-freya From d3ecbc1b647ee7cf074a8f06384d68168657d9c6 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 19 Nov 2025 13:21:18 -0500 Subject: controlcenter: correcting margins/padding after refactor --- modules/controlcenter/audio/AudioPane.qml | 40 +++++++++--------------- modules/controlcenter/bluetooth/BtPane.qml | 1 - modules/controlcenter/network/NetworkingPane.qml | 1 - 3 files changed, 15 insertions(+), 27 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index dc3ba56..02cda5b 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -216,31 +216,22 @@ Item { } rightContent: Component { - Item { - anchors.fill: parent - anchors.topMargin: Appearance.padding.large * 2 - anchors.bottomMargin: Appearance.padding.large * 2 - anchors.leftMargin: 0 - anchors.rightMargin: 0 - - StyledFlickable { - id: rightAudioFlickable - anchors.fill: parent - flickableDirection: Flickable.VerticalFlick - contentHeight: contentLayout.height - - StyledScrollBar.vertical: StyledScrollBar { - flickable: rightAudioFlickable - } + StyledFlickable { + id: rightAudioFlickable + flickableDirection: Flickable.VerticalFlick + contentHeight: contentLayout.height - ColumnLayout { - id: contentLayout + StyledScrollBar.vertical: StyledScrollBar { + flickable: rightAudioFlickable + } - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: Appearance.padding.large * 2 - anchors.rightMargin: Appearance.padding.large * 2 - spacing: Appearance.spacing.normal + ColumnLayout { + id: contentLayout + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: Appearance.spacing.normal ConnectionHeader { icon: "volume_up" @@ -528,5 +519,4 @@ Item { } } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/modules/controlcenter/bluetooth/BtPane.qml b/modules/controlcenter/bluetooth/BtPane.qml index 9d0a4c1..c20f14b 100644 --- a/modules/controlcenter/bluetooth/BtPane.qml +++ b/modules/controlcenter/bluetooth/BtPane.qml @@ -50,7 +50,6 @@ Item { id: rightLoader anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 asynchronous: true sourceComponent: rightBtPane.targetComponent diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 127a42a..9a7a4e1 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -480,7 +480,6 @@ Item { id: rightLoader anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 opacity: 1 scale: 1 -- cgit v1.2.3-freya From e8fc13630c2fb67d75325e72ba66a811d3c1f4c9 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 19 Nov 2025 17:20:00 -0500 Subject: refactor: SettingsHeader on all panels --- .../controlcenter/appearance/AppearancePane.qml | 16 +---- modules/controlcenter/audio/AudioPane.qml | 2 +- modules/controlcenter/bluetooth/Details.qml | 18 ++--- modules/controlcenter/bluetooth/Settings.qml | 16 ++--- .../controlcenter/components/SettingsHeader.qml | 59 ++++++++++++++++ modules/controlcenter/launcher/LauncherPane.qml | 81 +++++++++++----------- modules/controlcenter/launcher/Settings.qml | 16 ++--- modules/controlcenter/network/EthernetSettings.qml | 16 ++--- modules/controlcenter/network/NetworkSettings.qml | 16 ++--- modules/controlcenter/network/WirelessSettings.qml | 16 ++--- 10 files changed, 128 insertions(+), 128 deletions(-) create mode 100644 modules/controlcenter/components/SettingsHeader.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index 2041bf8..3ba0549 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -97,19 +97,9 @@ Item { anchors.top: parent.top spacing: Appearance.spacing.normal - MaterialIcon { - Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - Layout.topMargin: 0 - text: "palette" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Appearance Settings") - font.pointSize: Appearance.font.size.large - font.bold: true + SettingsHeader { + icon: "palette" + title: qsTr("Appearance Settings") } StyledText { diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index 02cda5b..9b0c7d2 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -233,7 +233,7 @@ Item { anchors.top: parent.top spacing: Appearance.spacing.normal - ConnectionHeader { + SettingsHeader { icon: "volume_up" title: qsTr("Audio Settings") } diff --git a/modules/controlcenter/bluetooth/Details.qml b/modules/controlcenter/bluetooth/Details.qml index c9d10cd..5496966 100644 --- a/modules/controlcenter/bluetooth/Details.qml +++ b/modules/controlcenter/bluetooth/Details.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.effects @@ -41,20 +42,9 @@ StyledFlickable { anchors.top: parent.top spacing: Appearance.spacing.normal - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - animate: true - text: Icons.getBluetoothIcon(root.device?.icon ?? "") - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - animate: true - text: root.device?.name ?? "" - font.pointSize: Appearance.font.size.large - font.bold: true + SettingsHeader { + icon: Icons.getBluetoothIcon(root.device?.icon ?? "") + title: root.device?.name ?? "" } StyledText { diff --git a/modules/controlcenter/bluetooth/Settings.qml b/modules/controlcenter/bluetooth/Settings.qml index c8453b6..b3245ab 100644 --- a/modules/controlcenter/bluetooth/Settings.qml +++ b/modules/controlcenter/bluetooth/Settings.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.effects @@ -17,18 +18,9 @@ ColumnLayout { spacing: Appearance.spacing.normal - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - text: "bluetooth" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Bluetooth Settings") - font.pointSize: Appearance.font.size.large - font.bold: true + SettingsHeader { + icon: "bluetooth" + title: qsTr("Bluetooth Settings") } StyledText { diff --git a/modules/controlcenter/components/SettingsHeader.qml b/modules/controlcenter/components/SettingsHeader.qml new file mode 100644 index 0000000..9a77968 --- /dev/null +++ b/modules/controlcenter/components/SettingsHeader.qml @@ -0,0 +1,59 @@ +pragma ComponentBehavior: Bound + +import qs.components +import qs.config +import QtQuick +import QtQuick.Layouts + +/** + * SettingsHeader + * + * Reusable header component for settings panes. Displays a large icon and title + * in a consistent format across all settings screens. + * + * Usage: + * ```qml + * SettingsHeader { + * icon: "router" + * title: qsTr("Network Settings") + * } + * ``` + */ +Item { + id: root + + /** + * Material icon name to display + */ + required property string icon + + /** + * Title text to display + */ + required property string title + + Layout.fillWidth: true + implicitHeight: column.implicitHeight + + ColumnLayout { + id: column + + anchors.centerIn: parent + spacing: Appearance.spacing.normal + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + text: root.icon + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: root.title + font.pointSize: Appearance.font.size.large + font.bold: true + } + } +} + diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index a8c5c76..cf965e8 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -509,53 +509,54 @@ Item { spacing: Appearance.spacing.normal - Item { - Layout.alignment: Qt.AlignHCenter - Layout.leftMargin: Appearance.padding.large * 2 - Layout.rightMargin: Appearance.padding.large * 2 - Layout.topMargin: Appearance.padding.large * 2 - implicitWidth: iconLoader.implicitWidth - implicitHeight: iconLoader.implicitHeight - - Loader { - id: iconLoader - sourceComponent: parent.parent.displayedApp ? appIconComponent : defaultIconComponent + // Show SettingsHeader when no app is selected, or show app icon + title when app is selected + SettingsHeader { + Layout.leftMargin: Appearance.padding.large * 2 + Layout.rightMargin: Appearance.padding.large * 2 + Layout.topMargin: Appearance.padding.large * 2 + visible: displayedApp === null + icon: "apps" + title: qsTr("Launcher Applications") } - Component { - id: appIconComponent - IconImage { - implicitSize: Appearance.font.size.extraLarge * 3 * 2 - source: { - const app = iconLoader.parent.parent.displayedApp; - if (!app) return "image-missing"; - const entry = app.entry; - if (entry && entry.icon) { - return Quickshell.iconPath(entry.icon, "image-missing"); + // App icon and title display (shown when app is selected) + Item { + Layout.alignment: Qt.AlignHCenter + Layout.leftMargin: Appearance.padding.large * 2 + Layout.rightMargin: Appearance.padding.large * 2 + Layout.topMargin: Appearance.padding.large * 2 + visible: displayedApp !== null + implicitWidth: Math.max(appIconImage.implicitWidth, appTitleText.implicitWidth) + implicitHeight: appIconImage.implicitHeight + Appearance.spacing.normal + appTitleText.implicitHeight + + ColumnLayout { + anchors.centerIn: parent + spacing: Appearance.spacing.normal + + IconImage { + id: appIconImage + Layout.alignment: Qt.AlignHCenter + implicitSize: Appearance.font.size.extraLarge * 3 * 2 + source: { + const app = appDetailsLayout.displayedApp; + if (!app) return "image-missing"; + const entry = app.entry; + if (entry && entry.icon) { + return Quickshell.iconPath(entry.icon, "image-missing"); + } + return "image-missing"; } - return "image-missing"; } - } - } - Component { - id: defaultIconComponent - MaterialIcon { - text: "apps" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true + StyledText { + id: appTitleText + Layout.alignment: Qt.AlignHCenter + text: displayedApp ? (displayedApp.name || displayedApp.entry?.name || qsTr("Application Details")) : "" + font.pointSize: Appearance.font.size.large + font.bold: true + } } } - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - Layout.leftMargin: Appearance.padding.large * 2 - Layout.rightMargin: Appearance.padding.large * 2 - text: displayedApp ? (displayedApp.name || displayedApp.entry?.name || qsTr("Application Details")) : qsTr("Launcher Applications") - font.pointSize: Appearance.font.size.large - font.bold: true - } Item { Layout.fillWidth: true diff --git a/modules/controlcenter/launcher/Settings.qml b/modules/controlcenter/launcher/Settings.qml index 1fef7f5..161221e 100644 --- a/modules/controlcenter/launcher/Settings.qml +++ b/modules/controlcenter/launcher/Settings.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.effects @@ -16,18 +17,9 @@ ColumnLayout { spacing: Appearance.spacing.normal - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - text: "apps" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Launcher Settings") - font.pointSize: Appearance.font.size.large - font.bold: true + SettingsHeader { + icon: "apps" + title: qsTr("Launcher Settings") } SectionHeader { diff --git a/modules/controlcenter/network/EthernetSettings.qml b/modules/controlcenter/network/EthernetSettings.qml index 161492c..f0f66b4 100644 --- a/modules/controlcenter/network/EthernetSettings.qml +++ b/modules/controlcenter/network/EthernetSettings.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.effects @@ -16,18 +17,9 @@ ColumnLayout { spacing: Appearance.spacing.normal - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - text: "cable" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Ethernet settings") - font.pointSize: Appearance.font.size.large - font.bold: true + SettingsHeader { + icon: "cable" + title: qsTr("Ethernet settings") } StyledText { diff --git a/modules/controlcenter/network/NetworkSettings.qml b/modules/controlcenter/network/NetworkSettings.qml index 75a7660..22e07cb 100644 --- a/modules/controlcenter/network/NetworkSettings.qml +++ b/modules/controlcenter/network/NetworkSettings.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.effects @@ -16,18 +17,9 @@ ColumnLayout { spacing: Appearance.spacing.normal - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - text: "router" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Network Settings") - font.pointSize: Appearance.font.size.large - font.bold: true + SettingsHeader { + icon: "router" + title: qsTr("Network Settings") } SectionHeader { diff --git a/modules/controlcenter/network/WirelessSettings.qml b/modules/controlcenter/network/WirelessSettings.qml index 0eb1578..f87fe39 100644 --- a/modules/controlcenter/network/WirelessSettings.qml +++ b/modules/controlcenter/network/WirelessSettings.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.effects @@ -16,18 +17,9 @@ ColumnLayout { spacing: Appearance.spacing.normal - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - text: "wifi" - font.pointSize: Appearance.font.size.extraLarge * 3 - font.bold: true - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Network settings") - font.pointSize: Appearance.font.size.large - font.bold: true + SettingsHeader { + icon: "wifi" + title: qsTr("Network settings") } SectionHeader { -- cgit v1.2.3-freya From 70ec8cea651c0f49e9ccf25b6e8685d81ac6710b Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 19 Nov 2025 19:11:41 -0500 Subject: refactor: NetworkConnection util created, migrated all functions --- modules/bar/popouts/Network.qml | 34 +++--- modules/bar/popouts/WirelessPassword.qml | 3 +- modules/controlcenter/network/NetworkingPane.qml | 43 +------- modules/controlcenter/network/WirelessDetails.qml | 38 +------ modules/controlcenter/network/WirelessList.qml | 44 +------- .../network/WirelessPasswordDialog.qml | 3 +- utils/NetworkConnection.qml | 122 +++++++++++++++++++++ 7 files changed, 144 insertions(+), 143 deletions(-) create mode 100644 utils/NetworkConnection.qml (limited to 'modules/controlcenter/network') diff --git a/modules/bar/popouts/Network.qml b/modules/bar/popouts/Network.qml index b9f66c4..0e99613 100644 --- a/modules/bar/popouts/Network.qml +++ b/modules/bar/popouts/Network.qml @@ -131,27 +131,19 @@ ColumnLayout { Nmcli.disconnectFromNetwork(); } else { root.connectingToSsid = networkItem.modelData.ssid; - // Check if network is secure - if (networkItem.modelData.isSecure) { - // Try to connect first - will show password dialog if password is needed - Nmcli.connectToNetwork(networkItem.modelData.ssid, "", networkItem.modelData.bssid, result => { - if (result && result.needsPassword) { - // Password is required - show password dialog - root.passwordNetwork = networkItem.modelData; - root.showPasswordDialog = true; - root.wrapper.currentName = "wirelesspassword"; - } else if (result && result.success) { - // Connection successful with saved password - root.connectingToSsid = ""; - } else { - // Connection failed for other reasons - root.connectingToSsid = ""; - } - }); - } else { - // Open network, no password needed - Nmcli.connectToNetwork(networkItem.modelData.ssid, "", networkItem.modelData.bssid, null); - } + NetworkConnection.handleConnect( + networkItem.modelData, + null, + (network) => { + // Password is required - show password dialog + root.passwordNetwork = network; + root.showPasswordDialog = true; + root.wrapper.currentName = "wirelesspassword"; + } + ); + + // Clear connecting state if connection succeeds immediately (saved profile) + // This is handled by the onActiveChanged connection below } } } diff --git a/modules/bar/popouts/WirelessPassword.qml b/modules/bar/popouts/WirelessPassword.qml index d91c87c..5da50b6 100644 --- a/modules/bar/popouts/WirelessPassword.qml +++ b/modules/bar/popouts/WirelessPassword.qml @@ -4,6 +4,7 @@ import qs.components import qs.components.controls import qs.services import qs.config +import qs.utils import Quickshell import QtQuick import QtQuick.Layouts @@ -452,7 +453,7 @@ ColumnLayout { text = qsTr("Connecting..."); // Connect to network - Nmcli.connectToNetwork(root.network.ssid, password, root.network.bssid || "", result => { + NetworkConnection.connectWithPassword(root.network, password, result => { if (result && result.success) // Connection successful, monitor will handle the rest {} else if (result && result.needsPassword) { diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 9a7a4e1..d76e8f5 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -415,7 +415,7 @@ Item { if (modelData && modelData.active) { Nmcli.disconnectFromNetwork(); } else if (modelData) { - handleWirelessConnect(modelData); + NetworkConnection.handleConnect(modelData, root.session, null); } } } @@ -586,46 +586,5 @@ Item { Nmcli.loadSavedConnections(() => {}); } } - - function handleWirelessConnect(network): void { - if (Nmcli.active && Nmcli.active.ssid !== network.ssid) { - Nmcli.disconnectFromNetwork(); - Qt.callLater(() => { - connectToWirelessNetwork(network); - }); - } else { - connectToWirelessNetwork(network); - } - } - - function connectToWirelessNetwork(network): void { - if (network.isSecure) { - const hasSavedProfile = Nmcli.hasSavedProfile(network.ssid); - - if (hasSavedProfile) { - Nmcli.connectToNetwork(network.ssid, "", network.bssid, null); - } else { - Nmcli.connectToNetworkWithPasswordCheck( - network.ssid, - network.isSecure, - (result) => { - if (result.needsPassword) { - if (Nmcli.pendingConnection) { - Nmcli.connectionCheckTimer.stop(); - Nmcli.immediateCheckTimer.stop(); - Nmcli.immediateCheckTimer.checkCount = 0; - Nmcli.pendingConnection = null; - } - root.session.network.showPasswordDialog = true; - root.session.network.pendingNetwork = network; - } - }, - network.bssid - ); - } - } else { - Nmcli.connectToNetwork(network.ssid, "", network.bssid, null); - } - } } diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index 09abff3..57c06c8 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -8,6 +8,7 @@ import qs.components.effects import qs.components.containers import qs.services import qs.config +import qs.utils import QtQuick import QtQuick.Layouts @@ -125,7 +126,7 @@ Item { checked: root.network?.active ?? false toggle.onToggled: { if (checked) { - root.handleConnect(); + NetworkConnection.handleConnect(root.network, root.session, null); } else { Nmcli.disconnectFromNetwork(); } @@ -207,39 +208,4 @@ Item { } } - function handleConnect(): void { - if (Nmcli.active && Nmcli.active.ssid !== root.network.ssid) { - Nmcli.disconnectFromNetwork(); - Qt.callLater(() => { - connectToNetwork(); - }); - } else { - connectToNetwork(); - } - } - - function connectToNetwork(): void { - if (root.network.isSecure) { - const hasSavedProfile = Nmcli.hasSavedProfile(root.network.ssid); - - if (hasSavedProfile) { - Nmcli.connectToNetwork(root.network.ssid, "", root.network.bssid, null); - } else { - Nmcli.connectToNetworkWithPasswordCheck(root.network.ssid, root.network.isSecure, result => { - if (result.needsPassword) { - if (Nmcli.pendingConnection) { - Nmcli.connectionCheckTimer.stop(); - Nmcli.immediateCheckTimer.stop(); - Nmcli.immediateCheckTimer.checkCount = 0; - Nmcli.pendingConnection = null; - } - root.session.network.showPasswordDialog = true; - root.session.network.pendingNetwork = root.network; - } - }, root.network.bssid); - } - } else { - Nmcli.connectToNetwork(root.network.ssid, "", root.network.bssid, null); - } - } } diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml index 00af47a..18f728c 100644 --- a/modules/controlcenter/network/WirelessList.qml +++ b/modules/controlcenter/network/WirelessList.qml @@ -7,6 +7,7 @@ import qs.components.controls import qs.components.containers import qs.services import qs.config +import qs.utils import QtQuick import QtQuick.Layouts @@ -193,7 +194,7 @@ ColumnLayout { if (modelData.active) { Nmcli.disconnectFromNetwork(); } else { - handleConnect(modelData); + NetworkConnection.handleConnect(modelData, root.session, null); } } } @@ -217,45 +218,4 @@ ColumnLayout { Nmcli.loadSavedConnections(() => {}); } } - - function handleConnect(network): void { - if (Nmcli.active && Nmcli.active.ssid !== network.ssid) { - Nmcli.disconnectFromNetwork(); - Qt.callLater(() => { - connectToNetwork(network); - }); - } else { - connectToNetwork(network); - } - } - - function connectToNetwork(network): void { - if (network.isSecure) { - const hasSavedProfile = Nmcli.hasSavedProfile(network.ssid); - - if (hasSavedProfile) { - Nmcli.connectToNetwork(network.ssid, "", network.bssid, null); - } else { - Nmcli.connectToNetworkWithPasswordCheck( - network.ssid, - network.isSecure, - (result) => { - if (result.needsPassword) { - if (Nmcli.pendingConnection) { - Nmcli.connectionCheckTimer.stop(); - Nmcli.immediateCheckTimer.stop(); - Nmcli.immediateCheckTimer.checkCount = 0; - Nmcli.pendingConnection = null; - } - root.session.network.showPasswordDialog = true; - root.session.network.pendingNetwork = network; - } - }, - network.bssid - ); - } - } else { - Nmcli.connectToNetwork(network.ssid, "", network.bssid, null); - } - } } \ No newline at end of file diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index 4b350be..0f1a5cd 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -8,6 +8,7 @@ import qs.components.effects import qs.components.containers import qs.services import qs.config +import qs.utils import Quickshell import QtQuick import QtQuick.Layouts @@ -391,7 +392,7 @@ Item { text = qsTr("Connecting..."); // Connect to network - Nmcli.connectToNetwork(root.network.ssid, password, root.network.bssid || "", result => { + NetworkConnection.connectWithPassword(root.network, password, result => { if (result && result.success) // Connection successful, monitor will handle the rest {} else if (result && result.needsPassword) { diff --git a/utils/NetworkConnection.qml b/utils/NetworkConnection.qml new file mode 100644 index 0000000..c7595b1 --- /dev/null +++ b/utils/NetworkConnection.qml @@ -0,0 +1,122 @@ +pragma Singleton + +import qs.services +import QtQuick + +/** + * NetworkConnection + * + * Centralized utility for network connection logic. Provides a single source of truth + * for connecting to wireless networks, eliminating code duplication across + * controlcenter components and bar popouts. + * + * Usage: + * ```qml + * import qs.utils + * + * // With Session object (controlcenter) + * NetworkConnection.handleConnect(network, session); + * + * // Without Session object (bar popouts) - provide password dialog callback + * NetworkConnection.handleConnect(network, null, (network) => { + * // Show password dialog + * root.passwordNetwork = network; + * root.showPasswordDialog = true; + * }); + * ``` + */ +QtObject { + id: root + + /** + * Handle network connection with automatic disconnection if needed. + * If there's an active network different from the target, disconnects first, + * then connects to the target network. + * + * @param network The network object to connect to (must have ssid property) + * @param session Optional Session object (for controlcenter - must have network property with showPasswordDialog and pendingNetwork) + * @param onPasswordNeeded Optional callback function(network) called when password is needed (for bar popouts) + */ + function handleConnect(network, session, onPasswordNeeded): void { + if (!network) { + return; + } + + if (Nmcli.active && Nmcli.active.ssid !== network.ssid) { + Nmcli.disconnectFromNetwork(); + Qt.callLater(() => { + root.connectToNetwork(network, session, onPasswordNeeded); + }); + } else { + root.connectToNetwork(network, session, onPasswordNeeded); + } + } + + /** + * Connect to a wireless network. + * Handles both secured and open networks, checks for saved profiles, + * and shows password dialog if needed. + * + * @param network The network object to connect to (must have ssid, isSecure, bssid properties) + * @param session Optional Session object (for controlcenter - must have network property with showPasswordDialog and pendingNetwork) + * @param onPasswordNeeded Optional callback function(network) called when password is needed (for bar popouts) + */ + function connectToNetwork(network, session, onPasswordNeeded): void { + if (!network) { + return; + } + + if (network.isSecure) { + const hasSavedProfile = Nmcli.hasSavedProfile(network.ssid); + + if (hasSavedProfile) { + Nmcli.connectToNetwork(network.ssid, "", network.bssid, null); + } else { + // Use password check with callback + Nmcli.connectToNetworkWithPasswordCheck( + network.ssid, + network.isSecure, + (result) => { + if (result.needsPassword) { + // Clear pending connection if exists + if (Nmcli.pendingConnection) { + Nmcli.connectionCheckTimer.stop(); + Nmcli.immediateCheckTimer.stop(); + Nmcli.immediateCheckTimer.checkCount = 0; + Nmcli.pendingConnection = null; + } + + // Handle password dialog - use session if available, otherwise use callback + if (session && session.network) { + session.network.showPasswordDialog = true; + session.network.pendingNetwork = network; + } else if (onPasswordNeeded) { + onPasswordNeeded(network); + } + } + }, + network.bssid + ); + } + } else { + Nmcli.connectToNetwork(network.ssid, "", network.bssid, null); + } + } + + /** + * Connect to a wireless network with a provided password. + * Used by password dialogs when the user has already entered a password. + * + * @param network The network object to connect to (must have ssid, bssid properties) + * @param password The password to use for connection + * @param onResult Optional callback function(result) called with connection result + */ + function connectWithPassword(network, password, onResult): void { + if (!network) { + return; + } + + Nmcli.connectToNetwork(network.ssid, password || "", network.bssid || "", onResult || null); + } +} + -- cgit v1.2.3-freya From 2d26626643e447b5c9f6f78619e8472bc35ea599 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 19 Nov 2025 19:25:26 -0500 Subject: refactor: SplitPaneWithDetails integrated --- modules/controlcenter/bluetooth/BtPane.qml | 84 +++---------- .../components/SplitPaneWithDetails.qml | 130 ++++++++++++++++++++ modules/controlcenter/network/EthernetPane.qml | 121 ++++--------------- modules/controlcenter/network/WirelessPane.qml | 131 ++++----------------- 4 files changed, 190 insertions(+), 276 deletions(-) create mode 100644 modules/controlcenter/components/SplitPaneWithDetails.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/bluetooth/BtPane.qml b/modules/controlcenter/bluetooth/BtPane.qml index c20f14b..3cd6ef9 100644 --- a/modules/controlcenter/bluetooth/BtPane.qml +++ b/modules/controlcenter/bluetooth/BtPane.qml @@ -2,87 +2,39 @@ pragma ComponentBehavior: Bound import ".." import "../components" +import qs.components import qs.components.controls -import qs.components.effects import qs.components.containers import qs.config import Quickshell.Widgets import Quickshell.Bluetooth import QtQuick -import QtQuick.Layouts -Item { +SplitPaneWithDetails { id: root required property Session session anchors.fill: parent - SplitPaneLayout { - anchors.fill: parent + activeItem: session.bt.active + paneIdGenerator: function(item) { + return item ? (item.address || "") : ""; + } - leftContent: Component { - DeviceList { - anchors.fill: parent - session: root.session - } + leftContent: Component { + DeviceList { + session: root.session } + } - rightContent: Component { - Item { - id: rightBtPane - - property BluetoothDevice pane: root.session.bt.active - property string paneId: pane ? (pane.address || "") : "" - property Component targetComponent: settings - property Component nextComponent: settings - - function getComponentForPane() { - return pane ? details : settings; - } - - Component.onCompleted: { - targetComponent = getComponentForPane(); - nextComponent = targetComponent; - } - - Loader { - id: rightLoader - - anchors.fill: parent - - asynchronous: true - sourceComponent: rightBtPane.targetComponent - } - - Behavior on paneId { - PaneTransition { - target: rightLoader - propertyActions: [ - PropertyAction { - target: rightBtPane - property: "targetComponent" - value: rightBtPane.nextComponent - } - ] - } - } - - Connections { - target: root.session.bt - function onActiveChanged() { - rightBtPane.pane = root.session.bt.active; - rightBtPane.nextComponent = rightBtPane.getComponentForPane(); - rightBtPane.paneId = pane ? (pane.address || "") : ""; - } - } - } + rightDetailsComponent: Component { + Details { + session: root.session } } - Component { - id: settings - + rightSettingsComponent: Component { StyledFlickable { id: settingsFlickable flickableDirection: Flickable.VerticalFlick @@ -102,12 +54,4 @@ Item { } } } - - Component { - id: details - - Details { - session: root.session - } - } } diff --git a/modules/controlcenter/components/SplitPaneWithDetails.qml b/modules/controlcenter/components/SplitPaneWithDetails.qml new file mode 100644 index 0000000..e139aca --- /dev/null +++ b/modules/controlcenter/components/SplitPaneWithDetails.qml @@ -0,0 +1,130 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.effects +import qs.components.containers +import qs.config +import Quickshell.Widgets +import QtQuick +import QtQuick.Layouts + +/** + * SplitPaneWithDetails + * + * A reusable component that provides a split-pane layout with a list on the left + * and a details/settings view on the right. The right pane automatically switches + * between details and settings views based on whether an item is selected. + * + * This component eliminates duplication across WirelessPane, EthernetPane, and BtPane + * by providing a standardized pattern for split-pane layouts with transition animations. + * + * Usage: + * ```qml + * SplitPaneWithDetails { + * activeItem: root.session.network.active + * leftContent: Component { + * WirelessList { + * session: root.session + * } + * } + * rightDetailsComponent: Component { + * WirelessDetails { + * session: root.session + * } + * } + * rightSettingsComponent: Component { + * StyledFlickable { + * WirelessSettings { + * session: root.session + * } + * } + * } + * paneIdGenerator: (item) => item ? (item.ssid || item.bssid || "") : "" + * } + * ``` + */ +Item { + id: root + + required property Component leftContent + required property Component rightDetailsComponent + required property Component rightSettingsComponent + + property var activeItem: null + property var paneIdGenerator: function(item) { return item ? String(item) : ""; } + + // Optional: Additional component to overlay on top (e.g., password dialogs) + property Component overlayComponent: null + + SplitPaneLayout { + id: splitLayout + + anchors.fill: parent + + leftContent: root.leftContent + + rightContent: Component { + Item { + id: rightPaneItem + + property var pane: root.activeItem + property string paneId: root.paneIdGenerator(pane) + property Component targetComponent: root.rightSettingsComponent + property Component nextComponent: root.rightSettingsComponent + + function getComponentForPane() { + return pane ? root.rightDetailsComponent : root.rightSettingsComponent; + } + + Component.onCompleted: { + targetComponent = getComponentForPane(); + nextComponent = targetComponent; + } + + Loader { + id: rightLoader + + anchors.fill: parent + + opacity: 1 + scale: 1 + transformOrigin: Item.Center + + clip: false + asynchronous: true + sourceComponent: rightPaneItem.targetComponent + } + + Behavior on paneId { + PaneTransition { + target: rightLoader + propertyActions: [ + PropertyAction { + target: rightPaneItem + property: "targetComponent" + value: rightPaneItem.nextComponent + } + ] + } + } + + onPaneChanged: { + nextComponent = getComponentForPane(); + paneId = root.paneIdGenerator(pane); + } + } + } + } + + // Overlay component (e.g., password dialogs) + Loader { + id: overlayLoader + + anchors.fill: parent + z: 1000 + sourceComponent: root.overlayComponent + active: root.overlayComponent !== null + } +} + diff --git a/modules/controlcenter/network/EthernetPane.qml b/modules/controlcenter/network/EthernetPane.qml index fc979c3..126535a 100644 --- a/modules/controlcenter/network/EthernetPane.qml +++ b/modules/controlcenter/network/EthernetPane.qml @@ -1,131 +1,50 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components -import qs.components.effects import qs.components.containers import qs.config import Quickshell.Widgets import QtQuick -import QtQuick.Layouts -RowLayout { +SplitPaneWithDetails { id: root required property Session session anchors.fill: parent - spacing: 0 - - Item { - Layout.preferredWidth: Math.floor(parent.width * 0.4) - Layout.minimumWidth: 420 - Layout.fillHeight: true + activeItem: session.ethernet.active + paneIdGenerator: function(item) { + return item ? (item.interface || "") : ""; + } + leftContent: Component { EthernetList { - anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - session: root.session } + } - InnerBorder { - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 + rightDetailsComponent: Component { + EthernetDetails { + session: root.session } } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - ClippingRectangle { - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: rightBorder.innerRadius - color: "transparent" + rightSettingsComponent: Component { + StyledFlickable { + flickableDirection: Flickable.VerticalFlick + contentHeight: settingsInner.height clip: true - Loader { - id: loader - - property var pane: root.session.ethernet.active - property string paneId: pane ? (pane.interface || "") : "" - property Component targetComponent: settings - property Component nextComponent: settings - - anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 - - opacity: 1 - scale: 1 - transformOrigin: Item.Center - - clip: true - asynchronous: true - sourceComponent: loader.targetComponent - - Component.onCompleted: { - targetComponent = pane ? details : settings; - nextComponent = targetComponent; - } - - Behavior on paneId { - PaneTransition { - target: loader - propertyActions: [ - PropertyAction { - target: loader - property: "targetComponent" - value: loader.nextComponent - } - ] - } - } - - onPaneChanged: { - nextComponent = pane ? details : settings; - paneId = pane ? (pane.interface || "") : ""; - } - } - } - - InnerBorder { - id: rightBorder - - leftThickness: Appearance.padding.normal / 2 - } - - Component { - id: settings - - StyledFlickable { - flickableDirection: Flickable.VerticalFlick - contentHeight: settingsInner.height - clip: true - - EthernetSettings { - id: settingsInner - - anchors.left: parent.left - anchors.right: parent.right - session: root.session - } - } - } - - Component { - id: details + EthernetSettings { + id: settingsInner - EthernetDetails { + anchors.left: parent.left + anchors.right: parent.right session: root.session } } } -} \ No newline at end of file +} diff --git a/modules/controlcenter/network/WirelessPane.qml b/modules/controlcenter/network/WirelessPane.qml index 0c8f0ae..109d416 100644 --- a/modules/controlcenter/network/WirelessPane.qml +++ b/modules/controlcenter/network/WirelessPane.qml @@ -1,136 +1,57 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components -import qs.components.effects import qs.components.containers import qs.config import Quickshell.Widgets import QtQuick -import QtQuick.Layouts -RowLayout { +SplitPaneWithDetails { id: root required property Session session anchors.fill: parent - spacing: 0 - - Item { - Layout.preferredWidth: Math.floor(parent.width * 0.4) - Layout.minimumWidth: 420 - Layout.fillHeight: true + activeItem: session.network.active + paneIdGenerator: function(item) { + return item ? (item.ssid || item.bssid || "") : ""; + } + leftContent: Component { WirelessList { - anchors.fill: parent - anchors.margins: Appearance.padding.large + Appearance.padding.normal - anchors.leftMargin: Appearance.padding.large - anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 - session: root.session } - - InnerBorder { - leftThickness: 0 - rightThickness: Appearance.padding.normal / 2 - } } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - ClippingRectangle { - anchors.fill: parent - anchors.margins: Appearance.padding.normal - anchors.leftMargin: 0 - anchors.rightMargin: Appearance.padding.normal / 2 - - radius: rightBorder.innerRadius - color: "transparent" - - Loader { - id: loader - - property var pane: root.session.network.active - property string paneId: pane ? (pane.ssid || pane.bssid || "") : "" - property Component targetComponent: settings - property Component nextComponent: settings - - anchors.fill: parent - anchors.margins: Appearance.padding.large * 2 - - opacity: 1 - scale: 1 - transformOrigin: Item.Center - - clip: false - asynchronous: true - sourceComponent: loader.targetComponent - - Component.onCompleted: { - targetComponent = pane ? details : settings; - nextComponent = targetComponent; - } - - Behavior on paneId { - PaneTransition { - target: loader - propertyActions: [ - PropertyAction { - target: loader - property: "targetComponent" - value: loader.nextComponent - } - ] - } - } - - onPaneChanged: { - nextComponent = pane ? details : settings; - paneId = pane ? (pane.ssid || pane.bssid || "") : ""; - } - } - } - - InnerBorder { - id: rightBorder - - leftThickness: Appearance.padding.normal / 2 + rightDetailsComponent: Component { + WirelessDetails { + session: root.session } + } - Component { - id: settings - - StyledFlickable { - flickableDirection: Flickable.VerticalFlick - contentHeight: settingsInner.height - clip: true - - WirelessSettings { - id: settingsInner - - anchors.left: parent.left - anchors.right: parent.right - session: root.session - } - } - } + rightSettingsComponent: Component { + StyledFlickable { + flickableDirection: Flickable.VerticalFlick + contentHeight: settingsInner.height + clip: true - Component { - id: details + WirelessSettings { + id: settingsInner - WirelessDetails { + anchors.left: parent.left + anchors.right: parent.right session: root.session } } } - WirelessPasswordDialog { - anchors.fill: parent - session: root.session - z: 1000 + overlayComponent: Component { + WirelessPasswordDialog { + anchors.fill: parent + session: root.session + } } -} \ No newline at end of file +} -- 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/network') 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/network') 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 From 147410e39bf4e0474deca3980dcaa724464cf5c3 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 19 Nov 2025 21:15:40 -0500 Subject: cleanup: removed unnecessary comments --- modules/controlcenter/ControlCenter.qml | 1 - modules/controlcenter/Panes.qml | 18 ------------ modules/controlcenter/Session.qml | 1 - .../controlcenter/appearance/AppearancePane.qml | 28 ++----------------- modules/controlcenter/audio/AudioPane.qml | 1 - modules/controlcenter/bluetooth/Details.qml | 1 - modules/controlcenter/bluetooth/Settings.qml | 4 +-- modules/controlcenter/components/DeviceDetails.qml | 7 ----- modules/controlcenter/components/DeviceList.qml | 10 ++----- .../controlcenter/components/PaneTransition.qml | 12 -------- .../controlcenter/components/SplitPaneLayout.qml | 8 ------ .../components/SplitPaneWithDetails.qml | 2 -- modules/controlcenter/launcher/LauncherPane.qml | 27 ++---------------- modules/controlcenter/network/NetworkingPane.qml | 2 -- modules/controlcenter/network/WirelessDetails.qml | 9 ------ modules/controlcenter/network/WirelessList.qml | 3 -- .../network/WirelessPasswordDialog.qml | 27 ++---------------- modules/controlcenter/state/BluetoothState.qml | 5 ---- modules/controlcenter/state/EthernetState.qml | 1 - modules/controlcenter/state/LauncherState.qml | 1 - modules/controlcenter/state/NetworkState.qml | 3 -- modules/controlcenter/taskbar/TaskbarPane.qml | 32 ---------------------- 22 files changed, 9 insertions(+), 194 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/ControlCenter.qml b/modules/controlcenter/ControlCenter.qml index 3642a33..fdb824e 100644 --- a/modules/controlcenter/ControlCenter.qml +++ b/modules/controlcenter/ControlCenter.qml @@ -97,6 +97,5 @@ Item { } } - // Expose initialOpeningComplete for NavRail to prevent tab switching during opening animation readonly property bool initialOpeningComplete: panes.initialOpeningComplete } diff --git a/modules/controlcenter/Panes.qml b/modules/controlcenter/Panes.qml index b9256a9..833a411 100644 --- a/modules/controlcenter/Panes.qml +++ b/modules/controlcenter/Panes.qml @@ -19,7 +19,6 @@ ClippingRectangle { required property Session session - // Expose initialOpeningComplete so parent can check if opening animation is done readonly property bool initialOpeningComplete: layout.initialOpeningComplete color: "transparent" @@ -27,7 +26,6 @@ ClippingRectangle { focus: false activeFocusOnTab: false - // Clear focus when clicking anywhere in the panes area MouseArea { anchors.fill: parent z: -1 @@ -37,7 +35,6 @@ ClippingRectangle { } } - // Clear focus when switching panes Connections { target: root.session @@ -54,8 +51,6 @@ ClippingRectangle { clip: true property bool animationComplete: true - // Track if initial opening animation has completed - // During initial opening, only the active pane loads to avoid hiccups property bool initialOpeningComplete: false Timer { @@ -66,8 +61,6 @@ ClippingRectangle { } } - // Timer to detect when initial opening animation completes - // Uses large duration to cover both normal and detached opening cases Timer { id: initialOpeningTimer interval: Appearance.anim.durations.large @@ -94,7 +87,6 @@ ClippingRectangle { Connections { target: root.session function onActiveIndexChanged(): void { - // Mark animation as incomplete and start delay timer layout.animationComplete = false; animationDelayTimer.restart(); } @@ -110,28 +102,21 @@ ClippingRectangle { implicitWidth: root.width implicitHeight: root.height - // Track if this pane has ever been loaded to enable caching property bool hasBeenLoaded: false - // Function to compute if this pane should be active function updateActive(): void { const diff = Math.abs(root.session.activeIndex - pane.paneIndex); const isActivePane = diff === 0; let shouldBeActive = false; - // During initial opening animation, only load the active pane - // This prevents hiccups from multiple panes loading simultaneously if (!layout.initialOpeningComplete) { shouldBeActive = isActivePane; } else { - // After initial opening, allow current and adjacent panes for smooth transitions if (diff <= 1) { shouldBeActive = true; } else if (pane.hasBeenLoaded) { - // For distant panes that have been loaded before, keep them active to preserve cached data shouldBeActive = true; } else { - // For new distant panes, wait until animation completes to avoid heavy loading during transition shouldBeActive = layout.animationComplete; } } @@ -152,12 +137,10 @@ ClippingRectangle { } onActiveChanged: { - // Mark pane as loaded when it becomes active if (active && !pane.hasBeenLoaded) { pane.hasBeenLoaded = true; } - // Load the component with initial properties when activated if (active && !item) { loader.setSource(pane.componentPath, { "session": root.session @@ -166,7 +149,6 @@ ClippingRectangle { } onItemChanged: { - // Mark pane as loaded when item is created if (item) { pane.hasBeenLoaded = true; } diff --git a/modules/controlcenter/Session.qml b/modules/controlcenter/Session.qml index 9c6a754..0408a1a 100644 --- a/modules/controlcenter/Session.qml +++ b/modules/controlcenter/Session.qml @@ -11,7 +11,6 @@ QtObject { property int activeIndex: 0 property bool navExpanded: false - // Pane-specific state objects readonly property BluetoothState bt: BluetoothState {} readonly property NetworkState network: NetworkState {} readonly property EthernetState ethernet: EthernetState {} diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index 3ba0549..5b7e859 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -22,7 +22,6 @@ Item { required property Session session - // Appearance settings property real animDurationsScale: Config.appearance.anim.durations.scale ?? 1 property string fontFamilyMaterial: Config.appearance.font.family.material ?? "Material Symbols Rounded" property string fontFamilyMono: Config.appearance.font.family.mono ?? "CaskaydiaCove NF" @@ -37,7 +36,6 @@ Item { property real borderRounding: Config.border.rounding ?? 1 property real borderThickness: Config.border.thickness ?? 1 - // Background settings property bool desktopClockEnabled: Config.background.desktopClock.enabled ?? false property bool backgroundEnabled: Config.background.enabled ?? true property bool visualiserEnabled: Config.background.visualiser.enabled ?? false @@ -127,13 +125,8 @@ Item { anchors.fill: parent asynchronous: true active: { - // Lazy load: only activate when: - // 1. Right pane is loaded AND - // 2. Appearance pane is active (index 3) or adjacent (for smooth transitions) - // This prevents loading all wallpapers when control center opens but appearance pane isn't visible const isActive = root.session.activeIndex === 3; const isAdjacent = Math.abs(root.session.activeIndex - 3) === 1; - // Access loader through SplitPaneLayout's rightLoader const splitLayout = root.children[0]; const loader = splitLayout && splitLayout.rightLoader ? splitLayout.rightLoader : null; const shouldActivate = loader && loader.item !== null && (isActive || isAdjacent); @@ -150,7 +143,6 @@ Item { onActiveChanged: { if (!active && wallpaperLoader.item) { const container = wallpaperLoader.item; - // Access timer through wallpaperGrid if (container && container.wallpaperGrid) { const grid = container.wallpaperGrid; if (grid.imageUpdateTimer) { @@ -186,20 +178,17 @@ Item { } } - // Lazy loading model: loads one image at a time, only when touching bottom - // This prevents GridView from creating all delegates at once QtObject { id: lazyModel property var sourceList: null - property int loadedCount: 0 // Total items available to load - property int visibleCount: 0 // Items actually exposed to GridView (only visible + buffer) + property int loadedCount: 0 + property int visibleCount: 0 property int totalCount: 0 function initialize(list) { sourceList = list; totalCount = list ? list.length : 0; - // Start with enough items to fill the initial viewport (~3 rows) const initialRows = 3; const cols = wallpaperGrid.columnsCount > 0 ? wallpaperGrid.columnsCount : 3; const initialCount = Math.min(initialRows * cols, totalCount); @@ -216,7 +205,6 @@ Item { } function updateVisibleCount(neededCount) { - // Always round up to complete rows to avoid incomplete rows in the grid const cols = wallpaperGrid.columnsCount > 0 ? wallpaperGrid.columnsCount : 1; const maxVisible = Math.min(neededCount, loadedCount); const rows = Math.ceil(maxVisible / cols); @@ -237,7 +225,6 @@ Item { readonly property int minCellWidth: 200 + Appearance.spacing.normal readonly property int columnsCount: Math.max(1, Math.floor(parent.width / minCellWidth)) - // Height based on visible items only - prevents GridView from creating all delegates readonly property int layoutPreferredHeight: { if (!lazyModel || lazyModel.visibleCount === 0 || columnsCount === 0) { return 0; @@ -255,7 +242,6 @@ Item { topMargin: 0 bottomMargin: 0 - // Use ListModel for incremental updates to prevent flashing when new items are added ListModel { id: wallpaperListModel } @@ -270,7 +256,6 @@ Item { const newCount = lazyModel.visibleCount; const currentCount = wallpaperListModel.count; - // Only append new items - never remove or replace existing ones if (newCount > currentCount) { const flickable = wallpaperGridContainer.parentFlickable; const oldScrollY = flickable ? flickable.contentY : 0; @@ -279,7 +264,6 @@ Item { wallpaperListModel.append({modelData: lazyModel.sourceList[i]}); } - // Preserve scroll position after model update if (flickable) { Qt.callLater(function() { if (Math.abs(flickable.contentY - oldScrollY) < 1) { @@ -382,7 +366,6 @@ Item { const neededCount = Math.min((neededBottomRow + 1) * wallpaperGrid.columnsCount, lazyModel.loadedCount); lazyModel.updateVisibleCount(neededCount); - // Load more when we're within 1 row of running out of loaded items const loadedRows = Math.ceil(lazyModel.loadedCount / wallpaperGrid.columnsCount); const rowsRemaining = loadedRows - (bottomRow + 1); @@ -434,7 +417,6 @@ Item { const neededCount = Math.min((neededBottomRow + 1) * wallpaperGrid.columnsCount, lazyModel.loadedCount); lazyModel.updateVisibleCount(neededCount); - // Load more when we're within 1 row of running out of loaded items const loadedRows = Math.ceil(lazyModel.loadedCount / wallpaperGrid.columnsCount); const rowsRemaining = loadedRows - (bottomRow + 1); @@ -450,8 +432,6 @@ Item { } } - - // Parent Flickable handles scrolling interactive: false @@ -786,11 +766,9 @@ Item { function onClicked(): void { const variant = modelData.variant; - // Optimistic update - set immediately for responsive UI Schemes.currentVariant = variant; Quickshell.execDetached(["caelestia", "scheme", "set", "-v", variant]); - // Reload after a delay to confirm changes Qt.callLater(() => { reloadTimer.restart(); }); @@ -873,11 +851,9 @@ Item { const flavour = modelData.flavour; const schemeKey = `${name} ${flavour}`; - // Optimistic update - set immediately for responsive UI Schemes.currentScheme = schemeKey; Quickshell.execDetached(["caelestia", "scheme", "set", "-n", name, "-f", flavour]); - // Reload after a delay to confirm changes Qt.callLater(() => { reloadTimer.restart(); }); diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml index 9b0c7d2..694e178 100644 --- a/modules/controlcenter/audio/AudioPane.qml +++ b/modules/controlcenter/audio/AudioPane.qml @@ -40,7 +40,6 @@ Item { anchors.right: parent.right spacing: Appearance.spacing.normal - // Audio header above the collapsible sections RowLayout { Layout.fillWidth: true spacing: Appearance.spacing.smaller diff --git a/modules/controlcenter/bluetooth/Details.qml b/modules/controlcenter/bluetooth/Details.qml index b260458..5299045 100644 --- a/modules/controlcenter/bluetooth/Details.qml +++ b/modules/controlcenter/bluetooth/Details.qml @@ -440,7 +440,6 @@ StyledFlickable { } } - // FAB Menu (positioned absolutely relative to flickable) ColumnLayout { anchors.right: fabRoot.right anchors.bottom: fabRoot.top diff --git a/modules/controlcenter/bluetooth/Settings.qml b/modules/controlcenter/bluetooth/Settings.qml index b3245ab..c547240 100644 --- a/modules/controlcenter/bluetooth/Settings.qml +++ b/modules/controlcenter/bluetooth/Settings.qml @@ -328,7 +328,7 @@ ColumnLayout { anchors.left: parent.left - text: qsTr("Rename adapter (currently does not work)") // FIXME: remove disclaimer when fixed + text: qsTr("Rename adapter (currently does not work)") color: Colours.palette.m3outline font.pointSize: Appearance.font.size.small } @@ -345,8 +345,6 @@ ColumnLayout { readOnly: !root.session.bt.editingAdapterName onAccepted: { root.session.bt.editingAdapterName = false; - // Doesn't work for now, will be added to QS later - // root.session.bt.currentAdapter.name = text; } leftPadding: Appearance.padding.normal diff --git a/modules/controlcenter/components/DeviceDetails.qml b/modules/controlcenter/components/DeviceDetails.qml index 768e77a..d2e8835 100644 --- a/modules/controlcenter/components/DeviceDetails.qml +++ b/modules/controlcenter/components/DeviceDetails.qml @@ -18,10 +18,7 @@ Item { 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 @@ -35,7 +32,6 @@ Item { anchors.top: parent.top spacing: Appearance.spacing.normal - // Header component (e.g., ConnectionHeader or SettingsHeader) Loader { id: headerLoader @@ -44,7 +40,6 @@ Item { visible: root.headerComponent !== null } - // Top content (optional) Loader { id: topContentLoader @@ -53,7 +48,6 @@ Item { visible: root.topContent !== null } - // Sections Repeater { model: root.sections @@ -65,7 +59,6 @@ Item { } } - // Bottom content (optional) Loader { id: bottomContentLoader diff --git a/modules/controlcenter/components/DeviceList.qml b/modules/controlcenter/components/DeviceList.qml index a6821d8..75dd913 100644 --- a/modules/controlcenter/components/DeviceList.qml +++ b/modules/controlcenter/components/DeviceList.qml @@ -28,7 +28,6 @@ ColumnLayout { spacing: Appearance.spacing.small - // Header with action buttons (optional) Loader { id: headerLoader @@ -37,7 +36,6 @@ ColumnLayout { visible: root.headerComponent !== null && root.showHeader } - // Title and description row RowLayout { Layout.fillWidth: true Layout.topMargin: root.headerComponent ? 0 : 0 @@ -61,10 +59,8 @@ ColumnLayout { } } - // Expose view for access from parent components property alias view: view - // Description text StyledText { visible: root.description !== "" Layout.fillWidth: true @@ -72,20 +68,18 @@ ColumnLayout { color: Colours.palette.m3outline } - // List view StyledListView { id: view Layout.fillWidth: true - // Use contentHeight to show all items without estimation implicitHeight: contentHeight model: root.model delegate: root.delegate spacing: Appearance.spacing.small / 2 - interactive: false // Disable individual scrolling - parent pane handles it - clip: false // Don't clip - let parent handle scrolling + interactive: false + clip: false } } diff --git a/modules/controlcenter/components/PaneTransition.qml b/modules/controlcenter/components/PaneTransition.qml index 1da4afb..d1814b5 100644 --- a/modules/controlcenter/components/PaneTransition.qml +++ b/modules/controlcenter/components/PaneTransition.qml @@ -3,26 +3,17 @@ pragma ComponentBehavior: Bound import qs.config import QtQuick -// Reusable pane transition animation component -// Provides standard fade-out/scale-down → update → fade-in/scale-up animation -// Used when switching between detail/settings views in panes SequentialAnimation { id: root - // The Loader element to animate required property Item target - - // Optional list of PropertyActions to execute during the transition - // These typically update the component being displayed property list propertyActions - // Animation parameters (with sensible defaults) property real scaleFrom: 1.0 property real scaleTo: 0.8 property real opacityFrom: 1.0 property real opacityTo: 0.0 - // Fade out and scale down ParallelAnimation { NumberAnimation { target: root.target @@ -45,8 +36,6 @@ SequentialAnimation { } } - // Execute property actions (component switching, state updates, etc.) - // This is where the component change happens while invisible ScriptAction { script: { for (let i = 0; i < root.propertyActions.length; i++) { @@ -58,7 +47,6 @@ SequentialAnimation { } } - // Fade in and scale up ParallelAnimation { NumberAnimation { target: root.target diff --git a/modules/controlcenter/components/SplitPaneLayout.qml b/modules/controlcenter/components/SplitPaneLayout.qml index 7bd7db0..8b4f0d9 100644 --- a/modules/controlcenter/components/SplitPaneLayout.qml +++ b/modules/controlcenter/components/SplitPaneLayout.qml @@ -15,19 +15,14 @@ RowLayout { property Component leftContent: null property Component rightContent: null - // Left pane configuration property real leftWidthRatio: 0.4 property int leftMinimumWidth: 420 property var leftLoaderProperties: ({}) - - // Right pane configuration property var rightLoaderProperties: ({}) - // Expose loaders for customization (access via splitLayout.leftLoader or splitLayout.rightLoader) property alias leftLoader: leftLoader property alias rightLoader: rightLoader - // Left pane Item { id: leftPane @@ -57,7 +52,6 @@ RowLayout { asynchronous: true sourceComponent: root.leftContent - // Apply any additional properties from leftLoaderProperties Component.onCompleted: { for (const key in root.leftLoaderProperties) { leftLoader[key] = root.leftLoaderProperties[key]; @@ -74,7 +68,6 @@ RowLayout { } } - // Right pane Item { id: rightPane @@ -101,7 +94,6 @@ RowLayout { asynchronous: true sourceComponent: root.rightContent - // Apply any additional properties from rightLoaderProperties Component.onCompleted: { for (const key in root.rightLoaderProperties) { rightLoader[key] = root.rightLoaderProperties[key]; diff --git a/modules/controlcenter/components/SplitPaneWithDetails.qml b/modules/controlcenter/components/SplitPaneWithDetails.qml index 6af8c1a..e873923 100644 --- a/modules/controlcenter/components/SplitPaneWithDetails.qml +++ b/modules/controlcenter/components/SplitPaneWithDetails.qml @@ -19,7 +19,6 @@ Item { property var activeItem: null property var paneIdGenerator: function(item) { return item ? String(item) : ""; } - // Optional: Additional component to overlay on top (e.g., password dialogs) property Component overlayComponent: null SplitPaneLayout { @@ -82,7 +81,6 @@ Item { } } - // Overlay component (e.g., password dialogs) Loader { id: overlayLoader diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml index 803d7e0..47f87cc 100644 --- a/modules/controlcenter/launcher/LauncherPane.qml +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -62,26 +62,20 @@ Item { const appId = root.selectedApp.id || root.selectedApp.entry?.id; - // Create a new array to ensure change detection const hiddenApps = Config.launcher.hiddenApps ? [...Config.launcher.hiddenApps] : []; if (isHidden) { - // Add to hiddenApps if not already there if (!hiddenApps.includes(appId)) { hiddenApps.push(appId); } } else { - // Remove from hiddenApps const index = hiddenApps.indexOf(appId); if (index !== -1) { hiddenApps.splice(index, 1); } } - // Update Config Config.launcher.hiddenApps = hiddenApps; - - // Persist changes to disk Config.save(); } @@ -90,15 +84,13 @@ Item { id: allAppsDb path: `${Paths.state}/apps.sqlite` - entries: DesktopEntries.applications.values // No filter - show all apps + entries: DesktopEntries.applications.values } property string searchText: "" function filterApps(search: string): list { - // If search is empty, return all apps directly if (!search || search.trim() === "") { - // Convert QQmlListProperty to array const apps = []; for (let i = 0; i < allAppsDb.apps.length; i++) { apps.push(allAppsDb.apps[i]); @@ -110,7 +102,6 @@ Item { return []; } - // Prepare apps for fuzzy search const preparedApps = []; for (let i = 0; i < allAppsDb.apps.length; i++) { const app = allAppsDb.apps[i]; @@ -121,14 +112,12 @@ Item { }); } - // Perform fuzzy search const results = Fuzzy.go(search, preparedApps, { all: true, keys: ["name"], scoreFn: r => r[0].score }); - // Return sorted by score (highest first) return results .sort((a, b) => b._score - a._score) .map(r => r.obj._item); @@ -192,7 +181,6 @@ Item { if (root.session.launcher.active) { root.session.launcher.active = null; } else { - // Toggle to show settings - if there are apps, select the first one, otherwise show settings if (root.filteredApps.length > 0) { root.session.launcher.active = root.filteredApps[0]; } @@ -302,13 +290,7 @@ Item { Layout.fillWidth: true Layout.fillHeight: true asynchronous: true - active: { - // Lazy load: activate when left pane is loaded - // The ListView will load asynchronously, and search will work because filteredApps - // is updated regardless of whether the ListView is loaded - // Access loader through parent - this will be set when component loads - return true; - } + active: true sourceComponent: StyledListView { id: appsListView @@ -418,11 +400,9 @@ Item { sourceComponent: rightLauncherPane.targetComponent active: true - // Expose displayedApp to loaded components property var displayedApp: rightLauncherPane.displayedApp onItemChanged: { - // Ensure displayedApp is set when item is created (for async loading) if (item && rightLauncherPane.pane && rightLauncherPane.displayedApp !== rightLauncherPane.pane) { rightLauncherPane.displayedApp = rightLauncherPane.pane; } @@ -508,12 +488,10 @@ Item { id: appDetailsLayout anchors.fill: parent - // Get displayedApp from parent Loader (the Loader has displayedApp property we set) readonly property var displayedApp: parent && parent.displayedApp !== undefined ? parent.displayedApp : null spacing: Appearance.spacing.normal - // Show SettingsHeader when no app is selected, or show app icon + title when app is selected SettingsHeader { Layout.leftMargin: Appearance.padding.large * 2 Layout.rightMargin: Appearance.padding.large * 2 @@ -523,7 +501,6 @@ Item { title: qsTr("Launcher Applications") } - // App icon and title display (shown when app is selected) Item { Layout.alignment: Qt.AlignHCenter Layout.leftMargin: Appearance.padding.large * 2 diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index e28d35c..4446428 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -45,7 +45,6 @@ Item { anchors.right: parent.right spacing: Appearance.spacing.normal - // Network header above the collapsible sections RowLayout { Layout.fillWidth: true spacing: Appearance.spacing.smaller @@ -102,7 +101,6 @@ Item { root.session.ethernet.active = null; root.session.network.active = null; } else { - // Toggle to show settings - prefer ethernet if available, otherwise wireless if (Nmcli.ethernetDevices.length > 0) { root.session.ethernet.active = Nmcli.ethernetDevices[0]; } else if (Nmcli.networks.length > 0) { diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index 7f6a4aa..cf16400 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -27,7 +27,6 @@ DeviceDetails { } onNetworkChanged: { - // Restart timer when network changes connectionUpdateTimer.stop(); if (network && network.ssid) { connectionUpdateTimer.start(); @@ -48,11 +47,9 @@ DeviceDetails { updateDeviceDetails(); } function onWirelessDeviceDetailsChanged() { - // When details are updated, check if we should stop the timer if (network && network.ssid) { const isActive = network.active || (Nmcli.active && Nmcli.active.ssid === network.ssid); if (isActive && Nmcli.wirelessDeviceDetails && Nmcli.wirelessDeviceDetails !== null) { - // We have details for the active network, stop the timer connectionUpdateTimer.stop(); } } @@ -65,22 +62,16 @@ DeviceDetails { repeat: true running: network && network.ssid onTriggered: { - // Periodically check if network becomes active and update details if (network) { const isActive = network.active || (Nmcli.active && Nmcli.active.ssid === network.ssid); if (isActive) { - // Network is active - check if we have details if (!Nmcli.wirelessDeviceDetails || Nmcli.wirelessDeviceDetails === null) { - // Network is active but we don't have details yet, fetch them Nmcli.getWirelessDeviceDetails("", () => { - // After fetching, check if we got details - if not, timer will try again }); } else { - // We have details, can stop the timer connectionUpdateTimer.stop(); } } else { - // Network is not active, clear details if (Nmcli.wirelessDeviceDetails !== null) { Nmcli.wirelessDeviceDetails = null; } diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml index 4726712..9dabe9d 100644 --- a/modules/controlcenter/network/WirelessList.qml +++ b/modules/controlcenter/network/WirelessList.qml @@ -33,10 +33,8 @@ DeviceList { 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; }) } @@ -114,7 +112,6 @@ DeviceList { StateLayer { function onClicked(): void { root.session.network.active = modelData; - // Check if we need to refresh saved connections when selecting a network if (modelData && modelData.ssid) { root.checkSavedProfileForNetwork(modelData.ssid); } diff --git a/modules/controlcenter/network/WirelessPasswordDialog.qml b/modules/controlcenter/network/WirelessPasswordDialog.qml index 0f1a5cd..7c046af 100644 --- a/modules/controlcenter/network/WirelessPasswordDialog.qml +++ b/modules/controlcenter/network/WirelessPasswordDialog.qml @@ -19,7 +19,6 @@ Item { required property Session session readonly property var network: { - // Prefer pendingNetwork, then active network if (session.network.pendingNetwork) { return session.network.pendingNetwork; } @@ -157,12 +156,10 @@ Item { focus: true Keys.onPressed: event => { - // Ensure we have focus when receiving keyboard input if (!activeFocus) { forceActiveFocus(); } - // Clear error when user starts typing if (connectButton.hasError && event.text && event.text.length > 0) { connectButton.hasError = false; } @@ -191,7 +188,6 @@ Item { target: root.session.network function onShowPasswordDialogChanged(): void { if (root.session.network.showPasswordDialog) { - // Use callLater to ensure focus happens after dialog is fully rendered Qt.callLater(() => { passwordContainer.forceActiveFocus(); passwordContainer.passwordBuffer = ""; @@ -205,7 +201,6 @@ Item { target: root function onVisibleChanged(): void { if (root.visible) { - // Use callLater to ensure focus happens after dialog is fully rendered Qt.callLater(() => { passwordContainer.forceActiveFocus(); }); @@ -383,46 +378,36 @@ Item { return; } - // Clear any previous error hasError = false; - - // Set connecting state connecting = true; enabled = false; text = qsTr("Connecting..."); - // Connect to network NetworkConnection.connectWithPassword(root.network, password, result => { - if (result && result.success) - // Connection successful, monitor will handle the rest - {} else if (result && result.needsPassword) { - // Shouldn't happen since we provided password + if (result && result.success) { + } else if (result && result.needsPassword) { connectionMonitor.stop(); connecting = false; hasError = true; enabled = true; text = qsTr("Connect"); passwordContainer.passwordBuffer = ""; - // Delete the failed connection if (root.network && root.network.ssid) { Nmcli.forgetNetwork(root.network.ssid); } } else { - // Connection failed immediately - show error connectionMonitor.stop(); connecting = false; hasError = true; enabled = true; text = qsTr("Connect"); passwordContainer.passwordBuffer = ""; - // Delete the failed connection if (root.network && root.network.ssid) { Nmcli.forgetNetwork(root.network.ssid); } } }); - // Start monitoring connection connectionMonitor.start(); } } @@ -435,19 +420,14 @@ Item { return; } - // Check if we're connected to the target network (case-insensitive SSID comparison) const isConnected = root.network && Nmcli.active && Nmcli.active.ssid && Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); if (isConnected) { - // Successfully connected - give it a moment for network list to update - // Use Timer for actual delay connectionSuccessTimer.start(); return; } - // Check for connection failures - if pending connection was cleared but we're not connected if (Nmcli.pendingConnection === null && connectButton.connecting) { - // Wait a bit more before giving up (allow time for connection to establish) if (connectionMonitor.repeatCount > 10) { connectionMonitor.stop(); connectButton.connecting = false; @@ -455,7 +435,6 @@ Item { connectButton.enabled = true; connectButton.text = qsTr("Connect"); passwordContainer.passwordBuffer = ""; - // Delete the failed connection if (root.network && root.network.ssid) { Nmcli.forgetNetwork(root.network.ssid); } @@ -486,7 +465,6 @@ Item { id: connectionSuccessTimer interval: 500 onTriggered: { - // Double-check connection is still active if (root.visible && Nmcli.active && Nmcli.active.ssid) { const stillConnected = Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim(); if (stillConnected) { @@ -514,7 +492,6 @@ Item { connectButton.enabled = true; connectButton.text = qsTr("Connect"); passwordContainer.passwordBuffer = ""; - // Delete the failed connection Nmcli.forgetNetwork(ssid); } } diff --git a/modules/controlcenter/state/BluetoothState.qml b/modules/controlcenter/state/BluetoothState.qml index db8c7e1..00497ce 100644 --- a/modules/controlcenter/state/BluetoothState.qml +++ b/modules/controlcenter/state/BluetoothState.qml @@ -4,13 +4,8 @@ import QtQuick QtObject { id: root - // Active selected device property BluetoothDevice active: null - - // Current adapter being used property BluetoothAdapter currentAdapter: Bluetooth.defaultAdapter - - // UI state flags property bool editingAdapterName: false property bool fabMenuOpen: false property bool editingDeviceName: false diff --git a/modules/controlcenter/state/EthernetState.qml b/modules/controlcenter/state/EthernetState.qml index 25b243a..c5da7aa 100644 --- a/modules/controlcenter/state/EthernetState.qml +++ b/modules/controlcenter/state/EthernetState.qml @@ -3,7 +3,6 @@ import QtQuick QtObject { id: root - // Active selected ethernet interface property var active: null } diff --git a/modules/controlcenter/state/LauncherState.qml b/modules/controlcenter/state/LauncherState.qml index cd9eeb6..c5da7aa 100644 --- a/modules/controlcenter/state/LauncherState.qml +++ b/modules/controlcenter/state/LauncherState.qml @@ -3,7 +3,6 @@ import QtQuick QtObject { id: root - // Active selected application property var active: null } diff --git a/modules/controlcenter/state/NetworkState.qml b/modules/controlcenter/state/NetworkState.qml index 651a35c..da13e65 100644 --- a/modules/controlcenter/state/NetworkState.qml +++ b/modules/controlcenter/state/NetworkState.qml @@ -3,10 +3,7 @@ import QtQuick QtObject { id: root - // Active selected wireless network property var active: null - - // Password dialog state property bool showPasswordDialog: false property var pendingNetwork: null } diff --git a/modules/controlcenter/taskbar/TaskbarPane.qml b/modules/controlcenter/taskbar/TaskbarPane.qml index 1c3adbc..f452b07 100644 --- a/modules/controlcenter/taskbar/TaskbarPane.qml +++ b/modules/controlcenter/taskbar/TaskbarPane.qml @@ -18,15 +18,10 @@ Item { required property Session session - // Clock property bool clockShowIcon: Config.bar.clock.showIcon ?? true - - // Bar Behavior property bool persistent: Config.bar.persistent ?? true property bool showOnHover: Config.bar.showOnHover ?? true property int dragThreshold: Config.bar.dragThreshold ?? 20 - - // Status Icons property bool showAudio: Config.bar.status.showAudio ?? true property bool showMicrophone: Config.bar.status.showMicrophone ?? true property bool showKbLayout: Config.bar.status.showKbLayout ?? false @@ -34,25 +29,17 @@ Item { property bool showBluetooth: Config.bar.status.showBluetooth ?? true property bool showBattery: Config.bar.status.showBattery ?? true property bool showLockStatus: Config.bar.status.showLockStatus ?? true - - // Tray Settings property bool trayBackground: Config.bar.tray.background ?? false property bool trayCompact: Config.bar.tray.compact ?? false property bool trayRecolour: Config.bar.tray.recolour ?? false - - // Workspaces property int workspacesShown: Config.bar.workspaces.shown ?? 5 property bool workspacesActiveIndicator: Config.bar.workspaces.activeIndicator ?? true property bool workspacesOccupiedBg: Config.bar.workspaces.occupiedBg ?? false property bool workspacesShowWindows: Config.bar.workspaces.showWindows ?? false property bool workspacesPerMonitor: Config.bar.workspaces.perMonitorWorkspaces ?? true - - // Scroll Actions property bool scrollWorkspaces: Config.bar.scrollActions.workspaces ?? true property bool scrollVolume: Config.bar.scrollActions.volume ?? true property bool scrollBrightness: Config.bar.scrollActions.brightness ?? true - - // Popouts property bool popoutActiveWindow: Config.bar.popouts.activeWindow ?? true property bool popoutTray: Config.bar.popouts.tray ?? true property bool popoutStatusIcons: Config.bar.popouts.statusIcons ?? true @@ -60,7 +47,6 @@ Item { anchors.fill: parent Component.onCompleted: { - // Update entries if (Config.bar.entries) { entriesModel.clear(); for (let i = 0; i < Config.bar.entries.length; i++) { @@ -74,15 +60,10 @@ Item { } function saveConfig(entryIndex, entryEnabled) { - // Update clock setting Config.bar.clock.showIcon = root.clockShowIcon; - - // Update bar behavior Config.bar.persistent = root.persistent; Config.bar.showOnHover = root.showOnHover; Config.bar.dragThreshold = root.dragThreshold; - - // Update status icons Config.bar.status.showAudio = root.showAudio; Config.bar.status.showMicrophone = root.showMicrophone; Config.bar.status.showKbLayout = root.showKbLayout; @@ -90,35 +71,24 @@ Item { Config.bar.status.showBluetooth = root.showBluetooth; Config.bar.status.showBattery = root.showBattery; Config.bar.status.showLockStatus = root.showLockStatus; - - // Update tray settings Config.bar.tray.background = root.trayBackground; Config.bar.tray.compact = root.trayCompact; Config.bar.tray.recolour = root.trayRecolour; - - // Update workspaces Config.bar.workspaces.shown = root.workspacesShown; Config.bar.workspaces.activeIndicator = root.workspacesActiveIndicator; Config.bar.workspaces.occupiedBg = root.workspacesOccupiedBg; Config.bar.workspaces.showWindows = root.workspacesShowWindows; Config.bar.workspaces.perMonitorWorkspaces = root.workspacesPerMonitor; - - // Update scroll actions Config.bar.scrollActions.workspaces = root.scrollWorkspaces; Config.bar.scrollActions.volume = root.scrollVolume; Config.bar.scrollActions.brightness = root.scrollBrightness; - - // Update popouts Config.bar.popouts.activeWindow = root.popoutActiveWindow; Config.bar.popouts.tray = root.popoutTray; Config.bar.popouts.statusIcons = root.popoutStatusIcons; - // Update entries from the model (same approach as clock - use provided value if available) const entries = []; for (let i = 0; i < entriesModel.count; i++) { const entry = entriesModel.get(i); - // If this is the entry being updated, use the provided value (same as clock toggle reads from switch) - // Otherwise use the value from the model let enabled = entry.enabled; if (entryIndex !== undefined && i === entryIndex) { enabled = entryEnabled; @@ -129,8 +99,6 @@ Item { }); } Config.bar.entries = entries; - - // Persist changes to disk Config.save(); } -- cgit v1.2.3-freya From a243b6148b03d3effb7b86993f8ce89911e49b80 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 19 Nov 2025 21:57:55 -0500 Subject: refactor: replaced input fields with SliderInput components --- .../controlcenter/appearance/AppearancePane.qml | 1122 +++----------------- modules/controlcenter/components/DeviceDetails.qml | 2 +- modules/controlcenter/components/SliderInput.qml | 207 ++++ .../components/controls/SliderInput.qml | 179 ++++ modules/controlcenter/network/EthernetDetails.qml | 4 +- modules/controlcenter/network/WirelessDetails.qml | 4 +- modules/controlcenter/taskbar/TaskbarPane.qml | 106 +- 7 files changed, 550 insertions(+), 1074 deletions(-) create mode 100644 modules/controlcenter/components/SliderInput.qml create mode 100644 modules/controlcenter/components/controls/SliderInput.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index 5b7e859..dec260d 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -962,98 +962,20 @@ Item { SectionContainer { contentSpacing: Appearance.spacing.normal - ColumnLayout { + SliderInput { Layout.fillWidth: true - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - text: qsTr("Animation duration scale") - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } - - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: animDurationsInput.implicitHeight + Appearance.padding.small * 2 - color: animDurationsInputHover.containsMouse || animDurationsInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: animDurationsInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: animDurationsInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } - - StyledTextField { - id: animDurationsInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.1; top: 5.0 } - - Component.onCompleted: { - text = (rootPane.animDurationsScale).toFixed(1); - } - - onTextChanged: { - if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0.1 && val <= 5.0) { - rootPane.animDurationsScale = val; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0.1 || val > 5.0) { - text = (rootPane.animDurationsScale).toFixed(1); - } - } - } - } - - StyledText { - text: "×" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - } - } - - StyledSlider { - id: animDurationsSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0.1 - to: 5.0 - value: rootPane.animDurationsScale - onMoved: { - rootPane.animDurationsScale = animDurationsSlider.value; - if (!animDurationsInput.activeFocus) { - animDurationsInput.text = (animDurationsSlider.value).toFixed(1); - } - rootPane.saveConfig(); - } + + label: qsTr("Animation duration scale") + value: rootPane.animDurationsScale + from: 0.1 + to: 5.0 + decimals: 1 + suffix: "×" + validator: DoubleValidator { bottom: 0.1; top: 5.0 } + + onValueModified: (newValue) => { + rootPane.animDurationsScale = newValue; + rootPane.saveConfig(); } } } @@ -1312,98 +1234,20 @@ Item { SectionContainer { contentSpacing: Appearance.spacing.normal - ColumnLayout { + SliderInput { Layout.fillWidth: true - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - text: qsTr("Font size scale") - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } - - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: fontSizeInput.implicitHeight + Appearance.padding.small * 2 - color: fontSizeInputHover.containsMouse || fontSizeInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: fontSizeInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: fontSizeInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } - - StyledTextField { - id: fontSizeInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.7; top: 1.5 } - - Component.onCompleted: { - text = (rootPane.fontSizeScale).toFixed(1); - } - - onTextChanged: { - if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0.7 && val <= 1.5) { - rootPane.fontSizeScale = val; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0.7 || val > 1.5) { - text = (rootPane.fontSizeScale).toFixed(1); - } - } - } - } - - StyledText { - text: "×" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - } - } - - StyledSlider { - id: fontSizeSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0.7 - to: 1.5 - value: rootPane.fontSizeScale - onMoved: { - rootPane.fontSizeScale = fontSizeSlider.value; - if (!fontSizeInput.activeFocus) { - fontSizeInput.text = (fontSizeSlider.value).toFixed(1); - } - rootPane.saveConfig(); - } + + label: qsTr("Font size scale") + value: rootPane.fontSizeScale + from: 0.7 + to: 1.5 + decimals: 2 + suffix: "×" + validator: DoubleValidator { bottom: 0.7; top: 1.5 } + + onValueModified: (newValue) => { + rootPane.fontSizeScale = newValue; + rootPane.saveConfig(); } } } @@ -1417,98 +1261,20 @@ Item { SectionContainer { contentSpacing: Appearance.spacing.normal - ColumnLayout { + SliderInput { Layout.fillWidth: true - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - text: qsTr("Padding scale") - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } - - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: paddingInput.implicitHeight + Appearance.padding.small * 2 - color: paddingInputHover.containsMouse || paddingInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: paddingInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: paddingInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } - - StyledTextField { - id: paddingInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.5; top: 2.0 } - - Component.onCompleted: { - text = (rootPane.paddingScale).toFixed(1); - } - - onTextChanged: { - if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0.5 && val <= 2.0) { - rootPane.paddingScale = val; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0.5 || val > 2.0) { - text = (rootPane.paddingScale).toFixed(1); - } - } - } - } - - StyledText { - text: "×" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - } - } - - StyledSlider { - id: paddingSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0.5 - to: 2.0 - value: rootPane.paddingScale - onMoved: { - rootPane.paddingScale = paddingSlider.value; - if (!paddingInput.activeFocus) { - paddingInput.text = (paddingSlider.value).toFixed(1); - } - rootPane.saveConfig(); - } + + label: qsTr("Padding scale") + value: rootPane.paddingScale + from: 0.5 + to: 2.0 + decimals: 1 + suffix: "×" + validator: DoubleValidator { bottom: 0.5; top: 2.0 } + + onValueModified: (newValue) => { + rootPane.paddingScale = newValue; + rootPane.saveConfig(); } } } @@ -1516,98 +1282,20 @@ Item { SectionContainer { contentSpacing: Appearance.spacing.normal - ColumnLayout { + SliderInput { Layout.fillWidth: true - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - text: qsTr("Rounding scale") - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } - - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: roundingInput.implicitHeight + Appearance.padding.small * 2 - color: roundingInputHover.containsMouse || roundingInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: roundingInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: roundingInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } - - StyledTextField { - id: roundingInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.1; top: 5.0 } - - Component.onCompleted: { - text = (rootPane.roundingScale).toFixed(1); - } - - onTextChanged: { - if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0.1 && val <= 5.0) { - rootPane.roundingScale = val; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0.1 || val > 5.0) { - text = (rootPane.roundingScale).toFixed(1); - } - } - } - } - - StyledText { - text: "×" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - } - } - - StyledSlider { - id: roundingSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0.1 - to: 5.0 - value: rootPane.roundingScale - onMoved: { - rootPane.roundingScale = roundingSlider.value; - if (!roundingInput.activeFocus) { - roundingInput.text = (roundingSlider.value).toFixed(1); - } - rootPane.saveConfig(); - } + + label: qsTr("Rounding scale") + value: rootPane.roundingScale + from: 0.1 + to: 5.0 + decimals: 1 + suffix: "×" + validator: DoubleValidator { bottom: 0.1; top: 5.0 } + + onValueModified: (newValue) => { + rootPane.roundingScale = newValue; + rootPane.saveConfig(); } } } @@ -1615,98 +1303,20 @@ Item { SectionContainer { contentSpacing: Appearance.spacing.normal - ColumnLayout { + SliderInput { Layout.fillWidth: true - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - text: qsTr("Spacing scale") - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } - - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: spacingInput.implicitHeight + Appearance.padding.small * 2 - color: spacingInputHover.containsMouse || spacingInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: spacingInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: spacingInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } - - StyledTextField { - id: spacingInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.1; top: 2.0 } - - Component.onCompleted: { - text = (rootPane.spacingScale).toFixed(1); - } - - onTextChanged: { - if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0.1 && val <= 2.0) { - rootPane.spacingScale = val; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0.1 || val > 2.0) { - text = (rootPane.spacingScale).toFixed(1); - } - } - } - } - - StyledText { - text: "×" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - } - } - - StyledSlider { - id: spacingSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0.1 - to: 2.0 - value: rootPane.spacingScale - onMoved: { - rootPane.spacingScale = spacingSlider.value; - if (!spacingInput.activeFocus) { - spacingInput.text = (spacingSlider.value).toFixed(1); - } - rootPane.saveConfig(); - } + + label: qsTr("Spacing scale") + value: rootPane.spacingScale + from: 0.1 + to: 2.0 + decimals: 1 + suffix: "×" + validator: DoubleValidator { bottom: 0.1; top: 2.0 } + + onValueModified: (newValue) => { + rootPane.spacingScale = newValue; + rootPane.saveConfig(); } } } @@ -1729,98 +1339,21 @@ Item { SectionContainer { contentSpacing: Appearance.spacing.normal - ColumnLayout { + SliderInput { Layout.fillWidth: true - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - text: qsTr("Transparency base") - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } - - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: transparencyBaseInput.implicitHeight + Appearance.padding.small * 2 - color: transparencyBaseInputHover.containsMouse || transparencyBaseInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: transparencyBaseInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: transparencyBaseInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } - - StyledTextField { - id: transparencyBaseInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: IntValidator { bottom: 0; top: 100 } - - Component.onCompleted: { - text = Math.round(rootPane.transparencyBase * 100).toString(); - } - - onTextChanged: { - if (activeFocus) { - const val = parseInt(text); - if (!isNaN(val) && val >= 0 && val <= 100) { - rootPane.transparencyBase = val / 100; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseInt(text); - if (isNaN(val) || val < 0 || val > 100) { - text = Math.round(rootPane.transparencyBase * 100).toString(); - } - } - } - } - - StyledText { - text: "%" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - } - } - - StyledSlider { - id: baseSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0 - to: 100 - value: rootPane.transparencyBase * 100 - onMoved: { - rootPane.transparencyBase = baseSlider.value / 100; - if (!transparencyBaseInput.activeFocus) { - transparencyBaseInput.text = Math.round(baseSlider.value).toString(); - } - rootPane.saveConfig(); - } + + label: qsTr("Transparency base") + value: rootPane.transparencyBase * 100 + from: 0 + to: 100 + suffix: "%" + validator: IntValidator { bottom: 0; top: 100 } + formatValueFunction: (val) => Math.round(val).toString() + parseValueFunction: (text) => parseInt(text) + + onValueModified: (newValue) => { + rootPane.transparencyBase = newValue / 100; + rootPane.saveConfig(); } } } @@ -1828,98 +1361,21 @@ Item { SectionContainer { contentSpacing: Appearance.spacing.normal - ColumnLayout { + SliderInput { Layout.fillWidth: true - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - text: qsTr("Transparency layers") - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } - - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: transparencyLayersInput.implicitHeight + Appearance.padding.small * 2 - color: transparencyLayersInputHover.containsMouse || transparencyLayersInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: transparencyLayersInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: transparencyLayersInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } - - StyledTextField { - id: transparencyLayersInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: IntValidator { bottom: 0; top: 100 } - - Component.onCompleted: { - text = Math.round(rootPane.transparencyLayers * 100).toString(); - } - - onTextChanged: { - if (activeFocus) { - const val = parseInt(text); - if (!isNaN(val) && val >= 0 && val <= 100) { - rootPane.transparencyLayers = val / 100; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseInt(text); - if (isNaN(val) || val < 0 || val > 100) { - text = Math.round(rootPane.transparencyLayers * 100).toString(); - } - } - } - } - - StyledText { - text: "%" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - } - } - - StyledSlider { - id: layersSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0 - to: 100 - value: rootPane.transparencyLayers * 100 - onMoved: { - rootPane.transparencyLayers = layersSlider.value / 100; - if (!transparencyLayersInput.activeFocus) { - transparencyLayersInput.text = Math.round(layersSlider.value).toString(); - } - rootPane.saveConfig(); - } + + label: qsTr("Transparency layers") + value: rootPane.transparencyLayers * 100 + from: 0 + to: 100 + suffix: "%" + validator: IntValidator { bottom: 0; top: 100 } + formatValueFunction: (val) => Math.round(val).toString() + parseValueFunction: (text) => parseInt(text) + + onValueModified: (newValue) => { + rootPane.transparencyLayers = newValue / 100; + rootPane.saveConfig(); } } } @@ -1933,92 +1389,20 @@ Item { SectionContainer { contentSpacing: Appearance.spacing.normal - ColumnLayout { + SliderInput { Layout.fillWidth: true - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - text: qsTr("Border rounding") - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } - - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: borderRoundingInput.implicitHeight + Appearance.padding.small * 2 - color: borderRoundingInputHover.containsMouse || borderRoundingInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: borderRoundingInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: borderRoundingInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } - - StyledTextField { - id: borderRoundingInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.1; top: 100 } - - Component.onCompleted: { - text = (rootPane.borderRounding).toFixed(1); - } - - onTextChanged: { - if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0.1 && val <= 100) { - rootPane.borderRounding = val; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0.1 || val > 100) { - text = (rootPane.borderRounding).toFixed(1); - } - } - } - } - } - - StyledSlider { - id: borderRoundingSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0.1 - to: 100 - value: rootPane.borderRounding - onMoved: { - rootPane.borderRounding = borderRoundingSlider.value; - if (!borderRoundingInput.activeFocus) { - borderRoundingInput.text = (borderRoundingSlider.value).toFixed(1); - } - rootPane.saveConfig(); - } + + label: qsTr("Border rounding") + value: rootPane.borderRounding + from: 0.1 + to: 100 + decimals: 1 + suffix: "px" + validator: DoubleValidator { bottom: 0.1; top: 100 } + + onValueModified: (newValue) => { + rootPane.borderRounding = newValue; + rootPane.saveConfig(); } } } @@ -2026,92 +1410,20 @@ Item { SectionContainer { contentSpacing: Appearance.spacing.normal - ColumnLayout { + SliderInput { Layout.fillWidth: true - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - text: qsTr("Border thickness") - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } - - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: borderThicknessInput.implicitHeight + Appearance.padding.small * 2 - color: borderThicknessInputHover.containsMouse || borderThicknessInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: borderThicknessInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: borderThicknessInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } - - StyledTextField { - id: borderThicknessInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0.1; top: 100 } - - Component.onCompleted: { - text = (rootPane.borderThickness).toFixed(1); - } - - onTextChanged: { - if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0.1 && val <= 100) { - rootPane.borderThickness = val; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0.1 || val > 100) { - text = (rootPane.borderThickness).toFixed(1); - } - } - } - } - } - - StyledSlider { - id: borderThicknessSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0.1 - to: 100 - value: rootPane.borderThickness - onMoved: { - rootPane.borderThickness = borderThicknessSlider.value; - if (!borderThicknessInput.activeFocus) { - borderThicknessInput.text = (borderThicknessSlider.value).toFixed(1); - } - rootPane.saveConfig(); - } + + label: qsTr("Border thickness") + value: rootPane.borderThickness + from: 0.1 + to: 100 + decimals: 1 + suffix: "px" + validator: DoubleValidator { bottom: 0.1; top: 100 } + + onValueModified: (newValue) => { + rootPane.borderThickness = newValue; + rootPane.saveConfig(); } } } @@ -2168,186 +1480,40 @@ Item { SectionContainer { contentSpacing: Appearance.spacing.normal - ColumnLayout { + SliderInput { Layout.fillWidth: true - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - text: qsTr("Visualiser rounding") - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } - - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: visualiserRoundingInput.implicitHeight + Appearance.padding.small * 2 - color: visualiserRoundingInputHover.containsMouse || visualiserRoundingInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: visualiserRoundingInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: visualiserRoundingInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } - - StyledTextField { - id: visualiserRoundingInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: IntValidator { bottom: 0; top: 10 } - - Component.onCompleted: { - text = Math.round(rootPane.visualiserRounding).toString(); - } - - onTextChanged: { - if (activeFocus) { - const val = parseInt(text); - if (!isNaN(val) && val >= 0 && val <= 10) { - rootPane.visualiserRounding = val; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseInt(text); - if (isNaN(val) || val < 0 || val > 10) { - text = Math.round(rootPane.visualiserRounding).toString(); - } - } - } - } + + label: qsTr("Visualiser rounding") + value: rootPane.visualiserRounding + from: 0 + to: 10 + stepSize: 1 + validator: IntValidator { bottom: 0; top: 10 } + formatValueFunction: (val) => Math.round(val).toString() + parseValueFunction: (text) => parseInt(text) + + onValueModified: (newValue) => { + rootPane.visualiserRounding = Math.round(newValue); + rootPane.saveConfig(); } - - StyledSlider { - id: visualiserRoundingSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0 - to: 10 - stepSize: 1 - value: rootPane.visualiserRounding - onMoved: { - rootPane.visualiserRounding = Math.round(visualiserRoundingSlider.value); - if (!visualiserRoundingInput.activeFocus) { - visualiserRoundingInput.text = Math.round(visualiserRoundingSlider.value).toString(); - } - rootPane.saveConfig(); } } - } - } SectionContainer { contentSpacing: Appearance.spacing.normal - ColumnLayout { + SliderInput { Layout.fillWidth: true - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - text: qsTr("Visualiser spacing") - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } - - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: visualiserSpacingInput.implicitHeight + Appearance.padding.small * 2 - color: visualiserSpacingInputHover.containsMouse || visualiserSpacingInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: visualiserSpacingInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: visualiserSpacingInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } - - StyledTextField { - id: visualiserSpacingInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: DoubleValidator { bottom: 0; top: 2 } - - Component.onCompleted: { - text = (rootPane.visualiserSpacing).toFixed(1); - } - - onTextChanged: { - if (activeFocus) { - const val = parseFloat(text); - if (!isNaN(val) && val >= 0 && val <= 2) { - rootPane.visualiserSpacing = val; - rootPane.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseFloat(text); - if (isNaN(val) || val < 0 || val > 2) { - text = (rootPane.visualiserSpacing).toFixed(1); - } - } - } - } - } - - StyledSlider { - id: visualiserSpacingSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0 - to: 2 - value: rootPane.visualiserSpacing - onMoved: { - rootPane.visualiserSpacing = visualiserSpacingSlider.value; - if (!visualiserSpacingInput.activeFocus) { - visualiserSpacingInput.text = (visualiserSpacingSlider.value).toFixed(1); - } - rootPane.saveConfig(); - } + + label: qsTr("Visualiser spacing") + value: rootPane.visualiserSpacing + from: 0 + to: 2 + validator: DoubleValidator { bottom: 0; top: 2 } + + onValueModified: (newValue) => { + rootPane.visualiserSpacing = newValue; + rootPane.saveConfig(); } } } diff --git a/modules/controlcenter/components/DeviceDetails.qml b/modules/controlcenter/components/DeviceDetails.qml index d2e8835..8cc9177 100644 --- a/modules/controlcenter/components/DeviceDetails.qml +++ b/modules/controlcenter/components/DeviceDetails.qml @@ -12,7 +12,7 @@ import QtQuick.Layouts Item { id: root - required property Session session + property Session session property var device: null property Component headerComponent: null diff --git a/modules/controlcenter/components/SliderInput.qml b/modules/controlcenter/components/SliderInput.qml new file mode 100644 index 0000000..3d7cd4d --- /dev/null +++ b/modules/controlcenter/components/SliderInput.qml @@ -0,0 +1,207 @@ +pragma ComponentBehavior: Bound + +import qs.components +import qs.components.controls +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + property string label: "" + property real value: 0 + property real from: 0 + property real to: 100 + property real stepSize: 0 + property var validator: null + property string suffix: "" // Optional suffix text (e.g., "×", "px") + property int decimals: 1 // Number of decimal places to show (default: 1) + property var formatValueFunction: null // Optional custom format function + property var parseValueFunction: null // Optional custom parse function + + function formatValue(val: real): string { + if (formatValueFunction) { + return formatValueFunction(val); + } + // Default format function + // Check if it's an IntValidator (IntValidator doesn't have a 'decimals' property) + if (validator && validator.bottom !== undefined && validator.decimals === undefined) { + return Math.round(val).toString(); + } + // For DoubleValidator or no validator, use the decimals property + return val.toFixed(root.decimals); + } + + function parseValue(text: string): real { + if (parseValueFunction) { + return parseValueFunction(text); + } + // Default parse function + if (validator && validator.bottom !== undefined) { + // Check if it's an integer validator + if (validator.top !== undefined && validator.top === Math.floor(validator.top)) { + return parseInt(text); + } + } + return parseFloat(text); + } + + signal valueModified(real newValue) + + property bool _initialized: false + + spacing: Appearance.spacing.small + + Component.onCompleted: { + // Set initialized flag after a brief delay to allow component to fully load + Qt.callLater(() => { + _initialized = true; + }); + } + + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + StyledText { + visible: root.label !== "" + text: root.label + font.pointSize: Appearance.font.size.normal + } + + Item { + Layout.fillWidth: true + } + + StyledRect { + Layout.preferredWidth: 70 + implicitHeight: inputField.implicitHeight + Appearance.padding.small * 2 + color: inputHover.containsMouse || inputField.activeFocus + ? Colours.layer(Colours.palette.m3surfaceContainer, 3) + : Colours.layer(Colours.palette.m3surfaceContainer, 2) + radius: Appearance.rounding.small + border.width: 1 + border.color: inputField.activeFocus + ? Colours.palette.m3primary + : Qt.alpha(Colours.palette.m3outline, 0.3) + + Behavior on color { CAnim {} } + Behavior on border.color { CAnim {} } + + MouseArea { + id: inputHover + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.NoButton + } + + StyledTextField { + id: inputField + anchors.centerIn: parent + width: parent.width - Appearance.padding.normal + horizontalAlignment: TextInput.AlignHCenter + validator: root.validator + + Component.onCompleted: { + // Initialize text without triggering valueModified signal + text = root.formatValue(root.value); + } + + onTextChanged: { + if (activeFocus) { + const val = root.parseValue(text); + if (!isNaN(val)) { + // Validate against validator bounds if available + let isValid = true; + if (root.validator) { + if (root.validator.bottom !== undefined && val < root.validator.bottom) { + isValid = false; + } + if (root.validator.top !== undefined && val > root.validator.top) { + isValid = false; + } + } + + if (isValid) { + root.valueModified(val); + } + } + } + } + + onEditingFinished: { + const val = root.parseValue(text); + let isValid = true; + if (root.validator) { + if (root.validator.bottom !== undefined && val < root.validator.bottom) { + isValid = false; + } + if (root.validator.top !== undefined && val > root.validator.top) { + isValid = false; + } + } + + if (isNaN(val) || !isValid) { + text = root.formatValue(root.value); + } + } + } + } + + StyledText { + visible: root.suffix !== "" + text: root.suffix + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.normal + } + } + + StyledSlider { + id: slider + + Layout.fillWidth: true + implicitHeight: Appearance.padding.normal * 3 + + from: root.from + to: root.to + stepSize: root.stepSize + + // Use Binding to allow slider to move freely during dragging + Binding { + target: slider + property: "value" + value: root.value + when: !slider.pressed + } + + onValueChanged: { + // Update input field text in real-time as slider moves during dragging + // Always update when slider value changes (during dragging or external updates) + if (!inputField.activeFocus) { + const newValue = root.stepSize > 0 ? Math.round(value / root.stepSize) * root.stepSize : value; + inputField.text = root.formatValue(newValue); + } + } + + onMoved: { + const newValue = root.stepSize > 0 ? Math.round(value / root.stepSize) * root.stepSize : value; + root.valueModified(newValue); + if (!inputField.activeFocus) { + inputField.text = root.formatValue(newValue); + } + } + } + + // Update input field when value changes externally (slider is already bound) + onValueChanged: { + // Only update if component is initialized to avoid issues during creation + if (root._initialized && !inputField.activeFocus) { + inputField.text = root.formatValue(root.value); + } + } +} + diff --git a/modules/controlcenter/components/controls/SliderInput.qml b/modules/controlcenter/components/controls/SliderInput.qml new file mode 100644 index 0000000..a114f7f --- /dev/null +++ b/modules/controlcenter/components/controls/SliderInput.qml @@ -0,0 +1,179 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.effects +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + property string label: "" + property real value: 0 + property real from: 0 + property real to: 100 + property real stepSize: 0 + property var validator: null + property string suffix: "" // Optional suffix text (e.g., "×", "px") + property var formatValueFunction: null // Optional custom format function + property var parseValueFunction: null // Optional custom parse function + + function formatValue(val: real): string { + if (formatValueFunction) { + return formatValueFunction(val); + } + // Default format function + if (validator && validator.bottom !== undefined) { + // Check if it's an integer validator + if (validator.top !== undefined && validator.top === Math.floor(validator.top)) { + return Math.round(val).toString(); + } + } + return val.toFixed(1); + } + + function parseValue(text: string): real { + if (parseValueFunction) { + return parseValueFunction(text); + } + // Default parse function + if (validator && validator.bottom !== undefined) { + // Check if it's an integer validator + if (validator.top !== undefined && validator.top === Math.floor(validator.top)) { + return parseInt(text); + } + } + return parseFloat(text); + } + + signal valueChanged(real newValue) + + spacing: Appearance.spacing.small + + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + StyledText { + visible: root.label !== "" + text: root.label + font.pointSize: Appearance.font.size.normal + } + + Item { + Layout.fillWidth: true + } + + StyledRect { + Layout.preferredWidth: 70 + implicitHeight: inputField.implicitHeight + Appearance.padding.small * 2 + color: inputHover.containsMouse || inputField.activeFocus + ? Colours.layer(Colours.palette.m3surfaceContainer, 3) + : Colours.layer(Colours.palette.m3surfaceContainer, 2) + radius: Appearance.rounding.small + border.width: 1 + border.color: inputField.activeFocus + ? Colours.palette.m3primary + : Qt.alpha(Colours.palette.m3outline, 0.3) + + Behavior on color { CAnim {} } + Behavior on border.color { CAnim {} } + + MouseArea { + id: inputHover + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.NoButton + } + + StyledTextField { + id: inputField + anchors.centerIn: parent + width: parent.width - Appearance.padding.normal + horizontalAlignment: TextInput.AlignHCenter + validator: root.validator + + Component.onCompleted: { + text = root.formatValue(root.value); + } + + onTextChanged: { + if (activeFocus) { + const val = root.parseValue(text); + if (!isNaN(val)) { + // Validate against validator bounds if available + let isValid = true; + if (root.validator) { + if (root.validator.bottom !== undefined && val < root.validator.bottom) { + isValid = false; + } + if (root.validator.top !== undefined && val > root.validator.top) { + isValid = false; + } + } + + if (isValid) { + root.valueChanged(val); + } + } + } + } + + onEditingFinished: { + const val = root.parseValue(text); + let isValid = true; + if (root.validator) { + if (root.validator.bottom !== undefined && val < root.validator.bottom) { + isValid = false; + } + if (root.validator.top !== undefined && val > root.validator.top) { + isValid = false; + } + } + + if (isNaN(val) || !isValid) { + text = root.formatValue(root.value); + } + } + } + } + + StyledText { + visible: root.suffix !== "" + text: root.suffix + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.normal + } + } + + StyledSlider { + id: slider + + Layout.fillWidth: true + implicitHeight: Appearance.padding.normal * 3 + + from: root.from + to: root.to + stepSize: root.stepSize + value: root.value + + onMoved: { + const newValue = root.stepSize > 0 ? Math.round(value / root.stepSize) * root.stepSize : value; + root.valueChanged(newValue); + if (!inputField.activeFocus) { + inputField.text = root.formatValue(newValue); + } + } + } + + // Update input field when value changes externally (slider is already bound) + onValueChanged: { + if (!inputField.activeFocus) { + inputField.text = root.formatValue(root.value); + } + } +} + diff --git a/modules/controlcenter/network/EthernetDetails.qml b/modules/controlcenter/network/EthernetDetails.qml index ad078ec..1cd6c0a 100644 --- a/modules/controlcenter/network/EthernetDetails.qml +++ b/modules/controlcenter/network/EthernetDetails.qml @@ -15,8 +15,8 @@ DeviceDetails { id: root required property Session session - readonly property var ethernetDevice: session.ethernet.active - + readonly property var ethernetDevice: root.session.ethernet.active + device: ethernetDevice Component.onCompleted: { diff --git a/modules/controlcenter/network/WirelessDetails.qml b/modules/controlcenter/network/WirelessDetails.qml index cf16400..47d42c2 100644 --- a/modules/controlcenter/network/WirelessDetails.qml +++ b/modules/controlcenter/network/WirelessDetails.qml @@ -17,8 +17,8 @@ DeviceDetails { id: root required property Session session - readonly property var network: session.network.active - + readonly property var network: root.session.network.active + device: network Component.onCompleted: { diff --git a/modules/controlcenter/taskbar/TaskbarPane.qml b/modules/controlcenter/taskbar/TaskbarPane.qml index f452b07..38c1179 100644 --- a/modules/controlcenter/taskbar/TaskbarPane.qml +++ b/modules/controlcenter/taskbar/TaskbarPane.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ".." +import "../components" import qs.components import qs.components.controls import qs.components.effects @@ -520,98 +521,21 @@ Item { SectionContainer { contentSpacing: Appearance.spacing.normal - ColumnLayout { + SliderInput { Layout.fillWidth: true - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.normal - - StyledText { - text: qsTr("Drag threshold") - font.pointSize: Appearance.font.size.normal - } - - Item { - Layout.fillWidth: true - } - - StyledRect { - Layout.preferredWidth: 70 - implicitHeight: dragThresholdInput.implicitHeight + Appearance.padding.small * 2 - color: dragThresholdInputHover.containsMouse || dragThresholdInput.activeFocus - ? Colours.layer(Colours.palette.m3surfaceContainer, 3) - : Colours.layer(Colours.palette.m3surfaceContainer, 2) - radius: Appearance.rounding.small - border.width: 1 - border.color: dragThresholdInput.activeFocus - ? Colours.palette.m3primary - : Qt.alpha(Colours.palette.m3outline, 0.3) - - Behavior on color { CAnim {} } - Behavior on border.color { CAnim {} } - - MouseArea { - id: dragThresholdInputHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.IBeamCursor - acceptedButtons: Qt.NoButton - } - - StyledTextField { - id: dragThresholdInput - anchors.centerIn: parent - width: parent.width - Appearance.padding.normal - horizontalAlignment: TextInput.AlignHCenter - validator: IntValidator { bottom: 0; top: 100 } - - Component.onCompleted: { - text = root.dragThreshold.toString(); - } - - onTextChanged: { - if (activeFocus) { - const val = parseInt(text); - if (!isNaN(val) && val >= 0 && val <= 100) { - root.dragThreshold = val; - root.saveConfig(); - } - } - } - onEditingFinished: { - const val = parseInt(text); - if (isNaN(val) || val < 0 || val > 100) { - text = root.dragThreshold.toString(); - } - } - } - } - - StyledText { - text: "px" - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.normal - } - } - - StyledSlider { - id: dragThresholdSlider - - Layout.fillWidth: true - implicitHeight: Appearance.padding.normal * 3 - - from: 0 - to: 100 - value: root.dragThreshold - onMoved: { - root.dragThreshold = Math.round(dragThresholdSlider.value); - if (!dragThresholdInput.activeFocus) { - dragThresholdInput.text = Math.round(dragThresholdSlider.value).toString(); - } - root.saveConfig(); - } + + label: qsTr("Drag threshold") + value: root.dragThreshold + from: 0 + to: 100 + suffix: "px" + validator: IntValidator { bottom: 0; top: 100 } + formatValueFunction: (val) => Math.round(val).toString() + parseValueFunction: (text) => parseInt(text) + + onValueModified: (newValue) => { + root.dragThreshold = Math.round(newValue); + root.saveConfig(); } } } -- cgit v1.2.3-freya From bf3a1781d12271a7edb9ead26eb59740f87877fd Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 19 Nov 2025 22:18:10 -0500 Subject: refactor: reorganized AppearancePane sections into dedicated components --- .../controlcenter/appearance/AppearancePane.qml | 515 +-------------------- .../appearance/sections/AnimationsSection.qml | 42 ++ .../appearance/sections/BackgroundSection.qml | 105 +++++ .../appearance/sections/BorderSection.qml | 63 +++ .../appearance/sections/ColorSchemeSection.qml | 147 ++++++ .../appearance/sections/ColorVariantSection.qml | 92 ++++ .../appearance/sections/ScalesSection.qml | 84 ++++ .../appearance/sections/ThemeModeSection.qml | 24 + .../appearance/sections/TransparencySection.qml | 74 +++ modules/controlcenter/network/NetworkingPane.qml | 14 +- 10 files changed, 659 insertions(+), 501 deletions(-) create mode 100644 modules/controlcenter/appearance/sections/AnimationsSection.qml create mode 100644 modules/controlcenter/appearance/sections/BackgroundSection.qml create mode 100644 modules/controlcenter/appearance/sections/BorderSection.qml create mode 100644 modules/controlcenter/appearance/sections/ColorSchemeSection.qml create mode 100644 modules/controlcenter/appearance/sections/ColorVariantSection.qml create mode 100644 modules/controlcenter/appearance/sections/ScalesSection.qml create mode 100644 modules/controlcenter/appearance/sections/ThemeModeSection.qml create mode 100644 modules/controlcenter/appearance/sections/TransparencySection.qml (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml index dec260d..d72700f 100644 --- a/modules/controlcenter/appearance/AppearancePane.qml +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound import ".." import "../components" +import "./sections" import "../../launcher/services" import qs.components import qs.components.controls @@ -680,6 +681,8 @@ Item { anchors.left: parent.left anchors.right: parent.right spacing: Appearance.spacing.small + + readonly property var rootPane: sidebarFlickable.rootPane readonly property bool allSectionsExpanded: themeModeSection.expanded && @@ -724,261 +727,21 @@ Item { } } - CollapsibleSection { + ThemeModeSection { id: themeModeSection - title: qsTr("Theme mode") - description: qsTr("Light or dark theme") - showBackground: true - - SwitchRow { - label: qsTr("Dark mode") - checked: !Colours.currentLight - onToggled: checked => { - Colours.setMode(checked ? "dark" : "light"); - } - } } - CollapsibleSection { + ColorVariantSection { id: colorVariantSection - title: qsTr("Color variant") - description: qsTr("Material theme variant") - showBackground: true - - ColumnLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small / 2 - - Repeater { - model: M3Variants.list - - delegate: StyledRect { - required property var modelData - - Layout.fillWidth: true - - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, modelData.variant === Schemes.currentVariant ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: modelData.variant === Schemes.currentVariant ? 1 : 0 - border.color: Colours.palette.m3primary - - StateLayer { - function onClicked(): void { - const variant = modelData.variant; - - Schemes.currentVariant = variant; - Quickshell.execDetached(["caelestia", "scheme", "set", "-v", variant]); - - Qt.callLater(() => { - reloadTimer.restart(); - }); - } - } - - Timer { - id: reloadTimer - interval: 300 - onTriggered: { - Schemes.reload(); - } - } - - RowLayout { - id: variantRow - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: Appearance.padding.normal - - spacing: Appearance.spacing.normal - - MaterialIcon { - text: modelData.icon - font.pointSize: Appearance.font.size.large - fill: modelData.variant === Schemes.currentVariant ? 1 : 0 - } - - StyledText { - Layout.fillWidth: true - text: modelData.name - font.weight: modelData.variant === Schemes.currentVariant ? 500 : 400 - } - - MaterialIcon { - visible: modelData.variant === Schemes.currentVariant - text: "check" - color: Colours.palette.m3primary - font.pointSize: Appearance.font.size.large - } - } - - implicitHeight: variantRow.implicitHeight + Appearance.padding.normal * 2 - } - } - } } - CollapsibleSection { + ColorSchemeSection { id: colorSchemeSection - title: qsTr("Color scheme") - description: qsTr("Available color schemes") - showBackground: true - - ColumnLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.small / 2 - - Repeater { - model: Schemes.list - - delegate: StyledRect { - required property var modelData - - Layout.fillWidth: true - - readonly property string schemeKey: `${modelData.name} ${modelData.flavour}` - readonly property bool isCurrent: schemeKey === Schemes.currentScheme - - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) - radius: Appearance.rounding.normal - border.width: isCurrent ? 1 : 0 - border.color: Colours.palette.m3primary - - StateLayer { - function onClicked(): void { - const name = modelData.name; - const flavour = modelData.flavour; - const schemeKey = `${name} ${flavour}`; - - Schemes.currentScheme = schemeKey; - Quickshell.execDetached(["caelestia", "scheme", "set", "-n", name, "-f", flavour]); - - Qt.callLater(() => { - reloadTimer.restart(); - }); - } - } - - Timer { - id: reloadTimer - interval: 300 - onTriggered: { - Schemes.reload(); - } - } - - RowLayout { - id: schemeRow - - anchors.fill: parent - anchors.margins: Appearance.padding.normal - - spacing: Appearance.spacing.normal - - StyledRect { - id: preview - - Layout.alignment: Qt.AlignVCenter - - border.width: 1 - border.color: Qt.alpha(`#${modelData.colours?.outline}`, 0.5) - - color: `#${modelData.colours?.surface}` - radius: Appearance.rounding.full - implicitWidth: iconPlaceholder.implicitWidth - implicitHeight: iconPlaceholder.implicitWidth - - MaterialIcon { - id: iconPlaceholder - visible: false - text: "circle" - font.pointSize: Appearance.font.size.large - } - - Item { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - - implicitWidth: parent.implicitWidth / 2 - clip: true - - StyledRect { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - - implicitWidth: preview.implicitWidth - color: `#${modelData.colours?.primary}` - radius: Appearance.rounding.full - } - } - } - - Column { - Layout.fillWidth: true - spacing: 0 - - StyledText { - text: modelData.flavour ?? "" - font.pointSize: Appearance.font.size.normal - } - - StyledText { - text: modelData.name ?? "" - font.pointSize: Appearance.font.size.small - color: Colours.palette.m3outline - - elide: Text.ElideRight - anchors.left: parent.left - anchors.right: parent.right - } - } - - Loader { - active: isCurrent - asynchronous: true - - sourceComponent: MaterialIcon { - text: "check" - color: Colours.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.large - } - } - } - - implicitHeight: schemeRow.implicitHeight + Appearance.padding.normal * 2 - } - } - } } - CollapsibleSection { + AnimationsSection { id: animationsSection - title: qsTr("Animations") - showBackground: true - - SectionContainer { - contentSpacing: Appearance.spacing.normal - - SliderInput { - Layout.fillWidth: true - - label: qsTr("Animation duration scale") - value: rootPane.animDurationsScale - from: 0.1 - to: 5.0 - decimals: 1 - suffix: "×" - validator: DoubleValidator { bottom: 0.1; top: 5.0 } - - onValueModified: (newValue) => { - rootPane.animDurationsScale = newValue; - rootPane.saveConfig(); - } - } - } + rootPane: sidebarFlickable.rootPane } CollapsibleSection { @@ -1253,270 +1016,24 @@ Item { } } - CollapsibleSection { + ScalesSection { id: scalesSection - title: qsTr("Scales") - showBackground: true - - SectionContainer { - contentSpacing: Appearance.spacing.normal - - SliderInput { - Layout.fillWidth: true - - label: qsTr("Padding scale") - value: rootPane.paddingScale - from: 0.5 - to: 2.0 - decimals: 1 - suffix: "×" - validator: DoubleValidator { bottom: 0.5; top: 2.0 } - - onValueModified: (newValue) => { - rootPane.paddingScale = newValue; - rootPane.saveConfig(); - } - } - } - - SectionContainer { - contentSpacing: Appearance.spacing.normal - - SliderInput { - Layout.fillWidth: true - - label: qsTr("Rounding scale") - value: rootPane.roundingScale - from: 0.1 - to: 5.0 - decimals: 1 - suffix: "×" - validator: DoubleValidator { bottom: 0.1; top: 5.0 } - - onValueModified: (newValue) => { - rootPane.roundingScale = newValue; - rootPane.saveConfig(); - } - } - } - - SectionContainer { - contentSpacing: Appearance.spacing.normal - - SliderInput { - Layout.fillWidth: true - - label: qsTr("Spacing scale") - value: rootPane.spacingScale - from: 0.1 - to: 2.0 - decimals: 1 - suffix: "×" - validator: DoubleValidator { bottom: 0.1; top: 2.0 } - - onValueModified: (newValue) => { - rootPane.spacingScale = newValue; - rootPane.saveConfig(); - } - } - } + rootPane: sidebarFlickable.rootPane } - CollapsibleSection { + TransparencySection { id: transparencySection - title: qsTr("Transparency") - showBackground: true - - SwitchRow { - label: qsTr("Transparency enabled") - checked: rootPane.transparencyEnabled - onToggled: checked => { - rootPane.transparencyEnabled = checked; - rootPane.saveConfig(); - } - } - - SectionContainer { - contentSpacing: Appearance.spacing.normal - - SliderInput { - Layout.fillWidth: true - - label: qsTr("Transparency base") - value: rootPane.transparencyBase * 100 - from: 0 - to: 100 - suffix: "%" - validator: IntValidator { bottom: 0; top: 100 } - formatValueFunction: (val) => Math.round(val).toString() - parseValueFunction: (text) => parseInt(text) - - onValueModified: (newValue) => { - rootPane.transparencyBase = newValue / 100; - rootPane.saveConfig(); - } - } - } - - SectionContainer { - contentSpacing: Appearance.spacing.normal - - SliderInput { - Layout.fillWidth: true - - label: qsTr("Transparency layers") - value: rootPane.transparencyLayers * 100 - from: 0 - to: 100 - suffix: "%" - validator: IntValidator { bottom: 0; top: 100 } - formatValueFunction: (val) => Math.round(val).toString() - parseValueFunction: (text) => parseInt(text) - - onValueModified: (newValue) => { - rootPane.transparencyLayers = newValue / 100; - rootPane.saveConfig(); - } - } - } + rootPane: sidebarFlickable.rootPane } - CollapsibleSection { + BorderSection { id: borderSection - title: qsTr("Border") - showBackground: true - - SectionContainer { - contentSpacing: Appearance.spacing.normal - - SliderInput { - Layout.fillWidth: true - - label: qsTr("Border rounding") - value: rootPane.borderRounding - from: 0.1 - to: 100 - decimals: 1 - suffix: "px" - validator: DoubleValidator { bottom: 0.1; top: 100 } - - onValueModified: (newValue) => { - rootPane.borderRounding = newValue; - rootPane.saveConfig(); - } - } - } - - SectionContainer { - contentSpacing: Appearance.spacing.normal - - SliderInput { - Layout.fillWidth: true - - label: qsTr("Border thickness") - value: rootPane.borderThickness - from: 0.1 - to: 100 - decimals: 1 - suffix: "px" - validator: DoubleValidator { bottom: 0.1; top: 100 } - - onValueModified: (newValue) => { - rootPane.borderThickness = newValue; - rootPane.saveConfig(); - } - } - } + rootPane: sidebarFlickable.rootPane } - CollapsibleSection { + BackgroundSection { id: backgroundSection - title: qsTr("Background") - showBackground: true - - SwitchRow { - label: qsTr("Desktop clock") - checked: rootPane.desktopClockEnabled - onToggled: checked => { - rootPane.desktopClockEnabled = checked; - rootPane.saveConfig(); - } - } - - SwitchRow { - label: qsTr("Background enabled") - checked: rootPane.backgroundEnabled - onToggled: checked => { - rootPane.backgroundEnabled = checked; - rootPane.saveConfig(); - } - } - - StyledText { - Layout.topMargin: Appearance.spacing.normal - text: qsTr("Visualiser") - font.pointSize: Appearance.font.size.larger - font.weight: 500 - } - - SwitchRow { - label: qsTr("Visualiser enabled") - checked: rootPane.visualiserEnabled - onToggled: checked => { - rootPane.visualiserEnabled = checked; - rootPane.saveConfig(); - } - } - - SwitchRow { - label: qsTr("Visualiser auto hide") - checked: rootPane.visualiserAutoHide - onToggled: checked => { - rootPane.visualiserAutoHide = checked; - rootPane.saveConfig(); - } - } - - SectionContainer { - contentSpacing: Appearance.spacing.normal - - SliderInput { - Layout.fillWidth: true - - label: qsTr("Visualiser rounding") - value: rootPane.visualiserRounding - from: 0 - to: 10 - stepSize: 1 - validator: IntValidator { bottom: 0; top: 10 } - formatValueFunction: (val) => Math.round(val).toString() - parseValueFunction: (text) => parseInt(text) - - onValueModified: (newValue) => { - rootPane.visualiserRounding = Math.round(newValue); - rootPane.saveConfig(); - } - } - } - - SectionContainer { - contentSpacing: Appearance.spacing.normal - - SliderInput { - Layout.fillWidth: true - - label: qsTr("Visualiser spacing") - value: rootPane.visualiserSpacing - from: 0 - to: 2 - validator: DoubleValidator { bottom: 0; top: 2 } - - onValueModified: (newValue) => { - rootPane.visualiserSpacing = newValue; - rootPane.saveConfig(); - } - } - } + rootPane: sidebarFlickable.rootPane } } } diff --git a/modules/controlcenter/appearance/sections/AnimationsSection.qml b/modules/controlcenter/appearance/sections/AnimationsSection.qml new file mode 100644 index 0000000..03fc2b1 --- /dev/null +++ b/modules/controlcenter/appearance/sections/AnimationsSection.qml @@ -0,0 +1,42 @@ +pragma ComponentBehavior: Bound + +import ".." +import "../../components" +import qs.components +import qs.components.controls +import qs.components.containers +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +CollapsibleSection { + id: root + + required property var rootPane + + title: qsTr("Animations") + showBackground: true + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + SliderInput { + Layout.fillWidth: true + + label: qsTr("Animation duration scale") + value: rootPane.animDurationsScale + from: 0.1 + to: 5.0 + decimals: 1 + suffix: "×" + validator: DoubleValidator { bottom: 0.1; top: 5.0 } + + onValueModified: (newValue) => { + rootPane.animDurationsScale = newValue; + rootPane.saveConfig(); + } + } + } +} + diff --git a/modules/controlcenter/appearance/sections/BackgroundSection.qml b/modules/controlcenter/appearance/sections/BackgroundSection.qml new file mode 100644 index 0000000..8754e73 --- /dev/null +++ b/modules/controlcenter/appearance/sections/BackgroundSection.qml @@ -0,0 +1,105 @@ +pragma ComponentBehavior: Bound + +import ".." +import "../../components" +import qs.components +import qs.components.controls +import qs.components.containers +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +CollapsibleSection { + id: root + + required property var rootPane + + title: qsTr("Background") + showBackground: true + + SwitchRow { + label: qsTr("Desktop clock") + checked: rootPane.desktopClockEnabled + onToggled: checked => { + rootPane.desktopClockEnabled = checked; + rootPane.saveConfig(); + } + } + + SwitchRow { + label: qsTr("Background enabled") + checked: rootPane.backgroundEnabled + onToggled: checked => { + rootPane.backgroundEnabled = checked; + rootPane.saveConfig(); + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Visualiser") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + SwitchRow { + label: qsTr("Visualiser enabled") + checked: rootPane.visualiserEnabled + onToggled: checked => { + rootPane.visualiserEnabled = checked; + rootPane.saveConfig(); + } + } + + SwitchRow { + label: qsTr("Visualiser auto hide") + checked: rootPane.visualiserAutoHide + onToggled: checked => { + rootPane.visualiserAutoHide = checked; + rootPane.saveConfig(); + } + } + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + SliderInput { + Layout.fillWidth: true + + label: qsTr("Visualiser rounding") + value: rootPane.visualiserRounding + from: 0 + to: 10 + stepSize: 1 + validator: IntValidator { bottom: 0; top: 10 } + formatValueFunction: (val) => Math.round(val).toString() + parseValueFunction: (text) => parseInt(text) + + onValueModified: (newValue) => { + rootPane.visualiserRounding = Math.round(newValue); + rootPane.saveConfig(); + } + } + } + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + SliderInput { + Layout.fillWidth: true + + label: qsTr("Visualiser spacing") + value: rootPane.visualiserSpacing + from: 0 + to: 2 + validator: DoubleValidator { bottom: 0; top: 2 } + + onValueModified: (newValue) => { + rootPane.visualiserSpacing = newValue; + rootPane.saveConfig(); + } + } + } +} + diff --git a/modules/controlcenter/appearance/sections/BorderSection.qml b/modules/controlcenter/appearance/sections/BorderSection.qml new file mode 100644 index 0000000..dae26c3 --- /dev/null +++ b/modules/controlcenter/appearance/sections/BorderSection.qml @@ -0,0 +1,63 @@ +pragma ComponentBehavior: Bound + +import ".." +import "../../components" +import qs.components +import qs.components.controls +import qs.components.containers +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +CollapsibleSection { + id: root + + required property var rootPane + + title: qsTr("Border") + showBackground: true + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + SliderInput { + Layout.fillWidth: true + + label: qsTr("Border rounding") + value: rootPane.borderRounding + from: 0.1 + to: 100 + decimals: 1 + suffix: "px" + validator: DoubleValidator { bottom: 0.1; top: 100 } + + onValueModified: (newValue) => { + rootPane.borderRounding = newValue; + rootPane.saveConfig(); + } + } + } + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + SliderInput { + Layout.fillWidth: true + + label: qsTr("Border thickness") + value: rootPane.borderThickness + from: 0.1 + to: 100 + decimals: 1 + suffix: "px" + validator: DoubleValidator { bottom: 0.1; top: 100 } + + onValueModified: (newValue) => { + rootPane.borderThickness = newValue; + rootPane.saveConfig(); + } + } + } +} + diff --git a/modules/controlcenter/appearance/sections/ColorSchemeSection.qml b/modules/controlcenter/appearance/sections/ColorSchemeSection.qml new file mode 100644 index 0000000..c0e5eb5 --- /dev/null +++ b/modules/controlcenter/appearance/sections/ColorSchemeSection.qml @@ -0,0 +1,147 @@ +pragma ComponentBehavior: Bound + +import ".." +import "../../../launcher/services" +import qs.components +import qs.components.controls +import qs.components.containers +import qs.services +import qs.config +import Quickshell +import QtQuick +import QtQuick.Layouts + +CollapsibleSection { + title: qsTr("Color scheme") + description: qsTr("Available color schemes") + showBackground: true + + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small / 2 + + Repeater { + model: Schemes.list + + delegate: StyledRect { + required property var modelData + + Layout.fillWidth: true + + readonly property string schemeKey: `${modelData.name} ${modelData.flavour}` + readonly property bool isCurrent: schemeKey === Schemes.currentScheme + + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: isCurrent ? 1 : 0 + border.color: Colours.palette.m3primary + + StateLayer { + function onClicked(): void { + const name = modelData.name; + const flavour = modelData.flavour; + const schemeKey = `${name} ${flavour}`; + + Schemes.currentScheme = schemeKey; + Quickshell.execDetached(["caelestia", "scheme", "set", "-n", name, "-f", flavour]); + + Qt.callLater(() => { + reloadTimer.restart(); + }); + } + } + + Timer { + id: reloadTimer + interval: 300 + onTriggered: { + Schemes.reload(); + } + } + + RowLayout { + id: schemeRow + + anchors.fill: parent + anchors.margins: Appearance.padding.normal + + spacing: Appearance.spacing.normal + + StyledRect { + id: preview + + Layout.alignment: Qt.AlignVCenter + + border.width: 1 + border.color: Qt.alpha(`#${modelData.colours?.outline}`, 0.5) + + color: `#${modelData.colours?.surface}` + radius: Appearance.rounding.full + implicitWidth: iconPlaceholder.implicitWidth + implicitHeight: iconPlaceholder.implicitWidth + + MaterialIcon { + id: iconPlaceholder + visible: false + text: "circle" + font.pointSize: Appearance.font.size.large + } + + Item { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + + implicitWidth: parent.implicitWidth / 2 + clip: true + + StyledRect { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + + implicitWidth: preview.implicitWidth + color: `#${modelData.colours?.primary}` + radius: Appearance.rounding.full + } + } + } + + Column { + Layout.fillWidth: true + spacing: 0 + + StyledText { + text: modelData.flavour ?? "" + font.pointSize: Appearance.font.size.normal + } + + StyledText { + text: modelData.name ?? "" + font.pointSize: Appearance.font.size.small + color: Colours.palette.m3outline + + elide: Text.ElideRight + anchors.left: parent.left + anchors.right: parent.right + } + } + + Loader { + active: isCurrent + asynchronous: true + + sourceComponent: MaterialIcon { + text: "check" + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.large + } + } + } + + implicitHeight: schemeRow.implicitHeight + Appearance.padding.normal * 2 + } + } + } +} + diff --git a/modules/controlcenter/appearance/sections/ColorVariantSection.qml b/modules/controlcenter/appearance/sections/ColorVariantSection.qml new file mode 100644 index 0000000..98c3d7c --- /dev/null +++ b/modules/controlcenter/appearance/sections/ColorVariantSection.qml @@ -0,0 +1,92 @@ +pragma ComponentBehavior: Bound + +import ".." +import "../../../launcher/services" +import qs.components +import qs.components.controls +import qs.components.containers +import qs.services +import qs.config +import Quickshell +import QtQuick +import QtQuick.Layouts + +CollapsibleSection { + title: qsTr("Color variant") + description: qsTr("Material theme variant") + showBackground: true + + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small / 2 + + Repeater { + model: M3Variants.list + + delegate: StyledRect { + required property var modelData + + Layout.fillWidth: true + + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, modelData.variant === Schemes.currentVariant ? Colours.tPalette.m3surfaceContainer.a : 0) + radius: Appearance.rounding.normal + border.width: modelData.variant === Schemes.currentVariant ? 1 : 0 + border.color: Colours.palette.m3primary + + StateLayer { + function onClicked(): void { + const variant = modelData.variant; + + Schemes.currentVariant = variant; + Quickshell.execDetached(["caelestia", "scheme", "set", "-v", variant]); + + Qt.callLater(() => { + reloadTimer.restart(); + }); + } + } + + Timer { + id: reloadTimer + interval: 300 + onTriggered: { + Schemes.reload(); + } + } + + RowLayout { + id: variantRow + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal + + spacing: Appearance.spacing.normal + + MaterialIcon { + text: modelData.icon + font.pointSize: Appearance.font.size.large + fill: modelData.variant === Schemes.currentVariant ? 1 : 0 + } + + StyledText { + Layout.fillWidth: true + text: modelData.name + font.weight: modelData.variant === Schemes.currentVariant ? 500 : 400 + } + + MaterialIcon { + visible: modelData.variant === Schemes.currentVariant + text: "check" + color: Colours.palette.m3primary + font.pointSize: Appearance.font.size.large + } + } + + implicitHeight: variantRow.implicitHeight + Appearance.padding.normal * 2 + } + } + } +} + diff --git a/modules/controlcenter/appearance/sections/ScalesSection.qml b/modules/controlcenter/appearance/sections/ScalesSection.qml new file mode 100644 index 0000000..f74923b --- /dev/null +++ b/modules/controlcenter/appearance/sections/ScalesSection.qml @@ -0,0 +1,84 @@ +pragma ComponentBehavior: Bound + +import ".." +import "../../components" +import qs.components +import qs.components.controls +import qs.components.containers +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +CollapsibleSection { + id: root + + required property var rootPane + + title: qsTr("Scales") + showBackground: true + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + SliderInput { + Layout.fillWidth: true + + label: qsTr("Padding scale") + value: rootPane.paddingScale + from: 0.5 + to: 2.0 + decimals: 1 + suffix: "×" + validator: DoubleValidator { bottom: 0.5; top: 2.0 } + + onValueModified: (newValue) => { + rootPane.paddingScale = newValue; + rootPane.saveConfig(); + } + } + } + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + SliderInput { + Layout.fillWidth: true + + label: qsTr("Rounding scale") + value: rootPane.roundingScale + from: 0.1 + to: 5.0 + decimals: 1 + suffix: "×" + validator: DoubleValidator { bottom: 0.1; top: 5.0 } + + onValueModified: (newValue) => { + rootPane.roundingScale = newValue; + rootPane.saveConfig(); + } + } + } + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + SliderInput { + Layout.fillWidth: true + + label: qsTr("Spacing scale") + value: rootPane.spacingScale + from: 0.1 + to: 2.0 + decimals: 1 + suffix: "×" + validator: DoubleValidator { bottom: 0.1; top: 2.0 } + + onValueModified: (newValue) => { + rootPane.spacingScale = newValue; + rootPane.saveConfig(); + } + } + } +} + diff --git a/modules/controlcenter/appearance/sections/ThemeModeSection.qml b/modules/controlcenter/appearance/sections/ThemeModeSection.qml new file mode 100644 index 0000000..c136437 --- /dev/null +++ b/modules/controlcenter/appearance/sections/ThemeModeSection.qml @@ -0,0 +1,24 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.containers +import qs.services +import qs.config +import QtQuick + +CollapsibleSection { + title: qsTr("Theme mode") + description: qsTr("Light or dark theme") + showBackground: true + + SwitchRow { + label: qsTr("Dark mode") + checked: !Colours.currentLight + onToggled: checked => { + Colours.setMode(checked ? "dark" : "light"); + } + } +} + diff --git a/modules/controlcenter/appearance/sections/TransparencySection.qml b/modules/controlcenter/appearance/sections/TransparencySection.qml new file mode 100644 index 0000000..c9dbfb8 --- /dev/null +++ b/modules/controlcenter/appearance/sections/TransparencySection.qml @@ -0,0 +1,74 @@ +pragma ComponentBehavior: Bound + +import ".." +import "../../components" +import qs.components +import qs.components.controls +import qs.components.containers +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +CollapsibleSection { + id: root + + required property var rootPane + + title: qsTr("Transparency") + showBackground: true + + SwitchRow { + label: qsTr("Transparency enabled") + checked: rootPane.transparencyEnabled + onToggled: checked => { + rootPane.transparencyEnabled = checked; + rootPane.saveConfig(); + } + } + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + SliderInput { + Layout.fillWidth: true + + label: qsTr("Transparency base") + value: rootPane.transparencyBase * 100 + from: 0 + to: 100 + suffix: "%" + validator: IntValidator { bottom: 0; top: 100 } + formatValueFunction: (val) => Math.round(val).toString() + parseValueFunction: (text) => parseInt(text) + + onValueModified: (newValue) => { + rootPane.transparencyBase = newValue / 100; + rootPane.saveConfig(); + } + } + } + + SectionContainer { + contentSpacing: Appearance.spacing.normal + + SliderInput { + Layout.fillWidth: true + + label: qsTr("Transparency layers") + value: rootPane.transparencyLayers * 100 + from: 0 + to: 100 + suffix: "%" + validator: IntValidator { bottom: 0; top: 100 } + formatValueFunction: (val) => Math.round(val).toString() + parseValueFunction: (text) => parseInt(text) + + onValueModified: (newValue) => { + rootPane.transparencyLayers = newValue / 100; + rootPane.saveConfig(); + } + } + } +} + diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml index 4446428..b430cce 100644 --- a/modules/controlcenter/network/NetworkingPane.qml +++ b/modules/controlcenter/network/NetworkingPane.qml @@ -175,16 +175,26 @@ Item { Connections { target: root.session.ethernet function onActiveChanged() { + // Clear wireless when ethernet is selected + if (root.session.ethernet.active && root.session.network.active) { + root.session.network.active = null; + return; // Let the network.onActiveChanged handle the update + } rightPaneItem.nextComponent = rightPaneItem.getComponentForPane(); - rightPaneItem.paneId = rightPaneItem.ethernetPane ? ("eth:" + (rightPaneItem.ethernetPane.interface || "")) : (rightPaneItem.wirelessPane ? ("wifi:" + (rightPaneItem.wirelessPane.ssid || rightPaneItem.wirelessPane.bssid || "")) : "settings"); + // paneId will automatically update via property binding } } Connections { target: root.session.network function onActiveChanged() { + // Clear ethernet when wireless is selected + if (root.session.network.active && root.session.ethernet.active) { + root.session.ethernet.active = null; + return; // Let the ethernet.onActiveChanged handle the update + } rightPaneItem.nextComponent = rightPaneItem.getComponentForPane(); - rightPaneItem.paneId = rightPaneItem.ethernetPane ? ("eth:" + (rightPaneItem.ethernetPane.interface || "")) : (rightPaneItem.wirelessPane ? ("wifi:" + (rightPaneItem.wirelessPane.ssid || rightPaneItem.wirelessPane.bssid || "")) : "settings"); + // paneId will automatically update via property binding } } -- cgit v1.2.3-freya From a15554ffde440ef85a93182fc765f3e006999cde Mon Sep 17 00:00:00 2001 From: ATMDA Date: Thu, 20 Nov 2025 08:13:59 -0500 Subject: refactor: EthernetList and WirelessList components removed border properties --- modules/controlcenter/network/EthernetList.qml | 2 - modules/controlcenter/network/WirelessList.qml | 82 ++++++++++++++++++++------ 2 files changed, 64 insertions(+), 20 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/EthernetList.qml b/modules/controlcenter/network/EthernetList.qml index ea3ece5..bdc43e8 100644 --- a/modules/controlcenter/network/EthernetList.qml +++ b/modules/controlcenter/network/EthernetList.qml @@ -62,8 +62,6 @@ DeviceList { color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.activeItem === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal - border.width: root.activeItem === modelData ? 1 : 0 - border.color: Colours.palette.m3primary StateLayer { function onClicked(): void { diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml index 9dabe9d..43d56d2 100644 --- a/modules/controlcenter/network/WirelessList.qml +++ b/modules/controlcenter/network/WirelessList.qml @@ -6,6 +6,7 @@ import "." import qs.components import qs.components.controls import qs.components.containers +import qs.components.effects import qs.services import qs.config import qs.utils @@ -106,8 +107,6 @@ DeviceList { color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.activeItem === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal - border.width: root.activeItem === modelData ? 1 : 0 - border.color: Colours.palette.m3primary StateLayer { function onClicked(): void { @@ -139,32 +138,79 @@ DeviceList { id: icon anchors.centerIn: parent - text: modelData.isSecure ? "lock" : "wifi" + text: Icons.getNetworkIcon(modelData.strength) font.pointSize: Appearance.font.size.large fill: modelData.active ? 1 : 0 color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface } + + StyledRect { + id: lockBadge + + visible: 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 + } + } } - StyledText { + ColumnLayout { Layout.fillWidth: true - elide: Text.ElideRight - maximumLineCount: 1 - text: modelData.ssid || qsTr("Unknown") - } + spacing: 0 - StyledText { - text: modelData.active ? qsTr("Connected") : (modelData.isSecure ? qsTr("Secured") : qsTr("Open")) - color: modelData.active ? Colours.palette.m3primary : Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - font.weight: modelData.active ? 500 : 400 - } + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 + + text: modelData.ssid || qsTr("Unknown") + } - StyledText { - text: qsTr("%1%").arg(modelData.strength) - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.smaller + + StyledText { + Layout.fillWidth: true + text: { + 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.active ? Colours.palette.m3primary : Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + font.weight: modelData.active ? 500 : 400 + elide: Text.ElideRight + } + } } StyledRect { -- cgit v1.2.3-freya From ea9ab75db1903fd3c8117e63ae9f55b53d2142e9 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Thu, 20 Nov 2025 08:18:29 -0500 Subject: refactor: EthernetList correction on selected style --- modules/controlcenter/network/EthernetList.qml | 54 ++++++++++++++++++-------- 1 file changed, 38 insertions(+), 16 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/EthernetList.qml b/modules/controlcenter/network/EthernetList.qml index bdc43e8..e0d4b3f 100644 --- a/modules/controlcenter/network/EthernetList.qml +++ b/modules/controlcenter/network/EthernetList.qml @@ -56,14 +56,20 @@ DeviceList { delegate: Component { StyledRect { + id: ethernetItem + required property var modelData + readonly property bool isActive: root.activeItem && modelData && root.activeItem.interface === modelData.interface width: ListView.view ? ListView.view.width : undefined + implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 - color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.activeItem === modelData ? Colours.tPalette.m3surfaceContainer.a : 0) + color: Qt.alpha(Colours.tPalette.m3surfaceContainer, ethernetItem.isActive ? Colours.tPalette.m3surfaceContainer.a : 0) radius: Appearance.rounding.normal StateLayer { + id: stateLayer + function onClicked(): void { root.session.ethernet.active = modelData; } @@ -72,9 +78,7 @@ DeviceList { RowLayout { id: rowLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter + anchors.fill: parent anchors.margins: Appearance.padding.normal spacing: Appearance.spacing.normal @@ -86,6 +90,12 @@ DeviceList { radius: Appearance.rounding.normal color: modelData.connected ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh + StyledRect { + anchors.fill: parent + radius: parent.radius + color: Qt.alpha(modelData.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface, stateLayer.pressed ? 0.1 : stateLayer.containsMouse ? 0.08 : 0) + } + MaterialIcon { id: icon @@ -94,25 +104,36 @@ DeviceList { font.pointSize: Appearance.font.size.large fill: modelData.connected ? 1 : 0 color: modelData.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface + + Behavior on fill { + Anim {} + } } } - StyledText { + ColumnLayout { Layout.fillWidth: true - elide: Text.ElideRight - maximumLineCount: 1 - text: modelData.interface || qsTr("Unknown") - } + spacing: 0 - StyledText { - 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 + StyledText { + Layout.fillWidth: true + text: modelData.interface || qsTr("Unknown") + elide: Text.ElideRight + } + + StyledText { + Layout.fillWidth: true + text: modelData.connected ? qsTr("Connected") : qsTr("Disconnected") + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + elide: Text.ElideRight + } } StyledRect { + id: connectBtn + implicitWidth: implicitHeight implicitHeight: connectIcon.implicitHeight + Appearance.padding.smaller * 2 @@ -120,6 +141,8 @@ DeviceList { color: Qt.alpha(Colours.palette.m3primaryContainer, modelData.connected ? 1 : 0) StateLayer { + color: modelData.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface + function onClicked(): void { if (modelData.connected && modelData.connection) { Nmcli.disconnectEthernet(modelData.connection, () => {}); @@ -133,13 +156,12 @@ DeviceList { id: connectIcon anchors.centerIn: parent + animate: true text: modelData.connected ? "link_off" : "link" color: modelData.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface } } } - - implicitHeight: rowLayout.implicitHeight + Appearance.padding.normal * 2 } } -- cgit v1.2.3-freya From ae74eabfca2fa177235d25b364dc7dafe0f539d2 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Thu, 20 Nov 2025 08:24:19 -0500 Subject: refactor: EthernetList styling --- modules/controlcenter/network/EthernetList.qml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/EthernetList.qml b/modules/controlcenter/network/EthernetList.qml index e0d4b3f..4f4dc8a 100644 --- a/modules/controlcenter/network/EthernetList.qml +++ b/modules/controlcenter/network/EthernetList.qml @@ -122,12 +122,18 @@ DeviceList { elide: Text.ElideRight } - StyledText { + RowLayout { Layout.fillWidth: true - text: modelData.connected ? qsTr("Connected") : qsTr("Disconnected") - color: Colours.palette.m3outline - font.pointSize: Appearance.font.size.small - elide: Text.ElideRight + spacing: Appearance.spacing.smaller + + 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 + } } } -- cgit v1.2.3-freya From 5a369702d1e227df0842ad31d8d93710ad068add Mon Sep 17 00:00:00 2001 From: ATMDA Date: Thu, 20 Nov 2025 08:36:10 -0500 Subject: controlcenter: updated wireless icons --- modules/controlcenter/network/WirelessList.qml | 35 +------------------------- utils/Icons.qml | 32 +++++++++++++++-------- 2 files changed, 23 insertions(+), 44 deletions(-) (limited to 'modules/controlcenter/network') diff --git a/modules/controlcenter/network/WirelessList.qml b/modules/controlcenter/network/WirelessList.qml index 43d56d2..8159291 100644 --- a/modules/controlcenter/network/WirelessList.qml +++ b/modules/controlcenter/network/WirelessList.qml @@ -138,44 +138,11 @@ DeviceList { id: icon anchors.centerIn: parent - text: Icons.getNetworkIcon(modelData.strength) + text: Icons.getNetworkIcon(modelData.strength, modelData.isSecure) font.pointSize: Appearance.font.size.large fill: modelData.active ? 1 : 0 color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface } - - StyledRect { - id: lockBadge - - visible: 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 { diff --git a/utils/Icons.qml b/utils/Icons.qml index b3fe8ab..b3f00c5 100644 --- a/utils/Icons.qml +++ b/utils/Icons.qml @@ -116,16 +116,28 @@ Singleton { return fallback; } - function getNetworkIcon(strength: int): string { - if (strength >= 80) - return "signal_wifi_4_bar"; - if (strength >= 60) - return "network_wifi_3_bar"; - if (strength >= 40) - return "network_wifi_2_bar"; - if (strength >= 20) - return "network_wifi_1_bar"; - return "signal_wifi_0_bar"; + function getNetworkIcon(strength: int, isSecure = false): string { + if (isSecure) { + if (strength >= 80) + return "network_wifi_locked"; + if (strength >= 60) + return "network_wifi_3_bar_locked"; + if (strength >= 40) + return "network_wifi_2_bar_locked"; + if (strength >= 20) + return "network_wifi_1_bar_locked"; + return "signal_wifi_0_bar"; + } else { + if (strength >= 80) + return "network_wifi"; + if (strength >= 60) + return "network_wifi_3_bar"; + if (strength >= 40) + return "network_wifi_2_bar"; + if (strength >= 20) + return "network_wifi_1_bar"; + return "signal_wifi_0_bar"; + } } function getBluetoothIcon(icon: string): string { -- cgit v1.2.3-freya