NBusyIndicator (aka Spinner)

This commit is contained in:
quadbyte 2025-08-11 13:43:29 -04:00
parent 13841f959d
commit ee323964f0
2 changed files with 334 additions and 277 deletions

View file

@ -12,306 +12,308 @@ NLoader {
content: Component { content: Component {
NPanel { NPanel {
id: wifiPanel id: wifiPanel
property string passwordPromptSsid: "" property string passwordPromptSsid: ""
property string passwordInput: "" property string passwordInput: ""
property bool showPasswordPrompt: false property bool showPasswordPrompt: false
Connections { Connections {
target: wifiPanel target: wifiPanel
ignoreUnknownSignals: true ignoreUnknownSignals: true
function onDismissed() { function onDismissed() {
wifiPanel.visible = false wifiPanel.visible = false
network.onMenuClosed() 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()
}
}
} }
}
NDivider {} WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
ListView { Rectangle {
id: networkList color: Colors.backgroundSecondary
Layout.fillWidth: true radius: Style.radiusMedium * scaling
Layout.fillHeight: true border.color: Colors.backgroundTertiary
model: Object.values(network.networks) 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 spacing: Style.marginMedium * scaling
clip: true
delegate: Item { RowLayout {
width: parent.width Layout.fillWidth: true
height: modelData.ssid === passwordPromptSsid spacing: Style.marginMedium * scaling
&& showPasswordPrompt ? 108 : 48 // 48 for network + 60 for password prompt
ColumnLayout { NText {
anchors.fill: parent text: "wifi"
spacing: 0 font.family: "Material Symbols Outlined"
font.pointSize: Style.fontSizeXL * scaling
color: Colors.accentPrimary
}
Rectangle { NText {
Layout.fillWidth: true text: "WiFi"
Layout.preferredHeight: 48 font.pointSize: Style.fontSizeLarge * scaling
radius: Style.radiusMedium * scaling font.bold: true
color: modelData.connected ? Colors.accentPrimary : (networkMouseArea.containsMouse ? Colors.highlight : "transparent") color: Colors.textPrimary
Layout.fillWidth: true
}
RowLayout { NToggle {
anchors.fill: parent baseSize: Style.baseWidgetSize * 0.75
anchors.margins: Style.marginSmall * scaling value: Settings.data.network.wifiEnabled
spacing: Style.marginSmall * scaling onToggled: function (value) {
Settings.data.network.wifiEnabled = value
NText { // TBC: This should be done in a service
text: network.signalIcon(modelData.signal) Quickshell.execDetached(["nmcli", "radio", "wifi", Settings.data.network.wifiEnabled ? "on" : "off"])
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)
}
}
}
} }
}
// Password prompt section NIconButton {
Rectangle { icon: "refresh"
id: passwordPromptSection onClicked: function () {
Layout.fillWidth: true network.refreshNetworks()
Layout.preferredHeight: modelData.ssid === passwordPromptSsid && showPasswordPrompt ? 60 : 0 }
Layout.margins: 8 }
visible: modelData.ssid === passwordPromptSsid && showPasswordPrompt
color: Colors.surfaceVariant
radius: Style.radiusSmall * scaling
RowLayout { NIconButton {
anchors.fill: parent icon: "close"
anchors.margins: Style.spacingSmall * scaling onClicked: function () {
spacing: Style.spacingSmall * scaling wifiPanel.visible = false
network.onMenuClosed()
}
}
}
Item { NDivider {}
Layout.fillWidth: true
Layout.preferredHeight: 36 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 { Rectangle {
anchors.fill: parent Layout.preferredWidth: 80
radius: 8 Layout.preferredHeight: 36
color: "transparent" radius: Style.radiusMedium * scaling
border.color: passwordInputField.activeFocus ? Colors.accentPrimary : Colors.outline color: Colors.accentPrimary
border.width: 1 border.color: Colors.accentPrimary
border.width: 0
TextInput { Behavior on color {
id: passwordInputField ColorAnimation {
duration: Style.animationFast
}
}
NText {
anchors.centerIn: parent
text: "Connect"
color: Colors.backgroundPrimary
font.pointSize: Style.fontSizeSmall * scaling
}
MouseArea {
anchors.fill: parent anchors.fill: parent
anchors.margins: Style.spacingMedium * scaling onClicked: {
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) network.submitPassword(passwordPromptSsid, passwordInput)
showPasswordPrompt = false showPasswordPrompt = false
} }
cursorShape: Qt.PointingHandCursor
MouseArea { hoverEnabled: true
id: passwordInputMouseArea onEntered: parent.color = Qt.darker(Colors.accentPrimary, 1.1)
anchors.fill: parent onExited: parent.color = Colors.accentPrimary
onClicked: passwordInputField.forceActiveFocus()
}
} }
} }
} }
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 {
} }
} }
} }
}

View file

@ -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
}
}
}