From 5d7151e79e04d8a6073ecb4ea4a14c5c9bdcfc52 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sun, 9 Nov 2025 17:54:16 -0500 Subject: controlcenter: network and audio panels --- modules/controlcenter/NavRail.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'modules/controlcenter/NavRail.qml') diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml index 22c13a3..70438a6 100644 --- a/modules/controlcenter/NavRail.qml +++ b/modules/controlcenter/NavRail.qml @@ -168,7 +168,7 @@ Item { } NavItem { - icon: "tune" + icon: "volume_up" label: "audio" } } -- cgit v1.2.3-freya From 8d7bb827ebd8284706577518e2e58d6a61972889 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sun, 9 Nov 2025 22:48:38 -0500 Subject: controlcenter: appearance pane --- modules/controlcenter/NavRail.qml | 5 + modules/controlcenter/Panes.qml | 8 + modules/controlcenter/Session.qml | 2 +- .../controlcenter/appearance/AppearancePane.qml | 520 +++++++++++++++++++++ 4 files changed, 534 insertions(+), 1 deletion(-) create mode 100644 modules/controlcenter/appearance/AppearancePane.qml (limited to 'modules/controlcenter/NavRail.qml') diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml index 70438a6..96bbb65 100644 --- a/modules/controlcenter/NavRail.qml +++ b/modules/controlcenter/NavRail.qml @@ -171,6 +171,11 @@ Item { icon: "volume_up" label: "audio" } + + NavItem { + icon: "palette" + label: "appearance" + } } component NavItem: Item { diff --git a/modules/controlcenter/Panes.qml b/modules/controlcenter/Panes.qml index cc4a4cb..5b1039c 100644 --- a/modules/controlcenter/Panes.qml +++ b/modules/controlcenter/Panes.qml @@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound import "bluetooth" import "network" import "audio" +import "appearance" import qs.components import qs.services import qs.config @@ -44,6 +45,13 @@ ClippingRectangle { } } + Pane { + index: 3 + sourceComponent: AppearancePane { + session: root.session + } + } + Behavior on y { Anim {} } diff --git a/modules/controlcenter/Session.qml b/modules/controlcenter/Session.qml index b716b20..4ac09a4 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: ["network", "bluetooth", "audio"] + readonly property list panes: ["network", "bluetooth", "audio", "appearance"] required property var root property bool floating: false diff --git a/modules/controlcenter/appearance/AppearancePane.qml b/modules/controlcenter/appearance/AppearancePane.qml new file mode 100644 index 0000000..ab00345 --- /dev/null +++ b/modules/controlcenter/appearance/AppearancePane.qml @@ -0,0 +1,520 @@ +pragma ComponentBehavior: Bound + +import ".." +import "../../launcher/services" +import qs.components +import qs.components.controls +import qs.components.effects +import qs.components.containers +import qs.components.images +import qs.services +import qs.config +import Caelestia.Models +import Quickshell +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 + + ColumnLayout { + 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 + + RowLayout { + spacing: Appearance.spacing.smaller + + StyledText { + text: qsTr("Settings") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + Item { + Layout.fillWidth: true + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Theme mode") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("Light or dark theme") + color: Colours.palette.m3outline + } + + StyledRect { + Layout.fillWidth: true + implicitHeight: modeToggle.implicitHeight + Appearance.padding.large * 2 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + + RowLayout { + id: modeToggle + + 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("Dark mode") + } + + StyledSwitch { + checked: !Colours.currentLight + onToggled: { + Colours.setMode(checked ? "dark" : "light"); + } + } + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Color variant") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("Material theme variant") + color: Colours.palette.m3outline + } + + StyledListView { + Layout.fillWidth: true + Layout.fillHeight: true + + model: M3Variants.list + spacing: Appearance.spacing.small / 2 + clip: true + + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent + } + + delegate: StyledRect { + required property var modelData + + anchors.left: parent.left + anchors.right: parent.right + + 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 { + Quickshell.execDetached(["caelestia", "scheme", "set", "-v", modelData.variant]); + 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 + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Color scheme") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("Available color schemes") + color: Colours.palette.m3outline + } + + StyledListView { + Layout.fillWidth: true + Layout.fillHeight: true + + model: Schemes.list + spacing: Appearance.spacing.small / 2 + clip: true + + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent + } + + delegate: StyledRect { + required property var modelData + + anchors.left: parent.left + anchors.right: parent.right + + 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 { + Quickshell.execDetached(["caelestia", "scheme", "set", "-n", modelData.name, "-f", modelData.flavour]); + Schemes.reload(); + } + } + + RowLayout { + id: schemeRow + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal + + spacing: Appearance.spacing.normal + + Item { + readonly property real itemHeight: schemeRow.implicitHeight || 50 + Layout.preferredWidth: itemHeight * 0.8 + Layout.preferredHeight: itemHeight * 0.8 + + StyledRect { + id: preview + + anchors.verticalCenter: parent.verticalCenter + + border.width: 1 + border.color: Qt.alpha(`#${modelData.colours?.outline}`, 0.5) + + color: `#${modelData.colours?.surface}` + radius: Appearance.rounding.full + implicitWidth: parent.itemHeight * 0.8 + implicitHeight: parent.itemHeight * 0.8 + + 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 + } + } + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + StyledText { + text: modelData.name + font.weight: isCurrent ? 500 : 400 + } + + StyledText { + text: modelData.flavour + font.pointSize: Appearance.font.size.small + color: Colours.palette.m3outline + } + } + + MaterialIcon { + visible: isCurrent + text: "check" + color: Colours.palette.m3primary + font.pointSize: Appearance.font.size.large + } + } + + implicitHeight: schemeRow.implicitHeight + Appearance.padding.normal * 2 + } + } + } + + InnerBorder { + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + StyledFlickable { + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + flickableDirection: Flickable.VerticalFlick + contentHeight: contentLayout.implicitHeight + + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent + } + + ColumnLayout { + id: contentLayout + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + + spacing: Appearance.spacing.normal + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + 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 + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + Layout.alignment: Qt.AlignHCenter + text: qsTr("Theme mode") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Colours.currentLight ? qsTr("Light mode") : qsTr("Dark mode") + color: Colours.palette.m3outline + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + Layout.alignment: Qt.AlignHCenter + text: qsTr("Wallpaper") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Select a wallpaper") + color: Colours.palette.m3outline + } + + GridLayout { + Layout.fillWidth: true + Layout.topMargin: Appearance.spacing.normal + Layout.alignment: Qt.AlignHCenter + + 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; + const itemWidth = 180; + const spacing = columnSpacing; + return cols * itemWidth + (cols - 1) * spacing; + } + + Repeater { + model: Wallpapers.list + + delegate: Item { + required property var modelData + + Layout.preferredWidth: 180 + Layout.preferredHeight: 120 + Layout.minimumWidth: 180 + Layout.minimumHeight: 120 + + readonly property bool isCurrent: modelData.path === Wallpapers.actualCurrent + readonly property real imageWidth: Math.max(1, width) + readonly property real imageHeight: Math.max(1, height) + + StateLayer { + radius: Appearance.rounding.normal + + function onClicked(): void { + Wallpapers.setWallpaper(modelData.path); + } + } + + StyledClippingRect { + id: image + + anchors.fill: parent + color: Colours.tPalette.m3surfaceContainer + radius: Appearance.rounding.normal + + border.width: isCurrent ? 2 : 0 + border.color: Colours.palette.m3primary + + CachingImage { + id: cachingImage + + 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 + } + } + } + + // Fallback: Direct image load if caching fails or is slow + Image { + id: fallbackImage + + anchors.fill: parent + source: modelData.path + asynchronous: true + fillMode: Image.PreserveAspectCrop + sourceSize: Qt.size( + 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 + running: cachingImage.status === Image.Loading || (cachingImage.status !== Image.Ready && cachingImage.status !== Image.Null) + onTriggered: { + if (cachingImage.status !== Image.Ready && fallbackImage.status === Image.Ready) { + fallbackImage.opacity = 1; + } + } + } + + // Also check status changes + onStatusChanged: { + if (status === Image.Ready && cachingImage.status !== Image.Ready) { + Qt.callLater(() => { + if (cachingImage.status !== Image.Ready) { + fallbackImage.opacity = 1; + } + }); + } + } + + Behavior on opacity { + NumberAnimation { + duration: 200 + } + } + } + + MaterialIcon { + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Appearance.padding.small + + visible: isCurrent + text: "check_circle" + color: Colours.palette.m3primary + font.pointSize: Appearance.font.size.large + } + } + + StyledText { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: Appearance.padding.small + + text: modelData.relativePath + font.pointSize: Appearance.font.size.small + color: isCurrent ? Colours.palette.m3primary : Colours.palette.m3onSurface + elide: Text.ElideRight + } + } + } + } + } + } + + InnerBorder { + leftThickness: Appearance.padding.normal / 2 + } + } +} + + -- cgit v1.2.3-freya From 1debf488ee1ed24763a01c2e1bf5c3f4119de18f Mon Sep 17 00:00:00 2001 From: ATMDA Date: Mon, 10 Nov 2025 10:00:04 -0500 Subject: controlcenter: ethernet panel (debug) --- modules/controlcenter/NavRail.qml | 9 +- modules/controlcenter/Panes.qml | 14 +- modules/controlcenter/Session.qml | 7 +- modules/controlcenter/ethernet/EthernetDetails.qml | 204 ++++++++++++++++ modules/controlcenter/ethernet/EthernetList.qml | 262 +++++++++++++++++++++ modules/controlcenter/ethernet/EthernetPane.qml | 156 ++++++++++++ .../controlcenter/ethernet/EthernetSettings.qml | 155 ++++++++++++ services/Network.qml | 183 +++++++++++++- 8 files changed, 983 insertions(+), 7 deletions(-) create mode 100644 modules/controlcenter/ethernet/EthernetDetails.qml create mode 100644 modules/controlcenter/ethernet/EthernetList.qml create mode 100644 modules/controlcenter/ethernet/EthernetPane.qml create mode 100644 modules/controlcenter/ethernet/EthernetSettings.qml (limited to 'modules/controlcenter/NavRail.qml') diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml index 96bbb65..b4fbf94 100644 --- a/modules/controlcenter/NavRail.qml +++ b/modules/controlcenter/NavRail.qml @@ -158,8 +158,13 @@ Item { NavItem { Layout.topMargin: Appearance.spacing.large * 2 - icon: "network_manage" - label: "network" + icon: "cable" + label: "ethernet" + } + + NavItem { + icon: "wifi" + label: "wireless" } NavItem { diff --git a/modules/controlcenter/Panes.qml b/modules/controlcenter/Panes.qml index 5b1039c..4f4a337 100644 --- a/modules/controlcenter/Panes.qml +++ b/modules/controlcenter/Panes.qml @@ -1,5 +1,6 @@ pragma ComponentBehavior: Bound +import "ethernet" import "bluetooth" import "network" import "audio" @@ -26,27 +27,34 @@ ClippingRectangle { Pane { index: 0 - sourceComponent: NetworkPane { + sourceComponent: EthernetPane { session: root.session } } Pane { index: 1 - sourceComponent: BtPane { + sourceComponent: NetworkPane { session: root.session } } Pane { index: 2 - sourceComponent: AudioPane { + sourceComponent: BtPane { session: root.session } } Pane { index: 3 + sourceComponent: AudioPane { + session: root.session + } + } + + Pane { + index: 4 sourceComponent: AppearancePane { session: root.session } diff --git a/modules/controlcenter/Session.qml b/modules/controlcenter/Session.qml index 4ac09a4..f7c07e4 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: ["network", "bluetooth", "audio", "appearance"] + readonly property list panes: ["ethernet", "wireless", "bluetooth", "audio", "appearance"] required property var root property bool floating: false @@ -12,6 +12,7 @@ QtObject { readonly property Bt bt: Bt {} readonly property Network network: Network {} + readonly property Ethernet ethernet: Ethernet {} onActiveChanged: activeIndex = panes.indexOf(active) onActiveIndexChanged: active = panes[activeIndex] @@ -29,4 +30,8 @@ QtObject { property bool showPasswordDialog: false property var pendingNetwork } + + component Ethernet: QtObject { + property var active + } } diff --git a/modules/controlcenter/ethernet/EthernetDetails.qml b/modules/controlcenter/ethernet/EthernetDetails.qml new file mode 100644 index 0000000..9be3ddc --- /dev/null +++ b/modules/controlcenter/ethernet/EthernetDetails.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 device: session.ethernet.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: "cable" + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + animate: true + text: root.device?.interface ?? 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 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) { + if (root.device?.connection) { + Network.connectEthernet(root.device.connection); + } + } 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 + } + + 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 + + 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") + } + + StyledText { + text: root.device?.state ?? qsTr("Unknown") + 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/ethernet/EthernetList.qml b/modules/controlcenter/ethernet/EthernetList.qml new file mode 100644 index 0000000..d239fc6 --- /dev/null +++ b/modules/controlcenter/ethernet/EthernetList.qml @@ -0,0 +1,262 @@ +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" + + function onClicked(): void { + 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(Network.ethernetDeviceCount || Network.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: Network.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 + + 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) { + Network.disconnectEthernet(modelData.connection); + } else if (modelData.connection) { + Network.connectEthernet(modelData.connection); + } + } + } + + 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 + } + } + + 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/ethernet/EthernetPane.qml b/modules/controlcenter/ethernet/EthernetPane.qml new file mode 100644 index 0000000..fc3e1c0 --- /dev/null +++ b/modules/controlcenter/ethernet/EthernetPane.qml @@ -0,0 +1,156 @@ +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" + + 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: 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.interface || "") : ""; + } + } + } + + InnerBorder { + id: rightBorder + + leftThickness: Appearance.padding.normal / 2 + } + + Component { + id: settings + + StyledFlickable { + flickableDirection: Flickable.VerticalFlick + contentHeight: settingsInner.height + + 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 + } +} + + + + + + + + diff --git a/modules/controlcenter/ethernet/EthernetSettings.qml b/modules/controlcenter/ethernet/EthernetSettings.qml new file mode 100644 index 0000000..b780b55 --- /dev/null +++ b/modules/controlcenter/ethernet/EthernetSettings.qml @@ -0,0 +1,155 @@ +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(Network.ethernetDeviceCount || Network.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(Network.ethernetDevices.filter(d => d.connected).length) + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.small + } + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Debug Info") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledRect { + Layout.fillWidth: true + implicitHeight: debugInfo.implicitHeight + Appearance.padding.large * 2 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + + ColumnLayout { + id: debugInfo + + 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("Process running: %1").arg(Network.ethernetProcessRunning ? "Yes" : "No") + font.pointSize: Appearance.font.size.small + } + + StyledText { + text: qsTr("List length: %1").arg(Network.ethernetDevices.length) + font.pointSize: Appearance.font.size.small + } + + StyledText { + text: qsTr("Device count: %1").arg(Network.ethernetDeviceCount) + font.pointSize: Appearance.font.size.small + } + + StyledText { + Layout.topMargin: Appearance.spacing.normal + text: qsTr("Debug: %1").arg(Network.ethernetDebugInfo || "No info") + font.pointSize: Appearance.font.size.small + color: Colours.palette.m3outline + wrapMode: Text.Wrap + Layout.maximumWidth: parent.width + } + } + } +} + + + + + + + + + + + + + + + + + + + + + diff --git a/services/Network.qml b/services/Network.qml index 3ceadab..1dee367 100644 --- a/services/Network.qml +++ b/services/Network.qml @@ -7,11 +7,24 @@ import QtQuick Singleton { id: root + Component.onCompleted: { + // Trigger ethernet device detection after initialization + Qt.callLater(() => { + getEthernetDevices(); + }); + } + readonly property list networks: [] readonly property AccessPoint active: networks.find(n => n.active) ?? null property bool wifiEnabled: true readonly property bool scanning: rescanProc.running + property list ethernetDevices: [] + readonly property var activeEthernet: ethernetDevices.find(d => d.connected) ?? null + property int ethernetDeviceCount: 0 + property string ethernetDebugInfo: "" + property bool ethernetProcessRunning: false + function enableWifi(enabled: bool): void { const cmd = enabled ? "on" : "off"; enableWifiProc.exec(["nmcli", "radio", "wifi", cmd]); @@ -70,11 +83,27 @@ Singleton { wifiStatusProc.running = true; } + function getEthernetDevices(): void { + getEthernetDevicesProc.running = true; + } + + + function connectEthernet(connectionName: string): void { + connectEthernetProc.exec(["nmcli", "connection", "up", connectionName]); + } + + function disconnectEthernet(connectionName: string): void { + disconnectEthernetProc.exec(["nmcli", "connection", "down", connectionName]); + } + Process { running: true command: ["nmcli", "m"] stdout: SplitParser { - onRead: getNetworks.running = true + onRead: { + getNetworks.running = true; + getEthernetDevices(); + } } } @@ -336,6 +365,158 @@ Singleton { } } + Process { + id: getEthernetDevicesProc + + running: false + command: ["nmcli", "-g", "DEVICE,TYPE,STATE,CONNECTION", "device", "status"] + environment: ({ + LANG: "C.UTF-8", + LC_ALL: "C.UTF-8" + }) + onRunningChanged: { + root.ethernetProcessRunning = running; + if (!running) { + // Process finished, update debug info + Qt.callLater(() => { + if (root.ethernetDebugInfo === "" || root.ethernetDebugInfo.includes("Process exited")) { + root.ethernetDebugInfo = "Process finished, waiting for output..."; + } + }); + } + } + onExited: { + Qt.callLater(() => { + const outputLength = ethernetStdout.text ? ethernetStdout.text.length : 0; + root.ethernetDebugInfo = "Process exited with code: " + exitCode + ", output length: " + outputLength; + if (outputLength > 0) { + // Output was captured, process it + const output = ethernetStdout.text.trim(); + root.ethernetDebugInfo = "Processing output from onExited, length: " + output.length + "\nOutput: " + output.substring(0, 200); + root.processEthernetOutput(output); + } else { + root.ethernetDebugInfo = "No output captured in onExited"; + } + }); + } + stdout: StdioCollector { + id: ethernetStdout + 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); + } + } + } + + function processEthernetOutput(output: string): void { + const PLACEHOLDER = "STRINGWHICHHOPEFULLYWONTBEUSED"; + const rep = new RegExp("\\\\:", "g"); + const rep2 = new RegExp(PLACEHOLDER, "g"); + + const lines = output.split("\n"); + root.ethernetDebugInfo = "Processing " + lines.length + " lines"; + + const allDevices = lines.map(d => { + const dev = d.replace(rep, PLACEHOLDER).split(":"); + return { + interface: dev[0]?.replace(rep2, ":") ?? "", + type: dev[1]?.replace(rep2, ":") ?? "", + state: dev[2]?.replace(rep2, ":") ?? "", + 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; + + const ethernetDevices = ethernetOnly.map(d => { + const state = d.state || ""; + const connected = state === "100 (connected)" || state === "connected" || state.startsWith("connected"); + return { + interface: d.interface, + type: d.type, + state: state, + connection: d.connection, + connected: connected, + ipAddress: "", + gateway: "", + dns: [], + subnet: "", + macAddress: "", + 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 + // Create a new array and assign it to the property + const newDevices = []; + 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; + root.ethernetDebugInfo = "Final: Found " + ethernetDevices.length + " devices, List length: " + count + ", Parsed all: " + allDevices.length + ", Output length: " + output.length; + }); + } + + + Process { + id: connectEthernetProc + + onExited: { + getEthernetDevices(); + } + stdout: SplitParser { + onRead: getEthernetDevices() + } + stderr: StdioCollector { + onStreamFinished: { + const error = text.trim(); + if (error && error.length > 0 && !error.includes("successfully") && !error.includes("Connection activated")) { + console.warn("Ethernet connection error:", error); + } + } + } + } + + Process { + id: disconnectEthernetProc + + onExited: { + getEthernetDevices(); + } + stdout: SplitParser { + onRead: getEthernetDevices() + } + stderr: StdioCollector { + onStreamFinished: { + const error = text.trim(); + if (error && error.length > 0 && !error.includes("successfully") && !error.includes("disconnected")) { + console.warn("Ethernet disconnection error:", error); + } + } + } + } + component AccessPoint: QtObject { required property var lastIpcObject readonly property string ssid: lastIpcObject.ssid -- cgit v1.2.3-freya From 7ad940ea9f651187118048179689dfdbbdc71427 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Mon, 10 Nov 2025 15:57:11 -0500 Subject: controlcenter: taskbar object toggles --- modules/controlcenter/NavRail.qml | 5 + modules/controlcenter/Panes.qml | 8 + modules/controlcenter/Session.qml | 2 +- modules/controlcenter/taskbar/TaskbarPane.qml | 402 ++++++++++++++++++++++++++ 4 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 modules/controlcenter/taskbar/TaskbarPane.qml (limited to 'modules/controlcenter/NavRail.qml') diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml index b4fbf94..1f8c6b3 100644 --- a/modules/controlcenter/NavRail.qml +++ b/modules/controlcenter/NavRail.qml @@ -181,6 +181,11 @@ Item { icon: "palette" label: "appearance" } + + NavItem { + icon: "task_alt" + label: "taskbar" + } } component NavItem: Item { diff --git a/modules/controlcenter/Panes.qml b/modules/controlcenter/Panes.qml index 4f4a337..b30bcf2 100644 --- a/modules/controlcenter/Panes.qml +++ b/modules/controlcenter/Panes.qml @@ -5,6 +5,7 @@ import "bluetooth" import "network" import "audio" import "appearance" +import "taskbar" import qs.components import qs.services import qs.config @@ -60,6 +61,13 @@ ClippingRectangle { } } + Pane { + index: 5 + sourceComponent: TaskbarPane { + session: root.session + } + } + Behavior on y { Anim {} } diff --git a/modules/controlcenter/Session.qml b/modules/controlcenter/Session.qml index f7c07e4..cf31ec3 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"] + readonly property list panes: ["ethernet", "wireless", "bluetooth", "audio", "appearance", "taskbar"] required property var root property bool floating: false diff --git a/modules/controlcenter/taskbar/TaskbarPane.qml b/modules/controlcenter/taskbar/TaskbarPane.qml new file mode 100644 index 0000000..a367635 --- /dev/null +++ b/modules/controlcenter/taskbar/TaskbarPane.qml @@ -0,0 +1,402 @@ +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 qs.utils +import Quickshell +import Quickshell.Io +import QtQuick +import QtQuick.Layouts + +RowLayout { + id: root + + required property Session session + + anchors.fill: parent + + spacing: 0 + + FileView { + id: configFile + + path: `${Paths.config}/shell.json` + watchChanges: true + + onLoaded: { + try { + const config = JSON.parse(text()); + updateFromConfig(config); + } catch (e) { + console.error("Failed to parse config:", e); + } + } + } + + function updateFromConfig(config) { + // Update clock toggle + if (config.bar && config.bar.clock) { + clockShowIconSwitch.checked = config.bar.clock.showIcon !== false; + } + + // Update entries + if (config.bar && config.bar.entries) { + entriesModel.clear(); + for (const entry of config.bar.entries) { + entriesModel.append({ + id: entry.id, + enabled: entry.enabled !== false + }); + } + } + } + + function saveConfig(entryIndex, entryEnabled) { + if (!configFile.loaded) { + root.lastSaveStatus = "Error: Config file not loaded yet"; + root.debugInfo = "Config file not loaded yet, cannot save"; + return; + } + + try { + const config = JSON.parse(configFile.text()); + + // Update clock setting (same simple approach - read directly from the switch) + if (!config.bar) config.bar = {}; + if (!config.bar.clock) config.bar.clock = {}; + config.bar.clock.showIcon = clockShowIconSwitch.checked; + + // Update entries from the model (same approach as clock - use provided value if available) + if (!config.bar.entries) config.bar.entries = []; + config.bar.entries = []; + + let debugInfo = `saveConfig called\n`; + debugInfo += `entryIndex: ${entryIndex}\n`; + debugInfo += `entryEnabled: ${entryEnabled}\n`; + debugInfo += `entriesModel.count: ${entriesModel.count}\n\n`; + + 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; + debugInfo += `Entry ${i} (${entry.id}): Using provided value = ${entryEnabled}\n`; + } else { + debugInfo += `Entry ${i} (${entry.id}): Using model value = ${entry.enabled}\n`; + } + config.bar.entries.push({ + id: entry.id, + enabled: enabled + }); + } + + debugInfo += `\nFinal entries array:\n${JSON.stringify(config.bar.entries, null, 2)}\n`; + debugInfo += `\nFinal clock.showIcon: ${config.bar.clock.showIcon}\n`; + root.debugInfo = debugInfo; + + // Write back to file using setText (same simple approach that worked for clock) + const jsonString = JSON.stringify(config, null, 4); + configFile.setText(jsonString); + root.lastSaveStatus = `Saved! Entries count: ${config.bar.entries.length}`; + } catch (e) { + root.lastSaveStatus = `Error: ${e.message}`; + root.debugInfo = `Failed to save config:\n${e.message}\n${e.stack}`; + } + } + + ListModel { + id: entriesModel + } + + // Debug info + property string debugInfo: "" + property string lastSaveStatus: "" + + Item { + Layout.preferredWidth: Math.floor(parent.width * 0.4) + Layout.minimumWidth: 420 + Layout.fillHeight: true + + ColumnLayout { + 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 + + RowLayout { + spacing: Appearance.spacing.smaller + + StyledText { + text: qsTr("Settings") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + Item { + Layout.fillWidth: true + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Clock") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("Clock display settings") + color: Colours.palette.m3outline + } + + StyledRect { + Layout.fillWidth: true + implicitHeight: clockRow.implicitHeight + Appearance.padding.large * 2 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + + RowLayout { + id: clockRow + + 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 clock icon") + } + + StyledSwitch { + id: clockShowIconSwitch + checked: true + onToggled: { + root.saveConfig(); + } + } + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Taskbar Entries") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("Enable or disable taskbar entries") + color: Colours.palette.m3outline + } + + StyledListView { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.topMargin: 0 + + model: entriesModel + spacing: Appearance.spacing.small / 2 + clip: true + + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent + } + + delegate: StyledRect { + id: delegate + required property string id + required property bool enabled + required property int index + + anchors.left: parent.left + anchors.right: parent.right + + color: Colours.tPalette.m3surfaceContainer + radius: Appearance.rounding.normal + + RowLayout { + id: entryRow + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal + + spacing: Appearance.spacing.normal + + StyledText { + Layout.fillWidth: true + text: id.charAt(0).toUpperCase() + id.slice(1) + font.weight: enabled ? 500 : 400 + } + + StyledSwitch { + checked: enabled + onToggled: { + // Store the values in local variables to ensure they're accessible + // Access index from the delegate + const entryIndex = delegate.index; + const entryEnabled = checked; + console.log(`Entry toggle: index=${entryIndex}, checked=${entryEnabled}`); + // Update the model first + entriesModel.setProperty(entryIndex, "enabled", entryEnabled); + // Save immediately with the value directly (same technique as clock toggle) + // Clock toggle reads directly from clockShowIconSwitch.checked + // We pass the value directly here (same approach) + root.saveConfig(entryIndex, entryEnabled); + } + } + } + + implicitHeight: entryRow.implicitHeight + Appearance.padding.normal * 2 + } + } + } + + InnerBorder { + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + StyledFlickable { + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + flickableDirection: Flickable.VerticalFlick + contentHeight: contentLayout.implicitHeight + + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent + } + + ColumnLayout { + id: contentLayout + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + + spacing: Appearance.spacing.normal + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + text: "task_alt" + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Taskbar settings") + font.pointSize: Appearance.font.size.large + font.bold: true + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + Layout.alignment: Qt.AlignHCenter + text: qsTr("Clock") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: clockShowIconSwitch.checked ? qsTr("Clock icon enabled") : qsTr("Clock icon disabled") + color: Colours.palette.m3outline + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + Layout.alignment: Qt.AlignHCenter + text: qsTr("Taskbar Entries") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Configure which entries appear in the taskbar") + color: Colours.palette.m3outline + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + Layout.alignment: Qt.AlignHCenter + text: qsTr("Debug Info") + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledRect { + Layout.fillWidth: true + Layout.preferredHeight: 200 + Layout.maximumHeight: 300 + + radius: Appearance.rounding.normal + color: Colours.tPalette.m3surfaceContainer + clip: true + + StyledFlickable { + id: debugFlickable + anchors.fill: parent + anchors.margins: Appearance.padding.normal + contentHeight: debugText.implicitHeight + clip: true + + StyledScrollBar.vertical: StyledScrollBar { + flickable: debugFlickable + } + + TextEdit { + id: debugText + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + width: parent.width + + text: root.debugInfo || "No debug info yet" + font.pointSize: Appearance.font.size.small + color: Colours.palette.m3onSurface + wrapMode: TextEdit.Wrap + readOnly: true + selectByMouse: true + selectByKeyboard: true + } + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.small + Layout.alignment: Qt.AlignHCenter + text: root.lastSaveStatus || "" + font.pointSize: Appearance.font.size.small + color: root.lastSaveStatus.includes("Error") ? Colours.palette.m3error : Colours.palette.m3primary + } + } + } + + InnerBorder { + leftThickness: Appearance.padding.normal / 2 + } + } +} + -- cgit v1.2.3-freya From 97d461871a5fbfedb3aff080303145efb8a6fffc Mon Sep 17 00:00:00 2001 From: ATMDA Date: Tue, 11 Nov 2025 11:41:30 -0500 Subject: controlcenter: launcher panel, can hide/show applications --- modules/controlcenter/NavRail.qml | 5 + modules/controlcenter/Panes.qml | 8 + modules/controlcenter/Session.qml | 2 +- modules/controlcenter/launcher/LauncherPane.qml | 446 ++++++++++++++++++++++++ 4 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 modules/controlcenter/launcher/LauncherPane.qml (limited to 'modules/controlcenter/NavRail.qml') diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml index 1f8c6b3..ca36048 100644 --- a/modules/controlcenter/NavRail.qml +++ b/modules/controlcenter/NavRail.qml @@ -186,6 +186,11 @@ Item { icon: "task_alt" label: "taskbar" } + + NavItem { + icon: "apps" + label: "launcher" + } } component NavItem: Item { diff --git a/modules/controlcenter/Panes.qml b/modules/controlcenter/Panes.qml index b30bcf2..94bea9a 100644 --- a/modules/controlcenter/Panes.qml +++ b/modules/controlcenter/Panes.qml @@ -6,6 +6,7 @@ import "network" import "audio" import "appearance" import "taskbar" +import "launcher" import qs.components import qs.services import qs.config @@ -68,6 +69,13 @@ ClippingRectangle { } } + Pane { + index: 6 + sourceComponent: LauncherPane { + session: root.session + } + } + Behavior on y { Anim {} } diff --git a/modules/controlcenter/Session.qml b/modules/controlcenter/Session.qml index cf31ec3..d44898e 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"] + readonly property list panes: ["ethernet", "wireless", "bluetooth", "audio", "appearance", "taskbar", "launcher"] required property var root property bool floating: false diff --git a/modules/controlcenter/launcher/LauncherPane.qml b/modules/controlcenter/launcher/LauncherPane.qml new file mode 100644 index 0000000..7377f42 --- /dev/null +++ b/modules/controlcenter/launcher/LauncherPane.qml @@ -0,0 +1,446 @@ +pragma ComponentBehavior: Bound + +import ".." +import "../../launcher/services" +import qs.components +import qs.components.controls +import qs.components.effects +import qs.components.containers +import qs.services +import qs.config +import qs.utils +import Caelestia +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import QtQuick +import QtQuick.Layouts + +RowLayout { + id: root + + required property Session session + + property var selectedApp: null + property bool showDebugInfo: false + + anchors.fill: parent + + spacing: 0 + + FileView { + id: configFile + + path: `${Paths.config}/shell.json` + watchChanges: true + + onLoaded: { + try { + const config = JSON.parse(text()); + updateToggleState(); + } catch (e) { + console.error("Failed to parse config:", e); + } + } + } + + function updateToggleState() { + if (!root.selectedApp || !configFile.loaded) { + hideFromLauncherSwitch.checked = false; + return; + } + + try { + const config = JSON.parse(configFile.text()); + const appId = root.selectedApp.id || root.selectedApp.entry?.id; + + if (config.launcher && config.launcher.hiddenApps) { + hideFromLauncherSwitch.checked = config.launcher.hiddenApps.includes(appId); + } else { + hideFromLauncherSwitch.checked = false; + } + } catch (e) { + console.error("Failed to update toggle state:", e); + } + } + + function saveHiddenApps() { + if (!configFile.loaded || !root.selectedApp) { + return; + } + + 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; + const isHidden = hideFromLauncherSwitch.checked; + + 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); + } + } + + const jsonString = JSON.stringify(config, null, 4); + configFile.setText(jsonString); + } catch (e) { + console.error("Failed to save config:", e); + } + } + + onSelectedAppChanged: { + updateToggleState(); + } + + AppDb { + id: allAppsDb + + path: `${Paths.state}/apps.sqlite` + entries: DesktopEntries.applications.values // No filter - show all apps + } + + Item { + Layout.preferredWidth: Math.floor(parent.width * 0.4) + Layout.minimumWidth: 420 + Layout.fillHeight: true + + ColumnLayout { + 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 + + RowLayout { + spacing: Appearance.spacing.smaller + + StyledText { + text: qsTr("Settings") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + + Item { + Layout.fillWidth: true + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.large + text: qsTr("Applications (%1)").arg(allAppsDb.apps.length) + font.pointSize: Appearance.font.size.larger + font.weight: 500 + } + + StyledText { + text: qsTr("All applications available in the launcher") + color: Colours.palette.m3outline + } + + StyledListView { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.topMargin: Appearance.spacing.normal + + model: allAppsDb.apps + spacing: Appearance.spacing.small / 2 + clip: true + + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent + } + + delegate: StyledRect { + required property var modelData + + anchors.left: parent.left + anchors.right: parent.right + + readonly property bool isSelected: root.selectedApp === modelData + + 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 { + root.selectedApp = modelData; + } + } + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: Appearance.padding.normal + + spacing: Appearance.spacing.normal + + IconImage { + Layout.alignment: Qt.AlignVCenter + implicitSize: 32 + source: { + const entry = modelData.entry; + return entry ? Quickshell.iconPath(entry.icon, "image-missing") : "image-missing"; + } + } + + StyledText { + Layout.fillWidth: true + text: modelData.name || modelData.entry?.name || qsTr("Unknown") + font.pointSize: Appearance.font.size.normal + } + } + + implicitHeight: 40 + } + } + } + + InnerBorder { + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + ColumnLayout { + anchors.fill: parent + anchors.margins: Appearance.padding.large * 2 + + spacing: Appearance.spacing.normal + + Item { + Layout.alignment: Qt.AlignHCenter + implicitWidth: iconLoader.implicitWidth + implicitHeight: iconLoader.implicitHeight + + Loader { + id: iconLoader + sourceComponent: root.selectedApp ? appIconComponent : defaultIconComponent + } + + Component { + id: appIconComponent + IconImage { + implicitSize: Appearance.font.size.extraLarge * 3 * 2 + source: { + if (!root.selectedApp) return "image-missing"; + const entry = root.selectedApp.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 + } + } + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + 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 + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.topMargin: Appearance.spacing.large + + StyledFlickable { + anchors.fill: parent + flickableDirection: Flickable.VerticalFlick + contentHeight: debugLayout.implicitHeight + + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent + } + + ColumnLayout { + id: debugLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: Appearance.spacing.normal + + StyledRect { + Layout.fillWidth: true + Layout.topMargin: Appearance.spacing.normal + implicitHeight: hideToggleRow.implicitHeight + Appearance.padding.large * 2 + color: Colours.tPalette.m3surfaceContainer + radius: Appearance.rounding.normal + + RowLayout { + id: hideToggleRow + 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("Hide from launcher") + font.pointSize: Appearance.font.size.normal + } + + StyledSwitch { + id: hideFromLauncherSwitch + checked: false + enabled: root.selectedApp !== null && configFile.loaded + onToggled: { + root.saveHiddenApps(); + } + } + } + } + + StyledRect { + Layout.fillWidth: true + Layout.topMargin: Appearance.spacing.normal + implicitHeight: debugToggleRow.implicitHeight + Appearance.padding.large * 2 + color: Colours.tPalette.m3surfaceContainer + radius: Appearance.rounding.normal + + RowLayout { + id: debugToggleRow + 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 debug information") + font.pointSize: Appearance.font.size.normal + } + + StyledSwitch { + id: showDebugInfoSwitch + checked: root.showDebugInfo + onToggled: { + root.showDebugInfo = checked; + } + } + } + } + + StyledRect { + Layout.fillWidth: true + Layout.topMargin: Appearance.spacing.normal + Layout.preferredHeight: 300 + visible: root.showDebugInfo + color: Colours.tPalette.m3surfaceContainer + radius: Appearance.rounding.normal + clip: true + + ColumnLayout { + anchors.fill: parent + anchors.margins: Appearance.padding.normal + spacing: Appearance.spacing.small + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Debug Info - All Available Properties") + font.pointSize: Appearance.font.size.normal + font.weight: 500 + } + + StyledFlickable { + Layout.fillWidth: true + Layout.fillHeight: true + flickableDirection: Flickable.VerticalFlick + contentHeight: debugText.implicitHeight + clip: true + + StyledScrollBar.vertical: StyledScrollBar { + flickable: parent + } + + TextEdit { + id: debugText + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + width: parent.width + + text: { + if (!root.selectedApp) return "No app selected"; + + let debug = ""; + const app = root.selectedApp; + const entry = app.entry; + + debug += "=== App Properties ===\n"; + for (let prop in app) { + try { + const value = app[prop]; + debug += prop + ": " + (value !== null && value !== undefined ? String(value) : "null/undefined") + "\n"; + } catch (e) { + debug += prop + ": [error accessing]\n"; + } + } + + debug += "\n=== Entry Properties ===\n"; + if (entry) { + for (let prop in entry) { + try { + const value = entry[prop]; + debug += prop + ": " + (value !== null && value !== undefined ? String(value) : "null/undefined") + "\n"; + } catch (e) { + debug += prop + ": [error accessing]\n"; + } + } + } else { + debug += "entry is null\n"; + } + + return debug; + } + font.pointSize: Appearance.font.size.small + color: Colours.palette.m3onSurfaceVariant + wrapMode: TextEdit.Wrap + readOnly: true + selectByMouse: true + selectByKeyboard: true + } + } + } + } + } + } + } + } + + InnerBorder { + leftThickness: Appearance.padding.normal / 2 + } + } +} -- 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/NavRail.qml') 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/NavRail.qml') 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 7c2d49240c8fdd3c5eeb53f03691f681e9cc8eab Mon Sep 17 00:00:00 2001 From: ATMDA Date: Fri, 14 Nov 2025 15:34:22 -0500 Subject: controlcenter: changed labels for networking to network to match codebase --- modules/controlcenter/NavRail.qml | 2 +- modules/controlcenter/Session.qml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'modules/controlcenter/NavRail.qml') diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml index 80a1ee3..234f447 100644 --- a/modules/controlcenter/NavRail.qml +++ b/modules/controlcenter/NavRail.qml @@ -159,7 +159,7 @@ Item { NavItem { Layout.topMargin: Appearance.spacing.large * 2 icon: "router" - label: "networking" + label: "network" } NavItem { diff --git a/modules/controlcenter/Session.qml b/modules/controlcenter/Session.qml index 0aad702..efd360d 100644 --- a/modules/controlcenter/Session.qml +++ b/modules/controlcenter/Session.qml @@ -2,11 +2,11 @@ import Quickshell.Bluetooth import QtQuick QtObject { - readonly property list panes: ["networking", "bluetooth", "audio", "appearance", "taskbar", "launcher"] + readonly property list panes: ["network", "bluetooth", "audio", "appearance", "taskbar", "launcher"] required property var root property bool floating: false - property string active: "networking" + property string active: "network" property int activeIndex: 0 property bool navExpanded: false -- cgit v1.2.3-freya From 21e715468e9800a73c731921c27177731033d2ec Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sun, 16 Nov 2025 09:45:05 -0500 Subject: controlcenter: changed to popout vs floating to match the other settings buttons (fixed issues with resizing) --- modules/bar/components/Settings.qml | 4 +- modules/bar/components/SettingsIcon.qml | 4 +- modules/controlcenter/NavRail.qml | 119 +------------------------------- modules/drawers/Panels.qml | 1 + modules/utilities/Content.qml | 2 + modules/utilities/Wrapper.qml | 2 + modules/utilities/cards/Toggles.qml | 5 +- 7 files changed, 14 insertions(+), 123 deletions(-) (limited to 'modules/controlcenter/NavRail.qml') diff --git a/modules/bar/components/Settings.qml b/modules/bar/components/Settings.qml index e92fc46..0eceea7 100644 --- a/modules/bar/components/Settings.qml +++ b/modules/bar/components/Settings.qml @@ -21,7 +21,9 @@ Item { radius: Appearance.rounding.full function onClicked(): void { - WindowFactory.create(); + WindowFactory.create(null, { + active: "appearance" + }); } } diff --git a/modules/bar/components/SettingsIcon.qml b/modules/bar/components/SettingsIcon.qml index e92fc46..0eceea7 100644 --- a/modules/bar/components/SettingsIcon.qml +++ b/modules/bar/components/SettingsIcon.qml @@ -21,7 +21,9 @@ Item { radius: Appearance.rounding.full function onClicked(): void { - WindowFactory.create(); + WindowFactory.create(null, { + active: "appearance" + }); } } diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml index 234f447..8432f17 100644 --- a/modules/controlcenter/NavRail.qml +++ b/modules/controlcenter/NavRail.qml @@ -30,129 +30,12 @@ Item { PropertyChanges { layout.spacing: Appearance.spacing.small - menuIcon.opacity: 0 - menuIconExpanded.opacity: 1 - menuIcon.rotation: 180 - menuIconExpanded.rotation: 0 } } transitions: Transition { Anim { - properties: "spacing,opacity,rotation" - } - } - - Item { - id: menuBtn - - Layout.topMargin: Appearance.spacing.large - implicitWidth: menuIcon.implicitWidth + menuIcon.anchors.leftMargin * 2 - implicitHeight: menuIcon.implicitHeight + Appearance.padding.normal * 2 - - StateLayer { - radius: Appearance.rounding.small - - function onClicked(): void { - root.session.navExpanded = !root.session.navExpanded; - } - } - - MaterialIcon { - id: menuIcon - - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: Appearance.padding.large - - text: "menu" - font.pointSize: Appearance.font.size.large - } - - MaterialIcon { - id: menuIconExpanded - - anchors.fill: menuIcon - text: "menu_open" - font.pointSize: menuIcon.font.pointSize - opacity: 0 - rotation: -180 - } - } - - Loader { - asynchronous: true - active: !root.session.floating - visible: active - - sourceComponent: StyledRect { - readonly property int nonAnimWidth: normalWinIcon.implicitWidth + (root.session.navExpanded ? normalWinLabel.anchors.leftMargin + normalWinLabel.implicitWidth : 0) + normalWinIcon.anchors.leftMargin * 2 - - implicitWidth: nonAnimWidth - implicitHeight: root.session.navExpanded ? normalWinIcon.implicitHeight + Appearance.padding.normal * 2 : nonAnimWidth - - color: Colours.palette.m3primaryContainer - radius: Appearance.rounding.small - - StateLayer { - id: normalWinState - - color: Colours.palette.m3onPrimaryContainer - - function onClicked(): void { - root.session.root.close(); - WindowFactory.create(null, { - screen: root.screen, - active: root.session.active, - navExpanded: root.session.navExpanded - }); - } - } - - MaterialIcon { - id: normalWinIcon - - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: Appearance.padding.large - - text: "select_window" - color: Colours.palette.m3onPrimaryContainer - font.pointSize: Appearance.font.size.large - fill: 1 - } - - StyledText { - id: normalWinLabel - - anchors.left: normalWinIcon.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: Appearance.spacing.normal - - text: qsTr("Float window") - color: Colours.palette.m3onPrimaryContainer - opacity: root.session.navExpanded ? 1 : 0 - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.small - } - } - } - - Behavior on implicitWidth { - Anim { - duration: Appearance.anim.durations.expressiveDefaultSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial - } - } - - Behavior on implicitHeight { - Anim { - duration: Appearance.anim.durations.expressiveDefaultSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial - } - } + properties: "spacing" } } diff --git a/modules/drawers/Panels.qml b/modules/drawers/Panels.qml index 4ce1182..7705732 100644 --- a/modules/drawers/Panels.qml +++ b/modules/drawers/Panels.qml @@ -109,6 +109,7 @@ Item { visibilities: root.visibilities sidebar: sidebar + popouts: popouts anchors.bottom: parent.bottom anchors.right: parent.right diff --git a/modules/utilities/Content.qml b/modules/utilities/Content.qml index d5be824..902656d 100644 --- a/modules/utilities/Content.qml +++ b/modules/utilities/Content.qml @@ -8,6 +8,7 @@ Item { required property var props required property var visibilities + required property Item popouts implicitWidth: layout.implicitWidth implicitHeight: layout.implicitHeight @@ -28,6 +29,7 @@ Item { Toggles { visibilities: root.visibilities + popouts: root.popouts } } diff --git a/modules/utilities/Wrapper.qml b/modules/utilities/Wrapper.qml index dd784bc..77178e3 100644 --- a/modules/utilities/Wrapper.qml +++ b/modules/utilities/Wrapper.qml @@ -10,6 +10,7 @@ Item { required property var visibilities required property Item sidebar + required property Item popouts readonly property PersistentProperties props: PersistentProperties { property bool recordingListExpanded: false @@ -89,6 +90,7 @@ Item { implicitWidth: root.implicitWidth - Appearance.padding.large * 2 props: root.props visibilities: root.visibilities + popouts: root.popouts } } } diff --git a/modules/utilities/cards/Toggles.qml b/modules/utilities/cards/Toggles.qml index d3ad51e..71f4d1d 100644 --- a/modules/utilities/cards/Toggles.qml +++ b/modules/utilities/cards/Toggles.qml @@ -12,6 +12,7 @@ StyledRect { id: root required property var visibilities + required property Item popouts Layout.fillWidth: true implicitHeight: layout.implicitHeight + Appearance.padding.large * 2 @@ -67,9 +68,7 @@ StyledRect { toggle: false onClicked: { root.visibilities.utilities = false; - WindowFactory.create(null, { - screen: QsWindow.window?.screen ?? null - }); + root.popouts.detach("appearance"); } } -- cgit v1.2.3-freya From 8a33ad990511d7cce8c68ee416e6999cfa9b8477 Mon Sep 17 00:00:00 2001 From: ATMDA Date: Sun, 16 Nov 2025 14:53:20 -0500 Subject: controlcenter: restored floating window toggle --- modules/controlcenter/NavRail.qml | 77 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) (limited to 'modules/controlcenter/NavRail.qml') diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml index 8432f17..d1d432d 100644 --- a/modules/controlcenter/NavRail.qml +++ b/modules/controlcenter/NavRail.qml @@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound import qs.components import qs.services import qs.config +import qs.modules.controlcenter import Quickshell import QtQuick import QtQuick.Layouts @@ -39,6 +40,82 @@ Item { } } + Loader { + Layout.topMargin: Appearance.spacing.large + asynchronous: true + active: !root.session.floating + visible: active + + sourceComponent: StyledRect { + readonly property int nonAnimWidth: normalWinIcon.implicitWidth + (root.session.navExpanded ? normalWinLabel.anchors.leftMargin + normalWinLabel.implicitWidth : 0) + normalWinIcon.anchors.leftMargin * 2 + + implicitWidth: nonAnimWidth + implicitHeight: root.session.navExpanded ? normalWinIcon.implicitHeight + Appearance.padding.normal * 2 : nonAnimWidth + + color: Colours.palette.m3primaryContainer + radius: Appearance.rounding.small + + StateLayer { + id: normalWinState + + color: Colours.palette.m3onPrimaryContainer + + function onClicked(): void { + root.session.root.close(); + WindowFactory.create(null, { + active: root.session.active, + navExpanded: root.session.navExpanded + }); + } + } + + MaterialIcon { + id: normalWinIcon + + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: Appearance.padding.large + + text: "select_window" + color: Colours.palette.m3onPrimaryContainer + font.pointSize: Appearance.font.size.large + fill: 1 + } + + StyledText { + id: normalWinLabel + + anchors.left: normalWinIcon.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: Appearance.spacing.normal + + text: qsTr("Float window") + color: Colours.palette.m3onPrimaryContainer + opacity: root.session.navExpanded ? 1 : 0 + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.small + } + } + } + + Behavior on implicitWidth { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + } + + Behavior on implicitHeight { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + } + } + } + NavItem { Layout.topMargin: Appearance.spacing.large * 2 icon: "router" -- cgit v1.2.3-freya From 34abc91523de0c2632f725e2b2ffb39e8e0ef18d Mon Sep 17 00:00:00 2001 From: ATMDA Date: Mon, 17 Nov 2025 19:27:45 -0500 Subject: controlcenter: prevented changing panes before opening animation completes --- modules/controlcenter/ControlCenter.qml | 11 +++++ modules/controlcenter/NavRail.qml | 5 ++ modules/controlcenter/Panes.qml | 82 ++++++++++++++++++++++----------- 3 files changed, 71 insertions(+), 27 deletions(-) (limited to 'modules/controlcenter/NavRail.qml') diff --git a/modules/controlcenter/ControlCenter.qml b/modules/controlcenter/ControlCenter.qml index 8cdf01f..3642a33 100644 --- a/modules/controlcenter/ControlCenter.qml +++ b/modules/controlcenter/ControlCenter.qml @@ -64,6 +64,11 @@ Item { anchors.fill: parent function onWheel(event: WheelEvent): void { + // Prevent tab switching during initial opening animation to avoid blank pages + if (!panes.initialOpeningComplete) { + return; + } + if (event.angleDelta.y < 0) root.session.activeIndex = Math.min(root.session.activeIndex + 1, root.session.panes.length - 1); else if (event.angleDelta.y > 0) @@ -76,10 +81,13 @@ Item { screen: root.screen session: root.session + initialOpeningComplete: root.initialOpeningComplete } } Panes { + id: panes + Layout.fillWidth: true Layout.fillHeight: true @@ -88,4 +96,7 @@ Item { session: root.session } } + + // Expose initialOpeningComplete for NavRail to prevent tab switching during opening animation + readonly property bool initialOpeningComplete: panes.initialOpeningComplete } diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml index d1d432d..1de1a9e 100644 --- a/modules/controlcenter/NavRail.qml +++ b/modules/controlcenter/NavRail.qml @@ -13,6 +13,7 @@ Item { required property ShellScreen screen required property Session session + required property bool initialOpeningComplete implicitWidth: layout.implicitWidth + Appearance.padding.larger * 4 implicitHeight: layout.implicitHeight + Appearance.padding.large * 2 @@ -197,6 +198,10 @@ Item { color: item.active ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface function onClicked(): void { + // Prevent tab switching during initial opening animation to avoid blank pages + if (!root.initialOpeningComplete) { + return; + } root.session.active = item.label; } } diff --git a/modules/controlcenter/Panes.qml b/modules/controlcenter/Panes.qml index e97a3fe..756d73a 100644 --- a/modules/controlcenter/Panes.qml +++ b/modules/controlcenter/Panes.qml @@ -18,6 +18,9 @@ ClippingRectangle { required property Session session + // Expose initialOpeningComplete so parent can check if opening animation is done + readonly property bool initialOpeningComplete: layout.initialOpeningComplete + color: "transparent" clip: true focus: false @@ -140,6 +143,32 @@ ClippingRectangle { // 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.index); + 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; + } + } + + loader.active = shouldBeActive; + } Loader { id: loader @@ -147,35 +176,17 @@ ClippingRectangle { anchors.fill: parent clip: false asynchronous: true - active: { - const diff = Math.abs(root.session.activeIndex - pane.index); - const isActivePane = diff === 0; - - // During initial opening animation, only load the active pane - // This prevents hiccups from multiple panes loading simultaneously - if (!layout.initialOpeningComplete) { - if (isActivePane) { - pane.hasBeenLoaded = true; - return true; - } - // Defer all other panes until initial opening completes - return false; - } - - // After initial opening, allow current and adjacent panes for smooth transitions - if (diff <= 1) { + active: false + + Component.onCompleted: { + pane.updateActive(); + } + + onActiveChanged: { + // Mark pane as loaded when it becomes active + if (active && !pane.hasBeenLoaded) { pane.hasBeenLoaded = true; - return true; - } - - // For distant panes that have been loaded before, keep them active to preserve cached data - // Only wait for animation if pane hasn't been loaded yet - if (pane.hasBeenLoaded) { - return true; } - - // For new distant panes, wait until animation completes to avoid heavy loading during transition - return layout.animationComplete; } onItemChanged: { @@ -185,5 +196,22 @@ ClippingRectangle { } } } + + Connections { + target: root.session + function onActiveIndexChanged(): void { + pane.updateActive(); + } + } + + Connections { + target: layout + function onInitialOpeningCompleteChanged(): void { + pane.updateActive(); + } + function onAnimationCompleteChanged(): void { + pane.updateActive(); + } + } } } -- cgit v1.2.3-freya From 50dd4e1c441db43bc4dee5d5007f28c52cef848b Mon Sep 17 00:00:00 2001 From: ATMDA Date: Wed, 19 Nov 2025 14:07:53 -0500 Subject: refactor: created pane registry --- modules/controlcenter/NavRail.qml | 38 +++------- modules/controlcenter/PaneRegistry.qml | 133 +++++++++++++++++++++++++++++++++ modules/controlcenter/Panes.qml | 58 +++++--------- modules/controlcenter/Session.qml | 3 +- 4 files changed, 161 insertions(+), 71 deletions(-) create mode 100644 modules/controlcenter/PaneRegistry.qml (limited to 'modules/controlcenter/NavRail.qml') diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml index 1de1a9e..ef338b2 100644 --- a/modules/controlcenter/NavRail.qml +++ b/modules/controlcenter/NavRail.qml @@ -117,35 +117,15 @@ Item { } } - NavItem { - Layout.topMargin: Appearance.spacing.large * 2 - icon: "router" - label: "network" - } - - NavItem { - icon: "settings_bluetooth" - label: "bluetooth" - } - - NavItem { - icon: "volume_up" - label: "audio" - } - - NavItem { - icon: "palette" - label: "appearance" - } - - NavItem { - icon: "task_alt" - label: "taskbar" - } - - NavItem { - icon: "apps" - label: "launcher" + Repeater { + model: PaneRegistry.count + + NavItem { + required property int index + Layout.topMargin: index === 0 ? Appearance.spacing.large * 2 : 0 + icon: PaneRegistry.getByIndex(index).icon + label: PaneRegistry.getByIndex(index).label + } } } diff --git a/modules/controlcenter/PaneRegistry.qml b/modules/controlcenter/PaneRegistry.qml new file mode 100644 index 0000000..df63c6e --- /dev/null +++ b/modules/controlcenter/PaneRegistry.qml @@ -0,0 +1,133 @@ +pragma Singleton + +import QtQuick + +/** + * PaneRegistry + * + * Centralized registry for Control Center panes. This singleton provides a single + * source of truth for pane metadata (id, label, icon, component), eliminating + * the need for manual index management and making adding/removing panes trivial. + * + * Usage: + * - Panes.qml: Dynamically creates panes from registry + * - Session.qml: Derives panes list from registry + * - NavRail.qml: Uses registry for navigation items + */ +QtObject { + id: root + + /** + * Pane metadata structure: + * - id: Unique identifier for the pane (string) + * - label: Display label for the pane (string) + * - icon: Material icon name (string) + * - component: Component path relative to controlcenter module (string) + */ + readonly property list panes: [ + QtObject { + readonly property string id: "network" + readonly property string label: "network" + readonly property string icon: "router" + readonly property string component: "network/NetworkingPane.qml" + }, + QtObject { + readonly property string id: "bluetooth" + readonly property string label: "bluetooth" + readonly property string icon: "settings_bluetooth" + readonly property string component: "bluetooth/BtPane.qml" + }, + QtObject { + readonly property string id: "audio" + readonly property string label: "audio" + readonly property string icon: "volume_up" + readonly property string component: "audio/AudioPane.qml" + }, + QtObject { + readonly property string id: "appearance" + readonly property string label: "appearance" + readonly property string icon: "palette" + readonly property string component: "appearance/AppearancePane.qml" + }, + QtObject { + readonly property string id: "taskbar" + readonly property string label: "taskbar" + readonly property string icon: "task_alt" + readonly property string component: "taskbar/TaskbarPane.qml" + }, + QtObject { + readonly property string id: "launcher" + readonly property string label: "launcher" + readonly property string icon: "apps" + readonly property string component: "launcher/LauncherPane.qml" + } + ] + + /** + * Get the count of registered panes + */ + readonly property int count: panes.length + + /** + * Get pane labels as a list of strings + * Useful for Session.qml's panes property + */ + readonly property var labels: { + const result = []; + for (let i = 0; i < panes.length; i++) { + result.push(panes[i].label); + } + return result; + } + + /** + * Get pane metadata by index + * @param index The index of the pane + * @return The pane metadata object or null if index is out of bounds + */ + function getByIndex(index: int): QtObject { + if (index >= 0 && index < panes.length) { + return panes[index]; + } + return null; + } + + /** + * Get pane index by label + * @param label The label to search for + * @return The index of the pane or -1 if not found + */ + function getIndexByLabel(label: string): int { + for (let i = 0; i < panes.length; i++) { + if (panes[i].label === label) { + return i; + } + } + return -1; + } + + /** + * Get pane metadata by label + * @param label The label to search for + * @return The pane metadata object or null if not found + */ + function getByLabel(label: string): QtObject { + const index = getIndexByLabel(label); + return getByIndex(index); + } + + /** + * Get pane metadata by id + * @param id The id to search for + * @return The pane metadata object or null if not found + */ + function getById(id: string): QtObject { + for (let i = 0; i < panes.length; i++) { + if (panes[i].id === id) { + return panes[i]; + } + } + return null; + } +} + diff --git a/modules/controlcenter/Panes.qml b/modules/controlcenter/Panes.qml index 756d73a..b9256a9 100644 --- a/modules/controlcenter/Panes.qml +++ b/modules/controlcenter/Panes.qml @@ -9,6 +9,7 @@ import "launcher" import qs.components import qs.services import qs.config +import qs.modules.controlcenter import Quickshell.Widgets import QtQuick import QtQuick.Layouts @@ -76,45 +77,13 @@ ClippingRectangle { } } - Pane { - index: 0 - sourceComponent: NetworkingPane { - session: root.session - } - } - - Pane { - index: 1 - sourceComponent: BtPane { - session: root.session - } - } - - Pane { - index: 2 - sourceComponent: AudioPane { - session: root.session - } - } + Repeater { + model: PaneRegistry.count - Pane { - index: 3 - sourceComponent: AppearancePane { - session: root.session - } - } - - Pane { - index: 4 - sourceComponent: TaskbarPane { - session: root.session - } - } - - Pane { - index: 5 - sourceComponent: LauncherPane { - session: root.session + Pane { + required property int index + paneIndex: index + componentPath: PaneRegistry.getByIndex(index).component } } @@ -135,8 +104,8 @@ ClippingRectangle { component Pane: Item { id: pane - required property int index - property alias sourceComponent: loader.sourceComponent + required property int paneIndex + required property string componentPath implicitWidth: root.width implicitHeight: root.height @@ -146,7 +115,7 @@ ClippingRectangle { // Function to compute if this pane should be active function updateActive(): void { - const diff = Math.abs(root.session.activeIndex - pane.index); + const diff = Math.abs(root.session.activeIndex - pane.paneIndex); const isActivePane = diff === 0; let shouldBeActive = false; @@ -187,6 +156,13 @@ ClippingRectangle { 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 + }); + } } onItemChanged: { diff --git a/modules/controlcenter/Session.qml b/modules/controlcenter/Session.qml index 5d7b094..9c6a754 100644 --- a/modules/controlcenter/Session.qml +++ b/modules/controlcenter/Session.qml @@ -1,8 +1,9 @@ import QtQuick import "./state" +import qs.modules.controlcenter QtObject { - readonly property list panes: ["network", "bluetooth", "audio", "appearance", "taskbar", "launcher"] + readonly property list panes: PaneRegistry.labels required property var root property bool floating: false -- cgit v1.2.3-freya