mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-28 07:22:50 -05:00
Merge branch 'master' of https://github.com/bbedward/dank-material-dark-shell
This commit is contained in:
@@ -7,6 +7,8 @@ Rectangle {
|
||||
|
||||
property bool batteryPopupVisible: false
|
||||
|
||||
signal toggleBatteryPopup()
|
||||
|
||||
width: 70 // Increased width to accommodate percentage text
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
@@ -63,7 +65,7 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
batteryPopupVisible = !batteryPopupVisible
|
||||
toggleBatteryPopup()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,14 +78,14 @@ Rectangle {
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - theme.spacingM * 2
|
||||
spacing: theme.spacingM
|
||||
anchors.fill: parent
|
||||
anchors.margins: theme.spacingS
|
||||
spacing: theme.spacingS
|
||||
|
||||
// Show different content based on whether we have active media
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 80
|
||||
height: 60
|
||||
|
||||
// Placeholder when no media
|
||||
Column {
|
||||
@@ -117,8 +117,8 @@ Rectangle {
|
||||
|
||||
// Album Art
|
||||
Rectangle {
|
||||
width: 80
|
||||
height: 80
|
||||
width: 60
|
||||
height: 60
|
||||
radius: theme.cornerRadius
|
||||
color: Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.3)
|
||||
|
||||
@@ -152,7 +152,7 @@ Rectangle {
|
||||
|
||||
// Track Info
|
||||
Column {
|
||||
width: parent.width - 80 - theme.spacingM
|
||||
width: parent.width - 60 - theme.spacingM
|
||||
spacing: theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -186,45 +186,52 @@ Rectangle {
|
||||
}
|
||||
|
||||
// Progress bar
|
||||
Rectangle {
|
||||
id: progressBarBackground
|
||||
Item {
|
||||
id: progressBarContainer
|
||||
width: parent.width
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.3)
|
||||
visible: activePlayer !== null
|
||||
height: 24
|
||||
|
||||
Rectangle {
|
||||
id: progressFill
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: theme.primary
|
||||
|
||||
width: parent.width * ratio()
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: 100 }
|
||||
}
|
||||
}
|
||||
|
||||
// Drag handle
|
||||
Rectangle {
|
||||
id: progressHandle
|
||||
width: 12
|
||||
height: 12
|
||||
radius: 6
|
||||
color: theme.primary
|
||||
border.color: Qt.lighter(theme.primary, 1.3)
|
||||
border.width: 1
|
||||
|
||||
x: Math.max(0, Math.min(parent.width - width, progressFill.width - width/2))
|
||||
id: progressBarBackground
|
||||
width: parent.width
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.3)
|
||||
visible: activePlayer !== null
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
visible: activePlayer && activePlayer.length > 0
|
||||
scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1.0
|
||||
Rectangle {
|
||||
id: progressFill
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: theme.primary
|
||||
|
||||
width: parent.width * ratio()
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: 100 }
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150 }
|
||||
// Drag handle
|
||||
Rectangle {
|
||||
id: progressHandle
|
||||
width: 12
|
||||
height: 12
|
||||
radius: 6
|
||||
color: theme.primary
|
||||
border.color: Qt.lighter(theme.primary, 1.3)
|
||||
border.width: 1
|
||||
|
||||
x: Math.max(0, Math.min(parent.width - width, progressFill.width - width/2))
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
visible: activePlayer && activePlayer.length > 0
|
||||
scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,22 +241,14 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: activePlayer && activePlayer.length > 0 && activePlayer.canSeek
|
||||
preventStealing: true
|
||||
|
||||
property bool isSeeking: false
|
||||
|
||||
onClicked: function(mouse) {
|
||||
if (activePlayer && activePlayer.length > 0) {
|
||||
let ratio = mouse.x / width
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
currentPosition = seekPosition
|
||||
}
|
||||
}
|
||||
|
||||
onPressed: function(mouse) {
|
||||
isSeeking = true
|
||||
if (activePlayer && activePlayer.length > 0) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
currentPosition = seekPosition
|
||||
@@ -261,21 +260,54 @@ Rectangle {
|
||||
}
|
||||
|
||||
onPositionChanged: function(mouse) {
|
||||
if (pressed && activePlayer && activePlayer.length > 0) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
if (pressed && isSeeking && activePlayer && activePlayer.length > 0) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
currentPosition = seekPosition
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: function(mouse) {
|
||||
if (activePlayer && activePlayer.length > 0) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
currentPosition = seekPosition
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global mouse area for drag tracking
|
||||
MouseArea {
|
||||
id: progressGlobalMouseArea
|
||||
anchors.fill: parent.parent.parent // Fill the entire media player widget
|
||||
enabled: progressMouseArea.isSeeking
|
||||
visible: false
|
||||
preventStealing: true
|
||||
|
||||
onPositionChanged: function(mouse) {
|
||||
if (progressMouseArea.isSeeking && activePlayer && activePlayer.length > 0) {
|
||||
let globalPos = mapToItem(progressBarBackground, mouse.x, mouse.y)
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
currentPosition = seekPosition
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
progressMouseArea.isSeeking = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Control buttons - always visible
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: theme.spacingL
|
||||
spacing: theme.spacingM
|
||||
visible: activePlayer !== null
|
||||
height: 32
|
||||
|
||||
// Previous button
|
||||
Rectangle {
|
||||
@@ -313,9 +345,9 @@ Rectangle {
|
||||
|
||||
// Play/Pause button
|
||||
Rectangle {
|
||||
width: 36
|
||||
height: 36
|
||||
radius: 18
|
||||
width: 32
|
||||
height: 32
|
||||
radius: 16
|
||||
color: theme.primary
|
||||
|
||||
Text {
|
||||
|
||||
@@ -11,13 +11,14 @@ Item {
|
||||
|
||||
property int audioSubTab: 0 // 0: Output, 1: Input
|
||||
|
||||
// These should be bound from parent
|
||||
property real volumeLevel: 50
|
||||
property real micLevel: 50
|
||||
property string currentAudioSink: ""
|
||||
property string currentAudioSource: ""
|
||||
property var audioSinks: []
|
||||
property var audioSources: []
|
||||
readonly property real volumeLevel: AudioService.volumeLevel
|
||||
readonly property real micLevel: AudioService.micLevel
|
||||
readonly property bool volumeMuted: AudioService.sinkMuted
|
||||
readonly property bool micMuted: AudioService.sourceMuted
|
||||
readonly property string currentAudioSink: AudioService.currentAudioSink
|
||||
readonly property string currentAudioSource: AudioService.currentAudioSource
|
||||
readonly property var audioSinks: AudioService.audioSinks
|
||||
readonly property var audioSources: AudioService.audioSources
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
@@ -102,50 +103,64 @@ Item {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "volume_down"
|
||||
text: audioTab.volumeMuted ? "volume_off" : "volume_down"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
color: audioTab.volumeMuted ? Theme.error : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: AudioService.toggleMute()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: volumeSliderTrack
|
||||
Item {
|
||||
id: volumeSliderContainer
|
||||
width: parent.width - 80
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
height: 32
|
||||
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))
|
||||
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
|
||||
|
||||
scale: volumeMouseArea.containsMouse || volumeMouseArea.pressed ? 1.2 : 1.0
|
||||
Rectangle {
|
||||
id: volumeSliderFill
|
||||
width: parent.width * (audioTab.volumeLevel / 100)
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Theme.primary
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: 100 }
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150 }
|
||||
// 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.0
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,20 +169,56 @@ Item {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
preventStealing: true
|
||||
|
||||
onClicked: (mouse) => {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
property bool isDragging: false
|
||||
|
||||
onPressed: (mouse) => {
|
||||
isDragging = true
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width))
|
||||
let newVolume = Math.round(ratio * 100)
|
||||
AudioService.setVolume(newVolume)
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
isDragging = false
|
||||
}
|
||||
|
||||
onPositionChanged: (mouse) => {
|
||||
if (pressed) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
if (pressed && isDragging) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width))
|
||||
let newVolume = Math.round(ratio * 100)
|
||||
AudioService.setVolume(newVolume)
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: (mouse) => {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width))
|
||||
let newVolume = Math.round(ratio * 100)
|
||||
AudioService.setVolume(newVolume)
|
||||
}
|
||||
}
|
||||
|
||||
// Global mouse area for drag tracking
|
||||
MouseArea {
|
||||
id: volumeGlobalMouseArea
|
||||
anchors.fill: parent.parent.parent.parent.parent // Fill the entire control center
|
||||
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)
|
||||
AudioService.setVolume(newVolume)
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
volumeMouseArea.isDragging = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,13 +230,6 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: audioTab.volumeLevel + "%"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Output Devices
|
||||
@@ -224,14 +268,7 @@ Item {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Current: " + (function() {
|
||||
for (let sink of audioTab.audioSinks) {
|
||||
if (sink.name === audioTab.currentAudioSink) {
|
||||
return sink.displayName
|
||||
}
|
||||
}
|
||||
return audioTab.currentAudioSink
|
||||
})()
|
||||
text: "Current: " + (AudioService.currentSinkDisplayName || "None")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
@@ -283,10 +320,16 @@ Item {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.active ? "Selected" : ""
|
||||
text: {
|
||||
if (modelData.subtitle && modelData.subtitle !== "") {
|
||||
return modelData.subtitle + (modelData.active ? " • Selected" : "")
|
||||
} else {
|
||||
return modelData.active ? "Selected" : ""
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8)
|
||||
visible: modelData.active
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,8 +341,6 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
console.log("Clicked audio device:", JSON.stringify(modelData))
|
||||
console.log("Device name to set:", modelData.name)
|
||||
AudioService.setAudioSink(modelData.name)
|
||||
}
|
||||
}
|
||||
@@ -337,50 +378,64 @@ Item {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "mic"
|
||||
text: audioTab.micMuted ? "mic_off" : "mic"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
color: audioTab.micMuted ? Theme.error : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: AudioService.toggleMicMute()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: micSliderTrack
|
||||
Item {
|
||||
id: micSliderContainer
|
||||
width: parent.width - 80
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
height: 32
|
||||
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))
|
||||
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
|
||||
|
||||
scale: micMouseArea.containsMouse || micMouseArea.pressed ? 1.2 : 1.0
|
||||
Rectangle {
|
||||
id: micSliderFill
|
||||
width: parent.width * (audioTab.micLevel / 100)
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Theme.primary
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: 100 }
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150 }
|
||||
// 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.0
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,20 +444,56 @@ Item {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
preventStealing: true
|
||||
|
||||
onClicked: (mouse) => {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
property bool isDragging: false
|
||||
|
||||
onPressed: (mouse) => {
|
||||
isDragging = true
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width))
|
||||
let newMicLevel = Math.round(ratio * 100)
|
||||
AudioService.setMicLevel(newMicLevel)
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
isDragging = false
|
||||
}
|
||||
|
||||
onPositionChanged: (mouse) => {
|
||||
if (pressed) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
if (pressed && isDragging) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width))
|
||||
let newMicLevel = Math.round(ratio * 100)
|
||||
AudioService.setMicLevel(newMicLevel)
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: (mouse) => {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width))
|
||||
let newMicLevel = Math.round(ratio * 100)
|
||||
AudioService.setMicLevel(newMicLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// Global mouse area for drag tracking
|
||||
MouseArea {
|
||||
id: micGlobalMouseArea
|
||||
anchors.fill: parent.parent.parent.parent.parent // Fill the entire control center
|
||||
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)
|
||||
AudioService.setMicLevel(newMicLevel)
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
micMouseArea.isDragging = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,12 +506,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: audioTab.micLevel + "%"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Input Devices
|
||||
@@ -459,14 +544,7 @@ Item {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Current: " + (function() {
|
||||
for (let source of audioTab.audioSources) {
|
||||
if (source.name === audioTab.currentAudioSource) {
|
||||
return source.displayName
|
||||
}
|
||||
}
|
||||
return audioTab.currentAudioSource
|
||||
})()
|
||||
text: "Current: " + (AudioService.currentSourceDisplayName || "None")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
@@ -517,10 +595,16 @@ Item {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.active ? "Selected" : ""
|
||||
text: {
|
||||
if (modelData.subtitle && modelData.subtitle !== "") {
|
||||
return modelData.subtitle + (modelData.active ? " • Selected" : "")
|
||||
} else {
|
||||
return modelData.active ? "Selected" : ""
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8)
|
||||
visible: modelData.active
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,8 +616,6 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
console.log("Clicked audio source:", JSON.stringify(modelData))
|
||||
console.log("Source name to set:", modelData.name)
|
||||
AudioService.setAudioSource(modelData.name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,206 +6,445 @@ import Quickshell.Io
|
||||
import "../../Common"
|
||||
import "../../Services"
|
||||
|
||||
ScrollView {
|
||||
Item {
|
||||
id: bluetoothTab
|
||||
clip: true
|
||||
|
||||
// These should be bound from parent
|
||||
property bool bluetoothEnabled: false
|
||||
property var bluetoothDevices: []
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
// Bluetooth toggle
|
||||
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) :
|
||||
(bluetoothTab.bluetoothEnabled ? 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: bluetoothTab.bluetoothEnabled ? Theme.primary : "transparent"
|
||||
border.width: 2
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "bluetooth"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSizeLarge
|
||||
color: bluetoothTab.bluetoothEnabled ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: "Bluetooth"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: bluetoothTab.bluetoothEnabled ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Text {
|
||||
text: bluetoothTab.bluetoothEnabled ? "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: {
|
||||
BluetoothService.toggleBluetooth()
|
||||
}
|
||||
}
|
||||
}
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
// Bluetooth devices (when enabled)
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: bluetoothTab.bluetoothEnabled
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Text {
|
||||
text: "Paired Devices"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
// Real Bluetooth devices
|
||||
Repeater {
|
||||
model: bluetoothTab.bluetoothDevices
|
||||
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) :
|
||||
(bluetoothTab.bluetoothEnabled ? 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: bluetoothTab.bluetoothEnabled ? Theme.primary : "transparent"
|
||||
border.width: 2
|
||||
|
||||
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.spacingL
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
Text {
|
||||
text: "bluetooth"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSizeLarge
|
||||
color: bluetoothTab.bluetoothEnabled ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: {
|
||||
switch (modelData.type) {
|
||||
case "headset": return "headset"
|
||||
case "mouse": return "mouse"
|
||||
case "keyboard": return "keyboard"
|
||||
case "phone": return "smartphone"
|
||||
default: return "bluetooth"
|
||||
}
|
||||
}
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Bluetooth"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: bluetoothTab.bluetoothEnabled ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: modelData.name
|
||||
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: modelData.connected ? "Connected" : "Disconnected"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.battery >= 0 ? "• " + modelData.battery + "%" : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: modelData.battery >= 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: btDeviceArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
BluetoothService.toggleBluetoothDevice(modelData.mac)
|
||||
Text {
|
||||
text: bluetoothTab.bluetoothEnabled ? "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: {
|
||||
BluetoothService.toggleBluetooth()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Available devices for pairing (when enabled)
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: bluetoothTab.bluetoothEnabled
|
||||
|
||||
Row {
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: bluetoothTab.bluetoothEnabled
|
||||
|
||||
Text {
|
||||
text: "Available Devices"
|
||||
text: "Paired Devices"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item { width: 1; height: 1 }
|
||||
Repeater {
|
||||
model: bluetoothTab.bluetoothDevices
|
||||
|
||||
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
|
||||
|
||||
Text {
|
||||
text: {
|
||||
switch (modelData.type) {
|
||||
case "headset": return "headset"
|
||||
case "mouse": return "mouse"
|
||||
case "keyboard": return "keyboard"
|
||||
case "phone": return "smartphone"
|
||||
default: return "bluetooth"
|
||||
}
|
||||
}
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: {
|
||||
if (modelData.connecting) return Theme.primary
|
||||
if (modelData.connected) return Theme.primary
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
opacity: modelData.connecting ? 0.6 : 1.0
|
||||
|
||||
Behavior on opacity {
|
||||
SequentialAnimation {
|
||||
running: modelData.connecting
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { from: 1.0; to: 0.3; duration: 800 }
|
||||
NumberAnimation { from: 0.3; to: 1.0; duration: 800 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: modelData.name
|
||||
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: modelData.connectionStatus
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
if (modelData.connecting) return Theme.primary
|
||||
if (modelData.connectionFailed) return Theme.error
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.battery >= 0 ? "• " + modelData.battery + "%" : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: modelData.battery >= 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
|
||||
|
||||
Text {
|
||||
text: "more_vert"
|
||||
font.family: Theme.iconFont
|
||||
font.weight: Theme.iconFontWeight
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.6
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: btMenuButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: !modelData.connecting
|
||||
enabled: !modelData.connecting
|
||||
cursorShape: modelData.connecting ? Qt.ArrowCursor : Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (!modelData.connecting) {
|
||||
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 // Don't overlap with menu button
|
||||
hoverEnabled: !modelData.connecting
|
||||
enabled: !modelData.connecting
|
||||
cursorShape: modelData.connecting ? Qt.ArrowCursor : Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (!modelData.connecting) {
|
||||
BluetoothService.toggleBluetoothDevice(modelData.mac)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: bluetoothTab.bluetoothEnabled
|
||||
|
||||
Rectangle {
|
||||
width: Math.max(100, scanText.contentWidth + Theme.spacingM * 2)
|
||||
height: 32
|
||||
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 {
|
||||
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
|
||||
|
||||
Text {
|
||||
text: BluetoothService.scanning ? "stop" : "bluetooth_searching"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id: scanText
|
||||
text: BluetoothService.scanning ? "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.scanning) {
|
||||
BluetoothService.stopDiscovery()
|
||||
} else {
|
||||
BluetoothService.startDiscovery()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: BluetoothService.availableDevices
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 70
|
||||
radius: Theme.cornerRadius
|
||||
color: availableDeviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
(modelData.paired ? Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: modelData.paired ? Theme.secondary : (modelData.canPair ? Theme.primary : "transparent")
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: {
|
||||
switch (modelData.type) {
|
||||
case "headset": return "headset"
|
||||
case "mouse": return "mouse"
|
||||
case "keyboard": return "keyboard"
|
||||
case "phone": return "smartphone"
|
||||
case "watch": return "watch"
|
||||
case "speaker": return "speaker"
|
||||
case "tv": return "tv"
|
||||
default: return "bluetooth"
|
||||
}
|
||||
}
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: modelData.paired ? Theme.secondary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: modelData.name
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: modelData.paired ? Theme.secondary : Theme.surfaceText
|
||||
font.weight: modelData.paired ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.paired && modelData.connected) return "Connected"
|
||||
if (modelData.paired) return "Paired"
|
||||
return "Signal: " + modelData.signalStrength
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.rssi !== 0 ? "• " + modelData.rssi + " dBm" : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
visible: modelData.rssi !== 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 80
|
||||
height: 28
|
||||
radius: Theme.cornerRadiusSmall
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: actionButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
border.color: Theme.primary
|
||||
border.width: 1
|
||||
visible: modelData.canPair || modelData.paired
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.paired ? (modelData.connected ? "Disconnect" : "Connect") : "Pair"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: actionButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (modelData.paired) {
|
||||
if (modelData.connected) {
|
||||
BluetoothService.toggleBluetoothDevice(modelData.mac)
|
||||
} else {
|
||||
BluetoothService.connectDevice(modelData.mac)
|
||||
}
|
||||
} else {
|
||||
BluetoothService.pairDevice(modelData.mac)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: availableDeviceArea
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 90 // Don't overlap with action button
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (modelData.paired) {
|
||||
BluetoothService.toggleBluetoothDevice(modelData.mac)
|
||||
} else {
|
||||
BluetoothService.pairDevice(modelData.mac)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: BluetoothService.scanning && BluetoothService.availableDevices.length === 0
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: BluetoothService.scanning ? "search" : "bluetooth_searching"
|
||||
text: "sync"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 4
|
||||
font.pixelSize: Theme.iconSizeLarge
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: BluetoothService.scanning
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
@@ -214,169 +453,237 @@ ScrollView {
|
||||
}
|
||||
|
||||
Text {
|
||||
id: scanText
|
||||
text: BluetoothService.scanning ? "Scanning..." : "Scan"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
text: "Scanning for devices..."
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: scanArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: !BluetoothService.scanning
|
||||
|
||||
onClicked: {
|
||||
BluetoothService.startDiscovery()
|
||||
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: BluetoothService.availableDevices.length === 0 && !BluetoothService.scanning
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bluetoothContextMenuWindow
|
||||
property var deviceData: null
|
||||
property bool menuVisible: 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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
opacity: menuVisible ? 1.0 : 0.0
|
||||
scale: menuVisible ? 1.0 : 0.85
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Text {
|
||||
text: bluetoothContextMenuWindow.deviceData && bluetoothContextMenuWindow.deviceData.connected ? "link_off" : "link"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: 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) {
|
||||
BluetoothService.toggleBluetoothDevice(bluetoothContextMenuWindow.deviceData.mac)
|
||||
}
|
||||
bluetoothContextMenuWindow.hide()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Available devices list
|
||||
Repeater {
|
||||
model: BluetoothService.availableDevices
|
||||
Rectangle {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 70
|
||||
radius: Theme.cornerRadius
|
||||
color: availableDeviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
(modelData.paired ? Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: modelData.paired ? Theme.secondary : (modelData.canPair ? Theme.primary : "transparent")
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: {
|
||||
switch (modelData.type) {
|
||||
case "headset": return "headset"
|
||||
case "mouse": return "mouse"
|
||||
case "keyboard": return "keyboard"
|
||||
case "phone": return "smartphone"
|
||||
case "watch": return "watch"
|
||||
case "speaker": return "speaker"
|
||||
case "tv": return "tv"
|
||||
default: return "bluetooth"
|
||||
}
|
||||
}
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: modelData.paired ? Theme.secondary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: modelData.name
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: modelData.paired ? Theme.secondary : Theme.surfaceText
|
||||
font.weight: modelData.paired ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.paired && modelData.connected) return "Connected"
|
||||
if (modelData.paired) return "Paired"
|
||||
return "Signal: " + modelData.signalStrength
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.rssi !== 0 ? "• " + modelData.rssi + " dBm" : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
visible: modelData.rssi !== 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action button on the right
|
||||
Rectangle {
|
||||
width: 80
|
||||
height: 28
|
||||
radius: Theme.cornerRadiusSmall
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: actionButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
border.color: Theme.primary
|
||||
border.width: 1
|
||||
visible: modelData.canPair || modelData.paired
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.paired ? (modelData.connected ? "Disconnect" : "Connect") : "Pair"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: actionButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (modelData.paired) {
|
||||
if (modelData.connected) {
|
||||
BluetoothService.toggleBluetoothDevice(modelData.mac)
|
||||
} else {
|
||||
BluetoothService.connectDevice(modelData.mac)
|
||||
}
|
||||
} else {
|
||||
BluetoothService.pairDevice(modelData.mac)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: availableDeviceArea
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 90 // Don't overlap with action button
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (modelData.paired) {
|
||||
BluetoothService.toggleBluetoothDevice(modelData.mac)
|
||||
} else {
|
||||
BluetoothService.pairDevice(modelData.mac)
|
||||
}
|
||||
}
|
||||
}
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
// No devices message
|
||||
Text {
|
||||
text: "No devices found. Put your device in pairing mode and click Scan."
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: BluetoothService.availableDevices.length === 0 && !BluetoothService.scanning
|
||||
wrapMode: Text.WordWrap
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
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
|
||||
|
||||
Text {
|
||||
text: "delete"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: 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) {
|
||||
BluetoothService.removeDevice(bluetoothContextMenuWindow.deviceData.mac)
|
||||
}
|
||||
bluetoothContextMenuWindow.hide()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
visible: bluetoothContextMenuWindow.visible
|
||||
onClicked: {
|
||||
bluetoothContextMenuWindow.hide()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
x: bluetoothContextMenuWindow.x
|
||||
y: bluetoothContextMenuWindow.y
|
||||
width: bluetoothContextMenuWindow.width
|
||||
height: bluetoothContextMenuWindow.height
|
||||
onClicked: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,14 +676,6 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
visible: controlCenterPopup.currentTab === "audio"
|
||||
|
||||
// Bind properties from root
|
||||
volumeLevel: root.volumeLevel
|
||||
micLevel: root.micLevel
|
||||
currentAudioSink: root.currentAudioSink
|
||||
currentAudioSource: root.currentAudioSource
|
||||
audioSinks: root.audioSinks
|
||||
audioSources: root.audioSources
|
||||
}
|
||||
|
||||
// Bluetooth Tab
|
||||
|
||||
@@ -36,7 +36,7 @@ ScrollView {
|
||||
rightIcon: "brightness_high"
|
||||
enabled: BrightnessService.brightnessAvailable
|
||||
|
||||
onSliderValueChanged: (newValue) => {
|
||||
onSliderValueChanged: function(newValue) {
|
||||
BrightnessService.setBrightness(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,34 +110,79 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: sliderMouseArea
|
||||
Item {
|
||||
id: sliderContainer
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: slider.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: slider.enabled
|
||||
|
||||
onClicked: (mouse) => {
|
||||
if (slider.enabled) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum))
|
||||
slider.value = newValue
|
||||
slider.sliderValueChanged(newValue)
|
||||
MouseArea {
|
||||
id: sliderMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: slider.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: slider.enabled
|
||||
preventStealing: true
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
onPressed: (mouse) => {
|
||||
if (slider.enabled) {
|
||||
isDragging = true
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum))
|
||||
slider.value = newValue
|
||||
slider.sliderValueChanged(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
if (slider.enabled) {
|
||||
isDragging = false
|
||||
slider.sliderDragFinished(slider.value)
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: (mouse) => {
|
||||
if (pressed && isDragging && slider.enabled) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum))
|
||||
slider.value = newValue
|
||||
slider.sliderValueChanged(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: (mouse) => {
|
||||
if (slider.enabled) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum))
|
||||
slider.value = newValue
|
||||
slider.sliderValueChanged(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: (mouse) => {
|
||||
if (pressed && slider.enabled) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum))
|
||||
slider.value = newValue
|
||||
slider.sliderValueChanged(newValue)
|
||||
// Global mouse area for drag tracking
|
||||
MouseArea {
|
||||
id: sliderGlobalMouseArea
|
||||
anchors.fill: slider.parent // Fill the entire settings popup
|
||||
enabled: sliderMouseArea.isDragging
|
||||
visible: false
|
||||
preventStealing: true
|
||||
|
||||
onPositionChanged: (mouse) => {
|
||||
if (sliderMouseArea.isDragging && slider.enabled) {
|
||||
let globalPos = mapToItem(sliderTrack, mouse.x, mouse.y)
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / sliderTrack.width))
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum))
|
||||
slider.value = newValue
|
||||
slider.sliderValueChanged(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
if (slider.enabled) {
|
||||
slider.sliderDragFinished(slider.value)
|
||||
|
||||
onReleased: {
|
||||
if (sliderMouseArea.isDragging && slider.enabled) {
|
||||
sliderMouseArea.isDragging = false
|
||||
slider.sliderDragFinished(slider.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,10 +281,10 @@ PanelWindow {
|
||||
height: 80
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: ProcessMonitorService.totalSwapKB > 0 ?
|
||||
Qt.rgba(Theme.tertiary.r, Theme.tertiary.g, Theme.tertiary.b, 0.08) :
|
||||
Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.04)
|
||||
border.color: ProcessMonitorService.totalSwapKB > 0 ?
|
||||
Qt.rgba(Theme.tertiary.r, Theme.tertiary.g, Theme.tertiary.b, 0.2) :
|
||||
Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.2) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12)
|
||||
border.width: 1
|
||||
|
||||
@@ -298,7 +298,7 @@ PanelWindow {
|
||||
text: "Swap"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: ProcessMonitorService.totalSwapKB > 0 ? Theme.tertiary : Theme.surfaceText
|
||||
color: ProcessMonitorService.totalSwapKB > 0 ? Theme.warning : Theme.surfaceText
|
||||
opacity: 0.8
|
||||
}
|
||||
|
||||
|
||||
@@ -443,10 +443,10 @@ PanelWindow {
|
||||
height: 80
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: ProcessMonitorService.totalSwapKB > 0 ?
|
||||
Qt.rgba(Theme.tertiary.r, Theme.tertiary.g, Theme.tertiary.b, 0.08) :
|
||||
Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.04)
|
||||
border.color: ProcessMonitorService.totalSwapKB > 0 ?
|
||||
Qt.rgba(Theme.tertiary.r, Theme.tertiary.g, Theme.tertiary.b, 0.2) :
|
||||
Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.2) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12)
|
||||
border.width: 1
|
||||
|
||||
@@ -460,7 +460,7 @@ PanelWindow {
|
||||
text: "Swap"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: ProcessMonitorService.totalSwapKB > 0 ? Theme.tertiary : Theme.surfaceText
|
||||
color: ProcessMonitorService.totalSwapKB > 0 ? Theme.warning : Theme.surfaceText
|
||||
opacity: 0.8
|
||||
}
|
||||
|
||||
@@ -749,36 +749,365 @@ PanelWindow {
|
||||
// Define inline components for tabs
|
||||
Component {
|
||||
id: performanceTabComponent
|
||||
Rectangle {
|
||||
color: "transparent"
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingL
|
||||
// CPU Section - Compact with per-core bars
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 200
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.04)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
text: "analytics"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: 48
|
||||
color: Theme.primary
|
||||
opacity: 0.6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
// CPU Header with overall usage
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 32
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "CPU"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 80
|
||||
height: 24
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: ProcessMonitorService.totalCpuUsage.toFixed(1) + "%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Item { width: parent.width - 280; height: 1 }
|
||||
|
||||
Text {
|
||||
text: ProcessMonitorService.cpuCount + " cores"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Per-core CPU bars - Scrollable
|
||||
ScrollView {
|
||||
width: parent.width
|
||||
height: parent.height - 40
|
||||
clip: true
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 6
|
||||
|
||||
Repeater {
|
||||
model: ProcessMonitorService.perCoreCpuUsage.length
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 20
|
||||
spacing: Theme.spacingS
|
||||
|
||||
// Core label
|
||||
Text {
|
||||
text: "C" + index
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: 24
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
// Usage bar
|
||||
Rectangle {
|
||||
width: parent.width - 80
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * Math.min(1.0, ProcessMonitorService.perCoreCpuUsage[index] / 100)
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: {
|
||||
const usage = ProcessMonitorService.perCoreCpuUsage[index]
|
||||
if (usage > 80) return Theme.error
|
||||
if (usage > 60) return Theme.warning
|
||||
return Theme.primary
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage percentage
|
||||
Text {
|
||||
text: ProcessMonitorService.perCoreCpuUsage[index] ?
|
||||
ProcessMonitorService.perCoreCpuUsage[index].toFixed(0) + "%" : "0%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: 32
|
||||
horizontalAlignment: Text.AlignRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Memory Section - Simplified
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 80
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.04)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 4
|
||||
|
||||
Text {
|
||||
text: "Memory"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
Text {
|
||||
text: ProcessMonitorService.formatSystemMemory(ProcessMonitorService.usedMemoryKB) +
|
||||
" / " + ProcessMonitorService.formatSystemMemory(ProcessMonitorService.totalMemoryKB)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
Item { width: Theme.spacingL; height: 1 }
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 4
|
||||
width: 200
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 16
|
||||
radius: 8
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
|
||||
Rectangle {
|
||||
width: ProcessMonitorService.totalMemoryKB > 0 ?
|
||||
parent.width * (ProcessMonitorService.usedMemoryKB / ProcessMonitorService.totalMemoryKB) : 0
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: {
|
||||
const usage = ProcessMonitorService.totalMemoryKB > 0 ?
|
||||
(ProcessMonitorService.usedMemoryKB / ProcessMonitorService.totalMemoryKB) : 0
|
||||
if (usage > 0.9) return Theme.error
|
||||
if (usage > 0.7) return Theme.warning
|
||||
return Theme.secondary
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: Theme.mediumDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: ProcessMonitorService.totalMemoryKB > 0 ?
|
||||
((ProcessMonitorService.usedMemoryKB / ProcessMonitorService.totalMemoryKB) * 100).toFixed(1) + "% used" :
|
||||
"No data"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
Item { width: parent.width - 300; height: 1 }
|
||||
|
||||
// Swap info - compact
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 4
|
||||
visible: ProcessMonitorService.totalSwapKB > 0
|
||||
|
||||
Text {
|
||||
text: "Swap"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.warning
|
||||
}
|
||||
|
||||
Text {
|
||||
text: ProcessMonitorService.formatSystemMemory(ProcessMonitorService.usedSwapKB) +
|
||||
" / " + ProcessMonitorService.formatSystemMemory(ProcessMonitorService.totalSwapKB)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network & Disk I/O - Combined compact view
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 80
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// Network I/O
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 80
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.04)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
text: "Network"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Row {
|
||||
spacing: 4
|
||||
|
||||
Text {
|
||||
text: "↓"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.info
|
||||
}
|
||||
|
||||
Text {
|
||||
text: formatNetworkSpeed(ProcessMonitorService.networkRxRate)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 4
|
||||
|
||||
Text {
|
||||
text: "↑"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.error
|
||||
}
|
||||
|
||||
Text {
|
||||
text: formatNetworkSpeed(ProcessMonitorService.networkTxRate)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Performance Monitoring"
|
||||
font.pixelSize: Theme.fontSizeLarge + 2
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Real-time system performance charts\nwill be displayed here"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
// Disk I/O
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 80
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.04)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
text: "Disk"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Row {
|
||||
spacing: 4
|
||||
|
||||
Text {
|
||||
text: "R"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: formatDiskSpeed(ProcessMonitorService.diskReadRate)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 4
|
||||
|
||||
Text {
|
||||
text: "W"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.warning
|
||||
}
|
||||
|
||||
Text {
|
||||
text: formatDiskSpeed(ProcessMonitorService.diskWriteRate)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -786,36 +1115,481 @@ PanelWindow {
|
||||
|
||||
Component {
|
||||
id: systemTabComponent
|
||||
Rectangle {
|
||||
color: "transparent"
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingL
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "settings"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: 48
|
||||
color: Theme.primary
|
||||
opacity: 0.6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 140
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 140
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.bottomMargin: Theme.spacingM + 4
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: "computer"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "System"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Host: " + SystemMonitorService.hostname
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "OS: " + SystemMonitorService.distribution
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Arch: " + SystemMonitorService.architecture
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Kernel: " + SystemMonitorService.kernelVersion
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 140
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
border.color: Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.16)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.bottomMargin: Theme.spacingM + 4
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: "developer_board"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: Theme.secondary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Hardware"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "CPU: " + SystemMonitorService.cpuModel
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Motherboard: " + SystemMonitorService.motherboard
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "BIOS: " + SystemMonitorService.biosVersion
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Memory: " + SystemMonitorService.formatMemory(SystemMonitorService.totalMemory)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "System Information"
|
||||
font.pixelSize: Theme.fontSizeLarge + 2
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 120
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 120
|
||||
radius: Theme.cornerRadiusLarge
|
||||
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.16)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.bottomMargin: Theme.spacingM + 4
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: "timer"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: Theme.warning
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Uptime"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: SystemMonitorService.uptime
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Boot: " + SystemMonitorService.bootTime
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Load: " + SystemMonitorService.loadAverage
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 120
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.08)
|
||||
border.color: Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.16)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.bottomMargin: Theme.spacingM + 4
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: "list_alt"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: Theme.info
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Processes"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: SystemMonitorService.processCount + " Running"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: SystemMonitorService.threadCount + " Total Created"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Kernel information, schedulers,\nand system details will be shown here"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: Math.max(200, diskMountRepeater.count * 28 + 60)
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.08)
|
||||
border.color: Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.16)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: "storage"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: Theme.success
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Storage & Disks"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "I/O Scheduler: " + SystemMonitorService.scheduler
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
width: parent.width
|
||||
height: parent.height - 60
|
||||
clip: true
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 2
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 24
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: "Device"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
width: parent.width * 0.25
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Mount"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
width: parent.width * 0.2
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Size"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
width: parent.width * 0.15
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Used"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
width: parent.width * 0.15
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Available"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
width: parent.width * 0.15
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Use%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
width: parent.width * 0.1
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: diskMountRepeater
|
||||
model: SystemMonitorService.diskMounts
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 24
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: diskMouseArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.04) :
|
||||
"transparent"
|
||||
|
||||
MouseArea {
|
||||
id: diskMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: modelData.device
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width * 0.25
|
||||
elide: Text.ElideRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.mount
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width * 0.2
|
||||
elide: Text.ElideRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.size
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width * 0.15
|
||||
elide: Text.ElideRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.used
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width * 0.15
|
||||
elide: Text.ElideRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.avail
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width * 0.15
|
||||
elide: Text.ElideRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.percent
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
const percent = parseInt(modelData.percent)
|
||||
if (percent > 90) return Theme.error
|
||||
if (percent > 75) return Theme.warning
|
||||
return Theme.surfaceText
|
||||
}
|
||||
width: parent.width * 0.1
|
||||
elide: Text.ElideRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1155,10 +1929,13 @@ PanelWindow {
|
||||
processListWidget.isVisible = true
|
||||
ProcessMonitorService.updateSystemInfo()
|
||||
ProcessMonitorService.updateProcessList()
|
||||
SystemMonitorService.enableDetailedMonitoring(true)
|
||||
SystemMonitorService.updateSystemInfo()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
processListWidget.isVisible = false
|
||||
SystemMonitorService.enableDetailedMonitoring(false)
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
@@ -1168,4 +1945,27 @@ PanelWindow {
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for formatting
|
||||
function formatNetworkSpeed(bytesPerSec) {
|
||||
if (bytesPerSec < 1024) {
|
||||
return bytesPerSec.toFixed(0) + " B/s"
|
||||
} else if (bytesPerSec < 1024 * 1024) {
|
||||
return (bytesPerSec / 1024).toFixed(1) + " KB/s"
|
||||
} else if (bytesPerSec < 1024 * 1024 * 1024) {
|
||||
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s"
|
||||
} else {
|
||||
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s"
|
||||
}
|
||||
}
|
||||
|
||||
function formatDiskSpeed(bytesPerSec) {
|
||||
if (bytesPerSec < 1024 * 1024) {
|
||||
return (bytesPerSec / 1024).toFixed(1) + " KB/s"
|
||||
} else if (bytesPerSec < 1024 * 1024 * 1024) {
|
||||
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s"
|
||||
} else {
|
||||
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ Item {
|
||||
property bool hasActiveMedia: false
|
||||
property var activePlayer: null
|
||||
property bool cavaAvailable: false
|
||||
property bool configCreated: false
|
||||
|
||||
width: 20
|
||||
height: Theme.iconSize
|
||||
@@ -22,53 +21,20 @@ Item {
|
||||
running: true
|
||||
onExited: (exitCode) => {
|
||||
root.cavaAvailable = exitCode === 0
|
||||
if (root.cavaAvailable && !root.configCreated) {
|
||||
console.log("cava found - creating config and enabling real audio visualization")
|
||||
configWriter.running = true
|
||||
} else if (!root.cavaAvailable) {
|
||||
if (root.cavaAvailable) {
|
||||
console.log("cava found - enabling real audio visualization")
|
||||
cavaProcess.running = Qt.binding(() => root.hasActiveMedia && root.activePlayer?.playbackState === MprisPlaybackState.Playing)
|
||||
} else {
|
||||
console.log("cava not found - using fallback animation")
|
||||
fallbackTimer.running = Qt.binding(() => root.hasActiveMedia && root.activePlayer?.playbackState === MprisPlaybackState.Playing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: configWriter
|
||||
running: false
|
||||
command: [
|
||||
"sh", "-c",
|
||||
`cat > /tmp/quickshell_cava_config << 'EOF'
|
||||
[general]
|
||||
mode = normal
|
||||
framerate = 30
|
||||
autosens = 0
|
||||
sensitivity = 50
|
||||
bars = 4
|
||||
|
||||
[output]
|
||||
method = raw
|
||||
raw_target = /dev/stdout
|
||||
data_format = ascii
|
||||
channels = mono
|
||||
mono_option = average
|
||||
|
||||
[smoothing]
|
||||
noise_reduction = 20
|
||||
EOF`
|
||||
]
|
||||
|
||||
onExited: {
|
||||
root.configCreated = true
|
||||
if (root.cavaAvailable) {
|
||||
cavaProcess.running = Qt.binding(() => root.hasActiveMedia && root.activePlayer?.playbackState === MprisPlaybackState.Playing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: cavaProcess
|
||||
running: false
|
||||
command: ["cava", "-p", "/tmp/quickshell_cava_config"]
|
||||
command: ["sh", "-c", `printf '[general]\nmode=normal\nframerate=30\nautosens=0\nsensitivity=50\nbars=4\n[output]\nmethod=raw\nraw_target=/dev/stdout\ndata_format=ascii\nchannels=mono\nmono_option=average\n[smoothing]\nnoise_reduction=20' | cava -p /dev/stdin`]
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
|
||||
@@ -8,6 +8,7 @@ Rectangle {
|
||||
property string networkStatus: "disconnected"
|
||||
property string wifiSignalStrength: "good"
|
||||
property int volumeLevel: 50
|
||||
property bool volumeMuted: false
|
||||
property bool bluetoothAvailable: false
|
||||
property bool bluetoothEnabled: false
|
||||
property bool isActive: false
|
||||
@@ -62,7 +63,7 @@ Rectangle {
|
||||
|
||||
// Audio Icon
|
||||
Text {
|
||||
text: root.volumeLevel === 0 ? "volume_off" :
|
||||
text: root.volumeMuted ? "volume_off" :
|
||||
root.volumeLevel < 33 ? "volume_down" : "volume_up"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
|
||||
@@ -41,6 +41,7 @@ PanelWindow {
|
||||
property string networkStatus: "disconnected"
|
||||
property string wifiSignalStrength: "good"
|
||||
property int volumeLevel: 50
|
||||
property bool volumeMuted: false
|
||||
property bool bluetoothAvailable: false
|
||||
property bool bluetoothEnabled: false
|
||||
|
||||
@@ -298,6 +299,10 @@ PanelWindow {
|
||||
// Battery Widget
|
||||
BatteryWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
batteryPopupVisible: topBar.shellRoot.batteryPopupVisible
|
||||
onToggleBatteryPopup: {
|
||||
topBar.shellRoot.batteryPopupVisible = !topBar.shellRoot.batteryPopupVisible
|
||||
}
|
||||
}
|
||||
|
||||
ControlCenterButton {
|
||||
@@ -305,6 +310,7 @@ PanelWindow {
|
||||
networkStatus: topBar.networkStatus
|
||||
wifiSignalStrength: topBar.wifiSignalStrength
|
||||
volumeLevel: topBar.volumeLevel
|
||||
volumeMuted: topBar.volumeMuted
|
||||
bluetoothAvailable: topBar.bluetoothAvailable
|
||||
bluetoothEnabled: topBar.bluetoothEnabled
|
||||
isActive: topBar.shellRoot ? topBar.shellRoot.controlCenterVisible : false
|
||||
@@ -314,7 +320,7 @@ PanelWindow {
|
||||
topBar.shellRoot.controlCenterVisible = !topBar.shellRoot.controlCenterVisible
|
||||
if (topBar.shellRoot.controlCenterVisible) {
|
||||
WifiService.scanWifi()
|
||||
BluetoothService.scanDevices()
|
||||
// Bluetooth devices are automatically updated via signals
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user