diff --git a/Modules/Bar/WiFiMenu.qml b/Modules/Bar/WiFiMenu.qml index fa0e987..9ee0979 100644 --- a/Modules/Bar/WiFiMenu.qml +++ b/Modules/Bar/WiFiMenu.qml @@ -12,306 +12,308 @@ NLoader { content: Component { NPanel { - id: wifiPanel + id: wifiPanel - property string passwordPromptSsid: "" - property string passwordInput: "" - property bool showPasswordPrompt: false + property string passwordPromptSsid: "" + property string passwordInput: "" + property bool showPasswordPrompt: false - Connections { - target: wifiPanel - ignoreUnknownSignals: true - function onDismissed() { - wifiPanel.visible = false - network.onMenuClosed() - } - } - - WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand - - Rectangle { - color: Colors.backgroundSecondary - radius: Style.radiusMedium * scaling - border.color: Colors.backgroundTertiary - border.width: Math.max(1, Style.borderMedium * scaling) - width: 340 * scaling - height: 320 * scaling - anchors.top: parent.top - anchors.right: parent.right - anchors.topMargin: Style.marginTiny * scaling - anchors.rightMargin: Style.marginTiny * scaling - - ColumnLayout { - anchors.fill: parent - anchors.margins: Style.marginLarge * scaling - spacing: Style.marginMedium * scaling - - RowLayout { - Layout.fillWidth: true - spacing: Style.marginMedium * scaling - - NText { - text: "wifi" - font.family: "Material Symbols Outlined" - font.pointSize: Style.fontSizeXL * scaling - color: Colors.accentPrimary - } - - NText { - text: "WiFi" - font.pointSize: Style.fontSizeLarge * scaling - font.bold: true - color: Colors.textPrimary - Layout.fillWidth: true - } - - NToggle { - baseSize: Style.baseWidgetSize * 0.75 - value: Settings.data.network.wifiEnabled - onToggled: function (value) { - Settings.data.network.wifiEnabled = value - // TBC: This should be done in a service - Quickshell.execDetached(["nmcli", "radio", "wifi", Settings.data.network.wifiEnabled ? "on" : "off"]) - } - } - - NIconButton { - icon: "refresh" - onClicked: function () { - network.refreshNetworks() - } - } - - NIconButton { - icon: "close" - onClicked: function () { - wifiPanel.visible = false - network.onMenuClosed() - } - } + Connections { + target: wifiPanel + ignoreUnknownSignals: true + function onDismissed() { + wifiPanel.visible = false + network.onMenuClosed() } + } - NDivider {} + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand - ListView { - id: networkList - Layout.fillWidth: true - Layout.fillHeight: true - model: Object.values(network.networks) + Rectangle { + color: Colors.backgroundSecondary + radius: Style.radiusMedium * scaling + border.color: Colors.backgroundTertiary + border.width: Math.max(1, Style.borderMedium * scaling) + width: 340 * scaling + height: 320 * scaling + anchors.top: parent.top + anchors.right: parent.right + anchors.topMargin: Style.marginTiny * scaling + anchors.rightMargin: Style.marginTiny * scaling + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginLarge * scaling spacing: Style.marginMedium * scaling - clip: true - delegate: Item { - width: parent.width - height: modelData.ssid === passwordPromptSsid - && showPasswordPrompt ? 108 : 48 // 48 for network + 60 for password prompt + RowLayout { + Layout.fillWidth: true + spacing: Style.marginMedium * scaling - ColumnLayout { - anchors.fill: parent - spacing: 0 + NText { + text: "wifi" + font.family: "Material Symbols Outlined" + font.pointSize: Style.fontSizeXL * scaling + color: Colors.accentPrimary + } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 48 - radius: Style.radiusMedium * scaling - color: modelData.connected ? Colors.accentPrimary : (networkMouseArea.containsMouse ? Colors.highlight : "transparent") + NText { + text: "WiFi" + font.pointSize: Style.fontSizeLarge * scaling + font.bold: true + color: Colors.textPrimary + Layout.fillWidth: true + } - RowLayout { - anchors.fill: parent - anchors.margins: Style.marginSmall * scaling - spacing: Style.marginSmall * scaling - - NText { - text: network.signalIcon(modelData.signal) - font.family: "Material Symbols Outlined" - font.pointSize: Style.fontSizeXL * scaling - color: modelData.connected ? Colors.backgroundPrimary : (networkMouseArea.containsMouse ? Colors.backgroundPrimary : Colors.textPrimary) - } - - ColumnLayout { - Layout.fillWidth: true - spacing: Style.marginTiny * scaling - - // SSID - NText { - text: modelData.ssid || "Unknown Network" - font.pointSize: Style.fontSizeNormal * scaling - elide: Text.ElideRight - Layout.fillWidth: true - color: modelData.connected ? Colors.backgroundPrimary : (networkMouseArea.containsMouse ? Colors.backgroundPrimary : Colors.textPrimary) - } - - // Security Protocol - NText { - text: modelData.security && modelData.security !== "--" ? modelData.security : "Open" - font.pointSize: Style.fontSizeTiny * scaling - elide: Text.ElideRight - Layout.fillWidth: true - color: modelData.connected ? Colors.backgroundPrimary : (networkMouseArea.containsMouse ? Colors.backgroundPrimary : Colors.textPrimary) - } - - Text { - visible: network.connectStatusSsid === modelData.ssid && network.connectStatus === "error" - && network.connectError.length > 0 - text: network.connectError - color: Colors.error - font.pointSize: Style.fontSizeSmall * scaling - elide: Text.ElideRight - Layout.fillWidth: true - } - } - - Item { - Layout.preferredWidth: 22 - Layout.preferredHeight: 22 - visible: network.connectStatusSsid === modelData.ssid - && (network.connectStatus !== "" || network.connectingSsid === modelData.ssid) - - // TBC - // Spinner { - // visible: network.connectingSsid === modelData.ssid - // running: network.connectingSsid === modelData.ssid - // color: Colors.accentPrimary - // anchors.centerIn: parent - // size: 22 - // } - - NText { - visible: network.connectStatus === "success" && !network.connectingSsid - text: "check_circle" - font.family: "Material Symbols Outlined" - font.pointSize: 18 * scaling - color: "#43a047" // TBC: No! - anchors.centerIn: parent - } - - NText { - visible: network.connectStatus === "error" && !network.connectingSsid - text: "error" - font.family: "Material Symbols Outlined" - font.pointSize: Style.fontSizeSmall * scaling - color: Colors.error - anchors.centerIn: parent - } - } - - NText { - visible: modelData.connected - text: "connected" - color: networkMouseArea.containsMouse ? Colors.backgroundPrimary : Colors.accentPrimary - font.pointSize: Style.fontSizeSmall * scaling - } - } - - MouseArea { - id: networkMouseArea - anchors.fill: parent - hoverEnabled: true - onClicked: { - if (modelData.connected) { - network.disconnectNetwork(modelData.ssid) - } else if (network.isSecured(modelData.security) && !modelData.existing) { - passwordPromptSsid = modelData.ssid - showPasswordPrompt = true - passwordInput = "" // Clear previous input - Qt.callLater(function () { - passwordInputField.forceActiveFocus() - }) - } else { - network.connectNetwork(modelData.ssid, modelData.security) - } - } - } + NToggle { + baseSize: Style.baseWidgetSize * 0.75 + value: Settings.data.network.wifiEnabled + onToggled: function (value) { + Settings.data.network.wifiEnabled = value + // TBC: This should be done in a service + Quickshell.execDetached(["nmcli", "radio", "wifi", Settings.data.network.wifiEnabled ? "on" : "off"]) } + } - // Password prompt section - Rectangle { - id: passwordPromptSection - Layout.fillWidth: true - Layout.preferredHeight: modelData.ssid === passwordPromptSsid && showPasswordPrompt ? 60 : 0 - Layout.margins: 8 - visible: modelData.ssid === passwordPromptSsid && showPasswordPrompt - color: Colors.surfaceVariant - radius: Style.radiusSmall * scaling + NIconButton { + icon: "refresh" + onClicked: function () { + network.refreshNetworks() + } + } - RowLayout { - anchors.fill: parent - anchors.margins: Style.spacingSmall * scaling - spacing: Style.spacingSmall * scaling + NIconButton { + icon: "close" + onClicked: function () { + wifiPanel.visible = false + network.onMenuClosed() + } + } + } - Item { - Layout.fillWidth: true - Layout.preferredHeight: 36 + NDivider {} + + ListView { + id: networkList + Layout.fillWidth: true + Layout.fillHeight: true + model: Object.values(network.networks) + spacing: Style.marginMedium * scaling + clip: true + + delegate: Item { + width: parent.width + height: modelData.ssid === passwordPromptSsid + && showPasswordPrompt ? 108 * scaling : Style.baseWidgetSize * 1.5 * scaling + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: Style.baseWidgetSize * 1.5 * scaling + radius: Style.radiusMedium * scaling + color: modelData.connected ? Colors.accentPrimary : (networkMouseArea.containsMouse ? Colors.highlight : "transparent") + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginSmall * scaling + spacing: Style.marginSmall * scaling + + NText { + text: network.signalIcon(modelData.signal) + font.family: "Material Symbols Outlined" + font.pointSize: Style.fontSizeXL * scaling + color: modelData.connected ? Colors.backgroundPrimary : (networkMouseArea.containsMouse ? Colors.backgroundPrimary : Colors.textPrimary) + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginTiny * scaling + + // SSID + NText { + text: modelData.ssid || "Unknown Network" + font.pointSize: Style.fontSizeNormal * scaling + elide: Text.ElideRight + Layout.fillWidth: true + color: modelData.connected ? Colors.backgroundPrimary : (networkMouseArea.containsMouse ? Colors.backgroundPrimary : Colors.textPrimary) + } + + // Security Protocol + NText { + text: modelData.security && modelData.security !== "--" ? modelData.security : "Open" + font.pointSize: Style.fontSizeTiny * scaling + elide: Text.ElideRight + Layout.fillWidth: true + color: modelData.connected ? Colors.backgroundPrimary : (networkMouseArea.containsMouse ? Colors.backgroundPrimary : Colors.textPrimary) + } + + Text { + visible: network.connectStatusSsid === modelData.ssid && network.connectStatus === "error" + && network.connectError.length > 0 + text: network.connectError + color: Colors.error + font.pointSize: Style.fontSizeSmall * scaling + elide: Text.ElideRight + Layout.fillWidth: true + } + } + + Item { + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + visible: network.connectStatusSsid === modelData.ssid + && (network.connectStatus !== "" || network.connectingSsid === modelData.ssid) + + NBusyIndicator { + visible: network.connectingSsid === modelData.ssid + running: network.connectingSsid === modelData.ssid + color: Colors.accentPrimary + anchors.centerIn: parent + size: 22 * scaling + } + + // TBC: Does nothing on my setup + NText { + visible: network.connectStatus === "success" && !network.connectingSsid + text: "check_circle" + font.family: "Material Symbols Outlined" + font.pointSize: 18 * scaling + color: "#43a047" // TBC: No! + anchors.centerIn: parent + } + + // TBC: Does nothing on my setup + NText { + visible: network.connectStatus === "error" && !network.connectingSsid + text: "error" + font.family: "Material Symbols Outlined" + font.pointSize: Style.fontSizeSmall * scaling + color: Colors.error + anchors.centerIn: parent + } + } + + NText { + visible: modelData.connected + text: "connected" + font.pointSize: Style.fontSizeSmall * scaling + color: modelData.connected ? Colors.backgroundPrimary : (networkMouseArea.containsMouse ? Colors.backgroundPrimary : Colors.textPrimary) + } + } + + MouseArea { + id: networkMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + if (modelData.connected) { + network.disconnectNetwork(modelData.ssid) + } else if (network.isSecured(modelData.security) && !modelData.existing) { + passwordPromptSsid = modelData.ssid + showPasswordPrompt = true + passwordInput = "" // Clear previous input + Qt.callLater(function () { + passwordInputField.forceActiveFocus() + }) + } else { + network.connectNetwork(modelData.ssid, modelData.security) + } + } + } + } + + // Password prompt section + Rectangle { + id: passwordPromptSection + Layout.fillWidth: true + Layout.preferredHeight: modelData.ssid === passwordPromptSsid && showPasswordPrompt ? 60 : 0 + Layout.margins: 8 + visible: modelData.ssid === passwordPromptSsid && showPasswordPrompt + color: Colors.surfaceVariant + radius: Style.radiusSmall * scaling + + RowLayout { + anchors.fill: parent + anchors.margins: Style.spacingSmall * scaling + spacing: Style.spacingSmall * scaling + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 36 + + Rectangle { + anchors.fill: parent + radius: 8 + color: "transparent" + border.color: passwordInputField.activeFocus ? Colors.accentPrimary : Colors.outline + border.width: 1 + + TextInput { + id: passwordInputField + anchors.fill: parent + anchors.margins: Style.spacingMedium * scaling + text: passwordInput + font.pointSize: Style.fontSizeMedium * scaling + color: Colors.textPrimary + verticalAlignment: TextInput.AlignVCenter + clip: true + focus: true + selectByMouse: true + activeFocusOnTab: true + inputMethodHints: Qt.ImhNone + echoMode: TextInput.Password + onTextChanged: passwordInput = text + onAccepted: { + network.submitPassword(passwordPromptSsid, passwordInput) + showPasswordPrompt = false + } + + MouseArea { + id: passwordInputMouseArea + anchors.fill: parent + onClicked: passwordInputField.forceActiveFocus() + } + } + } + } Rectangle { - anchors.fill: parent - radius: 8 - color: "transparent" - border.color: passwordInputField.activeFocus ? Colors.accentPrimary : Colors.outline - border.width: 1 + Layout.preferredWidth: 80 + Layout.preferredHeight: 36 + radius: Style.radiusMedium * scaling + color: Colors.accentPrimary + border.color: Colors.accentPrimary + border.width: 0 - TextInput { - id: passwordInputField + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NText { + anchors.centerIn: parent + text: "Connect" + color: Colors.backgroundPrimary + font.pointSize: Style.fontSizeSmall * scaling + } + + MouseArea { anchors.fill: parent - anchors.margins: Style.spacingMedium * scaling - text: passwordInput - font.pointSize: Style.fontSizeMedium * scaling - color: Colors.textPrimary - verticalAlignment: TextInput.AlignVCenter - clip: true - focus: true - selectByMouse: true - activeFocusOnTab: true - inputMethodHints: Qt.ImhNone - echoMode: TextInput.Password - onTextChanged: passwordInput = text - onAccepted: { + onClicked: { network.submitPassword(passwordPromptSsid, passwordInput) showPasswordPrompt = false } - - MouseArea { - id: passwordInputMouseArea - anchors.fill: parent - onClicked: passwordInputField.forceActiveFocus() - } + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onEntered: parent.color = Qt.darker(Colors.accentPrimary, 1.1) + onExited: parent.color = Colors.accentPrimary } } } - - Rectangle { - Layout.preferredWidth: 80 - Layout.preferredHeight: 36 - radius: Style.radiusMedium * scaling - color: Colors.accentPrimary - border.color: Colors.accentPrimary - border.width: 0 - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - - NText { - anchors.centerIn: parent - text: "Connect" - color: Colors.backgroundPrimary - font.pointSize: Style.fontSizeSmall * scaling - } - - MouseArea { - anchors.fill: parent - onClicked: { - network.submitPassword(passwordPromptSsid, passwordInput) - showPasswordPrompt = false - } - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - onEntered: parent.color = Qt.darker(Colors.accentPrimary, 1.1) - onExited: parent.color = Colors.accentPrimary - } - } } } } @@ -321,4 +323,3 @@ NLoader { } } } -} \ No newline at end of file diff --git a/Widgets/NBusyIndicator.qml b/Widgets/NBusyIndicator.qml new file mode 100644 index 0000000..0f963e2 --- /dev/null +++ b/Widgets/NBusyIndicator.qml @@ -0,0 +1,56 @@ +import QtQuick +import qs.Services + +Item { + id: root + + readonly property real scaling: Scaling.scale(screen) + + property bool running: false + property color color: "white" + property int size: baseWidgetSize * 0.5 * scaling + property int strokeWidth: 2 * scaling + property int duration: 1000 + + implicitWidth: size + implicitHeight: size + + Canvas { + id: canvas + anchors.fill: parent + + onPaint: { + var ctx = getContext("2d") + ctx.reset() + + var centerX = width / 2 + var centerY = height / 2 + var radius = Math.min(width, height) / 2 - strokeWidth / 2 + + ctx.strokeStyle = root.color + ctx.lineWidth = root.strokeWidth + ctx.lineCap = "round" + + // Draw arc with gap (270 degrees with 90 degree gap) + ctx.beginPath() + ctx.arc(centerX, centerY, radius, -Math.PI / 2 + rotationAngle, -Math.PI / 2 + rotationAngle + Math.PI * 1.5) + ctx.stroke() + } + + property real rotationAngle: 0 + + onRotationAngleChanged: { + requestPaint() + } + + NumberAnimation { + target: canvas + property: "rotationAngle" + running: root.running + from: 0 + to: 2 * Math.PI + duration: root.duration + loops: Animation.Infinite + } + } +}