waffle: action center: wifi menu (without auth)

This commit is contained in:
end-4
2025-11-22 00:19:53 +01:00
parent fbfb81c83b
commit 79762e4193
15 changed files with 313 additions and 48 deletions
@@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2a4 4 0 0 1 4 4v2h2.5A1.5 1.5 0 0 1 20 9.5v11a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 4 20.5v-11A1.5 1.5 0 0 1 5.5 8H8V6a4 4 0 0 1 4-4Zm0 11.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3ZM12 4a2 2 0 0 0-2 2v2h4V6a2 2 0 0 0-2-2Z" fill="#212121"/></svg>

After

Width:  |  Height:  |  Size: 351 B

@@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2a4 4 0 0 1 4 4v2h1.75A2.25 2.25 0 0 1 20 10.25v9.5A2.25 2.25 0 0 1 17.75 22H6.25A2.25 2.25 0 0 1 4 19.75v-9.5A2.25 2.25 0 0 1 6.25 8H8V6a4 4 0 0 1 4-4Zm5.75 7.5H6.25a.75.75 0 0 0-.75.75v9.5c0 .414.336.75.75.75h11.5a.75.75 0 0 0 .75-.75v-9.5a.75.75 0 0 0-.75-.75Zm-5.75 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm0-10A2.5 2.5 0 0 0 9.5 6v2h5V6A2.5 2.5 0 0 0 12 3.5Z" fill="#212121"/></svg>

After

Width:  |  Height:  |  Size: 493 B

@@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2.001a4 4 0 0 1 3.771 2.666 1 1 0 0 1-1.84.774l-.045-.107a2 2 0 0 0-3.88.517L10.002 6v1.999h7.749a2.25 2.25 0 0 1 2.245 2.097l.005.154v9.496a2.25 2.25 0 0 1-2.096 2.245l-.154.005H6.25A2.25 2.25 0 0 1 4.005 19.9L4 19.746V10.25a2.25 2.25 0 0 1 2.096-2.245L6.25 8l1.751-.001v-2A3.999 3.999 0 0 1 12 2.002Zm0 11.498a1.499 1.499 0 1 0 0 2.998 1.499 1.499 0 0 0 0-2.998Z" fill="#212121"/></svg>

After

Width:  |  Height:  |  Size: 496 B

@@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2.004c1.875 0 3.334 1.206 3.928 3.003a.75.75 0 1 1-1.425.47C14.102 4.262 13.185 3.504 12 3.504c-1.407 0-2.42.958-2.496 2.551l-.005.195v1.749h8.251a2.25 2.25 0 0 1 2.245 2.097l.005.154v9.496a2.25 2.25 0 0 1-2.096 2.245l-.154.005H6.25A2.25 2.25 0 0 1 4.005 19.9L4 19.746V10.25a2.25 2.25 0 0 1 2.096-2.245L6.25 8l1.749-.001v-1.75C8 3.712 9.71 2.005 12 2.005ZM17.75 9.5H6.25a.75.75 0 0 0-.743.648l-.007.102v9.496c0 .38.282.693.648.743l.102.007h11.5a.75.75 0 0 0 .743-.648l.007-.102V10.25a.75.75 0 0 0-.648-.744L17.75 9.5Zm-5.75 4a1.499 1.499 0 1 1 0 2.996 1.499 1.499 0 0 1 0-2.997Z" fill="#212121"/></svg>

After

Width:  |  Height:  |  Size: 710 B

