mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 21:45:38 -05:00
refactor: mega refactoring of a bunch of things
This commit is contained in:
138
Modules/ControlCenter/Audio/AudioDevicesList.qml
Normal file
138
Modules/ControlCenter/Audio/AudioDevicesList.qml
Normal file
@@ -0,0 +1,138 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Pipewire
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property string currentSinkDisplayName: AudioService.sink ? AudioService.displayName(AudioService.sink) : ""
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "Output Device"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 35
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||
border.width: 1
|
||||
visible: AudioService.sink !== null
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "check_circle"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Current: " + (root.currentSinkDisplayName || "None")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values) return []
|
||||
let sinks = []
|
||||
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||
let node = Pipewire.nodes.values[i]
|
||||
if (!node || node.isStream) continue
|
||||
if ((node.type & PwNodeType.AudioSink) === PwNodeType.AudioSink) {
|
||||
sinks.push(node)
|
||||
}
|
||||
}
|
||||
return sinks
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData === AudioService.sink ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: modelData === AudioService.sink ? Theme.primary : "transparent"
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (modelData.name.includes("bluez"))
|
||||
return "headset";
|
||||
else if (modelData.name.includes("hdmi"))
|
||||
return "tv";
|
||||
else if (modelData.name.includes("usb"))
|
||||
return "headset";
|
||||
else
|
||||
return "speaker";
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: AudioService.displayName(modelData)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||
font.weight: modelData === AudioService.sink ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (AudioService.subtitle(modelData.name) && AudioService.subtitle(modelData.name) !== "")
|
||||
return AudioService.subtitle(modelData.name) + (modelData === AudioService.sink ? " • Selected" : "");
|
||||
else
|
||||
return modelData === AudioService.sink ? "Selected" : "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deviceArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData)
|
||||
Pipewire.preferredDefaultAudioSink = modelData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
136
Modules/ControlCenter/Audio/AudioInputDevicesList.qml
Normal file
136
Modules/ControlCenter/Audio/AudioInputDevicesList.qml
Normal file
@@ -0,0 +1,136 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Pipewire
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property string currentSourceDisplayName: AudioService.source ? AudioService.displayName(AudioService.source) : ""
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "Input Device"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 35
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||
border.width: 1
|
||||
visible: AudioService.source !== null
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "check_circle"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Current: " + (root.currentSourceDisplayName || "None")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values) return []
|
||||
let sources = []
|
||||
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||
let node = Pipewire.nodes.values[i]
|
||||
if (!node || node.isStream) continue
|
||||
if ((node.type & PwNodeType.AudioSource) === PwNodeType.AudioSource && !node.name.includes(".monitor")) {
|
||||
sources.push(node)
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: sourceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData === AudioService.source ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: modelData === AudioService.source ? Theme.primary : "transparent"
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (modelData.name.includes("bluez"))
|
||||
return "headset_mic";
|
||||
else if (modelData.name.includes("usb"))
|
||||
return "headset_mic";
|
||||
else
|
||||
return "mic";
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: AudioService.displayName(modelData)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
||||
font.weight: modelData === AudioService.source ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (AudioService.subtitle(modelData.name) && AudioService.subtitle(modelData.name) !== "")
|
||||
return AudioService.subtitle(modelData.name) + (modelData === AudioService.source ? " • Selected" : "");
|
||||
else
|
||||
return modelData === AudioService.source ? "Selected" : "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: sourceArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData)
|
||||
Pipewire.preferredDefaultAudioSource = modelData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
174
Modules/ControlCenter/Audio/MicrophoneControl.qml
Normal file
174
Modules/ControlCenter/Audio/MicrophoneControl.qml
Normal file
@@ -0,0 +1,174 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property real micLevel: (AudioService.source && AudioService.source.audio && AudioService.source.audio.volume * 100) || 0
|
||||
property bool micMuted: (AudioService.source && AudioService.source.audio && AudioService.source.audio.muted) || false
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "Microphone Level"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: root.micMuted ? "mic_off" : "mic"
|
||||
size: Theme.iconSize
|
||||
color: root.micMuted ? Theme.error : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (AudioService.source && AudioService.source.audio)
|
||||
AudioService.source.audio.muted = !AudioService.source.audio.muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: micSliderContainer
|
||||
|
||||
width: parent.width - 80
|
||||
height: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: micSliderTrack
|
||||
|
||||
width: parent.width
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: micSliderFill
|
||||
|
||||
width: parent.width * (root.micLevel / 100)
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Theme.primary
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: micHandle
|
||||
|
||||
width: 18
|
||||
height: 18
|
||||
radius: 9
|
||||
color: Theme.primary
|
||||
border.color: Qt.lighter(Theme.primary, 1.3)
|
||||
border.width: 2
|
||||
x: Math.max(0, Math.min(parent.width - width, micSliderFill.width - width / 2))
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
scale: micMouseArea.containsMouse || micMouseArea.pressed ? 1.2 : 1
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: micMouseArea
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
preventStealing: true
|
||||
onPressed: (mouse) => {
|
||||
isDragging = true;
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
||||
let newMicLevel = Math.round(ratio * 100);
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = false;
|
||||
AudioService.source.audio.volume = newMicLevel / 100;
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
isDragging = false;
|
||||
}
|
||||
onPositionChanged: (mouse) => {
|
||||
if (pressed && isDragging) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
||||
let newMicLevel = Math.round(ratio * 100);
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = false;
|
||||
AudioService.source.audio.volume = newMicLevel / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
onClicked: (mouse) => {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
||||
let newMicLevel = Math.round(ratio * 100);
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = false;
|
||||
AudioService.source.audio.volume = newMicLevel / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: micGlobalMouseArea
|
||||
|
||||
x: 0
|
||||
y: 0
|
||||
width: root.parent ? root.parent.width : 0
|
||||
height: root.parent ? root.parent.height : 0
|
||||
enabled: micMouseArea.isDragging
|
||||
visible: false
|
||||
preventStealing: true
|
||||
onPositionChanged: (mouse) => {
|
||||
if (micMouseArea.isDragging) {
|
||||
let globalPos = mapToItem(micSliderTrack, mouse.x, mouse.y);
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / micSliderTrack.width));
|
||||
let newMicLevel = Math.round(ratio * 100);
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = false;
|
||||
AudioService.source.audio.volume = newMicLevel / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
micMouseArea.isDragging = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "mic"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
174
Modules/ControlCenter/Audio/VolumeControl.qml
Normal file
174
Modules/ControlCenter/Audio/VolumeControl.qml
Normal file
@@ -0,0 +1,174 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property real volumeLevel: (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0
|
||||
property bool volumeMuted: (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.muted) || false
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "Volume"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: root.volumeMuted ? "volume_off" : "volume_down"
|
||||
size: Theme.iconSize
|
||||
color: root.volumeMuted ? Theme.error : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (AudioService.sink && AudioService.sink.audio)
|
||||
AudioService.sink.audio.muted = !AudioService.sink.audio.muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: volumeSliderContainer
|
||||
|
||||
width: parent.width - 80
|
||||
height: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: volumeSliderTrack
|
||||
|
||||
width: parent.width
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: volumeSliderFill
|
||||
|
||||
width: parent.width * (root.volumeLevel / 100)
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Theme.primary
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: volumeHandle
|
||||
|
||||
width: 18
|
||||
height: 18
|
||||
radius: 9
|
||||
color: Theme.primary
|
||||
border.color: Qt.lighter(Theme.primary, 1.3)
|
||||
border.width: 2
|
||||
x: Math.max(0, Math.min(parent.width - width, volumeSliderFill.width - width / 2))
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
scale: volumeMouseArea.containsMouse || volumeMouseArea.pressed ? 1.2 : 1
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: volumeMouseArea
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
preventStealing: true
|
||||
onPressed: (mouse) => {
|
||||
isDragging = true;
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
||||
let newVolume = Math.round(ratio * 100);
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
isDragging = false;
|
||||
}
|
||||
onPositionChanged: (mouse) => {
|
||||
if (pressed && isDragging) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
||||
let newVolume = Math.round(ratio * 100);
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
onClicked: (mouse) => {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
||||
let newVolume = Math.round(ratio * 100);
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: volumeGlobalMouseArea
|
||||
|
||||
x: 0
|
||||
y: 0
|
||||
width: root.parent ? root.parent.width : 0
|
||||
height: root.parent ? root.parent.height : 0
|
||||
enabled: volumeMouseArea.isDragging
|
||||
visible: false
|
||||
preventStealing: true
|
||||
onPositionChanged: (mouse) => {
|
||||
if (volumeMouseArea.isDragging) {
|
||||
let globalPos = mapToItem(volumeSliderTrack, mouse.x, mouse.y);
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / volumeSliderTrack.width));
|
||||
let newVolume = Math.round(ratio * 100);
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
volumeMouseArea.isDragging = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "volume_up"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,45 +5,34 @@ import Quickshell.Io
|
||||
import Quickshell.Services.Pipewire
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Modules.ControlCenter.Audio
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import "../../Widgets"
|
||||
|
||||
Item {
|
||||
id: audioTab
|
||||
|
||||
property int audioSubTab: 0 // 0: Output, 1: Input
|
||||
readonly property real volumeLevel: (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0
|
||||
readonly property real micLevel: (AudioService.source && AudioService.source.audio && AudioService.source.audio.volume * 100) || 0
|
||||
readonly property bool volumeMuted: (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.muted) || false
|
||||
readonly property bool micMuted: (AudioService.source && AudioService.source.audio && AudioService.source.audio.muted) || false
|
||||
readonly property string currentSinkDisplayName: AudioService.sink ? AudioService.displayName(AudioService.sink) : ""
|
||||
readonly property string currentSourceDisplayName: AudioService.source ? AudioService.displayName(AudioService.source) : ""
|
||||
property int audioSubTab: 0
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// Audio Sub-tabs
|
||||
DankTabBar {
|
||||
width: parent.width
|
||||
tabHeight: 40
|
||||
currentIndex: audioTab.audioSubTab
|
||||
showIcons: false
|
||||
model: [
|
||||
{
|
||||
"text": "Output"
|
||||
},
|
||||
{
|
||||
"text": "Input"
|
||||
}
|
||||
]
|
||||
model: [{
|
||||
"text": "Output"
|
||||
}, {
|
||||
"text": "Input"
|
||||
}]
|
||||
onTabClicked: function(index) {
|
||||
audioTab.audioSubTab = index;
|
||||
}
|
||||
}
|
||||
|
||||
// Output Tab Content
|
||||
ScrollView {
|
||||
width: parent.width
|
||||
height: parent.height - 48
|
||||
@@ -54,321 +43,16 @@ Item {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
// Volume Control
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "Volume"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: audioTab.volumeMuted ? "volume_off" : "volume_down"
|
||||
size: Theme.iconSize
|
||||
color: audioTab.volumeMuted ? Theme.error : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (AudioService.sink && AudioService.sink.audio)
|
||||
AudioService.sink.audio.muted = !AudioService.sink.audio.muted;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: volumeSliderContainer
|
||||
|
||||
width: parent.width - 80
|
||||
height: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: volumeSliderTrack
|
||||
|
||||
width: parent.width
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: volumeSliderFill
|
||||
|
||||
width: parent.width * (audioTab.volumeLevel / 100)
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Theme.primary
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Draggable handle
|
||||
Rectangle {
|
||||
id: volumeHandle
|
||||
|
||||
width: 18
|
||||
height: 18
|
||||
radius: 9
|
||||
color: Theme.primary
|
||||
border.color: Qt.lighter(Theme.primary, 1.3)
|
||||
border.width: 2
|
||||
x: Math.max(0, Math.min(parent.width - width, volumeSliderFill.width - width / 2))
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
scale: volumeMouseArea.containsMouse || volumeMouseArea.pressed ? 1.2 : 1
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: volumeMouseArea
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
preventStealing: true
|
||||
onPressed: (mouse) => {
|
||||
isDragging = true;
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
||||
let newVolume = Math.round(ratio * 100);
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
isDragging = false;
|
||||
}
|
||||
onPositionChanged: (mouse) => {
|
||||
if (pressed && isDragging) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
||||
let newVolume = Math.round(ratio * 100);
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
onClicked: (mouse) => {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
||||
let newVolume = Math.round(ratio * 100);
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global mouse area for drag tracking
|
||||
MouseArea {
|
||||
id: volumeGlobalMouseArea
|
||||
|
||||
x: 0
|
||||
y: 0
|
||||
width: audioTab.width
|
||||
height: audioTab.height
|
||||
enabled: volumeMouseArea.isDragging
|
||||
visible: false
|
||||
preventStealing: true
|
||||
onPositionChanged: (mouse) => {
|
||||
if (volumeMouseArea.isDragging) {
|
||||
let globalPos = mapToItem(volumeSliderTrack, mouse.x, mouse.y);
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / volumeSliderTrack.width));
|
||||
let newVolume = Math.round(ratio * 100);
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
volumeMouseArea.isDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "volume_up"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VolumeControl {
|
||||
}
|
||||
|
||||
// Output Devices
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "Output Device"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
// Current device indicator
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 35
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||
border.width: 1
|
||||
visible: AudioService.sink !== null
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "check_circle"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Current: " + (audioTab.currentSinkDisplayName || "None")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Real audio devices
|
||||
Repeater {
|
||||
model: {
|
||||
if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values) return []
|
||||
let sinks = []
|
||||
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||
let node = Pipewire.nodes.values[i]
|
||||
if (!node || node.isStream) continue
|
||||
if ((node.type & PwNodeType.AudioSink) === PwNodeType.AudioSink) {
|
||||
sinks.push(node)
|
||||
}
|
||||
}
|
||||
return sinks
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData === AudioService.sink ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: modelData === AudioService.sink ? Theme.primary : "transparent"
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (modelData.name.includes("bluez"))
|
||||
return "headset";
|
||||
else if (modelData.name.includes("hdmi"))
|
||||
return "tv";
|
||||
else if (modelData.name.includes("usb"))
|
||||
return "headset";
|
||||
else
|
||||
return "speaker";
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: AudioService.displayName(modelData)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||
font.weight: modelData === AudioService.sink ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (AudioService.subtitle(modelData.name) && AudioService.subtitle(modelData.name) !== "")
|
||||
return AudioService.subtitle(modelData.name) + (modelData === AudioService.sink ? " • Selected" : "");
|
||||
else
|
||||
return modelData === AudioService.sink ? "Selected" : "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text !== ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deviceArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData)
|
||||
Pipewire.preferredDefaultAudioSink = modelData;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AudioDevicesList {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Input Tab Content
|
||||
ScrollView {
|
||||
width: parent.width
|
||||
height: parent.height - 48
|
||||
@@ -379,312 +63,10 @@ Item {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
// Microphone Level Control
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "Microphone Level"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: audioTab.micMuted ? "mic_off" : "mic"
|
||||
size: Theme.iconSize
|
||||
color: audioTab.micMuted ? Theme.error : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (AudioService.source && AudioService.source.audio)
|
||||
AudioService.source.audio.muted = !AudioService.source.audio.muted;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: micSliderContainer
|
||||
|
||||
width: parent.width - 80
|
||||
height: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: micSliderTrack
|
||||
|
||||
width: parent.width
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: micSliderFill
|
||||
|
||||
width: parent.width * (audioTab.micLevel / 100)
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Theme.primary
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Draggable handle
|
||||
Rectangle {
|
||||
id: micHandle
|
||||
|
||||
width: 18
|
||||
height: 18
|
||||
radius: 9
|
||||
color: Theme.primary
|
||||
border.color: Qt.lighter(Theme.primary, 1.3)
|
||||
border.width: 2
|
||||
x: Math.max(0, Math.min(parent.width - width, micSliderFill.width - width / 2))
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
scale: micMouseArea.containsMouse || micMouseArea.pressed ? 1.2 : 1
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: micMouseArea
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
preventStealing: true
|
||||
onPressed: (mouse) => {
|
||||
isDragging = true;
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
||||
let newMicLevel = Math.round(ratio * 100);
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = false;
|
||||
AudioService.source.audio.volume = newMicLevel / 100;
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
isDragging = false;
|
||||
}
|
||||
onPositionChanged: (mouse) => {
|
||||
if (pressed && isDragging) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
||||
let newMicLevel = Math.round(ratio * 100);
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = false;
|
||||
AudioService.source.audio.volume = newMicLevel / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
onClicked: (mouse) => {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
||||
let newMicLevel = Math.round(ratio * 100);
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = false;
|
||||
AudioService.source.audio.volume = newMicLevel / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global mouse area for drag tracking
|
||||
MouseArea {
|
||||
id: micGlobalMouseArea
|
||||
|
||||
x: 0
|
||||
y: 0
|
||||
width: audioTab.width
|
||||
height: audioTab.height
|
||||
enabled: micMouseArea.isDragging
|
||||
visible: false
|
||||
preventStealing: true
|
||||
onPositionChanged: (mouse) => {
|
||||
if (micMouseArea.isDragging) {
|
||||
let globalPos = mapToItem(micSliderTrack, mouse.x, mouse.y);
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / micSliderTrack.width));
|
||||
let newMicLevel = Math.round(ratio * 100);
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = false;
|
||||
AudioService.source.audio.volume = newMicLevel / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
micMouseArea.isDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "mic"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MicrophoneControl {
|
||||
}
|
||||
|
||||
// Input Devices
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "Input Device"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
// Current device indicator
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 35
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||
border.width: 1
|
||||
visible: AudioService.source !== null
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "check_circle"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Current: " + (audioTab.currentSourceDisplayName || "None")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Real audio input devices
|
||||
Repeater {
|
||||
model: {
|
||||
if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values) return []
|
||||
let sources = []
|
||||
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||
let node = Pipewire.nodes.values[i]
|
||||
if (!node || node.isStream) continue
|
||||
if ((node.type & PwNodeType.AudioSource) === PwNodeType.AudioSource && !node.name.includes(".monitor")) {
|
||||
sources.push(node)
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: sourceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData === AudioService.source ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: modelData === AudioService.source ? Theme.primary : "transparent"
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (modelData.name.includes("bluez"))
|
||||
return "headset_mic";
|
||||
else if (modelData.name.includes("usb"))
|
||||
return "headset_mic";
|
||||
else
|
||||
return "mic";
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: AudioService.displayName(modelData)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
||||
font.weight: modelData === AudioService.source ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (AudioService.subtitle(modelData.name) && AudioService.subtitle(modelData.name) !== "")
|
||||
return AudioService.subtitle(modelData.name) + (modelData === AudioService.source ? " • Selected" : "");
|
||||
else
|
||||
return modelData === AudioService.source ? "Selected" : "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text !== ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: sourceArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData)
|
||||
Pipewire.preferredDefaultAudioSource = modelData;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AudioInputDevicesList {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
386
Modules/ControlCenter/Bluetooth/AvailableDevicesList.qml
Normal file
386
Modules/ControlCenter/Bluetooth/AvailableDevicesList.qml
Normal file
@@ -0,0 +1,386 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "Available Devices"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Math.max(140, scanText.contentWidth + Theme.spacingL * 2)
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: scanArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
border.color: Theme.primary
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "bluetooth_searching"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id: scanText
|
||||
|
||||
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Stop Scanning" : "Start Scanning"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: scanArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (BluetoothService.adapter) {
|
||||
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: noteColumn.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08)
|
||||
border.color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.2)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
id: noteColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "info"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.warning
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Pairing Limitation"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.warning
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Quickshell does not support pairing devices that require pin or confirmation."
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
|
||||
return [];
|
||||
|
||||
var filtered = Bluetooth.devices.values.filter((dev) => {
|
||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
||||
});
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
property bool canConnect: BluetoothService.canConnect(modelData)
|
||||
property bool isBusy: BluetoothService.isDeviceBusy(modelData)
|
||||
|
||||
width: parent.width
|
||||
height: 70
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (availableDeviceArea.containsMouse && !isBusy)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
|
||||
|
||||
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
||||
|
||||
if (modelData.blocked)
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08);
|
||||
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
|
||||
}
|
||||
border.color: {
|
||||
if (modelData.pairing)
|
||||
return Theme.warning;
|
||||
|
||||
if (modelData.blocked)
|
||||
return Theme.error;
|
||||
|
||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2);
|
||||
}
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.getDeviceIcon(modelData)
|
||||
size: Theme.iconSize
|
||||
color: {
|
||||
if (modelData.pairing)
|
||||
return Theme.warning;
|
||||
|
||||
if (modelData.blocked)
|
||||
return Theme.error;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: modelData.name || modelData.deviceName
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: {
|
||||
if (modelData.pairing)
|
||||
return Theme.warning;
|
||||
|
||||
if (modelData.blocked)
|
||||
return Theme.error;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
font.weight: modelData.pairing ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.pairing)
|
||||
return "Pairing...";
|
||||
if (modelData.blocked)
|
||||
return "Blocked";
|
||||
return BluetoothService.getSignalStrength(modelData);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
if (modelData.pairing)
|
||||
return Theme.warning;
|
||||
|
||||
if (modelData.blocked)
|
||||
return Theme.error;
|
||||
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.getSignalIcon(modelData)
|
||||
size: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: modelData.signalStrength !== undefined && modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
|
||||
}
|
||||
|
||||
Text {
|
||||
text: (modelData.signalStrength !== undefined && modelData.signalStrength > 0) ? modelData.signalStrength + "%" : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
visible: modelData.signalStrength !== undefined && modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 80
|
||||
height: 28
|
||||
radius: Theme.cornerRadiusSmall
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: modelData.state !== BluetoothDeviceState.Connecting
|
||||
color: {
|
||||
if (!canConnect && !isBusy)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3);
|
||||
|
||||
if (actionButtonArea.containsMouse && !isBusy)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
||||
|
||||
return "transparent";
|
||||
}
|
||||
border.color: canConnect || isBusy ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 1
|
||||
opacity: canConnect || isBusy ? 1 : 0.5
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (modelData.pairing)
|
||||
return "Pairing...";
|
||||
|
||||
if (modelData.blocked)
|
||||
return "Blocked";
|
||||
|
||||
return "Connect";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: canConnect || isBusy ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: actionButtonArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
|
||||
enabled: canConnect && !isBusy
|
||||
onClicked: {
|
||||
if (modelData) {
|
||||
BluetoothService.connectDeviceWithTrust(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: availableDeviceArea
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 90
|
||||
hoverEnabled: true
|
||||
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
|
||||
enabled: canConnect && !isBusy
|
||||
onClicked: {
|
||||
if (modelData) {
|
||||
BluetoothService.connectDeviceWithTrust(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
|
||||
return false;
|
||||
|
||||
var availableCount = Bluetooth.devices.values.filter((dev) => {
|
||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
||||
}).length;
|
||||
|
||||
return availableCount === 0;
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "sync"
|
||||
size: Theme.iconSizeLarge
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 2000
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Scanning for devices..."
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Make sure your device is in pairing mode"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "No devices found. Put your device in pairing mode and click Start Scanning."
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return true;
|
||||
|
||||
var availableCount = Bluetooth.devices.values.filter((dev) => {
|
||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
||||
}).length;
|
||||
|
||||
return availableCount === 0 && !BluetoothService.adapter.discovering;
|
||||
}
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
201
Modules/ControlCenter/Bluetooth/BluetoothContextMenu.qml
Normal file
201
Modules/ControlCenter/Bluetooth/BluetoothContextMenu.qml
Normal file
@@ -0,0 +1,201 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var deviceData: null
|
||||
property bool menuVisible: false
|
||||
property var parentItem
|
||||
|
||||
function show(x, y) {
|
||||
const menuWidth = 160;
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2;
|
||||
let finalX = x - menuWidth / 2;
|
||||
let finalY = y;
|
||||
finalX = Math.max(0, Math.min(finalX, parentItem.width - menuWidth));
|
||||
finalY = Math.max(0, Math.min(finalY, parentItem.height - menuHeight));
|
||||
root.x = finalX;
|
||||
root.y = finalY;
|
||||
root.visible = true;
|
||||
root.menuVisible = true;
|
||||
}
|
||||
|
||||
function hide() {
|
||||
root.menuVisible = false;
|
||||
Qt.callLater(() => {
|
||||
root.visible = false;
|
||||
});
|
||||
}
|
||||
|
||||
visible: false
|
||||
width: 160
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
z: 1000
|
||||
opacity: menuVisible ? 1 : 0
|
||||
scale: menuVisible ? 1 : 0.85
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 2
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -4
|
||||
radius: parent.radius
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
z: parent.z - 1
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: connectArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: root.deviceData && root.deviceData.connected ? "link_off" : "link"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.deviceData && root.deviceData.connected ? "Disconnect" : "Connect"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: connectArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.deviceData) {
|
||||
if (root.deviceData.connected) {
|
||||
root.deviceData.disconnect();
|
||||
} else {
|
||||
BluetoothService.connectDeviceWithTrust(root.deviceData);
|
||||
}
|
||||
}
|
||||
root.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: forgetArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "delete"
|
||||
size: Theme.iconSize - 2
|
||||
color: forgetArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Forget Device"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: forgetArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: forgetArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.deviceData) {
|
||||
root.deviceData.forget();
|
||||
}
|
||||
root.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Modules/ControlCenter/Bluetooth/BluetoothToggle.qml
Normal file
65
Modules/ControlCenter/Bluetooth/BluetoothToggle.qml
Normal file
@@ -0,0 +1,65 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: bluetoothToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (BluetoothService.adapter && BluetoothService.adapter.enabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12))
|
||||
border.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : "transparent"
|
||||
border.width: 2
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "bluetooth"
|
||||
size: Theme.iconSizeLarge
|
||||
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: "Bluetooth"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Text {
|
||||
text: BluetoothService.adapter && BluetoothService.adapter.enabled ? "Enabled" : "Disabled"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: bluetoothToggle
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (BluetoothService.adapter) {
|
||||
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
150
Modules/ControlCenter/Bluetooth/PairedDevicesList.qml
Normal file
150
Modules/ControlCenter/Bluetooth/PairedDevicesList.qml
Normal file
@@ -0,0 +1,150 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property var bluetoothContextMenuWindow
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
|
||||
Text {
|
||||
text: "Paired Devices"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: BluetoothService.adapter && BluetoothService.adapter.devices ? BluetoothService.adapter.devices.values.filter((dev) => {
|
||||
return dev && (dev.paired || dev.trusted);
|
||||
}) : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: btDeviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: modelData.connected ? Theme.primary : "transparent"
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.getDeviceIcon(modelData)
|
||||
size: Theme.iconSize
|
||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: modelData.name || modelData.deviceName
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||
font.weight: modelData.connected ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
text: BluetoothDeviceState.toString(modelData.state)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.batteryAvailable && modelData.battery > 0)
|
||||
return "• " + Math.round(modelData.battery * 100) + "%";
|
||||
|
||||
var btBattery = BatteryService.bluetoothDevices.find((dev) => {
|
||||
return dev.name === (modelData.name || modelData.deviceName) || dev.name.toLowerCase().includes((modelData.name || modelData.deviceName).toLowerCase()) || (modelData.name || modelData.deviceName).toLowerCase().includes(dev.name.toLowerCase());
|
||||
});
|
||||
return btBattery ? "• " + btBattery.percentage + "%" : "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: btMenuButton
|
||||
|
||||
width: 32
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: btMenuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "more_vert"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.6
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: btMenuButtonArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (bluetoothContextMenuWindow) {
|
||||
bluetoothContextMenuWindow.deviceData = modelData;
|
||||
let localPos = btMenuButtonArea.mapToItem(bluetoothContextMenuWindow.parentItem, btMenuButtonArea.width / 2, btMenuButtonArea.height);
|
||||
bluetoothContextMenuWindow.show(localPos.x, localPos.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: btDeviceArea
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 40
|
||||
hoverEnabled: true
|
||||
enabled: !BluetoothService.isDeviceBusy(modelData)
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.BusyCursor
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
modelData.disconnect();
|
||||
} else {
|
||||
BluetoothService.connectDeviceWithTrust(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Bluetooth
|
||||
|
||||
Item {
|
||||
id: bluetoothTab
|
||||
@@ -21,800 +22,19 @@ Item {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: bluetoothToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (BluetoothService.adapter && BluetoothService.adapter.enabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12))
|
||||
border.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : "transparent"
|
||||
border.width: 2
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "bluetooth"
|
||||
size: Theme.iconSizeLarge
|
||||
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: "Bluetooth"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Text {
|
||||
text: BluetoothService.adapter && BluetoothService.adapter.enabled ? "Enabled" : "Disabled"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: bluetoothToggle
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (BluetoothService.adapter) {
|
||||
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
|
||||
Text {
|
||||
text: "Paired Devices"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: BluetoothService.adapter && BluetoothService.adapter.devices ? BluetoothService.adapter.devices.values.filter((dev) => {
|
||||
return dev && (dev.paired || dev.trusted);
|
||||
}) : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: btDeviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: modelData.connected ? Theme.primary : "transparent"
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.getDeviceIcon(modelData)
|
||||
size: Theme.iconSize
|
||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: modelData.name || modelData.deviceName
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||
font.weight: modelData.connected ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
text: BluetoothDeviceState.toString(modelData.state)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.batteryAvailable && modelData.battery > 0)
|
||||
return "• " + Math.round(modelData.battery * 100) + "%";
|
||||
|
||||
var btBattery = BatteryService.bluetoothDevices.find((dev) => {
|
||||
return dev.name === (modelData.name || modelData.deviceName) || dev.name.toLowerCase().includes((modelData.name || modelData.deviceName).toLowerCase()) || (modelData.name || modelData.deviceName).toLowerCase().includes(dev.name.toLowerCase());
|
||||
});
|
||||
return btBattery ? "• " + btBattery.percentage + "%" : "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: btMenuButton
|
||||
|
||||
width: 32
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: btMenuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "more_vert"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.6
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: btMenuButtonArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
bluetoothContextMenuWindow.deviceData = modelData;
|
||||
let localPos = btMenuButtonArea.mapToItem(bluetoothTab, btMenuButtonArea.width / 2, btMenuButtonArea.height);
|
||||
bluetoothContextMenuWindow.show(localPos.x, localPos.y);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: btDeviceArea
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 40
|
||||
hoverEnabled: true
|
||||
enabled: !BluetoothService.isDeviceBusy(modelData)
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.BusyCursor
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
modelData.disconnect();
|
||||
} else {
|
||||
BluetoothService.connectDeviceWithTrust(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "Available Devices"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Math.max(140, scanText.contentWidth + Theme.spacingL * 2)
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: scanArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
border.color: Theme.primary
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "bluetooth_searching"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id: scanText
|
||||
|
||||
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Stop Scanning" : "Start Scanning"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: scanArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (BluetoothService.adapter) {
|
||||
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: noteColumn.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08)
|
||||
border.color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.2)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
id: noteColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "info"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.warning
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Pairing Limitation"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.warning
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Quickshell does not support pairing devices that require pin or confirmation."
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
|
||||
return [];
|
||||
|
||||
var filtered = Bluetooth.devices.values.filter((dev) => {
|
||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
||||
});
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
property bool canConnect: BluetoothService.canConnect(modelData)
|
||||
property bool isBusy: BluetoothService.isDeviceBusy(modelData)
|
||||
|
||||
width: parent.width
|
||||
height: 70
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (availableDeviceArea.containsMouse && !isBusy)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
|
||||
|
||||
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
||||
|
||||
if (modelData.blocked)
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08);
|
||||
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
|
||||
}
|
||||
border.color: {
|
||||
if (modelData.pairing)
|
||||
return Theme.warning;
|
||||
|
||||
if (modelData.blocked)
|
||||
return Theme.error;
|
||||
|
||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2);
|
||||
}
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.getDeviceIcon(modelData)
|
||||
size: Theme.iconSize
|
||||
color: {
|
||||
if (modelData.pairing)
|
||||
return Theme.warning;
|
||||
|
||||
if (modelData.blocked)
|
||||
return Theme.error;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: modelData.name || modelData.deviceName
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: {
|
||||
if (modelData.pairing)
|
||||
return Theme.warning;
|
||||
|
||||
if (modelData.blocked)
|
||||
return Theme.error;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
font.weight: modelData.pairing ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.pairing)
|
||||
return "Pairing...";
|
||||
if (modelData.blocked)
|
||||
return "Blocked";
|
||||
return BluetoothService.getSignalStrength(modelData);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
if (modelData.pairing)
|
||||
return Theme.warning;
|
||||
|
||||
if (modelData.blocked)
|
||||
return Theme.error;
|
||||
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.getSignalIcon(modelData)
|
||||
size: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: modelData.signalStrength !== undefined && modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
|
||||
}
|
||||
|
||||
Text {
|
||||
text: (modelData.signalStrength !== undefined && modelData.signalStrength > 0) ? modelData.signalStrength + "%" : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
visible: modelData.signalStrength !== undefined && modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 80
|
||||
height: 28
|
||||
radius: Theme.cornerRadiusSmall
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: modelData.state !== BluetoothDeviceState.Connecting
|
||||
color: {
|
||||
if (!canConnect && !isBusy)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3);
|
||||
|
||||
if (actionButtonArea.containsMouse && !isBusy)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
||||
|
||||
return "transparent";
|
||||
}
|
||||
border.color: canConnect || isBusy ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 1
|
||||
opacity: canConnect || isBusy ? 1 : 0.5
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (modelData.pairing)
|
||||
return "Pairing...";
|
||||
|
||||
if (modelData.blocked)
|
||||
return "Blocked";
|
||||
|
||||
return "Connect";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: canConnect || isBusy ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: actionButtonArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
|
||||
enabled: canConnect && !isBusy
|
||||
onClicked: {
|
||||
if (modelData) {
|
||||
BluetoothService.connectDeviceWithTrust(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: availableDeviceArea
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 90 // Don't overlap with action button
|
||||
hoverEnabled: true
|
||||
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
|
||||
enabled: canConnect && !isBusy
|
||||
onClicked: {
|
||||
if (modelData) {
|
||||
BluetoothService.connectDeviceWithTrust(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
|
||||
return false;
|
||||
|
||||
var availableCount = Bluetooth.devices.values.filter((dev) => {
|
||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
||||
}).length;
|
||||
|
||||
return availableCount === 0;
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "sync"
|
||||
size: Theme.iconSizeLarge
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 2000
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Scanning for devices..."
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Make sure your device is in pairing mode"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "No devices found. Put your device in pairing mode and click Start Scanning."
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return true;
|
||||
|
||||
var availableCount = Bluetooth.devices.values.filter((dev) => {
|
||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
||||
}).length;
|
||||
|
||||
return availableCount === 0 && !BluetoothService.adapter.discovering;
|
||||
}
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
BluetoothToggle { }
|
||||
|
||||
PairedDevicesList {
|
||||
bluetoothContextMenuWindow: bluetoothContextMenuWindow
|
||||
}
|
||||
|
||||
AvailableDevicesList { }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BluetoothContextMenu {
|
||||
id: bluetoothContextMenuWindow
|
||||
|
||||
property var deviceData: null
|
||||
property bool menuVisible: false
|
||||
|
||||
function show(x, y) {
|
||||
const menuWidth = 160;
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2;
|
||||
let finalX = x - menuWidth / 2;
|
||||
let finalY = y;
|
||||
finalX = Math.max(0, Math.min(finalX, bluetoothTab.width - menuWidth));
|
||||
finalY = Math.max(0, Math.min(finalY, bluetoothTab.height - menuHeight));
|
||||
bluetoothContextMenuWindow.x = finalX;
|
||||
bluetoothContextMenuWindow.y = finalY;
|
||||
bluetoothContextMenuWindow.visible = true;
|
||||
bluetoothContextMenuWindow.menuVisible = true;
|
||||
}
|
||||
|
||||
function hide() {
|
||||
bluetoothContextMenuWindow.menuVisible = false;
|
||||
Qt.callLater(() => {
|
||||
bluetoothContextMenuWindow.visible = false;
|
||||
});
|
||||
}
|
||||
|
||||
visible: false
|
||||
width: 160
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
z: 1000
|
||||
opacity: menuVisible ? 1 : 0
|
||||
scale: menuVisible ? 1 : 0.85
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 2
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -4
|
||||
radius: parent.radius
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
z: parent.z - 1
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: connectArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: bluetoothContextMenuWindow.deviceData && bluetoothContextMenuWindow.deviceData.connected ? "link_off" : "link"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: bluetoothContextMenuWindow.deviceData && bluetoothContextMenuWindow.deviceData.connected ? "Disconnect" : "Connect"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: connectArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (bluetoothContextMenuWindow.deviceData) {
|
||||
if (bluetoothContextMenuWindow.deviceData.connected) {
|
||||
bluetoothContextMenuWindow.deviceData.disconnect();
|
||||
} else {
|
||||
BluetoothService.connectDeviceWithTrust(bluetoothContextMenuWindow.deviceData);
|
||||
}
|
||||
}
|
||||
bluetoothContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: forgetArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "delete"
|
||||
size: Theme.iconSize - 2
|
||||
color: forgetArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Forget Device"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: forgetArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: forgetArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (bluetoothContextMenuWindow.deviceData) {
|
||||
bluetoothContextMenuWindow.deviceData.forget();
|
||||
}
|
||||
bluetoothContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
parentItem: bluetoothTab
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -832,7 +52,5 @@ Item {
|
||||
onClicked: {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import "../../Widgets"
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
@@ -9,7 +10,6 @@ import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import "../../Widgets"
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
@@ -25,13 +25,13 @@ PanelWindow {
|
||||
// Enable/disable WiFi auto-refresh based on control center visibility
|
||||
WifiService.autoRefreshEnabled = visible && NetworkService.wifiEnabled;
|
||||
// Stop bluetooth scanning when control center is closed
|
||||
if (!visible && BluetoothService.adapter && BluetoothService.adapter.discovering) {
|
||||
if (!visible && BluetoothService.adapter && BluetoothService.adapter.discovering)
|
||||
BluetoothService.adapter.discovering = false;
|
||||
}
|
||||
|
||||
// Refresh uptime when opened
|
||||
if (visible && UserInfoService) {
|
||||
if (visible && UserInfoService)
|
||||
UserInfoService.getUptime();
|
||||
}
|
||||
|
||||
}
|
||||
implicitWidth: 600
|
||||
implicitHeight: 500
|
||||
@@ -296,6 +296,7 @@ PanelWindow {
|
||||
|
||||
DankIcon {
|
||||
id: dankIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
name: root.powerOptionsExpanded ? "expand_less" : "power_settings_new"
|
||||
size: Theme.iconSize - 2
|
||||
@@ -617,47 +618,48 @@ PanelWindow {
|
||||
let tabs = ["network", "audio"];
|
||||
if (BluetoothService.available)
|
||||
tabs.push("bluetooth");
|
||||
|
||||
tabs.push("display");
|
||||
return tabs.indexOf(root.currentTab);
|
||||
}
|
||||
model: {
|
||||
let tabs = [{
|
||||
"text": "Network",
|
||||
"icon": "wifi",
|
||||
"id": "network"
|
||||
}];
|
||||
// Always show audio
|
||||
let tabs = [{
|
||||
"text": "Network",
|
||||
"icon": "wifi",
|
||||
"id": "network"
|
||||
}];
|
||||
// Always show audio
|
||||
tabs.push({
|
||||
"text": "Audio",
|
||||
"icon": "volume_up",
|
||||
"id": "audio"
|
||||
});
|
||||
// Show Bluetooth only if available
|
||||
if (BluetoothService.available)
|
||||
tabs.push({
|
||||
"text": "Audio",
|
||||
"icon": "volume_up",
|
||||
"id": "audio"
|
||||
});
|
||||
// Show Bluetooth only if available
|
||||
if (BluetoothService.available)
|
||||
tabs.push({
|
||||
"text": "Bluetooth",
|
||||
"icon": "bluetooth",
|
||||
"id": "bluetooth"
|
||||
});
|
||||
"text": "Bluetooth",
|
||||
"icon": "bluetooth",
|
||||
"id": "bluetooth"
|
||||
});
|
||||
|
||||
// Always show display
|
||||
tabs.push({
|
||||
"text": "Display",
|
||||
"icon": "brightness_6",
|
||||
"id": "display"
|
||||
});
|
||||
return tabs;
|
||||
// Always show display
|
||||
tabs.push({
|
||||
"text": "Display",
|
||||
"icon": "brightness_6",
|
||||
"id": "display"
|
||||
});
|
||||
return tabs;
|
||||
}
|
||||
onTabClicked: function(index) {
|
||||
let tabs = ["network", "audio"];
|
||||
if (BluetoothService.available)
|
||||
tabs.push("bluetooth");
|
||||
|
||||
tabs.push("display");
|
||||
root.currentTab = tabs[index];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Tab content area
|
||||
|
||||
122
Modules/ControlCenter/Network/EthernetCard.qml
Normal file
122
Modules/ControlCenter/Network/EthernetCard.qml
Normal file
@@ -0,0 +1,122 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: ethernetCard
|
||||
|
||||
width: parent.width
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (ethernetPreferenceArea.containsMouse && NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "ethernet")
|
||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8);
|
||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5);
|
||||
}
|
||||
border.color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: NetworkService.networkStatus === "ethernet" ? 2 : 1
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: ethernetToggle.left
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "lan"
|
||||
size: Theme.iconSize
|
||||
color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Ethernet"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: NetworkService.ethernetConnected ? (NetworkService.ethernetIP || "Connected") : "Disconnected"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
leftPadding: Theme.iconSize + Theme.spacingM
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
// Loading spinner for preference changes
|
||||
DankIcon {
|
||||
id: ethernetLoadingSpinner
|
||||
name: "refresh"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.right: ethernetToggle.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.changingPreference && NetworkService.targetPreference === "ethernet"
|
||||
z: 10
|
||||
|
||||
RotationAnimation {
|
||||
target: ethernetLoadingSpinner
|
||||
property: "rotation"
|
||||
running: ethernetLoadingSpinner.visible
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
}
|
||||
|
||||
// Ethernet toggle switch (matching WiFi style)
|
||||
DankToggle {
|
||||
id: ethernetToggle
|
||||
checked: NetworkService.ethernetConnected
|
||||
enabled: true
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
NetworkService.toggleNetworkConnection("ethernet");
|
||||
}
|
||||
}
|
||||
|
||||
// MouseArea for network preference (excluding toggle area)
|
||||
MouseArea {
|
||||
id: ethernetPreferenceArea
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 60 // Exclude toggle area
|
||||
hoverEnabled: true
|
||||
cursorShape: (NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "ethernet") ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "ethernet" && !NetworkService.changingNetworkPreference
|
||||
onClicked: {
|
||||
if (NetworkService.ethernetConnected && NetworkService.wifiEnabled) {
|
||||
console.log("Ethernet card clicked for preference");
|
||||
if (NetworkService.networkStatus !== "ethernet")
|
||||
NetworkService.setNetworkPreference("ethernet");
|
||||
else
|
||||
NetworkService.setNetworkPreference("auto");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
183
Modules/ControlCenter/Network/NetworkInfoDialog.qml
Normal file
183
Modules/ControlCenter/Network/NetworkInfoDialog.qml
Normal file
@@ -0,0 +1,183 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: root
|
||||
|
||||
property bool networkInfoDialogVisible: false
|
||||
property string networkSSID: ""
|
||||
property var networkData: null
|
||||
property string networkDetails: ""
|
||||
|
||||
function showNetworkInfo(ssid, data) {
|
||||
networkSSID = ssid;
|
||||
networkData = data;
|
||||
networkInfoDialogVisible = true;
|
||||
WifiService.fetchNetworkInfo(ssid);
|
||||
}
|
||||
|
||||
function hideDialog() {
|
||||
networkInfoDialogVisible = false;
|
||||
networkSSID = "";
|
||||
networkData = null;
|
||||
networkDetails = "";
|
||||
}
|
||||
|
||||
visible: networkInfoDialogVisible
|
||||
width: 600
|
||||
height: 500
|
||||
enableShadow: true
|
||||
onBackgroundClicked: {
|
||||
hideDialog();
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
networkSSID = "";
|
||||
networkData = null;
|
||||
networkDetails = "";
|
||||
}
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
|
||||
// Header
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
Column {
|
||||
width: parent.width - 40
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
text: "Network Information"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Details for \"" + networkSSID + "\""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||
onClicked: {
|
||||
root.hideDialog();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Network Details
|
||||
ScrollView {
|
||||
width: parent.width
|
||||
height: parent.height - 140
|
||||
clip: true
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
Flickable {
|
||||
contentWidth: parent.width
|
||||
contentHeight: detailsRect.height
|
||||
|
||||
Rectangle {
|
||||
id: detailsRect
|
||||
|
||||
width: parent.width
|
||||
height: Math.max(parent.parent.height, detailsText.contentHeight + Theme.spacingM * 2)
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
id: detailsText
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
text: WifiService.networkInfoDetails.replace(/\\n/g, '\n') || "No information available"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
wrapMode: Text.WordWrap
|
||||
lineHeight: 1.5
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Close Button
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Math.max(70, closeText.contentWidth + Theme.spacingM * 2)
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: closeArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||
|
||||
Text {
|
||||
id: closeText
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "Close"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.background
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.hideDialog();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
174
Modules/ControlCenter/Network/WiFiCard.qml
Normal file
174
Modules/ControlCenter/Network/WiFiCard.qml
Normal file
@@ -0,0 +1,174 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: wifiCard
|
||||
|
||||
property var refreshTimer
|
||||
|
||||
function getWiFiSignalIcon(signalStrength) {
|
||||
switch (signalStrength) {
|
||||
case "excellent": return "wifi";
|
||||
case "good": return "wifi_2_bar";
|
||||
case "fair": return "wifi_1_bar";
|
||||
case "poor": return "signal_wifi_0_bar";
|
||||
default: return "wifi";
|
||||
}
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (wifiPreferenceArea.containsMouse && NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "wifi")
|
||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8);
|
||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5);
|
||||
}
|
||||
border.color: NetworkService.networkStatus === "wifi" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: NetworkService.networkStatus === "wifi" ? 2 : 1
|
||||
visible: NetworkService.wifiAvailable
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: wifiToggle.left
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (!NetworkService.wifiEnabled) {
|
||||
return "wifi_off";
|
||||
} else if (WifiService.currentWifiSSID !== "") {
|
||||
return getWiFiSignalIcon(WifiService.wifiSignalStrength);
|
||||
} else {
|
||||
return "wifi";
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (!NetworkService.wifiEnabled) {
|
||||
return "WiFi is off";
|
||||
} else if (NetworkService.wifiEnabled && WifiService.currentWifiSSID) {
|
||||
return WifiService.currentWifiSSID || "Connected";
|
||||
} else {
|
||||
return "Not Connected";
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (!NetworkService.wifiEnabled) {
|
||||
return "Turn on WiFi to see networks";
|
||||
} else if (NetworkService.wifiEnabled && WifiService.currentWifiSSID) {
|
||||
return NetworkService.wifiIP || "Connected";
|
||||
} else {
|
||||
return "Select a network below";
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
leftPadding: Theme.iconSize + Theme.spacingM
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
// Loading spinner for preference changes
|
||||
DankIcon {
|
||||
id: wifiLoadingSpinner
|
||||
name: "refresh"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.right: wifiToggle.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.changingPreference && NetworkService.targetPreference === "wifi"
|
||||
z: 10
|
||||
|
||||
RotationAnimation {
|
||||
target: wifiLoadingSpinner
|
||||
property: "rotation"
|
||||
running: wifiLoadingSpinner.visible
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
}
|
||||
|
||||
// WiFi toggle switch
|
||||
DankToggle {
|
||||
id: wifiToggle
|
||||
checked: NetworkService.wifiEnabled
|
||||
enabled: true
|
||||
toggling: NetworkService.wifiToggling
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
if (NetworkService.wifiEnabled) {
|
||||
// When turning WiFi off, clear all cached WiFi data
|
||||
WifiService.currentWifiSSID = "";
|
||||
WifiService.wifiSignalStrength = "excellent";
|
||||
WifiService.wifiNetworks = [];
|
||||
WifiService.savedWifiNetworks = [];
|
||||
WifiService.connectionStatus = "";
|
||||
WifiService.connectingSSID = "";
|
||||
WifiService.isScanning = false;
|
||||
NetworkService.refreshNetworkStatus();
|
||||
}
|
||||
NetworkService.toggleWifiRadio();
|
||||
if (refreshTimer) {
|
||||
refreshTimer.triggered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MouseArea for network preference (excluding toggle area)
|
||||
MouseArea {
|
||||
id: wifiPreferenceArea
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 60 // Exclude toggle area
|
||||
hoverEnabled: true
|
||||
cursorShape: (NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "wifi") ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "wifi" && !NetworkService.changingNetworkPreference
|
||||
onClicked: {
|
||||
if (NetworkService.ethernetConnected && NetworkService.wifiEnabled) {
|
||||
console.log("WiFi card clicked for preference");
|
||||
if (NetworkService.networkStatus !== "wifi")
|
||||
NetworkService.setNetworkPreference("wifi");
|
||||
else
|
||||
NetworkService.setNetworkPreference("auto");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
280
Modules/ControlCenter/Network/WiFiContextMenu.qml
Normal file
280
Modules/ControlCenter/Network/WiFiContextMenu.qml
Normal file
@@ -0,0 +1,280 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: wifiContextMenuWindow
|
||||
|
||||
property var networkData: null
|
||||
property bool menuVisible: false
|
||||
property var parentItem
|
||||
property var wifiPasswordDialogRef
|
||||
property var networkInfoDialogRef
|
||||
|
||||
function show(x, y) {
|
||||
const menuWidth = 160;
|
||||
wifiContextMenuWindow.visible = true;
|
||||
|
||||
Qt.callLater(() => {
|
||||
const menuHeight = wifiMenuColumn.implicitHeight + Theme.spacingS * 2;
|
||||
let finalX = x - menuWidth / 2;
|
||||
let finalY = y + 4;
|
||||
|
||||
finalX = Math.max(Theme.spacingS, Math.min(finalX, parentItem.width - menuWidth - Theme.spacingS));
|
||||
finalY = Math.max(Theme.spacingS, Math.min(finalY, parentItem.height - menuHeight - Theme.spacingS));
|
||||
|
||||
if (finalY + menuHeight > parentItem.height - Theme.spacingS) {
|
||||
finalY = y - menuHeight - 4;
|
||||
finalY = Math.max(Theme.spacingS, finalY);
|
||||
}
|
||||
|
||||
wifiContextMenuWindow.x = finalX;
|
||||
wifiContextMenuWindow.y = finalY;
|
||||
wifiContextMenuWindow.menuVisible = true;
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
wifiContextMenuWindow.menuVisible = false;
|
||||
Qt.callLater(() => {
|
||||
wifiContextMenuWindow.visible = false;
|
||||
});
|
||||
}
|
||||
|
||||
visible: false
|
||||
width: 160
|
||||
height: wifiMenuColumn.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
z: 1000
|
||||
opacity: menuVisible ? 1 : 0
|
||||
scale: menuVisible ? 1 : 0.85
|
||||
|
||||
Component.onCompleted: {
|
||||
menuVisible = false;
|
||||
visible = false;
|
||||
}
|
||||
|
||||
// Drop shadow
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 2
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -4
|
||||
radius: parent.radius
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
z: parent.z - 1
|
||||
}
|
||||
|
||||
Column {
|
||||
id: wifiMenuColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
// Connect/Disconnect option
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: connectWifiArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: wifiContextMenuWindow.networkData && wifiContextMenuWindow.networkData.connected ? "wifi_off" : "wifi"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: wifiContextMenuWindow.networkData && wifiContextMenuWindow.networkData.connected ? "Disconnect" : "Connect"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: connectWifiArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (wifiContextMenuWindow.networkData) {
|
||||
if (wifiContextMenuWindow.networkData.connected) {
|
||||
WifiService.disconnectWifi();
|
||||
} else {
|
||||
if (wifiContextMenuWindow.networkData.saved) {
|
||||
WifiService.connectToWifi(wifiContextMenuWindow.networkData.ssid);
|
||||
} else if (wifiContextMenuWindow.networkData.secured) {
|
||||
if (wifiPasswordDialogRef) {
|
||||
wifiPasswordDialogRef.wifiPasswordSSID = wifiContextMenuWindow.networkData.ssid;
|
||||
wifiPasswordDialogRef.wifiPasswordInput = "";
|
||||
wifiPasswordDialogRef.wifiPasswordDialogVisible = true;
|
||||
}
|
||||
} else {
|
||||
WifiService.connectToWifi(wifiContextMenuWindow.networkData.ssid);
|
||||
}
|
||||
}
|
||||
}
|
||||
wifiContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
// Forget Network option (only for saved networks)
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: forgetWifiArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
||||
visible: wifiContextMenuWindow.networkData && (wifiContextMenuWindow.networkData.saved || wifiContextMenuWindow.networkData.connected)
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "delete"
|
||||
size: Theme.iconSize - 2
|
||||
color: forgetWifiArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Forget Network"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: forgetWifiArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: forgetWifiArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (wifiContextMenuWindow.networkData) {
|
||||
WifiService.forgetWifiNetwork(wifiContextMenuWindow.networkData.ssid);
|
||||
}
|
||||
wifiContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network Info option
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: infoWifiArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "info"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Network Info"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: infoWifiArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (wifiContextMenuWindow.networkData && networkInfoDialogRef) {
|
||||
networkInfoDialogRef.showNetworkInfo(wifiContextMenuWindow.networkData.ssid, wifiContextMenuWindow.networkData);
|
||||
}
|
||||
wifiContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
276
Modules/ControlCenter/Network/WiFiNetworksList.qml
Normal file
276
Modules/ControlCenter/Network/WiFiNetworksList.qml
Normal file
@@ -0,0 +1,276 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property var wifiContextMenuWindow
|
||||
property var sortedWifiNetworks
|
||||
property var wifiPasswordDialogRef
|
||||
|
||||
function getWiFiSignalIcon(signalStrength) {
|
||||
switch (signalStrength) {
|
||||
case "excellent": return "wifi";
|
||||
case "good": return "wifi_2_bar";
|
||||
case "fair": return "wifi_1_bar";
|
||||
case "poor": return "signal_wifi_0_bar";
|
||||
default: return "wifi";
|
||||
}
|
||||
}
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 100
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
visible: NetworkService.wifiEnabled
|
||||
spacing: Theme.spacingS
|
||||
|
||||
// Available Networks Section with refresh button (spanning version)
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: "Available Networks"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - 170
|
||||
height: 1
|
||||
}
|
||||
|
||||
// WiFi refresh button (spanning version)
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: refreshAreaSpan.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : WifiService.isScanning ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"
|
||||
|
||||
DankIcon {
|
||||
id: refreshIconSpan
|
||||
anchors.centerIn: parent
|
||||
name: "refresh"
|
||||
size: Theme.iconSize - 6
|
||||
color: refreshAreaSpan.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
rotation: WifiService.isScanning ? refreshIconSpan.rotation : 0
|
||||
|
||||
RotationAnimation {
|
||||
target: refreshIconSpan
|
||||
property: "rotation"
|
||||
running: WifiService.isScanning
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
|
||||
Behavior on rotation {
|
||||
RotationAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: refreshAreaSpan
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!WifiService.isScanning) {
|
||||
// Immediate visual feedback
|
||||
refreshIconSpan.rotation += 30;
|
||||
WifiService.scanWifi();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scrollable networks container
|
||||
Flickable {
|
||||
width: parent.width
|
||||
height: parent.height - 40
|
||||
clip: true
|
||||
contentWidth: width
|
||||
contentHeight: spanningNetworksColumn.height
|
||||
boundsBehavior: Flickable.DragAndOvershootBounds
|
||||
flickDeceleration: 8000
|
||||
maximumFlickVelocity: 15000
|
||||
|
||||
Column {
|
||||
id: spanningNetworksColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: NetworkService.wifiAvailable && NetworkService.wifiEnabled ? sortedWifiNetworks : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 38
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: networkArea2.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
border.color: modelData.connected ? Theme.primary : "transparent"
|
||||
border.width: modelData.connected ? 1 : 0
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingXS
|
||||
anchors.rightMargin: Theme.spacingM // Extra right margin for scrollbar
|
||||
|
||||
// Signal strength icon
|
||||
DankIcon {
|
||||
id: signalIcon2
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: getWiFiSignalIcon(modelData.signalStrength)
|
||||
size: Theme.iconSize - 2
|
||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
// Network info
|
||||
Column {
|
||||
anchors.left: signalIcon2.right
|
||||
anchors.leftMargin: Theme.spacingXS
|
||||
anchors.right: rightIcons2.left
|
||||
anchors.rightMargin: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: modelData.ssid
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||
font.weight: modelData.connected ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: {
|
||||
if (modelData.connected)
|
||||
return "Connected";
|
||||
if (WifiService.connectionStatus === "connecting" && WifiService.connectingSSID === modelData.ssid)
|
||||
return "Connecting...";
|
||||
if (WifiService.connectionStatus === "invalid_password" && WifiService.connectingSSID === modelData.ssid)
|
||||
return "Invalid password";
|
||||
if (modelData.saved)
|
||||
return "Saved" + (modelData.secured ? " • Secured" : " • Open");
|
||||
return modelData.secured ? "Secured" : "Open";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: {
|
||||
if (WifiService.connectionStatus === "connecting" && WifiService.connectingSSID === modelData.ssid)
|
||||
return Theme.primary;
|
||||
if (WifiService.connectionStatus === "invalid_password" && WifiService.connectingSSID === modelData.ssid)
|
||||
return Theme.error;
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
|
||||
}
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
// Right side icons
|
||||
Row {
|
||||
id: rightIcons2
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
// Lock icon (if secured)
|
||||
DankIcon {
|
||||
name: "lock"
|
||||
size: Theme.iconSize - 8
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
visible: modelData.secured
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
// Context menu button
|
||||
Rectangle {
|
||||
id: wifiMenuButton
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
color: wifiMenuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
|
||||
DankIcon {
|
||||
name: "more_vert"
|
||||
size: Theme.iconSize - 8
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.6
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wifiMenuButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
wifiContextMenuWindow.networkData = modelData;
|
||||
let buttonCenter = wifiMenuButtonArea.width / 2;
|
||||
let buttonBottom = wifiMenuButtonArea.height;
|
||||
let globalPos = wifiMenuButtonArea.mapToItem(wifiContextMenuWindow.parentItem, buttonCenter, buttonBottom);
|
||||
|
||||
Qt.callLater(() => {
|
||||
wifiContextMenuWindow.show(globalPos.x, globalPos.y);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: networkArea2
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 32 // Exclude menu button area
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData.connected)
|
||||
return;
|
||||
|
||||
if (modelData.saved) {
|
||||
WifiService.connectToWifi(modelData.ssid);
|
||||
} else if (modelData.secured) {
|
||||
if (wifiPasswordDialogRef) {
|
||||
wifiPasswordDialogRef.wifiPasswordSSID = modelData.ssid;
|
||||
wifiPasswordDialogRef.wifiPasswordInput = "";
|
||||
wifiPasswordDialogRef.wifiPasswordDialogVisible = true;
|
||||
}
|
||||
} else {
|
||||
WifiService.connectToWifi(modelData.ssid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,21 +6,13 @@ import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import "../../Widgets"
|
||||
import qs.Modules.ControlCenter.Network
|
||||
|
||||
Item {
|
||||
id: networkTab
|
||||
|
||||
// Helper function for consistent WiFi signal icons
|
||||
function getWiFiSignalIcon(signalStrength) {
|
||||
switch (signalStrength) {
|
||||
case "excellent": return "wifi";
|
||||
case "good": return "wifi_2_bar";
|
||||
case "fair": return "wifi_1_bar";
|
||||
case "poor": return "signal_wifi_0_bar";
|
||||
default: return "wifi";
|
||||
}
|
||||
}
|
||||
property var wifiPasswordDialogRef: wifiPasswordDialog
|
||||
property var networkInfoDialogRef: networkInfoDialog
|
||||
|
||||
// Properly sorted WiFi networks with connected networks first
|
||||
property var sortedWifiNetworks: {
|
||||
@@ -92,7 +84,6 @@ Item {
|
||||
height: parent.height
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
// WiFi Content in Flickable
|
||||
Flickable {
|
||||
width: parent.width
|
||||
@@ -106,165 +97,13 @@ Item {
|
||||
|
||||
Column {
|
||||
id: wifiContent
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// Current WiFi connection status card
|
||||
Rectangle {
|
||||
id: wifiCard
|
||||
width: parent.width
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (wifiPreferenceArea.containsMouse && NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "wifi")
|
||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8);
|
||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5);
|
||||
}
|
||||
border.color: NetworkService.networkStatus === "wifi" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: NetworkService.networkStatus === "wifi" ? 2 : 1
|
||||
visible: NetworkService.wifiAvailable
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: wifiToggle.left
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (!NetworkService.wifiEnabled) {
|
||||
return "wifi_off";
|
||||
} else if (WifiService.currentWifiSSID !== "") {
|
||||
return getWiFiSignalIcon(WifiService.wifiSignalStrength);
|
||||
} else {
|
||||
return "wifi";
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (!NetworkService.wifiEnabled) {
|
||||
return "WiFi is off";
|
||||
} else if (NetworkService.wifiEnabled && WifiService.currentWifiSSID) {
|
||||
return WifiService.currentWifiSSID || "Connected";
|
||||
} else {
|
||||
return "Not Connected";
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (!NetworkService.wifiEnabled) {
|
||||
return "Turn on WiFi to see networks";
|
||||
} else if (NetworkService.wifiEnabled && WifiService.currentWifiSSID) {
|
||||
return NetworkService.wifiIP || "Connected";
|
||||
} else {
|
||||
return "Select a network below";
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
leftPadding: Theme.iconSize + Theme.spacingM
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Loading spinner for preference changes
|
||||
DankIcon {
|
||||
id: wifiLoadingSpinner
|
||||
name: "refresh"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.right: wifiToggle.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.changingPreference && NetworkService.targetPreference === "wifi"
|
||||
z: 10
|
||||
|
||||
RotationAnimation {
|
||||
target: wifiLoadingSpinner
|
||||
property: "rotation"
|
||||
running: wifiLoadingSpinner.visible
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
}
|
||||
|
||||
// WiFi toggle switch
|
||||
DankToggle {
|
||||
id: wifiToggle
|
||||
checked: NetworkService.wifiEnabled
|
||||
enabled: true
|
||||
toggling: NetworkService.wifiToggling
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
if (NetworkService.wifiEnabled) {
|
||||
// When turning WiFi off, clear all cached WiFi data
|
||||
WifiService.currentWifiSSID = "";
|
||||
WifiService.wifiSignalStrength = "excellent";
|
||||
WifiService.wifiNetworks = [];
|
||||
WifiService.savedWifiNetworks = [];
|
||||
WifiService.connectionStatus = "";
|
||||
WifiService.connectingSSID = "";
|
||||
WifiService.isScanning = false;
|
||||
NetworkService.refreshNetworkStatus();
|
||||
}
|
||||
NetworkService.toggleWifiRadio();
|
||||
refreshTimer.triggered = true;
|
||||
}
|
||||
}
|
||||
|
||||
// MouseArea for network preference (excluding toggle area)
|
||||
MouseArea {
|
||||
id: wifiPreferenceArea
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 60 // Exclude toggle area
|
||||
hoverEnabled: true
|
||||
cursorShape: (NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "wifi") ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "wifi" && !NetworkService.changingNetworkPreference
|
||||
onClicked: {
|
||||
if (NetworkService.ethernetConnected && NetworkService.wifiEnabled) {
|
||||
console.log("WiFi card clicked for preference");
|
||||
if (NetworkService.networkStatus !== "wifi")
|
||||
NetworkService.setNetworkPreference("wifi");
|
||||
else
|
||||
NetworkService.setNetworkPreference("auto");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
WiFiCard {
|
||||
refreshTimer: refreshTimer
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
@@ -279,8 +118,6 @@ Item {
|
||||
height: parent.height
|
||||
spacing: Theme.spacingS
|
||||
|
||||
// Ethernet Header removed
|
||||
|
||||
// Ethernet Content in Flickable
|
||||
Flickable {
|
||||
width: parent.width
|
||||
@@ -294,124 +131,12 @@ Item {
|
||||
|
||||
Column {
|
||||
id: ethernetContent
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// Ethernet connection status card (matching WiFi height)
|
||||
Rectangle {
|
||||
id: ethernetCard
|
||||
width: parent.width
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (ethernetPreferenceArea.containsMouse && NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "ethernet")
|
||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8);
|
||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5);
|
||||
}
|
||||
border.color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: NetworkService.networkStatus === "ethernet" ? 2 : 1
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: ethernetToggle.left
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "lan"
|
||||
size: Theme.iconSize
|
||||
color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Ethernet"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: NetworkService.ethernetConnected ? (NetworkService.ethernetIP || "Connected") : "Disconnected"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
leftPadding: Theme.iconSize + Theme.spacingM
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
// Loading spinner for preference changes
|
||||
DankIcon {
|
||||
id: ethernetLoadingSpinner
|
||||
name: "refresh"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.right: ethernetToggle.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.changingPreference && NetworkService.targetPreference === "ethernet"
|
||||
z: 10
|
||||
|
||||
RotationAnimation {
|
||||
target: ethernetLoadingSpinner
|
||||
property: "rotation"
|
||||
running: ethernetLoadingSpinner.visible
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
}
|
||||
|
||||
// Ethernet toggle switch (matching WiFi style)
|
||||
DankToggle {
|
||||
id: ethernetToggle
|
||||
checked: NetworkService.ethernetConnected
|
||||
enabled: true
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
NetworkService.toggleNetworkConnection("ethernet");
|
||||
}
|
||||
}
|
||||
|
||||
// MouseArea for network preference (excluding toggle area)
|
||||
MouseArea {
|
||||
id: ethernetPreferenceArea
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 60 // Exclude toggle area
|
||||
hoverEnabled: true
|
||||
cursorShape: (NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "ethernet") ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "ethernet" && !NetworkService.changingNetworkPreference
|
||||
onClicked: {
|
||||
if (NetworkService.ethernetConnected && NetworkService.wifiEnabled) {
|
||||
console.log("Ethernet card clicked for preference");
|
||||
if (NetworkService.networkStatus !== "ethernet")
|
||||
NetworkService.setNetworkPreference("ethernet");
|
||||
else
|
||||
NetworkService.setNetworkPreference("auto");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
EthernetCard {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
@@ -459,253 +184,11 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// WiFi networks spanning across both columns when Ethernet preference button is hidden
|
||||
Column {
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 100
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
visible: NetworkService.wifiEnabled
|
||||
spacing: Theme.spacingS
|
||||
|
||||
// Available Networks Section with refresh button (spanning version)
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: "Available Networks"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - 170
|
||||
height: 1
|
||||
}
|
||||
|
||||
// WiFi refresh button (spanning version)
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: refreshAreaSpan.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : WifiService.isScanning ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"
|
||||
|
||||
DankIcon {
|
||||
id: refreshIconSpan
|
||||
anchors.centerIn: parent
|
||||
name: "refresh"
|
||||
size: Theme.iconSize - 6
|
||||
color: refreshAreaSpan.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
rotation: WifiService.isScanning ? refreshIconSpan.rotation : 0
|
||||
|
||||
RotationAnimation {
|
||||
target: refreshIconSpan
|
||||
property: "rotation"
|
||||
running: WifiService.isScanning
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
|
||||
Behavior on rotation {
|
||||
RotationAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: refreshAreaSpan
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!WifiService.isScanning) {
|
||||
// Immediate visual feedback
|
||||
refreshIconSpan.rotation += 30;
|
||||
WifiService.scanWifi();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scrollable networks container
|
||||
Flickable {
|
||||
width: parent.width
|
||||
height: parent.height - 40
|
||||
clip: true
|
||||
contentWidth: width
|
||||
contentHeight: spanningNetworksColumn.height
|
||||
boundsBehavior: Flickable.DragAndOvershootBounds
|
||||
flickDeceleration: 8000
|
||||
maximumFlickVelocity: 15000
|
||||
|
||||
Column {
|
||||
id: spanningNetworksColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: NetworkService.wifiAvailable && NetworkService.wifiEnabled ? sortedWifiNetworks : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 38
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: networkArea2.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
border.color: modelData.connected ? Theme.primary : "transparent"
|
||||
border.width: modelData.connected ? 1 : 0
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingXS
|
||||
anchors.rightMargin: Theme.spacingM // Extra right margin for scrollbar
|
||||
|
||||
// Signal strength icon
|
||||
DankIcon {
|
||||
id: signalIcon2
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: getWiFiSignalIcon(modelData.signalStrength)
|
||||
size: Theme.iconSize - 2
|
||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
// Network info
|
||||
Column {
|
||||
anchors.left: signalIcon2.right
|
||||
anchors.leftMargin: Theme.spacingXS
|
||||
anchors.right: rightIcons2.left
|
||||
anchors.rightMargin: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: modelData.ssid
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||
font.weight: modelData.connected ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: {
|
||||
if (modelData.connected)
|
||||
return "Connected";
|
||||
if (WifiService.connectionStatus === "connecting" && WifiService.connectingSSID === modelData.ssid)
|
||||
return "Connecting...";
|
||||
if (WifiService.connectionStatus === "invalid_password" && WifiService.connectingSSID === modelData.ssid)
|
||||
return "Invalid password";
|
||||
if (modelData.saved)
|
||||
return "Saved" + (modelData.secured ? " • Secured" : " • Open");
|
||||
return modelData.secured ? "Secured" : "Open";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: {
|
||||
if (WifiService.connectionStatus === "connecting" && WifiService.connectingSSID === modelData.ssid)
|
||||
return Theme.primary;
|
||||
if (WifiService.connectionStatus === "invalid_password" && WifiService.connectingSSID === modelData.ssid)
|
||||
return Theme.error;
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
|
||||
}
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
// Right side icons
|
||||
Row {
|
||||
id: rightIcons2
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
// Lock icon (if secured)
|
||||
DankIcon {
|
||||
name: "lock"
|
||||
size: Theme.iconSize - 8
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
visible: modelData.secured
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
// Context menu button
|
||||
Rectangle {
|
||||
id: wifiMenuButton
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
color: wifiMenuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
|
||||
DankIcon {
|
||||
name: "more_vert"
|
||||
size: Theme.iconSize - 8
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.6
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wifiMenuButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
wifiContextMenuWindow.networkData = modelData;
|
||||
let localPos = wifiMenuButtonArea.mapToItem(networkTab, wifiMenuButtonArea.width / 2, wifiMenuButtonArea.height);
|
||||
wifiContextMenuWindow.show(localPos.x, localPos.y);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: networkArea2
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 32 // Exclude menu button area
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData.connected)
|
||||
return;
|
||||
|
||||
if (modelData.saved) {
|
||||
// Saved network, connect directly
|
||||
WifiService.connectToWifi(modelData.ssid);
|
||||
} else if (modelData.secured) {
|
||||
// Secured network, need password - use root dialog
|
||||
wifiPasswordDialog.wifiPasswordSSID = modelData.ssid;
|
||||
wifiPasswordDialog.wifiPasswordInput = "";
|
||||
wifiPasswordDialog.wifiPasswordDialogVisible = true;
|
||||
} else {
|
||||
// Open network, connect directly
|
||||
WifiService.connectToWifi(modelData.ssid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
}
|
||||
// WiFi networks spanning across both columns when WiFi is enabled
|
||||
WiFiNetworksList {
|
||||
wifiContextMenuWindow: wifiContextMenuWindow
|
||||
sortedWifiNetworks: networkTab.sortedWifiNetworks
|
||||
wifiPasswordDialogRef: networkTab.wifiPasswordDialogRef
|
||||
}
|
||||
|
||||
// Timer for refreshing network status after WiFi toggle
|
||||
@@ -826,259 +309,11 @@ Item {
|
||||
}
|
||||
|
||||
// WiFi Context Menu Window
|
||||
Rectangle {
|
||||
WiFiContextMenu {
|
||||
id: wifiContextMenuWindow
|
||||
|
||||
property var networkData: null
|
||||
property bool menuVisible: false
|
||||
|
||||
function show(x, y) {
|
||||
const menuWidth = 160;
|
||||
const menuHeight = wifiMenuColumn.implicitHeight + Theme.spacingS * 2;
|
||||
let finalX = x - menuWidth / 2;
|
||||
let finalY = y;
|
||||
finalX = Math.max(0, Math.min(finalX, networkTab.width - menuWidth));
|
||||
finalY = Math.max(0, Math.min(finalY, networkTab.height - menuHeight));
|
||||
wifiContextMenuWindow.x = finalX;
|
||||
wifiContextMenuWindow.y = finalY;
|
||||
wifiContextMenuWindow.visible = true;
|
||||
wifiContextMenuWindow.menuVisible = true;
|
||||
}
|
||||
|
||||
function hide() {
|
||||
wifiContextMenuWindow.menuVisible = false;
|
||||
Qt.callLater(() => {
|
||||
wifiContextMenuWindow.visible = false;
|
||||
});
|
||||
}
|
||||
|
||||
visible: false
|
||||
width: 160
|
||||
height: wifiMenuColumn.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
z: 1000
|
||||
opacity: menuVisible ? 1 : 0
|
||||
scale: menuVisible ? 1 : 0.85
|
||||
|
||||
// Drop shadow
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 2
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -4
|
||||
radius: parent.radius
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
z: parent.z - 1
|
||||
}
|
||||
|
||||
Column {
|
||||
id: wifiMenuColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
// Connect/Disconnect option
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: connectWifiArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: wifiContextMenuWindow.networkData && wifiContextMenuWindow.networkData.connected ? "wifi_off" : "wifi"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: wifiContextMenuWindow.networkData && wifiContextMenuWindow.networkData.connected ? "Disconnect" : "Connect"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: connectWifiArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (wifiContextMenuWindow.networkData) {
|
||||
if (wifiContextMenuWindow.networkData.connected) {
|
||||
// Disconnect from current network
|
||||
WifiService.disconnectWifi();
|
||||
} else {
|
||||
// Connect to selected network
|
||||
if (wifiContextMenuWindow.networkData.saved) {
|
||||
WifiService.connectToWifi(wifiContextMenuWindow.networkData.ssid);
|
||||
} else if (wifiContextMenuWindow.networkData.secured) {
|
||||
// Show password dialog for secured networks
|
||||
wifiPasswordDialog.wifiPasswordSSID = wifiContextMenuWindow.networkData.ssid;
|
||||
wifiPasswordDialog.wifiPasswordInput = "";
|
||||
wifiPasswordDialog.wifiPasswordDialogVisible = true;
|
||||
} else {
|
||||
WifiService.connectToWifi(wifiContextMenuWindow.networkData.ssid);
|
||||
}
|
||||
}
|
||||
}
|
||||
wifiContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
// Forget Network option (only for saved networks)
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: forgetWifiArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
||||
visible: wifiContextMenuWindow.networkData && (wifiContextMenuWindow.networkData.saved || wifiContextMenuWindow.networkData.connected)
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "delete"
|
||||
size: Theme.iconSize - 2
|
||||
color: forgetWifiArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Forget Network"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: forgetWifiArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: forgetWifiArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (wifiContextMenuWindow.networkData) {
|
||||
WifiService.forgetWifiNetwork(wifiContextMenuWindow.networkData.ssid);
|
||||
}
|
||||
wifiContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network Info option
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: infoWifiArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "info"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Network Info"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: infoWifiArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (wifiContextMenuWindow.networkData) {
|
||||
networkInfoDialog.showNetworkInfo(wifiContextMenuWindow.networkData.ssid, wifiContextMenuWindow.networkData);
|
||||
}
|
||||
wifiContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
parentItem: networkTab
|
||||
wifiPasswordDialogRef: networkTab.wifiPasswordDialogRef
|
||||
networkInfoDialogRef: networkTab.networkInfoDialogRef
|
||||
}
|
||||
|
||||
// Background MouseArea to close the context menu
|
||||
|
||||
Reference in New Issue
Block a user