From 79762e41931e695f8132028e80eb4dff53e3e0f1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 22 Nov 2025 00:19:53 +0100 Subject: [PATCH] waffle: action center: wifi menu (without auth) --- .../icons/fluent/lock-closed-filled.svg | 1 + .../ii/assets/icons/fluent/lock-closed.svg | 1 + .../assets/icons/fluent/lock-open-filled.svg | 1 + .../ii/assets/icons/fluent/lock-open.svg | 1 + .../quickshell/ii/modules/common/Config.qml | 2 +- .../sidebarRight/wifiNetworks/WifiDialog.qml | 13 +- .../toggles/ActionCenterToggleButton.qml | 2 +- .../ActionCenterTogglesDelegateChooser.qml | 18 ++- .../actionCenter/wifi/WWifiNetworkItem.qml | 114 +++++++++++++++++ .../waffle/actionCenter/wifi/WifiControl.qml | 121 ++++++++++++++++++ .../ii/modules/waffle/looks/Looks.qml | 12 +- .../ii/modules/waffle/looks/WButton.qml | 38 ++++-- .../ii/modules/waffle/looks/WChoiceButton.qml | 12 +- .../ii/modules/waffle/looks/WIcons.qml | 18 ++- .../quickshell/ii/services/Network.qml | 7 + 15 files changed, 313 insertions(+), 48 deletions(-) create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/lock-closed-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/lock-closed.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/lock-open-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/lock-open.svg create mode 100644 dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WWifiNetworkItem.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/lock-closed-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/lock-closed-filled.svg new file mode 100644 index 000000000..31b51d02a --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/lock-closed-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/lock-closed.svg b/dots/.config/quickshell/ii/assets/icons/fluent/lock-closed.svg new file mode 100644 index 000000000..93966ef08 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/lock-closed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/lock-open-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/lock-open-filled.svg new file mode 100644 index 000000000..f7ebddd49 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/lock-open-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/lock-open.svg b/dots/.config/quickshell/ii/assets/icons/fluent/lock-open.svg new file mode 100644 index 000000000..a1d5a9592 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/lock-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index 83dc5a87b..66b76a2ca 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -581,7 +581,7 @@ Singleton { property bool leftAlignApps: false } property JsonObject actionCenter: JsonObject { - property list toggles: [ "network", "bluetooth", "easyEffects", "powerProfile", "idleInhibitor", "nightLight", "darkMode", "antiFlashbang", "cloudflareWarp", "mic", "audio", "musicRecognition", "notifications", "onScreenKeyboard", "gameMode", "screenSnip", "colorPicker" ] + property list toggles: [ "network", "bluetooth", "easyEffects", "powerProfile", "idleInhibitor", "nightLight", "darkMode", "antiFlashbang", "cloudflareWarp", "mic", "musicRecognition", "notifications", "onScreenKeyboard", "gameMode", "screenSnip", "colorPicker" ] } } } diff --git a/dots/.config/quickshell/ii/modules/ii/sidebarRight/wifiNetworks/WifiDialog.qml b/dots/.config/quickshell/ii/modules/ii/sidebarRight/wifiNetworks/WifiDialog.qml index 36683c353..f1de76a58 100644 --- a/dots/.config/quickshell/ii/modules/ii/sidebarRight/wifiNetworks/WifiDialog.qml +++ b/dots/.config/quickshell/ii/modules/ii/sidebarRight/wifiNetworks/WifiDialog.qml @@ -37,21 +37,12 @@ WindowDialog { spacing: 0 model: ScriptModel { - values: [...Network.wifiNetworks].sort((a, b) => { - if (a.active && !b.active) - return -1; - if (!a.active && b.active) - return 1; - return b.strength - a.strength; - }) + values: Network.friendlyWifiNetworks } delegate: WifiNetworkItem { required property WifiAccessPoint modelData wifiNetwork: modelData - anchors { - left: parent?.left - right: parent?.right - } + width: ListView.view.width } } WindowDialogSeparator {} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterToggleButton.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterToggleButton.qml index 3f2dc224b..41da6f7cd 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterToggleButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterToggleButton.qml @@ -27,7 +27,7 @@ ColumnLayout { property var mainAction: toggleModel?.mainAction ?? null property var altAction: toggleModel?.hasMenu ? (() => root.openMenu()) : (toggleModel?.altAction ?? null) property bool hasMenu: toggleModel?.hasMenu ?? false - property Item menu + property Component menu property color colBackground: toggled ? Looks.colors.accent : Looks.colors.bg2 property color colBackgroundHovered: toggled ? Looks.colors.accentHover : Looks.colors.bg2Hover diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterTogglesDelegateChooser.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterTogglesDelegateChooser.qml index e74ddaf0c..7f7c2b47b 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterTogglesDelegateChooser.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterTogglesDelegateChooser.qml @@ -1,13 +1,15 @@ pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.modules.waffle.looks -import QtQuick -import QtQuick.Layouts -import Quickshell +import qs.modules.waffle.actionCenter.wifi DelegateChooser { id: root @@ -21,13 +23,6 @@ DelegateChooser { icon: "flash-off" } } - DelegateChoice { - roleValue: "audio" - ActionCenterToggleButton { - toggleModel: AudioToggle {} - icon: "speaker-2" - } - } DelegateChoice { roleValue: "bluetooth" ActionCenterToggleButton { @@ -98,6 +93,9 @@ DelegateChooser { toggleModel: NetworkToggle {} name: toggleModel.statusText icon: WIcons.internetIcon + menu: Component { + WifiControl {} + } } } DelegateChoice { diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WWifiNetworkItem.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WWifiNetworkItem.qml new file mode 100644 index 000000000..0c1b93c8c --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WWifiNetworkItem.qml @@ -0,0 +1,114 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.services.network +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks +import qs.modules.waffle.actionCenter + +WChoiceButton { + id: root + required property WifiAccessPoint wifiNetwork + + property bool expanded: false + checked: expanded + clip: true + + horizontalPadding: 12 + verticalPadding: 6 + animateChoiceHighlight: false + + Behavior on implicitHeight { + animation: Looks.transition.resize.createObject(this) + } + + onClicked: expanded = !expanded + + contentItem: RowLayout { + id: contentItem + spacing: 12 + + FluentIcon { // Duotone hack + Layout.bottomMargin: 2 + Layout.alignment: Qt.AlignTop + property int strength: root.wifiNetwork?.strength ?? 0 + icon: "wifi-1" + implicitSize: 30 + color: Looks.colors.inactiveIcon + + FluentIcon { // Signal + property int strength: root.wifiNetwork?.strength ?? 0 + icon: WIcons.wifiIconForStrength(strength) + implicitSize: 30 + + FluentIcon { // Security + anchors { + right: parent.right + bottom: parent.bottom + } + visible: root?.wifiNetwork?.isSecure ?? false + icon: "lock-closed" + filled: true + implicitSize: 14 + } + } + } + + ColumnLayout { + Layout.topMargin: statusText.visible ? 4 : 7 + Layout.bottomMargin: 4 + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + spacing: 1 + + Behavior on Layout.topMargin { + animation: Looks.transition.move.createObject(this) + } + + WText { // Network name + Layout.fillWidth: true + elide: Text.ElideRight + font.pixelSize: Looks.font.pixelSize.large + text: root.wifiNetwork?.ssid ?? Translation.tr("Unknown") + } + WText { // Status + id: statusText + Layout.fillWidth: true + elide: Text.ElideRight + text: root.wifiNetwork?.active ? Translation.tr("Connected") : root.wifiNetwork?.isSecure ? Translation.tr("Secured") : Translation.tr("Not secured") + font.pixelSize: Looks.font.pixelSize.large + color: Looks.colors.subfg + visible: root.wifiNetwork?.active || root.expanded + Behavior on opacity { + animation: Looks.transition.opacity.createObject(this) + } + } + + WButton { + Layout.alignment: Qt.AlignRight + horizontalAlignment: Text.AlignHCenter + visible: root.expanded + checked: !(root.wifiNetwork?.active ?? false) + colBackgroundHover: Looks.colors.bg2Hover + colBackgroundActive: Looks.colors.bg2Active + inset: 0 + implicitHeight: 30 + implicitWidth: 148 + text: root.wifiNetwork?.active ? Translation.tr("Disconnect") : Translation.tr("Connect") + + onClicked: { + if (root.wifiNetwork?.active) { + Network.disconnectWifiNetwork(); + } else { + Network.connectToWifiNetwork(root.wifiNetwork); + } + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml new file mode 100644 index 000000000..ce6a2e6a7 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml @@ -0,0 +1,121 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import qs +import qs.services +import qs.services.network +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks +import qs.modules.waffle.actionCenter + +Item { + id: root + implicitWidth: 360 + implicitHeight: 352 + + Component.onCompleted: { + Network.rescanWifi(); + } + + PageColumn { + anchors.fill: parent + + BodyRectangle { + implicitHeight: 400 + implicitWidth: 50 + + ColumnLayout { + anchors.fill: parent + anchors.margins: 4 + spacing: 4 + + ColumnLayout { + implicitHeight: headerRow.implicitHeight + Layout.fillWidth: true + spacing: 0 + HeaderRow { + id: headerRow + Layout.fillWidth: true + title: qsTr("Wi-Fi") + } + FadeLoader { + Layout.leftMargin: -4 + Layout.rightMargin: -4 + Layout.fillWidth: true + shown: Network.wifiScanning + sourceComponent: StyledIndeterminateProgressBar { + id: progressBar + implicitHeight: 3 + background: null + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: progressBar.width + height: progressBar.height + radius: progressBar.height / 2 + } + } + } + } + } + + StyledListView { + id: listView + Layout.fillHeight: true + Layout.fillWidth: true + animateAppearance: false + + contentHeight: contentLayout.implicitHeight + contentWidth: width + clip: true + spacing: 4 + + model: ScriptModel { + values: Network.friendlyWifiNetworks + } + delegate: WWifiNetworkItem { + required property WifiAccessPoint modelData + wifiNetwork: modelData + width: ListView.view.width + } + } + } + } + + Separator {} + + FooterRectangle { + WButton { + id: moreSettingsButton + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + } + inset: 0 + implicitHeight: 40 + implicitWidth: contentItem.implicitWidth + 30 + color: "transparent" + + onClicked: { + Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "sidebarLeft", "toggle"]); + Quickshell.execDetached(["bash", "-c", Config.options.apps.network]); + } + + contentItem: Item { + anchors.centerIn: parent + implicitWidth: buttonText.implicitWidth + WText { + id: buttonText + anchors.centerIn: parent + text: qsTr("More Internet settings") + color: moreSettingsButton.pressed ? Looks.colors.fg : Looks.colors.fg1 + } + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml index e62657636..5ef0031f8 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml @@ -39,8 +39,10 @@ Singleton { property color bg2Hover: root.dark ? "#383838" : "#FDFDFD" property color bg2Active: root.dark ? "#333333" : "#FDFDFD" property color bg2Border: root.dark ? "#464646" : "#EEEEEE" + property color subfg: root.dark ? "#CED1D7" : "#5C5C5C" property color fg: root.dark ? "#FFFFFF" : "#000000" property color fg1: root.dark ? "#D1D1D1" : "#626262" + property color inactiveIcon: root.dark ? "#494949" : "#C4C4C4" property color controlBg: root.dark ? "#9B9B9B" : "#868686" property color controlFg: root.dark ? "#454545" : "#FFFFFF" property color danger: "#C42B1C" @@ -103,13 +105,21 @@ Singleton { } property Component opacity: Component { - NumberAnimation{ + NumberAnimation { duration: 120 easing.type: Easing.BezierSpline easing.bezierCurve: transition.easing.bezierCurve.easeIn } } + property Component resize: Component { // TODO: better curve needed + NumberAnimation { + duration: 200 + easing.type: Easing.BezierSpline + easing.bezierCurve: transition.easing.bezierCurve.easeIn + } + } + property Component enter: Component { NumberAnimation { duration: 250 diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml index d158c5949..76cf00e49 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml @@ -16,6 +16,7 @@ Button { property color colBackgroundToggledHover: Looks.colors.accentHover property color colBackgroundToggledActive: Looks.colors.accentActive property color colForeground: Looks.colors.fg + property color colForegroundToggled: Looks.colors.accentFg property alias backgroundOpacity: backgroundRect.opacity property color color: { if (root.checked) { @@ -35,25 +36,32 @@ Button { return root.colBackground; } } + property color fgColor: root.checked ? root.colForegroundToggled : root.colForeground + property alias horizontalAlignment: buttonText.horizontalAlignment + font { + family: Looks.font.family.ui + pixelSize: Looks.font.pixelSize.large + weight: Looks.font.weight.regular + } // Hover stuff - signal hoverTimedOut() + signal hoverTimedOut property bool shouldShowTooltip: false property Timer hoverTimer: Timer { id: hoverTimer running: root.hovered interval: 400 onTriggered: { - root.hoverTimedOut() + root.hoverTimedOut(); } } onHoverTimedOut: { - root.shouldShowTooltip = true + root.shouldShowTooltip = true; } onHoveredChanged: { if (!root.hovered) { - root.shouldShowTooltip = false - root.hoverTimer.stop() + root.shouldShowTooltip = false; + root.hoverTimer.stop(); } } @@ -92,10 +100,13 @@ Button { MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton | Qt.MiddleButton - onClicked: (event) => { - if (event.button === Qt.LeftButton) root.clicked(); - if (event.button === Qt.RightButton) root.altAction(); - if (event.button === Qt.MiddleButton) root.middleClickAction(); + onClicked: event => { + if (event.button === Qt.LeftButton) + root.clicked(); + if (event.button === Qt.RightButton) + root.altAction(); + if (event.button === Qt.MiddleButton) + root.middleClickAction(); } } @@ -122,18 +133,17 @@ Button { Layout.fillWidth: false Layout.alignment: Qt.AlignVCenter icon: root.icon.name - color: root.colForeground + color: root.fgColor visible: root.icon.name !== "" } WText { + id: buttonText Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft text: root.text horizontalAlignment: Text.AlignLeft - font { - pixelSize: Looks.font.pixelSize.large - } - color: root.colForeground + font: root.font + color: root.fgColor } } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WChoiceButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WChoiceButton.qml index 60de3d816..cd0d66d59 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WChoiceButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WChoiceButton.qml @@ -11,6 +11,8 @@ import qs.modules.waffle.looks WButton { id: root + property bool animateChoiceHighlight: true + Layout.fillWidth: true implicitWidth: contentItem.implicitWidth horizontalPadding: 10 @@ -18,7 +20,7 @@ WButton { inset: 0 buttonSpacing: 8 - property color color: { + color: { if (root.checked) { if (root.down) { return root.colBackgroundHover; @@ -36,6 +38,7 @@ WButton { return root.colBackground; } } + fgColor: colForeground background: Rectangle { id: backgroundRect @@ -54,11 +57,14 @@ WButton { implicitHeight: 3 radius: width / 2 color: Looks.colors.accent + property bool forceZeroHeight: true + height: forceZeroHeight ? 0 : Math.max(16, root.background.height - 18 * 2) Component.onCompleted: { - implicitHeight = 16; + forceZeroHeight = false; } - Behavior on implicitHeight { + Behavior on height { + enabled: root.animateChoiceHighlight animation: Looks.transition.opacity.createObject(this) } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml index 42836fbc7..6a5814bbc 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml @@ -7,18 +7,22 @@ import qs.services Singleton { id: root + function wifiIconForStrength(strength) { + if (strength > 75) + return "wifi-1"; + if (strength > 50) + return "wifi-2"; + if (strength > 25) + return "wifi-3"; + return "wifi-4"; + } + property string internetIcon: { if (Network.ethernet) return "ethernet"; if (Network.wifiEnabled) { const strength = Network.networkStrength; - if (strength > 75) - return "wifi-1"; - if (strength > 50) - return "wifi-2"; - if (strength > 25) - return "wifi-3"; - return "wifi-4"; + return wifiIconForStrength(strength); } if (Network.wifiStatus === "connecting") return "wifi-4"; diff --git a/dots/.config/quickshell/ii/services/Network.qml b/dots/.config/quickshell/ii/services/Network.qml index 7d16a9450..69bd5337d 100644 --- a/dots/.config/quickshell/ii/services/Network.qml +++ b/dots/.config/quickshell/ii/services/Network.qml @@ -23,6 +23,13 @@ Singleton { property WifiAccessPoint wifiConnectTarget readonly property list wifiNetworks: [] readonly property WifiAccessPoint active: wifiNetworks.find(n => n.active) ?? null + readonly property list friendlyWifiNetworks: [...wifiNetworks].sort((a, b) => { + if (a.active && !b.active) + return -1; + if (!a.active && b.active) + return 1; + return b.strength - a.strength; + }) property string wifiStatus: "disconnected" property string networkName: ""