mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-03 20:32:07 -04:00
switch hto monorepo structure
This commit is contained in:
203
quickshell/Modules/ControlCenter/Details/AudioInputDetail.qml
Normal file
203
quickshell/Modules/ControlCenter/Details/AudioInputDetail.qml
Normal file
@@ -0,0 +1,203 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
property bool hasInputVolumeSliderInCC: {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
return widgets.some(widget => widget.id === "inputVolumeSlider")
|
||||
}
|
||||
|
||||
implicitHeight: headerRow.height + (hasInputVolumeSliderInCC ? 0 : volumeSlider.height) + audioContent.height + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingS
|
||||
height: 40
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
text: I18n.tr("Input Devices")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: volumeSlider
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
height: 35
|
||||
spacing: 0
|
||||
visible: !hasInputVolumeSliderInCC
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
height: Theme.iconSize + Theme.spacingS * 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
|
||||
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
MouseArea {
|
||||
id: iconArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = !AudioService.source.audio.muted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!AudioService.source || !AudioService.source.audio) return "mic_off"
|
||||
let muted = AudioService.source.audio.muted
|
||||
return muted ? "mic_off" : "mic"
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: AudioService.source && AudioService.source.audio && !AudioService.source.audio.muted && AudioService.source.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
readonly property real actualVolumePercent: AudioService.source && AudioService.source.audio ? Math.round(AudioService.source.audio.volume * 100) : 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: AudioService.source && AudioService.source.audio
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
value: AudioService.source && AudioService.source.audio ? Math.min(100, Math.round(AudioService.source.audio.volume * 100)) : 0
|
||||
showValue: true
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceVariant
|
||||
|
||||
onSliderValueChanged: function(newValue) {
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.volume = newValue / 100
|
||||
if (newValue > 0 && AudioService.source.audio.muted) {
|
||||
AudioService.source.audio.muted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
id: audioContent
|
||||
anchors.top: hasInputVolumeSliderInCC ? headerRow.bottom : volumeSlider.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: hasInputVolumeSliderInCC ? Theme.spacingM : Theme.spacingS
|
||||
contentHeight: audioColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: audioColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: Pipewire.nodes.values.filter(node => {
|
||||
return node.audio && !node.isSink && !node.isStream
|
||||
})
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
border.color: modelData === AudioService.source ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (modelData.name.includes("bluez"))
|
||||
return "headset"
|
||||
else if (modelData.name.includes("usb"))
|
||||
return "headset"
|
||||
else
|
||||
return "mic"
|
||||
}
|
||||
size: Theme.iconSize - 4
|
||||
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - Theme.iconSize - Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: AudioService.displayName(modelData)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData === AudioService.source ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData === AudioService.source ? "Active" : "Available"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deviceMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData) {
|
||||
Pipewire.preferredDefaultAudioSource = modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
210
quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml
Normal file
210
quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml
Normal file
@@ -0,0 +1,210 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
property bool hasVolumeSliderInCC: {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
return widgets.some(widget => widget.id === "volumeSlider")
|
||||
}
|
||||
|
||||
implicitHeight: headerRow.height + (!hasVolumeSliderInCC ? volumeSlider.height : 0) + audioContent.height + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingS
|
||||
height: 40
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
text: I18n.tr("Audio Devices")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: volumeSlider
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
height: 35
|
||||
spacing: 0
|
||||
visible: !hasVolumeSliderInCC
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
height: Theme.iconSize + Theme.spacingS * 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
|
||||
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
MouseArea {
|
||||
id: iconArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = !AudioService.sink.audio.muted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!AudioService.sink || !AudioService.sink.audio) return "volume_off"
|
||||
let muted = AudioService.sink.audio.muted
|
||||
let volume = AudioService.sink.audio.volume
|
||||
if (muted || volume === 0.0) return "volume_off"
|
||||
if (volume <= 0.33) return "volume_down"
|
||||
if (volume <= 0.66) return "volume_up"
|
||||
return "volume_up"
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: AudioService.sink && AudioService.sink.audio && !AudioService.sink.audio.muted && AudioService.sink.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
readonly property real actualVolumePercent: AudioService.sink && AudioService.sink.audio ? Math.round(AudioService.sink.audio.volume * 100) : 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: AudioService.sink && AudioService.sink.audio
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
value: AudioService.sink && AudioService.sink.audio ? Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) : 0
|
||||
showValue: true
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceVariant
|
||||
|
||||
onSliderValueChanged: function(newValue) {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.volume = newValue / 100
|
||||
if (newValue > 0 && AudioService.sink.audio.muted) {
|
||||
AudioService.sink.audio.muted = false
|
||||
}
|
||||
AudioService.volumeChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
id: audioContent
|
||||
anchors.top: volumeSlider.visible ? volumeSlider.bottom : headerRow.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: volumeSlider.visible ? Theme.spacingS : Theme.spacingM
|
||||
contentHeight: audioColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: audioColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: Pipewire.nodes.values.filter(node => {
|
||||
return node.audio && node.isSink && !node.isStream
|
||||
})
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
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 - 4
|
||||
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - Theme.iconSize - Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: AudioService.displayName(modelData)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData === AudioService.sink ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData === AudioService.sink ? "Active" : "Available"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deviceMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData) {
|
||||
Pipewire.preferredDefaultAudioSink = modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
258
quickshell/Modules/ControlCenter/Details/BatteryDetail.qml
Normal file
258
quickshell/Modules/ControlCenter/Details/BatteryDetail.qml
Normal file
@@ -0,0 +1,258 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
function isActiveProfile(profile) {
|
||||
if (typeof PowerProfiles === "undefined") {
|
||||
return false
|
||||
}
|
||||
return PowerProfiles.profile === profile
|
||||
}
|
||||
|
||||
function setProfile(profile) {
|
||||
if (typeof PowerProfiles === "undefined") {
|
||||
ToastService.showError("power-profiles-daemon not available")
|
||||
return
|
||||
}
|
||||
PowerProfiles.profile = profile
|
||||
if (PowerProfiles.profile !== profile) {
|
||||
ToastService.showError("Failed to set power profile")
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
width: parent.width - Theme.spacingL * 2
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
width: parent.width
|
||||
height: 48
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: BatteryService.getBatteryIcon()
|
||||
size: Theme.iconSizeLarge
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||
return Theme.error
|
||||
if (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||
return Theme.primary
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - Theme.iconSizeLarge - Theme.spacingM
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : "Power"
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error
|
||||
}
|
||||
if (BatteryService.isCharging) {
|
||||
return Theme.primary
|
||||
}
|
||||
return Theme.surfaceText
|
||||
}
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryAvailable ? BatteryService.batteryStatus : "Management"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error
|
||||
}
|
||||
if (BatteryService.isCharging) {
|
||||
return Theme.primary
|
||||
}
|
||||
return Theme.surfaceText
|
||||
}
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!BatteryService.batteryAvailable) return "Power profile management available"
|
||||
const time = BatteryService.formatTimeRemaining()
|
||||
if (time !== "Unknown") {
|
||||
return BatteryService.isCharging ? `Time until full: ${time}` : `Time remaining: ${time}`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
visible: text.length > 0
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: BatteryService.batteryAvailable
|
||||
|
||||
StyledRect {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 64
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Health")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryHealth
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: {
|
||||
if (BatteryService.batteryHealth === "N/A") {
|
||||
return Theme.surfaceText
|
||||
}
|
||||
const healthNum = parseInt(BatteryService.batteryHealth)
|
||||
return healthNum < 80 ? Theme.error : Theme.surfaceText
|
||||
}
|
||||
font.weight: Font.Bold
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 64
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Capacity")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryCapacity > 0 ? `${BatteryService.batteryCapacity.toFixed(1)} Wh` : "Unknown"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Bold
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankButtonGroup {
|
||||
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
||||
property int currentProfileIndex: {
|
||||
if (typeof PowerProfiles === "undefined") return 1
|
||||
return profileModel.findIndex(profile => isActiveProfile(profile))
|
||||
}
|
||||
|
||||
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
|
||||
currentIndex: currentProfileIndex
|
||||
selectionMode: "single"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected) return
|
||||
setProfile(profileModel[index])
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: degradationContent.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||
border.color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3)
|
||||
border.width: 0
|
||||
visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None
|
||||
|
||||
Column {
|
||||
id: degradationContent
|
||||
width: parent.width - Theme.spacingL * 2
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "warning"
|
||||
size: Theme.iconSize
|
||||
color: Theme.error
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Power Profile Degradation")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.error
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: (typeof PowerProfiles !== "undefined") ? PerformanceDegradationReason.toString(PowerProfiles.degradationReason) : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.8)
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var device: null
|
||||
property bool modalVisible: false
|
||||
property var parentItem
|
||||
property var availableCodecs: []
|
||||
property string currentCodec: ""
|
||||
property bool isLoading: false
|
||||
|
||||
signal codecSelected(string deviceAddress, string codecName)
|
||||
|
||||
function show(bluetoothDevice) {
|
||||
device = bluetoothDevice;
|
||||
isLoading = true;
|
||||
availableCodecs = [];
|
||||
currentCodec = "";
|
||||
visible = true;
|
||||
modalVisible = true;
|
||||
queryCodecs();
|
||||
Qt.callLater(() => {
|
||||
focusScope.forceActiveFocus();
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
modalVisible = false;
|
||||
Qt.callLater(() => {
|
||||
visible = false;
|
||||
});
|
||||
}
|
||||
|
||||
function queryCodecs() {
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
BluetoothService.getAvailableCodecs(device, function(codecs, current) {
|
||||
availableCodecs = codecs;
|
||||
currentCodec = current;
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
function selectCodec(profileName) {
|
||||
if (!device || isLoading)
|
||||
return;
|
||||
|
||||
let selectedCodec = availableCodecs.find(c => c.profile === profileName);
|
||||
if (selectedCodec && device) {
|
||||
BluetoothService.updateDeviceCodec(device.address, selectedCodec.name);
|
||||
codecSelected(device.address, selectedCodec.name);
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
BluetoothService.switchCodec(device, profileName, function(success, message) {
|
||||
isLoading = false;
|
||||
if (success) {
|
||||
ToastService.showToast(message, ToastService.levelInfo);
|
||||
Qt.callLater(root.hide);
|
||||
} else {
|
||||
ToastService.showToast(message, ToastService.levelError);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
z: 2000
|
||||
|
||||
MouseArea {
|
||||
id: modalBlocker
|
||||
anchors.fill: parent
|
||||
visible: modalVisible
|
||||
enabled: modalVisible
|
||||
hoverEnabled: true
|
||||
preventStealing: true
|
||||
propagateComposedEvents: false
|
||||
|
||||
onClicked: root.hide()
|
||||
onWheel: (wheel) => { wheel.accepted = true }
|
||||
onPositionChanged: (mouse) => { mouse.accepted = true }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: modalBackground
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.5)
|
||||
opacity: modalVisible ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: focusScope
|
||||
|
||||
anchors.fill: parent
|
||||
focus: root.visible
|
||||
enabled: root.visible
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
root.hide()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: modalContent
|
||||
anchors.centerIn: parent
|
||||
width: 320
|
||||
height: contentColumn.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
opacity: modalVisible ? 1 : 0
|
||||
scale: modalVisible ? 1 : 0.9
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
preventStealing: true
|
||||
propagateComposedEvents: false
|
||||
onClicked: (mouse) => { mouse.accepted = true }
|
||||
onWheel: (wheel) => { wheel.accepted = true }
|
||||
onPositionChanged: (mouse) => { mouse.accepted = true }
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: device ? BluetoothService.getDeviceIcon(device) : "headset"
|
||||
size: Theme.iconSize + 4
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: device ? (device.name || device.deviceName) : ""
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Audio Codec Selection")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: isLoading ? "Loading codecs..." : `Current: ${currentCodec}`
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: isLoading ? Theme.primary : Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
visible: !isLoading
|
||||
|
||||
Repeater {
|
||||
model: availableCodecs
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (modelData.name === currentCodec)
|
||||
return Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency);
|
||||
else if (codecMouseArea.containsMouse)
|
||||
return Theme.surfaceHover;
|
||||
else
|
||||
return "transparent";
|
||||
}
|
||||
border.color: "transparent"
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 6
|
||||
radius: 3
|
||||
color: modelData.qualityColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: modelData.name
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: modelData.name === currentCodec ? Theme.primary : Theme.surfaceText
|
||||
font.weight: modelData.name === currentCodec ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.description
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "check"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: modelData.name === currentCodec
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: codecMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: modelData.name !== currentCodec && !isLoading
|
||||
onClicked: {
|
||||
selectCodec(modelData.profile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
570
quickshell/Modules/ControlCenter/Details/BluetoothDetail.qml
Normal file
570
quickshell/Modules/ControlCenter/Details/BluetoothDetail.qml
Normal file
@@ -0,0 +1,570 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modals
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
implicitHeight: BluetoothService.adapter && BluetoothService.adapter.enabled ? headerRow.height + bluetoothContent.height + Theme.spacingM : headerRow.height
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
property var bluetoothCodecModalRef: null
|
||||
property var devicesBeingPaired: new Set()
|
||||
|
||||
signal showCodecSelector(var device)
|
||||
|
||||
function isDeviceBeingPaired(deviceAddress) {
|
||||
return devicesBeingPaired.has(deviceAddress)
|
||||
}
|
||||
|
||||
function handlePairDevice(device) {
|
||||
if (!device) return
|
||||
|
||||
const deviceAddr = device.address
|
||||
devicesBeingPaired.add(deviceAddr)
|
||||
devicesBeingPairedChanged()
|
||||
|
||||
BluetoothService.pairDevice(device, function(response) {
|
||||
devicesBeingPaired.delete(deviceAddr)
|
||||
devicesBeingPairedChanged()
|
||||
|
||||
if (response.error) {
|
||||
ToastService.showError(I18n.tr("Pairing failed"), response.error)
|
||||
} else if (!BluetoothService.enhancedPairingAvailable) {
|
||||
ToastService.showSuccess(I18n.tr("Device paired"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function updateDeviceCodecDisplay(deviceAddress, codecName) {
|
||||
for (let i = 0; i < pairedRepeater.count; i++) {
|
||||
let item = pairedRepeater.itemAt(i)
|
||||
if (item && item.modelData && item.modelData.address === deviceAddress) {
|
||||
item.currentCodec = codecName
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingS
|
||||
height: 40
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
text: I18n.tr("Bluetooth Settings")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Math.max(0, parent.width - headerText.implicitWidth - scanButton.width - Theme.spacingM)
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: scanButton
|
||||
width: 100
|
||||
height: 36
|
||||
radius: 18
|
||||
color: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled)
|
||||
return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
return scanMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"
|
||||
}
|
||||
border.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 0
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "bluetooth_searching"
|
||||
size: 18
|
||||
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Scanning" : "Scan"
|
||||
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: scanMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
if (BluetoothService.adapter)
|
||||
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
id: bluetoothContent
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
contentHeight: bluetoothColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: bluetoothColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
Repeater {
|
||||
id: pairedRepeater
|
||||
model: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
|
||||
return []
|
||||
|
||||
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
|
||||
devices.sort((a, b) => {
|
||||
if (a.connected && !b.connected) return -1
|
||||
if (!a.connected && b.connected) return 1
|
||||
return (b.signalStrength || 0) - (a.signalStrength || 0)
|
||||
})
|
||||
return devices
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
property string currentCodec: BluetoothService.deviceCodecs[modelData.address] || ""
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
Component.onCompleted: {
|
||||
if (modelData.connected && BluetoothService.isAudioDevice(modelData)) {
|
||||
BluetoothService.refreshDeviceCodec(modelData)
|
||||
}
|
||||
}
|
||||
color: {
|
||||
if (modelData.state === BluetoothDeviceState.Connecting)
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
|
||||
if (deviceMouseArea.containsMouse)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
return Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
}
|
||||
border.color: {
|
||||
if (modelData.state === BluetoothDeviceState.Connecting)
|
||||
return Theme.warning
|
||||
if (modelData.connected)
|
||||
return Theme.primary
|
||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.getDeviceIcon(modelData)
|
||||
size: Theme.iconSize - 4
|
||||
color: {
|
||||
if (modelData.state === BluetoothDeviceState.Connecting)
|
||||
return Theme.warning
|
||||
if (modelData.connected)
|
||||
return Theme.primary
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 200
|
||||
|
||||
StyledText {
|
||||
text: modelData.name || modelData.deviceName || "Unknown Device"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData.connected ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (modelData.state === BluetoothDeviceState.Connecting)
|
||||
return "Connecting..."
|
||||
if (modelData.connected) {
|
||||
let status = "Connected"
|
||||
if (currentCodec) {
|
||||
status += " • " + currentCodec
|
||||
}
|
||||
return status
|
||||
}
|
||||
return "Paired"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
if (modelData.state === BluetoothDeviceState.Connecting)
|
||||
return Theme.warning
|
||||
return Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
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: Theme.surfaceVariantText
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.signalStrength !== undefined && modelData.signalStrength > 0 ? "• " + modelData.signalStrength + "%" : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
visible: text.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: pairedOptionsButton
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "more_horiz"
|
||||
buttonSize: 28
|
||||
onClicked: {
|
||||
if (bluetoothContextMenu.visible) {
|
||||
bluetoothContextMenu.close()
|
||||
} else {
|
||||
bluetoothContextMenu.currentDevice = modelData
|
||||
bluetoothContextMenu.popup(pairedOptionsButton, -bluetoothContextMenu.width + pairedOptionsButton.width, pairedOptionsButton.height + Theme.spacingXS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deviceMouseArea
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: pairedOptionsButton.width + Theme.spacingS
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
modelData.disconnect()
|
||||
} else {
|
||||
BluetoothService.connectDeviceWithTrust(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
visible: pairedRepeater.count > 0 && availableRepeater.count > 0
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 80
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.discovering && availableRepeater.count === 0
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "sync"
|
||||
size: 24
|
||||
color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.4)
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: parent.visible && BluetoothService.adapter && BluetoothService.adapter.discovering && availableRepeater.count === 0
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1500
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: availableRepeater
|
||||
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)
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
property bool canConnect: BluetoothService.canConnect(modelData)
|
||||
property bool isBusy: BluetoothService.isDeviceBusy(modelData) || isDeviceBeingPaired(modelData.address)
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: availableMouseArea.containsMouse && !isBusy ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 0
|
||||
opacity: (canConnect && !isBusy) ? 1 : 0.6
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.getDeviceIcon(modelData)
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 200
|
||||
|
||||
StyledText {
|
||||
text: modelData.name || modelData.deviceName || "Unknown Device"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (modelData.pairing || isBusy) return "Pairing..."
|
||||
if (modelData.blocked) return "Blocked"
|
||||
return BluetoothService.getSignalStrength(modelData)
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.signalStrength !== undefined && modelData.signalStrength > 0 ? "• " + modelData.signalStrength + "%" : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
visible: text.length > 0 && !modelData.pairing && !modelData.blocked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
if (isBusy) return "Pairing..."
|
||||
if (!canConnect) return "Cannot pair"
|
||||
return "Pair"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: (canConnect && !isBusy) ? Theme.primary : Theme.surfaceVariantText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: availableMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: canConnect && !isBusy
|
||||
onClicked: {
|
||||
root.handlePairDevice(modelData)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 60
|
||||
visible: !BluetoothService.adapter
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("No Bluetooth adapter found")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: bluetoothContextMenu
|
||||
width: 150
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
|
||||
property var currentDevice: null
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 0
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: bluetoothContextMenu.currentDevice && bluetoothContextMenu.currentDevice.connected ? "Disconnect" : "Connect"
|
||||
height: 32
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
if (bluetoothContextMenu.currentDevice) {
|
||||
if (bluetoothContextMenu.currentDevice.connected) {
|
||||
bluetoothContextMenu.currentDevice.disconnect()
|
||||
} else {
|
||||
BluetoothService.connectDeviceWithTrust(bluetoothContextMenu.currentDevice)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: I18n.tr("Audio Codec")
|
||||
height: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected ? 32 : 0
|
||||
visible: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
if (bluetoothContextMenu.currentDevice) {
|
||||
showCodecSelector(bluetoothContextMenu.currentDevice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: I18n.tr("Forget Device")
|
||||
height: 32
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.error
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
if (bluetoothContextMenu.currentDevice) {
|
||||
if (BluetoothService.enhancedPairingAvailable) {
|
||||
const devicePath = BluetoothService.getDevicePath(bluetoothContextMenu.currentDevice)
|
||||
DMSService.bluetoothRemove(devicePath, response => {
|
||||
if (response.error) {
|
||||
ToastService.showError(I18n.tr("Failed to remove device"), response.error)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
bluetoothContextMenu.currentDevice.forget()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothPairingModal {
|
||||
id: bluetoothPairingModal
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DMSService
|
||||
|
||||
function onBluetoothPairingRequest(data) {
|
||||
bluetoothPairingModal.show(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
460
quickshell/Modules/ControlCenter/Details/BrightnessDetail.qml
Normal file
460
quickshell/Modules/ControlCenter/Details/BrightnessDetail.qml
Normal file
@@ -0,0 +1,460 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string initialDeviceName: ""
|
||||
property string instanceId: ""
|
||||
property string screenName: ""
|
||||
|
||||
signal deviceNameChanged(string newDeviceName)
|
||||
|
||||
property string currentDeviceName: ""
|
||||
|
||||
function resolveDeviceName() {
|
||||
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if (screenName && screenName.length > 0) {
|
||||
const pins = SettingsData.brightnessDevicePins || {}
|
||||
const pinnedDevice = pins[screenName]
|
||||
if (pinnedDevice && pinnedDevice.length > 0) {
|
||||
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice)
|
||||
if (found) {
|
||||
return found.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (initialDeviceName && initialDeviceName.length > 0) {
|
||||
const found = DisplayService.devices.find(dev => dev.name === initialDeviceName)
|
||||
if (found) {
|
||||
return found.name
|
||||
}
|
||||
}
|
||||
|
||||
const currentDeviceNameFromService = DisplayService.currentDevice
|
||||
if (currentDeviceNameFromService) {
|
||||
const found = DisplayService.devices.find(dev => dev.name === currentDeviceNameFromService)
|
||||
if (found) {
|
||||
return found.name
|
||||
}
|
||||
}
|
||||
|
||||
const backlight = DisplayService.devices.find(d => d.class === "backlight")
|
||||
if (backlight) {
|
||||
return backlight.name
|
||||
}
|
||||
|
||||
const ddc = DisplayService.devices.find(d => d.class === "ddc")
|
||||
if (ddc) {
|
||||
return ddc.name
|
||||
}
|
||||
|
||||
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : ""
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
currentDeviceName = resolveDeviceName()
|
||||
}
|
||||
|
||||
property bool isPinnedToScreen: {
|
||||
if (!screenName || screenName.length === 0) {
|
||||
return false
|
||||
}
|
||||
const pins = SettingsData.brightnessDevicePins || {}
|
||||
return pins[screenName] === currentDeviceName
|
||||
}
|
||||
|
||||
function togglePinToScreen() {
|
||||
if (!screenName || screenName.length === 0 || !currentDeviceName || currentDeviceName.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}))
|
||||
|
||||
if (isPinnedToScreen) {
|
||||
delete pins[screenName]
|
||||
} else {
|
||||
pins[screenName] = currentDeviceName
|
||||
}
|
||||
|
||||
SettingsData.set("brightnessDevicePins", pins)
|
||||
}
|
||||
|
||||
implicitHeight: brightnessContent.height + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
DankFlickable {
|
||||
id: brightnessContent
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
contentHeight: brightnessColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: brightnessColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 100
|
||||
visible: !DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: DisplayService.brightnessAvailable ? "brightness_6" : "error"
|
||||
size: 32
|
||||
color: DisplayService.brightnessAvailable ? Theme.primary : Theme.error
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: DisplayService.brightnessAvailable ? "No brightness devices available" : "Brightness control not available"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 40
|
||||
visible: screenName && screenName.length > 0 && DisplayService.devices && DisplayService.devices.length > 1
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "monitor"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: screenName || "Unknown Monitor"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: pinRow.width + Theme.spacingS * 2
|
||||
height: 28
|
||||
radius: height / 2
|
||||
color: isPinnedToScreen ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05)
|
||||
|
||||
Row {
|
||||
id: pinRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
|
||||
DankIcon {
|
||||
name: isPinnedToScreen ? "push_pin" : "push_pin"
|
||||
size: 16
|
||||
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: isPinnedToScreen ? "Pinned" : "Pin"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.togglePinToScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: DisplayService.devices || []
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
property real deviceBrightness: {
|
||||
DisplayService.brightnessVersion
|
||||
return DisplayService.getDeviceBrightness(modelData.name)
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
height: 100
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
border.color: modelData.name === currentDeviceName ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: modelData.name === currentDeviceName ? 2 : 0
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Math.max(deviceIconColumn.height, deviceInfoColumn.height, exponentControls.height)
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
id: deviceIconColumn
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
const deviceClass = modelData.class || ""
|
||||
const deviceName = modelData.name || ""
|
||||
|
||||
if (deviceClass === "backlight" || deviceClass === "ddc") {
|
||||
if (deviceBrightness <= 33)
|
||||
return "brightness_low"
|
||||
if (deviceBrightness <= 66)
|
||||
return "brightness_medium"
|
||||
return "brightness_high"
|
||||
} else if (deviceName.includes("kbd")) {
|
||||
return "keyboard"
|
||||
} else {
|
||||
return "lightbulb"
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: modelData.name === currentDeviceName ? Theme.primary : Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: Math.round(deviceBrightness) + "%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: deviceInfoColumn
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.parent.width - deviceIconColumn.width - exponentControls.width - Theme.spacingM * 3
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const name = modelData.name || ""
|
||||
const deviceClass = modelData.class || ""
|
||||
if (deviceClass === "backlight") {
|
||||
return name.replace("_", " ").replace(/\b\w/g, c => c.toUpperCase())
|
||||
}
|
||||
return name
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData.name === currentDeviceName ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.name
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const deviceClass = modelData.class || ""
|
||||
if (deviceClass === "backlight")
|
||||
return "Backlight device"
|
||||
if (deviceClass === "ddc")
|
||||
return "DDC/CI monitor"
|
||||
if (deviceClass === "leds")
|
||||
return "LED device"
|
||||
return deviceClass
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: exponentControls
|
||||
width: 140
|
||||
height: 28
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
visible: SessionData.getBrightnessExponential(modelData.name)
|
||||
z: 1
|
||||
|
||||
StyledRect {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
opacity: SessionData.getBrightnessExponent(modelData.name) > 1.0 ? 1.0 : 0.4
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "remove"
|
||||
size: 14
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
stateColor: Theme.primary
|
||||
cornerRadius: parent.radius
|
||||
enabled: SessionData.getBrightnessExponent(modelData.name) > 1.0
|
||||
onClicked: {
|
||||
const current = SessionData.getBrightnessExponent(modelData.name)
|
||||
const newValue = Math.max(1.0, Math.round((current - 0.1) * 10) / 10)
|
||||
SessionData.setBrightnessExponent(modelData.name, newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: 50
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
border.width: 0
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: SessionData.getBrightnessExponent(modelData.name).toFixed(1)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
opacity: SessionData.getBrightnessExponent(modelData.name) < 2.5 ? 1.0 : 0.4
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "add"
|
||||
size: 14
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
stateColor: Theme.primary
|
||||
cornerRadius: parent.radius
|
||||
enabled: SessionData.getBrightnessExponent(modelData.name) < 2.5
|
||||
onClicked: {
|
||||
const current = SessionData.getBrightnessExponent(modelData.name)
|
||||
const newValue = Math.min(2.5, Math.round((current + 0.1) * 10) / 10)
|
||||
SessionData.setBrightnessExponent(modelData.name, newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 24
|
||||
radius: height / 2
|
||||
color: SessionData.getBrightnessExponential(modelData.name) ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
|
||||
DankIcon {
|
||||
name: "show_chart"
|
||||
size: 14
|
||||
color: SessionData.getBrightnessExponential(modelData.name) ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: SessionData.getBrightnessExponential(modelData.name) ? "Exponential" : "Linear"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: SessionData.getBrightnessExponential(modelData.name) ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
const currentState = SessionData.getBrightnessExponential(modelData.name)
|
||||
SessionData.setBrightnessExponential(modelData.name, !currentState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: 28
|
||||
anchors.rightMargin: SessionData.getBrightnessExponential(modelData.name) ? 145 : 0
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (screenName && screenName.length > 0 && modelData.name !== currentDeviceName) {
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}))
|
||||
if (pins[screenName]) {
|
||||
delete pins[screenName]
|
||||
SettingsData.set("brightnessDevicePins", pins)
|
||||
}
|
||||
}
|
||||
currentDeviceName = modelData.name
|
||||
deviceNameChanged(modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
166
quickshell/Modules/ControlCenter/Details/DiskUsageDetail.qml
Normal file
166
quickshell/Modules/ControlCenter/Details/DiskUsageDetail.qml
Normal file
@@ -0,0 +1,166 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string currentMountPath: "/"
|
||||
property string instanceId: ""
|
||||
|
||||
signal mountPathChanged(string newMountPath)
|
||||
|
||||
implicitHeight: diskContent.height + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["diskmounts"])
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["diskmounts"])
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
id: diskContent
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
contentHeight: diskColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: diskColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 100
|
||||
visible: !DgopService.dgopAvailable || !DgopService.diskMounts || DgopService.diskMounts.length === 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: DgopService.dgopAvailable ? "storage" : "error"
|
||||
size: 32
|
||||
color: DgopService.dgopAvailable ? Theme.primary : Theme.error
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: DgopService.dgopAvailable ? "No disk data available" : "dgop not available"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: DgopService.diskMounts || []
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
border.color: modelData.mount === currentMountPath ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: modelData.mount === currentMountPath ? 2 : 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
DankIcon {
|
||||
name: "storage"
|
||||
size: Theme.iconSize
|
||||
color: {
|
||||
const percentStr = modelData.percent?.replace("%", "") || "0"
|
||||
const percent = parseFloat(percentStr) || 0
|
||||
if (percent > 90) return Theme.error
|
||||
if (percent > 75) return Theme.warning
|
||||
return modelData.mount === currentMountPath ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const percentStr = modelData.percent?.replace("%", "") || "0"
|
||||
const percent = parseFloat(percentStr) || 0
|
||||
return percent.toFixed(0) + "%"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - 50 - Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: modelData.mount === "/" ? "Root Filesystem" : modelData.mount
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData.mount === currentMountPath ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.mount
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
visible: modelData.mount !== "/"
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `${modelData.used || "?"} / ${modelData.size || "?"}`
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
currentMountPath = modelData.mount
|
||||
mountPathChanged(modelData.mount)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
678
quickshell/Modules/ControlCenter/Details/NetworkDetail.qml
Normal file
678
quickshell/Modules/ControlCenter/Details/NetworkDetail.qml
Normal file
@@ -0,0 +1,678 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modals
|
||||
|
||||
Rectangle {
|
||||
implicitHeight: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return headerRow.height + wifiToggleContent.height + Theme.spacingM
|
||||
}
|
||||
if (NetworkService.wifiEnabled) {
|
||||
return headerRow.height + wifiContent.height + Theme.spacingM
|
||||
}
|
||||
return headerRow.height + wifiOffContent.height + Theme.spacingM
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
Component.onCompleted: {
|
||||
NetworkService.addRef()
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
NetworkService.removeRef()
|
||||
}
|
||||
|
||||
property int currentPreferenceIndex: {
|
||||
if (DMSService.apiVersion < 5) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (NetworkService.backend !== "networkmanager" || DMSService.apiVersion <= 10) {
|
||||
return 1
|
||||
}
|
||||
|
||||
const pref = NetworkService.userPreference
|
||||
const status = NetworkService.networkStatus
|
||||
let index = 1
|
||||
|
||||
if (pref === "ethernet") {
|
||||
index = 0
|
||||
} else if (pref === "wifi") {
|
||||
index = 1
|
||||
} else {
|
||||
index = status === "ethernet" ? 0 : 1
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingS
|
||||
height: 40
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
text: I18n.tr("Network Settings")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Math.max(0, parent.width - headerText.implicitWidth - preferenceControls.width - Theme.spacingM)
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
DankButtonGroup {
|
||||
id: preferenceControls
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
|
||||
|
||||
model: ["Ethernet", "WiFi"]
|
||||
currentIndex: currentPreferenceIndex
|
||||
selectionMode: "single"
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected) return
|
||||
console.log("NetworkDetail: Setting preference to", index === 0 ? "ethernet" : "wifi")
|
||||
NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: wifiToggleContent
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
visible: currentPreferenceIndex === 1 && NetworkService.wifiToggling
|
||||
height: visible ? 80 : 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: "sync"
|
||||
size: 32
|
||||
color: Theme.primary
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: NetworkService.wifiToggling
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: wifiOffContent
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
visible: currentPreferenceIndex === 1 && !NetworkService.wifiEnabled && !NetworkService.wifiToggling
|
||||
height: visible ? 120 : 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingL
|
||||
width: parent.width
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: "wifi_off"
|
||||
size: 48
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: I18n.tr("WiFi is off")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: 120
|
||||
height: 36
|
||||
radius: 18
|
||||
color: enableWifiButton.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.width: 0
|
||||
border.color: Theme.primary
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Enable WiFi")
|
||||
color: Theme.primary
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: enableWifiButton
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NetworkService.toggleWifiRadio()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
id: wiredContent
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
visible: currentPreferenceIndex === 0 && NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
|
||||
contentHeight: wiredColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: wiredColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const currentUuid = NetworkService.ethernetConnectionUuid
|
||||
const networks = NetworkService.wiredConnections
|
||||
let sorted = [...networks]
|
||||
sorted.sort((a, b) => {
|
||||
if (a.isActive && !b.isActive) return -1
|
||||
if (!a.isActive && b.isActive) return 1
|
||||
return a.id.localeCompare(b.id)
|
||||
})
|
||||
return sorted
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: wiredNetworkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
border.color: Theme.primary
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "lan"
|
||||
size: Theme.iconSize - 4
|
||||
color: modelData.isActive ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 200
|
||||
|
||||
StyledText {
|
||||
text: modelData.id || "Unknown Config"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: modelData.isActive ? Theme.primary : Theme.surfaceText
|
||||
font.weight: modelData.isActive ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: wiredOptionsButton
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "more_horiz"
|
||||
buttonSize: 28
|
||||
onClicked: {
|
||||
if (wiredNetworkContextMenu.visible) {
|
||||
wiredNetworkContextMenu.close()
|
||||
} else {
|
||||
wiredNetworkContextMenu.currentID = modelData.id
|
||||
wiredNetworkContextMenu.currentUUID = modelData.uuid
|
||||
wiredNetworkContextMenu.currentConnected = modelData.isActive
|
||||
wiredNetworkContextMenu.popup(wiredOptionsButton, -wiredNetworkContextMenu.width + wiredOptionsButton.width, wiredOptionsButton.height + Theme.spacingXS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wiredNetworkMouseArea
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: wiredOptionsButton.width + Theme.spacingS
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: function(event) {
|
||||
if (modelData.uuid !== NetworkService.ethernetConnectionUuid) {
|
||||
NetworkService.connectToSpecificWiredConfig(modelData.uuid)
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: wiredNetworkContextMenu
|
||||
width: 150
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
|
||||
property string currentID: ""
|
||||
property string currentUUID: ""
|
||||
property bool currentConnected: false
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 0
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Activate"
|
||||
height: !wiredNetworkContextMenu.currentConnected ? 32 : 0
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
if (!networkContextMenu.currentConnected) {
|
||||
NetworkService.connectToSpecificWiredConfig(wiredNetworkContextMenu.currentUUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: I18n.tr("Network Info")
|
||||
height: wiredNetworkContextMenu.currentConnected ? 32 : 0
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
let networkData = NetworkService.getWiredNetworkInfo(wiredNetworkContextMenu.currentUUID)
|
||||
networkWiredInfoModal.showNetworkInfo(wiredNetworkContextMenu.currentID, networkData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
id: wifiContent
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
visible: currentPreferenceIndex === 1 && NetworkService.wifiEnabled && !NetworkService.wifiToggling
|
||||
contentHeight: wifiColumn.height
|
||||
clip: true
|
||||
|
||||
property var frozenNetworks: []
|
||||
property bool menuOpen: false
|
||||
|
||||
Column {
|
||||
id: wifiColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 200
|
||||
visible: NetworkService.wifiInterface && NetworkService.wifiNetworks?.length < 1 && !NetworkService.wifiToggling && NetworkService.isScanning
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "refresh"
|
||||
size: 48
|
||||
color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.3)
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: NetworkService.isScanning
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const ssid = NetworkService.currentWifiSSID
|
||||
const networks = NetworkService.wifiNetworks
|
||||
let sorted = [...networks]
|
||||
sorted.sort((a, b) => {
|
||||
if (a.ssid === ssid) return -1
|
||||
if (b.ssid === ssid) return 1
|
||||
return b.signal - a.signal
|
||||
})
|
||||
if (!wifiContent.menuOpen) {
|
||||
wifiContent.frozenNetworks = sorted
|
||||
}
|
||||
return wifiContent.menuOpen ? wifiContent.frozenNetworks : sorted
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: networkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
border.color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
let strength = modelData.signal || 0
|
||||
if (strength >= 50) return "wifi"
|
||||
if (strength >= 25) return "wifi_2_bar"
|
||||
return "wifi_1_bar"
|
||||
}
|
||||
size: Theme.iconSize - 4
|
||||
color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 200
|
||||
|
||||
StyledText {
|
||||
text: modelData.ssid || "Unknown Network"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData.ssid === NetworkService.currentWifiSSID ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: modelData.ssid === NetworkService.currentWifiSSID ? "Connected •" : (modelData.secured ? "Secured •" : "Open •")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.saved ? "Saved" : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: (modelData.saved ? "• " : "") + modelData.signal + "%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: optionsButton
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "more_horiz"
|
||||
buttonSize: 28
|
||||
onClicked: {
|
||||
if (networkContextMenu.visible) {
|
||||
networkContextMenu.close()
|
||||
} else {
|
||||
wifiContent.menuOpen = true
|
||||
networkContextMenu.currentSSID = modelData.ssid
|
||||
networkContextMenu.currentSecured = modelData.secured
|
||||
networkContextMenu.currentConnected = modelData.ssid === NetworkService.currentWifiSSID
|
||||
networkContextMenu.currentSaved = modelData.saved
|
||||
networkContextMenu.currentSignal = modelData.signal
|
||||
networkContextMenu.currentAutoconnect = modelData.autoconnect || false
|
||||
networkContextMenu.popup(optionsButton, -networkContextMenu.width + optionsButton.width, optionsButton.height + Theme.spacingXS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: networkMouseArea
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: optionsButton.width + Theme.spacingS
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: function(event) {
|
||||
if (modelData.ssid !== NetworkService.currentWifiSSID) {
|
||||
if (modelData.secured && !modelData.saved) {
|
||||
if (DMSService.apiVersion >= 7) {
|
||||
NetworkService.connectToWifi(modelData.ssid)
|
||||
} else if (PopoutService.wifiPasswordModal) {
|
||||
PopoutService.wifiPasswordModal.show(modelData.ssid)
|
||||
}
|
||||
} else {
|
||||
NetworkService.connectToWifi(modelData.ssid)
|
||||
}
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: networkContextMenu
|
||||
width: 150
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
|
||||
property string currentSSID: ""
|
||||
property bool currentSecured: false
|
||||
property bool currentConnected: false
|
||||
property bool currentSaved: false
|
||||
property int currentSignal: 0
|
||||
property bool currentAutoconnect: false
|
||||
|
||||
onClosed: {
|
||||
wifiContent.menuOpen = false
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 0
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: networkContextMenu.currentConnected ? "Disconnect" : "Connect"
|
||||
height: 32
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
if (networkContextMenu.currentConnected) {
|
||||
NetworkService.disconnectWifi()
|
||||
} else {
|
||||
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
|
||||
if (DMSService.apiVersion >= 7) {
|
||||
NetworkService.connectToWifi(networkContextMenu.currentSSID)
|
||||
} else if (PopoutService.wifiPasswordModal) {
|
||||
PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID)
|
||||
}
|
||||
} else {
|
||||
NetworkService.connectToWifi(networkContextMenu.currentSSID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: I18n.tr("Network Info")
|
||||
height: 32
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID)
|
||||
networkInfoModal.showNetworkInfo(networkContextMenu.currentSSID, networkData)
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: networkContextMenu.currentAutoconnect ? I18n.tr("Disable Autoconnect") : I18n.tr("Enable Autoconnect")
|
||||
height: (networkContextMenu.currentSaved || networkContextMenu.currentConnected) && DMSService.apiVersion > 13 ? 32 : 0
|
||||
visible: (networkContextMenu.currentSaved || networkContextMenu.currentConnected) && DMSService.apiVersion > 13
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
NetworkService.setWifiAutoconnect(networkContextMenu.currentSSID, !networkContextMenu.currentAutoconnect)
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: I18n.tr("Forget Network")
|
||||
height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0
|
||||
visible: networkContextMenu.currentSaved || networkContextMenu.currentConnected
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.error
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
NetworkService.forgetWifiNetwork(networkContextMenu.currentSSID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetworkInfoModal {
|
||||
id: networkInfoModal
|
||||
}
|
||||
|
||||
NetworkWiredInfoModal {
|
||||
id: networkWiredInfoModal
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user