pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import ZShell.Components import ZShell.Config import qs.Services import qs.Modules.Bar.Popouts.Network import qs.Components import qs.Helpers CustomClippingRect { id: root required property var wrapper anchors.horizontalCenter: parent.horizontalCenter implicitHeight: networkPopContent.height + networkPopContent.anchors.margins * 2 implicitWidth: 500 + 8 * 2 radius: (20 - Tokens.padding.small) * Tokens.rounding.scale Component.onCompleted: Network.active = true Component.onDestruction: Network.active = false ColumnLayout { id: networkPopContent anchors.left: parent.left anchors.margins: Tokens.padding.large anchors.right: parent.right anchors.top: parent.top spacing: Tokens.spacing.extraSmall / 2 CustomText { Layout.preferredHeight: visible ? implicitHeight : 0 Layout.rightMargin: Tokens.padding.extraSmall font.pointSize: Tokens.font.size.large text: qsTr("Wifi") } Toggle { Layout.preferredHeight: visible ? implicitHeight : 0 checked: Network.wifiEnabled first: true last: Network.connectedNetworks.length === 0 subtext: qsTr("Toggle WiFi device state") text: checked ? qsTr("Enabled") : qsTr("Disabled") onToggled: Network.setWifi(checked) } ListView { id: rows Layout.fillWidth: true Layout.preferredHeight: contentHeight interactive: false section.property: "connected" spacing: Tokens.spacing.extraSmall / 2 add: Transition { Anim { from: 0 property: "opacity" to: 1 } } delegate: DelegateChooser { role: "isDivider" DelegateChoice { roleValue: true delegate: StatusDivider { } } DelegateChoice { delegate: NetworkItem { list: rows width: rows.width } } } displaced: Transition { Anim { easing.type: Easing.OutCubic property: "y" } } model: ScriptModel { values: { const byStrengthName = (a, b) => b.signalStrength - a.signalStrength || a.name.localeCompare(b.name); const cNetworks = [...Network.connectedNetworks]; const kNetworks = [...Network.knownNetworks]; const uNetworks = [...Network.unknownNetworks]; return [...cNetworks.sort(byStrengthName), ...(kNetworks.length > 0 ? [ { isDivider: true, type: "known" }, ...kNetworks.sort(byStrengthName)] : []), ...(uNetworks.length > 0 ? [ { isDivider: true, type: "unknown" }, ...uNetworks.sort(byStrengthName)] : [])]; } } move: Transition { Anim { easing.type: Easing.OutCubic property: "y" } } remove: Transition { Anim { property: "opacity" to: 0 } } section.delegate: Rectangle { required property string section color: "transparent" height: childrenRect.height width: ListView.view.width CustomText { text: parent.section } } } } NetworkOverlay { anchors.fill: parent } component ConnectedRect: CustomRect { id: bg property bool first property bool last bottomLeftRadius: last ? Tokens.rounding.large : Tokens.rounding.extraSmall bottomRightRadius: last ? Tokens.rounding.large : Tokens.rounding.extraSmall color: Colors.tPalette.m3surfaceContainer topLeftRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall topRightRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall } component NetworkItem: ConnectedRect { id: networkItem readonly property int connectedCount: Network.connectedNetworks.length readonly property bool connecting: Network.connectingNetwork === modelData property int horizontalPadding: Tokens.padding.largeIncreased readonly property bool inConnectedGroup: index < connectedCount // sectioning first / last item in list reminder // if index === network.connectedNetworks.length - 1 // if index === network.connectedNetworks.length - 1 + network.knownNetworks.length - 2 // if index === network.connectedNetworks.length - 1 + network.knownNetworks.length // list.model.values.length - 1 required property int index readonly property bool isSecure: Network.isSecure(modelData.security) required property ListView list required property var modelData property int verticalPadding: Tokens.padding.smaller Layout.fillWidth: true first: !inConnectedGroup && index === connectedCount + 1 implicitHeight: rowLayout.implicitHeight + verticalPadding * 2 last: inConnectedGroup ? index === connectedCount - 1 : index === list.count - 1 radius: Tokens.rounding.small RowLayout { id: rowLayout Layout.preferredHeight: visible ? rowLayout.implicitHeight + Tokens.padding.smaller * 2 : 0 anchors.fill: parent anchors.leftMargin: networkItem.horizontalPadding anchors.rightMargin: networkItem.horizontalPadding Column { id: column Layout.fillWidth: true CustomText { Layout.fillWidth: true Layout.leftMargin: Tokens.spacing.extraSmall Layout.rightMargin: Tokens.spacing.extraSmall color: networkItem.modelData.active ? Colors.palette.m3primary : Colors.palette.m3onSurface elide: Text.ElideRight text: networkItem.modelData.name } CustomText { id: subtext anchors.left: parent.left anchors.right: parent.right color: Colors.palette.m3outline elide: Text.ElideRight font.pointSize: Tokens.font.size.small text: networkItem.modelData.connected ? qsTr("Connected: %1 strength").arg(Math.round(networkItem.modelData.signalStrength * 100) + "%") : qsTr("%1 strength").arg(Math.round(networkItem.modelData.signalStrength * 100) + "%") } } Item { Layout.alignment: Qt.AlignRight implicitHeight: icon.height implicitWidth: icon.width MaterialIcon { id: icon color: networkItem.modelData.active ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant font.pointSize: Tokens.font.size.large opacity: networkItem.connecting ? 0 : 1 text: Icons.getNetworkIcon(networkItem.modelData.signalStrength * 100, networkItem.isSecure) Behavior on opacity { Anim { } } } Loader { active: opacity > 0 anchors.fill: parent opacity: networkItem.connecting ? 1 : 0 Behavior on opacity { Anim { } } sourceComponent: CircularIndicator { bgColor: "transparent" running: true } } } } StateLayer { color: networkItem.modelData.active ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface onClicked: { if (networkItem.isSecure && !networkItem.known) { PopupManager.requestOpen(Qt.resolvedUrl("Network/PSKOverlay.qml"), { network: networkItem.modelData }); } else { Network.requestConnect(networkItem.modelData); } } } } component StatusDivider: CustomText { required property var modelData bottomPadding: Tokens.spacing.extraSmall color: Colors.palette.m3onSurfaceVariant font.pointSize: Tokens.font.size.smaller text: { if (modelData.type === "known") { const count = Network.knownNetworks.filter(n => !n.connected).length; return qsTr("%1 known network%2").arg(count).arg(count === 1 ? "" : "s"); } if (modelData.type === "unknown") { const count = Network.unknownNetworks.filter(n => !n.connected).length; return qsTr("%1 network%2 available").arg(count).arg(count === 1 ? "" : "s"); } return qsTr("object unknown"); } topPadding: Tokens.spacing.small } component Toggle: CustomSwitch { id: toggleItem readonly property alias bg: bg property alias first: bg.first property alias last: bg.last property string subtext Layout.fillWidth: true cLayer: 2 horizontalPadding: Tokens.padding.largeIncreased implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2 implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2 indicator.anchors.right: right indicator.anchors.rightMargin: toggleItem.horizontalPadding indicator.anchors.verticalCenter: verticalCenter verticalPadding: Tokens.padding.normal background: ConnectedRect { id: bg StateLayer { id: stateLayer manualPressOverride: toggleItem.pressed } } contentItem: Item { anchors.left: parent.left anchors.leftMargin: toggleItem.horizontalPadding anchors.right: toggleItem.indicator.left anchors.rightMargin: Tokens.spacing.normal implicitHeight: column.implicitHeight implicitWidth: column.implicitWidth Column { id: column anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter spacing: 0 CustomText { id: label anchors.left: parent.left anchors.right: parent.right elide: Text.ElideRight font: { const f = Qt.font(label.font); f.pointSize = Tokens.font.size.smaller; return f; } text: toggleItem.text } CustomText { id: subtext anchors.left: parent.left anchors.right: parent.right color: Colors.palette.m3outline elide: Text.ElideRight font: { const f = Qt.font(subtext.font); f.pointSize = Tokens.font.size.small; return f; } text: toggleItem.subtext visible: toggleItem.subtext } } } onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY) } }