@@ -581,7 +581,7 @@ Singleton {
property bool leftAlignApps: false property bool leftAlignApps: false
} }
property JsonObject actionCenter: JsonObject { property JsonObject actionCenter: JsonObject {
property list<string> toggles: [ "network", "bluetooth", "easyEffects", "powerProfile", "idleInhibitor", "nightLight", "darkMode", "antiFlashbang", "cloudflareWarp", "mic", "audio", "musicRecognition", "notifications", "onScreenKeyboard", "gameMode", "screenSnip", "colorPicker" ] property list<string> toggles: [ "network", "bluetooth", "easyEffects", "powerProfile", "idleInhibitor", "nightLight", "darkMode", "antiFlashbang", "cloudflareWarp", "mic", "musicRecognition", "notifications", "onScreenKeyboard", "gameMode", "screenSnip", "colorPicker" ]
} }
} }
} }
@@ -37,21 +37,12 @@ WindowDialog {
spacing: 0 spacing: 0
model: ScriptModel { model: ScriptModel {
values: [...Network.wifiNetworks].sort((a, b) => { values: Network.friendlyWifiNetworks
if (a.active && !b.active)
return -1;
if (!a.active && b.active)
return 1;
return b.strength - a.strength;
})
} }
delegate: WifiNetworkItem { delegate: WifiNetworkItem {
required property WifiAccessPoint modelData required property WifiAccessPoint modelData
wifiNetwork: modelData wifiNetwork: modelData
anchors { width: ListView.view.width
left: parent?.left
right: parent?.right
}
} }
} }
WindowDialogSeparator {} WindowDialogSeparator {}
@@ -27,7 +27,7 @@ ColumnLayout {
property var mainAction: toggleModel?.mainAction ?? null property var mainAction: toggleModel?.mainAction ?? null
property var altAction: toggleModel?.hasMenu ? (() => root.openMenu()) : (toggleModel?.altAction ?? null) property var altAction: toggleModel?.hasMenu ? (() => root.openMenu()) : (toggleModel?.altAction ?? null)
property bool hasMenu: toggleModel?.hasMenu ?? false property bool hasMenu: toggleModel?.hasMenu ?? false
property Item menu property Component menu
property color colBackground: toggled ? Looks.colors.accent : Looks.colors.bg2 property color colBackground: toggled ? Looks.colors.accent : Looks.colors.bg2
property color colBackgroundHovered: toggled ? Looks.colors.accentHover : Looks.colors.bg2Hover property color colBackgroundHovered: toggled ? Looks.colors.accentHover : Looks.colors.bg2Hover
@@ -1,13 +1,15 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs import qs
import qs.services import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.common.models.quickToggles import qs.modules.common.models.quickToggles
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.modules.waffle.looks import qs.modules.waffle.looks
import QtQuick import qs.modules.waffle.actionCenter.wifi
import QtQuick.Layouts
import Quickshell
DelegateChooser { DelegateChooser {
id: root id: root
@@ -21,13 +23,6 @@ DelegateChooser {
icon: "flash-off" icon: "flash-off"
} }
} }
DelegateChoice {
roleValue: "audio"
ActionCenterToggleButton {
toggleModel: AudioToggle {}
icon: "speaker-2"
}
}
DelegateChoice { DelegateChoice {
roleValue: "bluetooth" roleValue: "bluetooth"
ActionCenterToggleButton { ActionCenterToggleButton {
@@ -98,6 +93,9 @@ DelegateChooser {
toggleModel: NetworkToggle {} toggleModel: NetworkToggle {}
name: toggleModel.statusText name: toggleModel.statusText
icon: WIcons.internetIcon icon: WIcons.internetIcon
menu: Component {
WifiControl {}
}
} }
} }
DelegateChoice { DelegateChoice {
@@ -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);
}
}
}
}
}
}
@@ -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
}
}
}
}
}
}
@@ -39,8 +39,10 @@ Singleton {
property color bg2Hover: root.dark ? "#383838" : "#FDFDFD" property color bg2Hover: root.dark ? "#383838" : "#FDFDFD"
property color bg2Active: root.dark ? "#333333" : "#FDFDFD" property color bg2Active: root.dark ? "#333333" : "#FDFDFD"
property color bg2Border: root.dark ? "#464646" : "#EEEEEE" property color bg2Border: root.dark ? "#464646" : "#EEEEEE"
property color subfg: root.dark ? "#CED1D7" : "#5C5C5C"
property color fg: root.dark ? "#FFFFFF" : "#000000" property color fg: root.dark ? "#FFFFFF" : "#000000"
property color fg1: root.dark ? "#D1D1D1" : "#626262" property color fg1: root.dark ? "#D1D1D1" : "#626262"
property color inactiveIcon: root.dark ? "#494949" : "#C4C4C4"
property color controlBg: root.dark ? "#9B9B9B" : "#868686" property color controlBg: root.dark ? "#9B9B9B" : "#868686"
property color controlFg: root.dark ? "#454545" : "#FFFFFF" property color controlFg: root.dark ? "#454545" : "#FFFFFF"
property color danger: "#C42B1C" property color danger: "#C42B1C"
@@ -103,13 +105,21 @@ Singleton {
} }
property Component opacity: Component { property Component opacity: Component {
NumberAnimation{ NumberAnimation {
duration: 120 duration: 120
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: transition.easing.bezierCurve.easeIn 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 { property Component enter: Component {
NumberAnimation { NumberAnimation {
duration: 250 duration: 250
@@ -16,6 +16,7 @@ Button {
property color colBackgroundToggledHover: Looks.colors.accentHover property color colBackgroundToggledHover: Looks.colors.accentHover
property color colBackgroundToggledActive: Looks.colors.accentActive property color colBackgroundToggledActive: Looks.colors.accentActive
property color colForeground: Looks.colors.fg property color colForeground: Looks.colors.fg
property color colForegroundToggled: Looks.colors.accentFg
property alias backgroundOpacity: backgroundRect.opacity property alias backgroundOpacity: backgroundRect.opacity
property color color: { property color color: {
if (root.checked) { if (root.checked) {
@@ -35,25 +36,32 @@ Button {
return root.colBackground; 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 // Hover stuff
signal hoverTimedOut() signal hoverTimedOut
property bool shouldShowTooltip: false property bool shouldShowTooltip: false
property Timer hoverTimer: Timer { property Timer hoverTimer: Timer {
id: hoverTimer id: hoverTimer
running: root.hovered running: root.hovered
interval: 400 interval: 400
onTriggered: { onTriggered: {
root.hoverTimedOut() root.hoverTimedOut();
} }
} }
onHoverTimedOut: { onHoverTimedOut: {
root.shouldShowTooltip = true root.shouldShowTooltip = true;
} }
onHoveredChanged: { onHoveredChanged: {
if (!root.hovered) { if (!root.hovered) {
root.shouldShowTooltip = false root.shouldShowTooltip = false;
root.hoverTimer.stop() root.hoverTimer.stop();
} }
} }
@@ -92,10 +100,13 @@ Button {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.RightButton | Qt.MiddleButton
onClicked: (event) => { onClicked: event => {
if (event.button === Qt.LeftButton) root.clicked(); if (event.button === Qt.LeftButton)
if (event.button === Qt.RightButton) root.altAction(); root.clicked();
if (event.button === Qt.MiddleButton) root.middleClickAction(); if (event.button === Qt.RightButton)
root.altAction();
if (event.button === Qt.MiddleButton)
root.middleClickAction();
} }
} }
@@ -122,18 +133,17 @@ Button {
Layout.fillWidth: false Layout.fillWidth: false
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
icon: root.icon.name icon: root.icon.name
color: root.colForeground color: root.fgColor
visible: root.icon.name !== "" visible: root.icon.name !== ""
} }
WText { WText {
id: buttonText
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
text: root.text text: root.text
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
font { font: root.font
pixelSize: Looks.font.pixelSize.large color: root.fgColor
}
color: root.colForeground
} }
} }
} }
@@ -11,6 +11,8 @@ import qs.modules.waffle.looks
WButton { WButton {
id: root id: root
property bool animateChoiceHighlight: true
Layout.fillWidth: true Layout.fillWidth: true
implicitWidth: contentItem.implicitWidth implicitWidth: contentItem.implicitWidth
horizontalPadding: 10 horizontalPadding: 10
@@ -18,7 +20,7 @@ WButton {
inset: 0 inset: 0
buttonSpacing: 8 buttonSpacing: 8
property color color: { color: {
if (root.checked) { if (root.checked) {
if (root.down) { if (root.down) {
return root.colBackgroundHover; return root.colBackgroundHover;
@@ -36,6 +38,7 @@ WButton {
return root.colBackground; return root.colBackground;
} }
} }
fgColor: colForeground
background: Rectangle { background: Rectangle {
id: backgroundRect id: backgroundRect
@@ -54,11 +57,14 @@ WButton {
implicitHeight: 3 implicitHeight: 3
radius: width / 2 radius: width / 2
color: Looks.colors.accent color: Looks.colors.accent
property bool forceZeroHeight: true
height: forceZeroHeight ? 0 : Math.max(16, root.background.height - 18 * 2)
Component.onCompleted: { Component.onCompleted: {
implicitHeight = 16; forceZeroHeight = false;
} }
Behavior on implicitHeight { Behavior on height {
enabled: root.animateChoiceHighlight
animation: Looks.transition.opacity.createObject(this) animation: Looks.transition.opacity.createObject(this)
} }
} }
@@ -7,18 +7,22 @@ import qs.services
Singleton { Singleton {
id: root 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: { property string internetIcon: {
if (Network.ethernet) if (Network.ethernet)
return "ethernet"; return "ethernet";
if (Network.wifiEnabled) { if (Network.wifiEnabled) {
const strength = Network.networkStrength; const strength = Network.networkStrength;
if (strength > 75) return wifiIconForStrength(strength);
return "wifi-1";
if (strength > 50)
return "wifi-2";
if (strength > 25)
return "wifi-3";
return "wifi-4";
} }
if (Network.wifiStatus === "connecting") if (Network.wifiStatus === "connecting")
return "wifi-4"; return "wifi-4";
@@ -23,6 +23,13 @@ Singleton {
property WifiAccessPoint wifiConnectTarget property WifiAccessPoint wifiConnectTarget
readonly property list<WifiAccessPoint> wifiNetworks: [] readonly property list<WifiAccessPoint> wifiNetworks: []
readonly property WifiAccessPoint active: wifiNetworks.find(n => n.active) ?? null readonly property WifiAccessPoint active: wifiNetworks.find(n => n.active) ?? null
readonly property list<var> 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 wifiStatus: "disconnected"
property string networkName: "" property string networkName: ""