Reworked the sidepanel file structure
This commit is contained in:
parent
5388260020
commit
0b5f1cd9e5
20 changed files with 4 additions and 1952 deletions
|
|
@ -1,347 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell.Wayland
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import qs.Settings
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property alias panel: bluetoothPanelModal
|
||||
|
||||
|
||||
property string statusMessage: ""
|
||||
property bool statusPopupVisible: false
|
||||
|
||||
function showStatus(msg) {
|
||||
statusMessage = msg
|
||||
statusPopupVisible = true
|
||||
}
|
||||
|
||||
function hideStatus() {
|
||||
statusPopupVisible = false
|
||||
}
|
||||
|
||||
function showAt() {
|
||||
bluetoothLogic.showAt()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
width: 36; height: 36
|
||||
radius: 18
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
color: bluetoothButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "bluetooth"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
color: bluetoothButtonArea.containsMouse
|
||||
? Theme.backgroundPrimary
|
||||
: Theme.accentPrimary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: bluetoothButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: bluetoothLogic.showAt()
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: bluetoothLogic
|
||||
|
||||
function showAt() {
|
||||
if (Bluetooth.defaultAdapter) {
|
||||
if (!Bluetooth.defaultAdapter.enabled)
|
||||
Bluetooth.defaultAdapter.enabled = true
|
||||
if (!Bluetooth.defaultAdapter.discovering)
|
||||
Bluetooth.defaultAdapter.discovering = true
|
||||
}
|
||||
bluetoothPanelModal.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: bluetoothPanelModal
|
||||
implicitWidth: 480
|
||||
implicitHeight: 780
|
||||
visible: false
|
||||
color: "transparent"
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.right: 0
|
||||
margins.top: 0
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible && Bluetooth.defaultAdapter && Bluetooth.defaultAdapter.discovering)
|
||||
Bluetooth.defaultAdapter.discovering = false
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 20
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 32
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 20
|
||||
Layout.preferredHeight: 48
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Text {
|
||||
text: "bluetooth"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 32
|
||||
color: Theme.accentPrimary
|
||||
}
|
||||
Text {
|
||||
text: "Bluetooth"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 26
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Rectangle {
|
||||
width: 36; height: 36; radius: 18
|
||||
color: closeButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "close"
|
||||
font.family: closeButtonArea.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
|
||||
font.pixelSize: 20
|
||||
color: closeButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
}
|
||||
MouseArea {
|
||||
id: closeButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: bluetoothPanelModal.visible = false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.12
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 640
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.margins: 0
|
||||
color: Theme.surfaceVariant
|
||||
radius: 18
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
anchors.topMargin: 32
|
||||
|
||||
Rectangle {
|
||||
id: bg
|
||||
anchors.fill: parent
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 12
|
||||
border.width: 1
|
||||
border.color: Theme.surfaceVariant
|
||||
z: 0
|
||||
}
|
||||
Rectangle {
|
||||
id: header
|
||||
color: "transparent"
|
||||
}
|
||||
Rectangle {
|
||||
id: listContainer
|
||||
anchors.top: header.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 24
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
ListView {
|
||||
id: deviceListView
|
||||
anchors.fill: parent
|
||||
spacing: 4
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
model: Bluetooth.defaultAdapter ? Bluetooth.defaultAdapter.devices : []
|
||||
|
||||
delegate: Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
color: "transparent"
|
||||
radius: 8
|
||||
|
||||
property bool userInitiatedDisconnect: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 8
|
||||
color: modelData.connected ? Qt.rgba(Theme.accentPrimary.r, Theme.accentPrimary.g, Theme.accentPrimary.b, 0.18)
|
||||
: (deviceMouseArea.containsMouse ? Theme.highlight : "transparent")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
spacing: 12
|
||||
|
||||
// Fixed-width icon for alignment
|
||||
Text {
|
||||
width: 28
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: modelData.connected ? "bluetooth" : "bluetooth_disabled"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 20
|
||||
color: modelData.connected ? Theme.accentPrimary : Theme.textSecondary
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
// Device name always fills width for alignment
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: modelData.name || "Unknown Device"
|
||||
font.family: Theme.fontFamily
|
||||
color: modelData.connected ? Theme.accentPrimary : Theme.textPrimary
|
||||
font.pixelSize: 14
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: modelData.address
|
||||
font.family: Theme.fontFamily
|
||||
color: modelData.connected ? Theme.accentPrimary : Theme.textSecondary
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Text {
|
||||
text: "Paired: " + modelData.paired + " | Trusted: " + modelData.trusted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 10
|
||||
color: Theme.textSecondary
|
||||
visible: true
|
||||
}
|
||||
// No "Connected" text here!
|
||||
}
|
||||
|
||||
Spinner {
|
||||
running: modelData.pairing || modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting
|
||||
color: Theme.textPrimary
|
||||
size: 16
|
||||
visible: running
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deviceMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
userInitiatedDisconnect = true
|
||||
modelData.disconnect()
|
||||
} else if (!modelData.paired) {
|
||||
modelData.pair()
|
||||
root.showStatus("Pairing... Please check your phone or system for a PIN dialog.")
|
||||
} else {
|
||||
modelData.connect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: modelData
|
||||
|
||||
function onPairedChanged() {
|
||||
if (modelData.paired) {
|
||||
root.showStatus("Paired! Now connecting...")
|
||||
modelData.connect()
|
||||
}
|
||||
}
|
||||
function onPairingChanged() {
|
||||
if (!modelData.pairing && !modelData.paired) {
|
||||
root.showStatus("Pairing failed or was cancelled.")
|
||||
}
|
||||
}
|
||||
function onConnectedChanged() {
|
||||
userInitiatedDisconnect = false
|
||||
}
|
||||
function onStateChanged() {
|
||||
// Optionally handle more granular feedback here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2
|
||||
anchors.top: listContainer.top
|
||||
anchors.bottom: listContainer.bottom
|
||||
width: 4
|
||||
radius: 2
|
||||
color: Theme.textSecondary
|
||||
opacity: deviceListView.contentHeight > deviceListView.height ? 0.3 : 0
|
||||
visible: opacity > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Status/Info popup
|
||||
Popup {
|
||||
id: statusPopup
|
||||
x: (parent.width - width) / 2
|
||||
y: 40
|
||||
width: Math.min(360, parent.width - 40)
|
||||
visible: root.statusPopupVisible
|
||||
modal: false
|
||||
focus: false
|
||||
background: Rectangle {
|
||||
color: Theme.accentPrimary // Use your theme's accent color
|
||||
radius: 8
|
||||
}
|
||||
contentItem: Text {
|
||||
text: root.statusMessage
|
||||
color: "white"
|
||||
wrapMode: Text.WordWrap
|
||||
padding: 12
|
||||
font.pixelSize: 14
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
// Auto-hide after 3 seconds
|
||||
statusPopupTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,424 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import qs.Settings
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
Rectangle {
|
||||
id: musicCard
|
||||
width: 360
|
||||
height: 250
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
anchors.fill: parent
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
|
||||
// Show fallback UI if no player is available
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
visible: !MusicManager.currentPlayer
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 16
|
||||
|
||||
Text {
|
||||
text: "music_note"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeHeader
|
||||
color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.3)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: MusicManager.hasPlayer ? "No controllable player selected" : "No music player detected"
|
||||
color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.6)
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main player UI
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18
|
||||
spacing: 12
|
||||
visible: !!MusicManager.currentPlayer
|
||||
|
||||
// Player selector
|
||||
ComboBox {
|
||||
id: playerSelector
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
visible: MusicManager.getAvailablePlayers().length > 1
|
||||
model: MusicManager.getAvailablePlayers()
|
||||
textRole: "identity"
|
||||
currentIndex: MusicManager.selectedPlayerIndex
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 120
|
||||
implicitHeight: 40
|
||||
color: Theme.surfaceVariant
|
||||
border.color: playerSelector.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
leftPadding: 12
|
||||
rightPadding: playerSelector.indicator.width + playerSelector.spacing
|
||||
text: playerSelector.displayText
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
indicator: Text {
|
||||
x: playerSelector.width - width - 12
|
||||
y: playerSelector.topPadding + (playerSelector.availableHeight - height) / 2
|
||||
text: "arrow_drop_down"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: playerSelector.height
|
||||
width: playerSelector.width
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
padding: 1
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: playerSelector.popup.visible ? playerSelector.delegateModel : null
|
||||
currentIndex: playerSelector.highlightedIndex
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator {}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surfaceVariant
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: playerSelector.width
|
||||
contentItem: Text {
|
||||
text: modelData.identity
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
highlighted: playerSelector.highlightedIndex === index
|
||||
|
||||
background: Rectangle {
|
||||
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: {
|
||||
MusicManager.selectedPlayerIndex = index;
|
||||
MusicManager.updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
// Album art with spectrum visualizer
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Album art container with circular spectrum overlay
|
||||
Item {
|
||||
id: albumArtContainer
|
||||
width: 96
|
||||
height: 96 // enough for spectrum and art (will adjust if needed)
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
|
||||
// Circular spectrum visualizer around album art
|
||||
CircularSpectrum {
|
||||
id: spectrum
|
||||
values: MusicManager.cavaValues
|
||||
anchors.centerIn: parent
|
||||
innerRadius: 30 // Position just outside 60x60 album art
|
||||
outerRadius: 48 // Extend bars outward from album art
|
||||
fillColor: Theme.accentPrimary
|
||||
strokeColor: Theme.accentPrimary
|
||||
strokeWidth: 0
|
||||
z: 0
|
||||
}
|
||||
|
||||
// Album art image
|
||||
Rectangle {
|
||||
id: albumArtwork
|
||||
width: 60
|
||||
height: 60
|
||||
anchors.centerIn: parent
|
||||
radius: 30 // circle
|
||||
color: Qt.darker(Theme.surface, 1.1)
|
||||
border.color: Qt.rgba(Theme.accentPrimary.r, Theme.accentPrimary.g, Theme.accentPrimary.b, 0.3)
|
||||
border.width: 1
|
||||
|
||||
Image {
|
||||
id: albumArt
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
mipmap: true
|
||||
cache: false
|
||||
asynchronous: true
|
||||
sourceSize.width: 60
|
||||
sourceSize.height: 60
|
||||
source: MusicManager.trackArtUrl
|
||||
visible: source.toString() !== ""
|
||||
|
||||
// Apply circular mask for rounded corners
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskSource: mask
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: mask
|
||||
|
||||
anchors.fill: albumArt
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
width: albumArt.width
|
||||
height: albumArt.height
|
||||
radius: albumArt.width / 2 // circle
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback icon when no album art available
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "album"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.4)
|
||||
visible: !albumArt.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track metadata
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
|
||||
Text {
|
||||
text: MusicManager.trackTitle
|
||||
color: Theme.textPrimary
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: MusicManager.trackArtist
|
||||
color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.8)
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: MusicManager.trackAlbum
|
||||
color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.6)
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Progress bar
|
||||
Rectangle {
|
||||
id: progressBarBackground
|
||||
width: parent.width
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.15)
|
||||
Layout.fillWidth: true
|
||||
|
||||
property real progressRatio: {
|
||||
if (!MusicManager.currentPlayer || !MusicManager.isPlaying || MusicManager.trackLength <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return Math.min(1, MusicManager.currentPosition / MusicManager.trackLength);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: progressFill
|
||||
width: progressBarBackground.progressRatio * parent.width
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Theme.accentPrimary
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interactive progress handle
|
||||
Rectangle {
|
||||
id: progressHandle
|
||||
width: 12
|
||||
height: 12
|
||||
radius: 6
|
||||
color: Theme.accentPrimary
|
||||
border.color: Qt.lighter(Theme.accentPrimary, 1.3)
|
||||
border.width: 1
|
||||
|
||||
x: Math.max(0, Math.min(parent.width - width, progressFill.width - width / 2))
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
visible: MusicManager.trackLength > 0
|
||||
scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse area for seeking
|
||||
MouseArea {
|
||||
id: progressMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: MusicManager.trackLength > 0 && MusicManager.canSeek
|
||||
|
||||
onClicked: function (mouse) {
|
||||
let ratio = mouse.x / width;
|
||||
MusicManager.seekByRatio(ratio);
|
||||
}
|
||||
|
||||
onPositionChanged: function (mouse) {
|
||||
if (pressed) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width));
|
||||
MusicManager.seekByRatio(ratio);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Media controls
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
// Previous button
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: previousButton.containsMouse ? Qt.rgba(Theme.accentPrimary.r, Theme.accentPrimary.g, Theme.accentPrimary.b, 0.2) : Qt.darker(Theme.surface, 1.1)
|
||||
border.color: Qt.rgba(Theme.accentPrimary.r, Theme.accentPrimary.g, Theme.accentPrimary.b, 0.3)
|
||||
border.width: 1
|
||||
|
||||
MouseArea {
|
||||
id: previousButton
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: MusicManager.canGoPrevious
|
||||
onClicked: MusicManager.previous()
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "skip_previous"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
color: previousButton.enabled ? Theme.accentPrimary : Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.3)
|
||||
}
|
||||
}
|
||||
|
||||
// Play/Pause button
|
||||
Rectangle {
|
||||
width: 36
|
||||
height: 36
|
||||
radius: 18
|
||||
color: playButton.containsMouse ? Qt.rgba(Theme.accentPrimary.r, Theme.accentPrimary.g, Theme.accentPrimary.b, 0.2) : Qt.darker(Theme.surface, 1.1)
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 2
|
||||
|
||||
MouseArea {
|
||||
id: playButton
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: MusicManager.canPlay || MusicManager.canPause
|
||||
onClicked: MusicManager.playPause()
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: MusicManager.isPlaying ? "pause" : "play_arrow"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
color: playButton.enabled ? Theme.accentPrimary : Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.3)
|
||||
}
|
||||
}
|
||||
|
||||
// Next button
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: nextButton.containsMouse ? Qt.rgba(Theme.accentPrimary.r, Theme.accentPrimary.g, Theme.accentPrimary.b, 0.2) : Qt.darker(Theme.surface, 1.1)
|
||||
border.color: Qt.rgba(Theme.accentPrimary.r, Theme.accentPrimary.g, Theme.accentPrimary.b, 0.3)
|
||||
border.width: 1
|
||||
|
||||
MouseArea {
|
||||
id: nextButton
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: MusicManager.canGoNext
|
||||
onClicked: MusicManager.next()
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "skip_next"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
color: nextButton.enabled ? Theme.accentPrimary : Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,460 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import qs.Settings
|
||||
import qs.Widgets.SettingsWindow
|
||||
import qs.Components
|
||||
|
||||
PanelWithOverlay {
|
||||
id: sidebarPopup
|
||||
property var shell: null
|
||||
|
||||
// Trigger initial weather loading when component is completed
|
||||
Component.onCompleted: {
|
||||
// Load initial weather data after a short delay to ensure all components are ready
|
||||
Qt.callLater(function() {
|
||||
if (weather && weather.fetchCityWeather) {
|
||||
weather.fetchCityWeather();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showAt() {
|
||||
sidebarPopupRect.showAt();
|
||||
}
|
||||
|
||||
function hidePopup() {
|
||||
sidebarPopupRect.hidePopup();
|
||||
}
|
||||
|
||||
function show() {
|
||||
sidebarPopupRect.showAt();
|
||||
}
|
||||
|
||||
function dismiss() {
|
||||
sidebarPopupRect.hidePopup();
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: sidebarPopupRect
|
||||
implicitWidth: 500
|
||||
implicitHeight: 800
|
||||
visible: parent.visible
|
||||
color: "transparent"
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
|
||||
|
||||
property real slideOffset: width
|
||||
property bool isAnimating: false
|
||||
|
||||
function showAt() {
|
||||
if (!sidebarPopup.visible) {
|
||||
sidebarPopup.visible = true;
|
||||
forceActiveFocus();
|
||||
slideAnim.from = width;
|
||||
slideAnim.to = 0;
|
||||
slideAnim.running = true;
|
||||
if (weather)
|
||||
weather.startWeatherFetch();
|
||||
if (systemWidget)
|
||||
systemWidget.panelVisible = true;
|
||||
if (quickAccessWidget)
|
||||
quickAccessWidget.panelVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
function hidePopup() {
|
||||
if (shell && shell.settingsWindow && shell.settingsWindow.visible) {
|
||||
shell.settingsWindow.visible = false;
|
||||
}
|
||||
|
||||
if (wifiPanelLoader.active && wifiPanelLoader.item && wifiPanelLoader.item.visible) {
|
||||
wifiPanelLoader.item.visible = false;
|
||||
}
|
||||
if (bluetoothPanelLoader.active && bluetoothPanelLoader.item && bluetoothPanelLoader.item.visible) {
|
||||
bluetoothPanelLoader.item.visible = false;
|
||||
}
|
||||
if (sidebarPopup.visible) {
|
||||
slideAnim.from = 0;
|
||||
slideAnim.to = width;
|
||||
slideAnim.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: slideAnim
|
||||
target: sidebarPopupRect
|
||||
property: "slideOffset"
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
|
||||
onStopped: {
|
||||
if (sidebarPopupRect.slideOffset === sidebarPopupRect.width) {
|
||||
sidebarPopup.visible = false;
|
||||
|
||||
if (weather)
|
||||
weather.stopWeatherFetch();
|
||||
if (systemWidget)
|
||||
systemWidget.panelVisible = false;
|
||||
if (quickAccessWidget)
|
||||
quickAccessWidget.panelVisible = false;
|
||||
}
|
||||
sidebarPopupRect.isAnimating = false;
|
||||
}
|
||||
|
||||
onStarted: {
|
||||
sidebarPopupRect.isAnimating = true;
|
||||
}
|
||||
}
|
||||
|
||||
property int leftPadding: 20
|
||||
property int bottomPadding: 20
|
||||
|
||||
Rectangle {
|
||||
id: mainRectangle
|
||||
width: sidebarPopupRect.width - sidebarPopupRect.leftPadding
|
||||
height: sidebarPopupRect.height - sidebarPopupRect.bottomPadding
|
||||
anchors.top: sidebarPopupRect.top
|
||||
x: sidebarPopupRect.leftPadding + sidebarPopupRect.slideOffset
|
||||
y: 0
|
||||
color: Theme.backgroundPrimary
|
||||
bottomLeftRadius: 20
|
||||
z: 0
|
||||
|
||||
Behavior on x {
|
||||
enabled: !sidebarPopupRect.isAnimating
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Access the shell's SettingsWindow instead of creating a new one
|
||||
|
||||
// LazyLoader for WifiPanel
|
||||
LazyLoader {
|
||||
id: wifiPanelLoader
|
||||
loading: false
|
||||
component: WifiPanel {}
|
||||
}
|
||||
|
||||
// LazyLoader for BluetoothPanel
|
||||
LazyLoader {
|
||||
id: bluetoothPanelLoader
|
||||
loading: false
|
||||
component: BluetoothPanel {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// SettingsIcon component
|
||||
SettingsIcon {
|
||||
id: settingsModal
|
||||
onWeatherRefreshRequested: {
|
||||
if (weather && weather.fetchCityWeather) {
|
||||
weather.fetchCityWeather();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Item {
|
||||
anchors.fill: mainRectangle
|
||||
x: sidebarPopupRect.slideOffset
|
||||
|
||||
Behavior on x {
|
||||
enabled: !sidebarPopupRect.isAnimating
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 16
|
||||
|
||||
System {
|
||||
id: systemWidget
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
z: 3
|
||||
}
|
||||
|
||||
Weather {
|
||||
id: weather
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
z: 2
|
||||
}
|
||||
|
||||
// Music and System Monitor row
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Music {
|
||||
z: 2
|
||||
}
|
||||
|
||||
SystemMonitor {
|
||||
id: systemMonitor
|
||||
z: 2
|
||||
}
|
||||
}
|
||||
|
||||
// Power profile, Wifi and Bluetooth row
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.preferredHeight: 80
|
||||
spacing: 16
|
||||
z: 3
|
||||
|
||||
PowerProfile {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.preferredHeight: 80
|
||||
}
|
||||
|
||||
// Network card containing Wifi and Bluetooth
|
||||
Rectangle {
|
||||
Layout.preferredHeight: 80
|
||||
Layout.preferredWidth: 140
|
||||
Layout.fillWidth: false
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 20
|
||||
|
||||
// Wifi button
|
||||
Rectangle {
|
||||
id: wifiButton
|
||||
width: 36
|
||||
height: 36
|
||||
radius: 18
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
color: wifiButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "wifi"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
color: wifiButtonArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wifiButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!wifiPanelLoader.active) {
|
||||
wifiPanelLoader.loading = true;
|
||||
}
|
||||
if (wifiPanelLoader.item) {
|
||||
wifiPanelLoader.item.showAt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledTooltip {
|
||||
text: "Wifi"
|
||||
targetItem: wifiButtonArea
|
||||
tooltipVisible: wifiButtonArea.containsMouse
|
||||
}
|
||||
}
|
||||
|
||||
// Bluetooth button
|
||||
Rectangle {
|
||||
id: bluetoothButton
|
||||
width: 36
|
||||
height: 36
|
||||
radius: 18
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
color: bluetoothButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "bluetooth"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
color: bluetoothButtonArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: bluetoothButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!bluetoothPanelLoader.active) {
|
||||
bluetoothPanelLoader.loading = true;
|
||||
}
|
||||
if (bluetoothPanelLoader.item) {
|
||||
bluetoothPanelLoader.item.showAt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledTooltip {
|
||||
text: "Bluetooth"
|
||||
targetItem: bluetoothButtonArea
|
||||
tooltipVisible: bluetoothButtonArea.containsMouse
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
// QuickAccess widget
|
||||
QuickAccess {
|
||||
id: quickAccessWidget
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: -16
|
||||
z: 2
|
||||
isRecording: sidebarPopupRect.isRecording
|
||||
|
||||
onRecordingRequested: {
|
||||
sidebarPopupRect.startRecording();
|
||||
}
|
||||
|
||||
onStopRecordingRequested: {
|
||||
sidebarPopupRect.stopRecording();
|
||||
}
|
||||
|
||||
onRecordingStateMismatch: function (actualState) {
|
||||
isRecording = actualState;
|
||||
quickAccessWidget.isRecording = actualState;
|
||||
}
|
||||
|
||||
onSettingsRequested: {
|
||||
// Use the SettingsModal's openSettings function
|
||||
if (typeof settingsModal !== 'undefined' && settingsModal && settingsModal.openSettings) {
|
||||
settingsModal.openSettings();
|
||||
}
|
||||
}
|
||||
|
||||
onWallpaperSelectorRequested: {
|
||||
// Use the SettingsModal's openSettings function with wallpaper tab (index 6)
|
||||
if (typeof settingsModal !== 'undefined' && settingsModal && settingsModal.openSettings) {
|
||||
settingsModal.openSettings(6); // 6 is the wallpaper tab index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Keys.onEscapePressed: sidebarPopupRect.hidePopup()
|
||||
}
|
||||
|
||||
// Recording properties
|
||||
property bool isRecording: false
|
||||
|
||||
// Start screen recording using Quickshell.execDetached
|
||||
function startRecording() {
|
||||
var currentDate = new Date();
|
||||
var hours = String(currentDate.getHours()).padStart(2, '0');
|
||||
var minutes = String(currentDate.getMinutes()).padStart(2, '0');
|
||||
var day = String(currentDate.getDate()).padStart(2, '0');
|
||||
var month = String(currentDate.getMonth() + 1).padStart(2, '0');
|
||||
var year = currentDate.getFullYear();
|
||||
|
||||
var filename = hours + "-" + minutes + "-" + day + "-" + month + "-" + year + ".mp4";
|
||||
var videoPath = Settings.settings.videoPath;
|
||||
if (videoPath && !videoPath.endsWith("/")) {
|
||||
videoPath += "/";
|
||||
}
|
||||
var outputPath = videoPath + filename;
|
||||
var command = "gpu-screen-recorder -w portal" +
|
||||
" -f " + Settings.settings.recordingFrameRate +
|
||||
" -a default_output" +
|
||||
" -k " + Settings.settings.recordingCodec +
|
||||
" -ac " + Settings.settings.audioCodec +
|
||||
" -q " + Settings.settings.recordingQuality +
|
||||
" -cursor " + (Settings.settings.showCursor ? "yes" : "no") +
|
||||
" -cr " + Settings.settings.colorRange +
|
||||
" -o " + outputPath;
|
||||
Quickshell.execDetached(["sh", "-c", command]);
|
||||
isRecording = true;
|
||||
quickAccessWidget.isRecording = true;
|
||||
}
|
||||
|
||||
// Stop recording using Quickshell.execDetached
|
||||
function stopRecording() {
|
||||
Quickshell.execDetached(["sh", "-c", "pkill -SIGINT -f 'gpu-screen-recorder.*portal'"]);
|
||||
// Optionally, force kill after a delay
|
||||
var cleanupTimer = Qt.createQmlObject('import QtQuick; Timer { interval: 3000; running: true; repeat: false }', sidebarPopupRect);
|
||||
cleanupTimer.triggered.connect(function () {
|
||||
Quickshell.execDetached(["sh", "-c", "pkill -9 -f 'gpu-screen-recorder.*portal' 2>/dev/null || true"]);
|
||||
cleanupTimer.destroy();
|
||||
});
|
||||
isRecording = false;
|
||||
quickAccessWidget.isRecording = false;
|
||||
}
|
||||
|
||||
// Clean up processes on destruction
|
||||
Component.onDestruction: {
|
||||
if (isRecording) {
|
||||
stopRecording();
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: Settings.settings.showCorners
|
||||
anchors.fill: parent
|
||||
sourceComponent: Item {
|
||||
Corners {
|
||||
id: sidebarCornerLeft
|
||||
position: "bottomright"
|
||||
size: 1.1
|
||||
fillColor: Theme.backgroundPrimary
|
||||
anchors.top: parent.top
|
||||
offsetX: -447 + sidebarPopupRect.slideOffset
|
||||
offsetY: 0
|
||||
|
||||
Behavior on offsetX {
|
||||
enabled: !sidebarPopupRect.isAnimating
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Corners {
|
||||
id: sidebarCornerBottom
|
||||
position: "bottomright"
|
||||
size: 1.1
|
||||
fillColor: Theme.backgroundPrimary
|
||||
anchors.bottom: sidebarPopupRect.bottom
|
||||
offsetX: 33 + sidebarPopupRect.slideOffset
|
||||
offsetY: 46
|
||||
|
||||
Behavior on offsetX {
|
||||
enabled: !sidebarPopupRect.isAnimating
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Settings
|
||||
import qs.Components
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
width: 200
|
||||
height: 70
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 20
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: 36; height: 36
|
||||
radius: 18
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
color: (typeof PowerProfiles !== 'undefined' && PowerProfiles.profile === PowerProfile.Performance)
|
||||
? Theme.accentPrimary
|
||||
: (perfMouseArea.containsMouse ? Theme.accentPrimary : "transparent")
|
||||
opacity: (typeof PowerProfiles !== 'undefined' && !PowerProfiles.hasPerformanceProfile) ? 0.4 : 1
|
||||
|
||||
Text {
|
||||
id: perfIcon
|
||||
anchors.centerIn: parent
|
||||
text: "speed"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
color: (typeof PowerProfiles !== 'undefined' && PowerProfiles.profile === PowerProfile.Performance) || perfMouseArea.containsMouse
|
||||
? Theme.backgroundPrimary
|
||||
: Theme.accentPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: perfMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: typeof PowerProfiles !== 'undefined' && PowerProfiles.hasPerformanceProfile
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (typeof PowerProfiles !== 'undefined')
|
||||
PowerProfiles.profile = PowerProfile.Performance;
|
||||
}
|
||||
onEntered: perfTooltip.tooltipVisible = true
|
||||
onExited: perfTooltip.tooltipVisible = false
|
||||
}
|
||||
StyledTooltip {
|
||||
id: perfTooltip
|
||||
text: "Performance Profile"
|
||||
tooltipVisible: false
|
||||
targetItem: perfIcon
|
||||
delay: 200
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: 36; height: 36
|
||||
radius: 18
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
color: (typeof PowerProfiles !== 'undefined' && PowerProfiles.profile === PowerProfile.Balanced)
|
||||
? Theme.accentPrimary
|
||||
: (balMouseArea.containsMouse ? Theme.accentPrimary : "transparent")
|
||||
opacity: 1
|
||||
|
||||
Text {
|
||||
id: balIcon
|
||||
anchors.centerIn: parent
|
||||
text: "balance"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
color: (typeof PowerProfiles !== 'undefined' && PowerProfiles.profile === PowerProfile.Balanced) || balMouseArea.containsMouse
|
||||
? Theme.backgroundPrimary
|
||||
: Theme.accentPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: balMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (typeof PowerProfiles !== 'undefined')
|
||||
PowerProfiles.profile = PowerProfile.Balanced;
|
||||
}
|
||||
onEntered: balTooltip.tooltipVisible = true
|
||||
onExited: balTooltip.tooltipVisible = false
|
||||
}
|
||||
StyledTooltip {
|
||||
id: balTooltip
|
||||
text: "Balanced Profile"
|
||||
tooltipVisible: false
|
||||
targetItem: balIcon
|
||||
delay: 200
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: 36; height: 36
|
||||
radius: 18
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
color: (typeof PowerProfiles !== 'undefined' && PowerProfiles.profile === PowerProfile.PowerSaver)
|
||||
? Theme.accentPrimary
|
||||
: (saveMouseArea.containsMouse ? Theme.accentPrimary : "transparent")
|
||||
opacity: 1
|
||||
|
||||
Text {
|
||||
id: saveIcon
|
||||
anchors.centerIn: parent
|
||||
text: "eco"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
color: (typeof PowerProfiles !== 'undefined' && PowerProfiles.profile === PowerProfile.PowerSaver) || saveMouseArea.containsMouse
|
||||
? Theme.backgroundPrimary
|
||||
: Theme.accentPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: saveMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (typeof PowerProfiles !== 'undefined')
|
||||
PowerProfiles.profile = PowerProfile.PowerSaver;
|
||||
}
|
||||
onEntered: saveTooltip.tooltipVisible = true
|
||||
onExited: saveTooltip.tooltipVisible = false
|
||||
}
|
||||
StyledTooltip {
|
||||
id: saveTooltip
|
||||
text: "Power Saver Profile"
|
||||
tooltipVisible: false
|
||||
targetItem: saveIcon
|
||||
delay: 200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Settings
|
||||
|
||||
Rectangle {
|
||||
id: quickAccessWidget
|
||||
width: 440
|
||||
height: 80
|
||||
color: "transparent"
|
||||
anchors.horizontalCenterOffset: -2
|
||||
|
||||
required property bool isRecording
|
||||
|
||||
signal recordingRequested()
|
||||
signal stopRecordingRequested()
|
||||
signal recordingStateMismatch(bool actualState)
|
||||
signal settingsRequested()
|
||||
signal wallpaperRequested()
|
||||
signal wallpaperSelectorRequested()
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
anchors.fill: parent
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18
|
||||
spacing: 12
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: settingsButton
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 44
|
||||
radius: 12
|
||||
color: settingsButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "settings"
|
||||
font.family: settingsButtonArea.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: settingsButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Settings"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
color: settingsButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: settingsButtonArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
settingsRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: recorderButton
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 44
|
||||
radius: 12
|
||||
color: isRecording ? Theme.accentPrimary :
|
||||
(recorderButtonArea.containsMouse ? Theme.accentPrimary : "transparent")
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: isRecording ? "radio_button_checked" : "radio_button_unchecked"
|
||||
font.family: (isRecording || recorderButtonArea.containsMouse) ? "Material Symbols Rounded" : "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: isRecording || recorderButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: isRecording ? "End" : "Record"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
color: isRecording || recorderButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: recorderButtonArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
if (isRecording) {
|
||||
stopRecordingRequested()
|
||||
} else {
|
||||
recordingRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: wallpaperButton
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 44
|
||||
radius: 12
|
||||
color: wallpaperButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "image"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: wallpaperButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Wallpaper"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
color: wallpaperButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wallpaperButtonArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
wallpaperSelectorRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
property bool panelVisible: false
|
||||
|
||||
|
||||
Timer {
|
||||
interval: 2000
|
||||
repeat: true
|
||||
running: panelVisible
|
||||
onTriggered: checkRecordingStatus()
|
||||
}
|
||||
|
||||
function checkRecordingStatus() {
|
||||
if (isRecording) {
|
||||
checkRecordingProcess.running = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Process {
|
||||
id: checkRecordingProcess
|
||||
command: ["pgrep", "-f", "gpu-screen-recorder.*portal"]
|
||||
onExited: function(exitCode, exitStatus) {
|
||||
var isActuallyRecording = exitCode === 0
|
||||
if (isRecording && !isActuallyRecording) {
|
||||
recordingStateMismatch(isActuallyRecording)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Settings
|
||||
import qs.Services
|
||||
import qs.Widgets.SettingsWindow
|
||||
import qs.Components
|
||||
|
||||
PanelWindow {
|
||||
id: settingsModal
|
||||
implicitWidth: 480
|
||||
implicitHeight: 780
|
||||
visible: false
|
||||
color: "transparent"
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.right: 0
|
||||
margins.top: 0
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
|
||||
// Signal to request weather refresh
|
||||
signal weatherRefreshRequested()
|
||||
|
||||
// Property to track the settings window instance
|
||||
property var settingsWindow: null
|
||||
|
||||
// Function to open the modal and initialize temp values
|
||||
function openSettings(initialTabIndex) {
|
||||
if (!settingsWindow) {
|
||||
// Create new window
|
||||
settingsWindow = settingsComponent.createObject(null); // No parent to avoid dependency issues
|
||||
if (settingsWindow) {
|
||||
// Set the initial tab if provided
|
||||
if (typeof initialTabIndex === 'number' && initialTabIndex >= 0 && initialTabIndex <= 8) {
|
||||
settingsWindow.activeTabIndex = initialTabIndex;
|
||||
}
|
||||
settingsWindow.visible = true;
|
||||
|
||||
// Show wallpaper selector if opening wallpaper tab (after window is visible)
|
||||
if (typeof initialTabIndex === 'number' && initialTabIndex === 6) {
|
||||
Qt.callLater(function() {
|
||||
if (settingsWindow && settingsWindow.showWallpaperSelector) {
|
||||
settingsWindow.showWallpaperSelector();
|
||||
}
|
||||
}, 100); // Small delay to ensure window is fully loaded
|
||||
}
|
||||
// Handle window closure
|
||||
settingsWindow.visibleChanged.connect(function() {
|
||||
if (settingsWindow && !settingsWindow.visible) {
|
||||
// Trigger weather refresh when settings close
|
||||
weatherRefreshRequested();
|
||||
var windowToDestroy = settingsWindow;
|
||||
settingsWindow = null;
|
||||
windowToDestroy.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (settingsWindow.visible) {
|
||||
// Close and destroy window
|
||||
var windowToDestroy = settingsWindow;
|
||||
settingsWindow = null;
|
||||
windowToDestroy.visible = false;
|
||||
windowToDestroy.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// Function to close the modal and release focus
|
||||
function closeSettings() {
|
||||
if (settingsWindow) {
|
||||
var windowToDestroy = settingsWindow;
|
||||
settingsWindow = null;
|
||||
windowToDestroy.visible = false;
|
||||
windowToDestroy.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: settingsComponent
|
||||
SettingsWindow {}
|
||||
}
|
||||
|
||||
// Clean up on destruction
|
||||
Component.onDestruction: {
|
||||
if (settingsWindow) {
|
||||
var windowToDestroy = settingsWindow;
|
||||
settingsWindow = null;
|
||||
windowToDestroy.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Settings
|
||||
import qs.Services
|
||||
import qs.Widgets.SettingsWindow
|
||||
import qs.Components
|
||||
|
||||
PanelWindow {
|
||||
id: settingsModal
|
||||
implicitWidth: 480
|
||||
implicitHeight: 780
|
||||
visible: false
|
||||
color: "transparent"
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.right: 0
|
||||
margins.top: 0
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
|
||||
// Property to track the settings window instance
|
||||
property var settingsWindow: null
|
||||
|
||||
// Function to open the modal and initialize temp values
|
||||
function openSettings() {
|
||||
if (!settingsWindow) {
|
||||
// Create new window
|
||||
settingsWindow = settingsComponent.createObject(null); // No parent to avoid dependency issues
|
||||
if (settingsWindow) {
|
||||
settingsWindow.visible = true;
|
||||
// Handle window closure
|
||||
settingsWindow.visibleChanged.connect(function() {
|
||||
if (settingsWindow && !settingsWindow.visible) {
|
||||
var windowToDestroy = settingsWindow;
|
||||
settingsWindow = null;
|
||||
windowToDestroy.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (settingsWindow.visible) {
|
||||
// Close and destroy window
|
||||
var windowToDestroy = settingsWindow;
|
||||
settingsWindow = null;
|
||||
windowToDestroy.visible = false;
|
||||
windowToDestroy.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// Function to close the modal and release focus
|
||||
function closeSettings() {
|
||||
if (settingsWindow) {
|
||||
var windowToDestroy = settingsWindow;
|
||||
settingsWindow = null;
|
||||
windowToDestroy.visible = false;
|
||||
windowToDestroy.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: settingsComponent
|
||||
SettingsWindow {}
|
||||
}
|
||||
|
||||
// Clean up on destruction
|
||||
Component.onDestruction: {
|
||||
if (settingsWindow) {
|
||||
var windowToDestroy = settingsWindow;
|
||||
settingsWindow = null;
|
||||
windowToDestroy.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh weather data when hidden
|
||||
onVisibleChanged: {
|
||||
if (!visible && typeof weather !== 'undefined' && weather !== null && weather.fetchCityWeather) {
|
||||
weather.fetchCityWeather();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,453 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Settings
|
||||
import qs.Widgets
|
||||
import qs.Widgets.LockScreen
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
import qs.Components
|
||||
|
||||
Rectangle {
|
||||
id: systemWidget
|
||||
width: 440
|
||||
height: 80
|
||||
color: "transparent"
|
||||
anchors.horizontalCenterOffset: -2
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
anchors.fill: parent
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18
|
||||
spacing: 12
|
||||
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: 48
|
||||
height: 48
|
||||
radius: 24
|
||||
color: Theme.accentPrimary
|
||||
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: 24
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 2
|
||||
z: 2
|
||||
}
|
||||
|
||||
Avatar {}
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: Quickshell.env("USER")
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "System Uptime: " + uptimeText
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 12
|
||||
color: Theme.textSecondary
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: systemButton
|
||||
width: 32
|
||||
height: 32
|
||||
radius: 16
|
||||
color: systemButtonArea.containsMouse || systemButtonArea.pressed ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "power_settings_new"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: systemButtonArea.containsMouse || systemButtonArea.pressed ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: systemButtonArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
systemMenu.visible = !systemMenu.visible;
|
||||
}
|
||||
}
|
||||
StyledTooltip {
|
||||
id: systemTooltip
|
||||
text: "System"
|
||||
targetItem: systemButton
|
||||
tooltipVisible: systemButtonArea.containsMouse
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PanelWithOverlay {
|
||||
id: systemMenu
|
||||
anchors.top: systemButton.bottom
|
||||
anchors.right: systemButton.right
|
||||
|
||||
Rectangle {
|
||||
|
||||
width: 160
|
||||
height: 220
|
||||
color: Theme.surface
|
||||
radius: 8
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
visible: true
|
||||
z: 9999
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
|
||||
|
||||
anchors.rightMargin: 32
|
||||
anchors.topMargin: systemButton.y + systemButton.height + 48
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 4
|
||||
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
radius: 6
|
||||
color: lockButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "lock_outline"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: lockButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Lock Screen"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14
|
||||
color: lockButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: lockButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
lockScreen.locked = true;
|
||||
systemMenu.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
radius: 6
|
||||
color: suspendButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "bedtime"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: suspendButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Suspend"
|
||||
font.pixelSize: 14
|
||||
color: suspendButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: suspendButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
suspend();
|
||||
systemMenu.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
radius: 6
|
||||
color: rebootButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "refresh"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: rebootButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Reboot"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14
|
||||
color: rebootButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: rebootButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
reboot();
|
||||
systemMenu.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
radius: 6
|
||||
color: logoutButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "exit_to_app"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: logoutButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Logout"
|
||||
font.pixelSize: 14
|
||||
color: logoutButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: logoutButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
logout();
|
||||
systemMenu.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
radius: 6
|
||||
color: shutdownButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "power_settings_new"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: shutdownButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Shutdown"
|
||||
font.pixelSize: 14
|
||||
color: shutdownButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: shutdownButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
shutdown();
|
||||
systemMenu.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
property string uptimeText: "--:--"
|
||||
|
||||
|
||||
Process {
|
||||
id: uptimeProcess
|
||||
command: ["sh", "-c", "uptime | awk -F 'up ' '{print $2}' | awk -F ',' '{print $1}' | xargs"]
|
||||
running: false
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
uptimeText = this.text.trim();
|
||||
uptimeProcess.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: shutdownProcess
|
||||
command: ["shutdown", "-h", "now"]
|
||||
running: false
|
||||
}
|
||||
|
||||
Process {
|
||||
id: rebootProcess
|
||||
command: ["reboot"]
|
||||
running: false
|
||||
}
|
||||
|
||||
Process {
|
||||
id: suspendProcess
|
||||
command: ["systemctl", "suspend"]
|
||||
running: false
|
||||
}
|
||||
|
||||
Process {
|
||||
id: logoutProcessNiri
|
||||
command: ["niri", "msg", "action", "quit", "--skip-confirmation"]
|
||||
running: false
|
||||
}
|
||||
|
||||
Process {
|
||||
id: logoutProcessHyprland
|
||||
command: ["hyprctl", "dispatch", "exit"]
|
||||
running: false
|
||||
}
|
||||
|
||||
Process {
|
||||
id: logoutProcess
|
||||
command: ["loginctl", "terminate-user", Quickshell.env("USER")]
|
||||
running: false
|
||||
}
|
||||
|
||||
function logout() {
|
||||
if (WorkspaceManager.isNiri) {
|
||||
logoutProcessNiri.running = true;
|
||||
} else if (WorkspaceManager.isHyprland) {
|
||||
logoutProcessHyprland.running = true;
|
||||
} else {
|
||||
|
||||
console.warn("No supported compositor detected for logout");
|
||||
}
|
||||
}
|
||||
|
||||
function suspend() {
|
||||
suspendProcess.running = true;
|
||||
}
|
||||
|
||||
function shutdown() {
|
||||
shutdownProcess.running = true;
|
||||
}
|
||||
|
||||
function reboot() {
|
||||
rebootProcess.running = true;
|
||||
}
|
||||
|
||||
property bool panelVisible: false
|
||||
|
||||
|
||||
onPanelVisibleChanged: {
|
||||
if (panelVisible) {
|
||||
updateSystemInfo();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Timer {
|
||||
interval: 60000
|
||||
repeat: true
|
||||
running: panelVisible
|
||||
onTriggered: updateSystemInfo()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
uptimeProcess.running = true;
|
||||
}
|
||||
|
||||
function updateSystemInfo() {
|
||||
uptimeProcess.running = true;
|
||||
}
|
||||
|
||||
|
||||
LockScreen {
|
||||
id: lockScreen
|
||||
}
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell.Io
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
import qs.Settings
|
||||
|
||||
Rectangle {
|
||||
id: systemMonitor
|
||||
width: 70
|
||||
height: 250
|
||||
color: "transparent"
|
||||
|
||||
// Track visibility state for panel integration
|
||||
property bool isVisible: false
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
anchors.fill: parent
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 12
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
|
||||
// CPU usage indicator with circular progress bar
|
||||
Item {
|
||||
width: 50; height: 50
|
||||
CircularProgressBar {
|
||||
id: cpuBar
|
||||
progress: Sysinfo.cpuUsage / 100
|
||||
size: 50
|
||||
strokeWidth: 4
|
||||
hasNotch: true
|
||||
notchIcon: "speed"
|
||||
notchIconSize: 14
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
MouseArea {
|
||||
id: cpuBarMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: cpuTooltip.tooltipVisible = true
|
||||
onExited: cpuTooltip.tooltipVisible = false
|
||||
}
|
||||
StyledTooltip {
|
||||
id: cpuTooltip
|
||||
text: 'CPU Usage: ' + Sysinfo.cpuUsage + '%'
|
||||
tooltipVisible: false
|
||||
targetItem: cpuBar
|
||||
delay: 200
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// CPU temperature indicator with circular progress bar
|
||||
Item {
|
||||
width: 50; height: 50
|
||||
CircularProgressBar {
|
||||
id: tempBar
|
||||
progress: Sysinfo.cpuTemp / 100
|
||||
size: 50
|
||||
strokeWidth: 4
|
||||
hasNotch: true
|
||||
units: "°C"
|
||||
notchIcon: "thermometer"
|
||||
notchIconSize: 14
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
MouseArea {
|
||||
id: tempBarMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: tempTooltip.tooltipVisible = true
|
||||
onExited: tempTooltip.tooltipVisible = false
|
||||
}
|
||||
StyledTooltip {
|
||||
id: tempTooltip
|
||||
text: 'CPU Temp: ' + Sysinfo.cpuTemp + '°C'
|
||||
tooltipVisible: false
|
||||
targetItem: tempBar
|
||||
delay: 200
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Memory usage indicator with circular progress bar
|
||||
Item {
|
||||
width: 50; height: 50
|
||||
CircularProgressBar {
|
||||
id: memBar
|
||||
progress: Sysinfo.memoryUsagePer / 100
|
||||
size: 50
|
||||
strokeWidth: 4
|
||||
hasNotch: true
|
||||
notchIcon: "memory"
|
||||
notchIconSize: 14
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
MouseArea {
|
||||
id: memBarMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: memTooltip.tooltipVisible = true
|
||||
onExited: memTooltip.tooltipVisible = false
|
||||
}
|
||||
StyledTooltip {
|
||||
id: memTooltip
|
||||
text: 'Memory Usage: ' + Sysinfo.memoryUsagePer + '% (' + Sysinfo.memoryUsageStr + ' used)'
|
||||
tooltipVisible: false
|
||||
targetItem: memBar
|
||||
delay: 200
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Disk usage indicator with circular progress bar
|
||||
Item {
|
||||
width: 50; height: 50
|
||||
CircularProgressBar {
|
||||
id: diskBar
|
||||
progress: Sysinfo.diskUsage / 100
|
||||
size: 50
|
||||
strokeWidth: 4
|
||||
hasNotch: true
|
||||
notchIcon: "storage"
|
||||
notchIconSize: 14
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
MouseArea {
|
||||
id: diskBarMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: diskTooltip.tooltipVisible = true
|
||||
onExited: diskTooltip.tooltipVisible = false
|
||||
}
|
||||
StyledTooltip {
|
||||
id: diskTooltip
|
||||
text: 'Disk Usage: ' + Sysinfo.diskUsage + '%'
|
||||
tooltipVisible: false
|
||||
targetItem: diskBar
|
||||
delay: 200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import qs.Settings
|
||||
import qs.Services
|
||||
|
||||
PanelWindow {
|
||||
id: wallpaperPanelModal
|
||||
implicitWidth: 480
|
||||
implicitHeight: 780
|
||||
visible: false
|
||||
color: "transparent"
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.right: 0
|
||||
margins.top: 0
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
|
||||
property var wallpapers: []
|
||||
|
||||
Connections {
|
||||
target: WallpaperManager
|
||||
function onWallpaperListChanged() {
|
||||
wallpapers = WallpaperManager.wallpaperList
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (wallpaperPanel.visible) {
|
||||
wallpapers = WallpaperManager.wallpaperList
|
||||
} else {
|
||||
wallpapers = []
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 20
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 32
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 20
|
||||
Layout.preferredHeight: 48
|
||||
Text {
|
||||
text: "image"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeHeader
|
||||
color: Theme.accentPrimary
|
||||
}
|
||||
Text {
|
||||
text: "Wallpapers"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeHeader
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Rectangle {
|
||||
width: 36
|
||||
height: 36
|
||||
radius: 18
|
||||
color: closeButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "close"
|
||||
font.family: closeButtonArea.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
color: closeButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
}
|
||||
MouseArea {
|
||||
id: closeButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
wallpaperPanel.visible = false;
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.12
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
anchors.topMargin: 16
|
||||
anchors.bottomMargin: 16
|
||||
anchors.leftMargin: 0
|
||||
anchors.rightMargin: 0
|
||||
anchors.margins: 0
|
||||
clip: true
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
GridView {
|
||||
id: wallpaperGrid
|
||||
anchors.fill: parent
|
||||
cellWidth: Math.max(120, (scrollView.width / 3) - 12)
|
||||
cellHeight: cellWidth * 0.6
|
||||
model: wallpapers
|
||||
cacheBuffer: 64
|
||||
leftMargin: 8
|
||||
rightMargin: 8
|
||||
topMargin: 8
|
||||
bottomMargin: 8
|
||||
delegate: Item {
|
||||
width: wallpaperGrid.cellWidth - 8
|
||||
height: wallpaperGrid.cellHeight - 8
|
||||
ClippingRectangle {
|
||||
id: wallpaperItem
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
color: Qt.darker(Theme.backgroundPrimary, 1.1)
|
||||
radius: 12
|
||||
border.color: Settings.settings.currentWallpaper === modelData ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 2
|
||||
Image {
|
||||
id: wallpaperImage
|
||||
anchors.fill: parent
|
||||
source: modelData
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
asynchronous: true
|
||||
cache: true
|
||||
smooth: true
|
||||
mipmap: true
|
||||
|
||||
sourceSize.width: Math.min(width, 480)
|
||||
sourceSize.height: Math.min(height, 270)
|
||||
|
||||
opacity: (wallpaperImage.status == Image.Ready) ? 1.0 : 0.0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
WallpaperManager.changeWallpaper(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,247 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import qs.Settings
|
||||
import "../../../Helpers/Weather.js" as WeatherHelper
|
||||
|
||||
Rectangle {
|
||||
id: weatherRoot
|
||||
width: 440
|
||||
height: 180
|
||||
color: "transparent"
|
||||
anchors.horizontalCenterOffset: -2
|
||||
|
||||
property string city: Settings.settings.weatherCity !== undefined ? Settings.settings.weatherCity : ""
|
||||
property var weatherData: null
|
||||
property string errorString: ""
|
||||
property bool isVisible: false
|
||||
property int lastFetchTime: 0
|
||||
property bool isLoading: false
|
||||
|
||||
// Auto-refetch weather when city changes
|
||||
Connections {
|
||||
target: Settings.settings
|
||||
function onWeatherCityChanged() {
|
||||
if (isVisible && city !== "") {
|
||||
// Force refresh when city changes
|
||||
lastFetchTime = 0;
|
||||
fetchCityWeather();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (isVisible) {
|
||||
fetchCityWeather()
|
||||
}
|
||||
}
|
||||
|
||||
function fetchCityWeather() {
|
||||
if (!city || city.trim() === "") {
|
||||
errorString = "No city configured";
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we should fetch new data (avoid fetching too frequently)
|
||||
var currentTime = Date.now();
|
||||
var timeSinceLastFetch = currentTime - lastFetchTime;
|
||||
|
||||
// Only skip if we have recent data AND lastFetchTime is not 0 (initial state)
|
||||
if (lastFetchTime > 0 && timeSinceLastFetch < 60000) { // 1 minute
|
||||
return; // Skip if last fetch was less than 1 minute ago
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
errorString = "";
|
||||
|
||||
WeatherHelper.fetchCityWeather(city,
|
||||
function(result) {
|
||||
weatherData = result.weather;
|
||||
lastFetchTime = currentTime;
|
||||
errorString = "";
|
||||
isLoading = false;
|
||||
},
|
||||
function(err) {
|
||||
errorString = err;
|
||||
isLoading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function startWeatherFetch() {
|
||||
isVisible = true
|
||||
// Force refresh when panel opens, regardless of time check
|
||||
lastFetchTime = 0;
|
||||
fetchCityWeather();
|
||||
}
|
||||
|
||||
function stopWeatherFetch() {
|
||||
isVisible = false
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
anchors.fill: parent
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18
|
||||
spacing: 12
|
||||
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
Layout.preferredWidth: 140
|
||||
|
||||
|
||||
Text {
|
||||
id: weatherIcon
|
||||
text: isLoading ? "sync" : (weatherData && weatherData.current_weather ? materialSymbolForCode(weatherData.current_weather.weathercode) : "cloud")
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 28
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: isLoading ? Theme.accentPrimary : Theme.accentPrimary
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
// Add rotation animation for loading state
|
||||
RotationAnimation on rotation {
|
||||
running: isLoading
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
Text {
|
||||
text: city
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
Text {
|
||||
text: weatherData && weatherData.timezone_abbreviation ? `(${weatherData.timezone_abbreviation})` : ""
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 10
|
||||
color: Theme.textSecondary
|
||||
leftPadding: 2
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: weatherData && weatherData.current_weather ? ((Settings.settings.useFahrenheit !== undefined ? Settings.settings.useFahrenheit : false) ? `${Math.round(weatherData.current_weather.temperature * 9/5 + 32)}°F` : `${Math.round(weatherData.current_weather.temperature)}°C`) : ((Settings.settings.useFahrenheit !== undefined ? Settings.settings.useFahrenheit : false) ? "--°F" : "--°C")
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 24
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.textSecondary.g, Theme.textSecondary.g, Theme.textSecondary.b, 0.12)
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 2
|
||||
Layout.bottomMargin: 2
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: weatherData && weatherData.daily && weatherData.daily.time
|
||||
|
||||
Repeater {
|
||||
model: weatherData && weatherData.daily && weatherData.daily.time ? 5 : 0
|
||||
delegate: ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Text {
|
||||
|
||||
text: Qt.formatDateTime(new Date(weatherData.daily.time[index]), "ddd")
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 12
|
||||
color: Theme.textSecondary
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
Text {
|
||||
|
||||
text: materialSymbolForCode(weatherData.daily.weathercode[index])
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
color: Theme.accentPrimary
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
Text {
|
||||
|
||||
text: weatherData && weatherData.daily ? ((Settings.settings.useFahrenheit !== undefined ? Settings.settings.useFahrenheit : false) ? `${Math.round(weatherData.daily.temperature_2m_max[index] * 9/5 + 32)}° / ${Math.round(weatherData.daily.temperature_2m_min[index] * 9/5 + 32)}°` : `${Math.round(weatherData.daily.temperature_2m_max[index])}° / ${Math.round(weatherData.daily.temperature_2m_min[index])}°`) : ((Settings.settings.useFahrenheit !== undefined ? Settings.settings.useFahrenheit : false) ? "--° / --°" : "--° / --°")
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 12
|
||||
color: Theme.textPrimary
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Text {
|
||||
text: errorString
|
||||
color: Theme.error
|
||||
visible: errorString !== ""
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 10
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function materialSymbolForCode(code) {
|
||||
if (code === 0) return "sunny";
|
||||
if (code === 1 || code === 2) return "partly_cloudy_day";
|
||||
if (code === 3) return "cloud";
|
||||
if (code >= 45 && code <= 48) return "foggy";
|
||||
if (code >= 51 && code <= 67) return "rainy";
|
||||
if (code >= 71 && code <= 77) return "weather_snowy";
|
||||
if (code >= 80 && code <= 82) return "rainy";
|
||||
if (code >= 95 && code <= 99) return "thunderstorm";
|
||||
return "cloud";
|
||||
}
|
||||
function weatherDescriptionForCode(code) {
|
||||
if (code === 0) return "Clear sky";
|
||||
if (code === 1) return "Mainly clear";
|
||||
if (code === 2) return "Partly cloudy";
|
||||
if (code === 3) return "Overcast";
|
||||
if (code === 45 || code === 48) return "Fog";
|
||||
if (code >= 51 && code <= 67) return "Drizzle";
|
||||
if (code >= 71 && code <= 77) return "Snow";
|
||||
if (code >= 80 && code <= 82) return "Rain showers";
|
||||
if (code >= 95 && code <= 99) return "Thunderstorm";
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,909 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell.Wayland
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Bluetooth
|
||||
import qs.Settings
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
|
||||
Item {
|
||||
property alias panel: wifiPanelModal
|
||||
|
||||
function showAt() {
|
||||
wifiPanelModal.visible = true;
|
||||
wifiLogic.refreshNetworks();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
existingNetwork.running = true;
|
||||
}
|
||||
|
||||
function signalIcon(signal) {
|
||||
if (signal >= 80)
|
||||
return "network_wifi";
|
||||
if (signal >= 60)
|
||||
return "network_wifi_3_bar";
|
||||
if (signal >= 40)
|
||||
return "network_wifi_2_bar";
|
||||
if (signal >= 20)
|
||||
return "network_wifi_1_bar";
|
||||
return "wifi_0_bar";
|
||||
}
|
||||
|
||||
Process {
|
||||
id: existingNetwork
|
||||
running: false
|
||||
command: ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const lines = text.split("\n");
|
||||
const networksMap = {};
|
||||
|
||||
refreshIndicator.running = true;
|
||||
refreshIndicator.visible = true;
|
||||
|
||||
for (let i = 0; i < lines.length; ++i) {
|
||||
const line = lines[i].trim();
|
||||
if (!line)
|
||||
continue;
|
||||
|
||||
const parts = line.split(":");
|
||||
if (parts.length < 2) {
|
||||
console.warn("Malformed nmcli output line:", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ssid = wifiLogic.replaceQuickshell(parts[0]);
|
||||
const type = parts[1];
|
||||
|
||||
if (ssid) {
|
||||
networksMap[ssid] = {
|
||||
ssid: ssid,
|
||||
type: type
|
||||
};
|
||||
}
|
||||
}
|
||||
scanProcess.existingNetwork = networksMap;
|
||||
scanProcess.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: scanProcess
|
||||
running: false
|
||||
command: ["nmcli", "-t", "-f", "SSID,SECURITY,SIGNAL,IN-USE", "device", "wifi", "list"]
|
||||
|
||||
property var existingNetwork
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const lines = text.split("\n");
|
||||
const networksMap = {};
|
||||
|
||||
for (let i = 0; i < lines.length; ++i) {
|
||||
const line = lines[i].trim();
|
||||
if (!line)
|
||||
continue;
|
||||
|
||||
const parts = line.split(":");
|
||||
if (parts.length < 4) {
|
||||
console.warn("Malformed nmcli output line:", line);
|
||||
continue;
|
||||
}
|
||||
const ssid = parts[0];
|
||||
const security = parts[1];
|
||||
const signal = parseInt(parts[2]);
|
||||
const inUse = parts[3] === "*";
|
||||
|
||||
if (ssid) {
|
||||
if (!networksMap[ssid]) {
|
||||
networksMap[ssid] = {
|
||||
ssid: ssid,
|
||||
security: security,
|
||||
signal: signal,
|
||||
connected: inUse,
|
||||
existing: ssid in scanProcess.existingNetwork
|
||||
};
|
||||
} else {
|
||||
const existingNet = networksMap[ssid];
|
||||
if (inUse) {
|
||||
existingNet.connected = true;
|
||||
}
|
||||
if (signal > existingNet.signal) {
|
||||
existingNet.signal = signal;
|
||||
existingNet.security = security;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
wifiLogic.networks = networksMap;
|
||||
scanProcess.existingNetwork = {};
|
||||
refreshIndicator.running = false;
|
||||
refreshIndicator.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: wifiLogic
|
||||
property var networks: {}
|
||||
property var anchorItem: null
|
||||
property real anchorX
|
||||
property real anchorY
|
||||
property string passwordPromptSsid: ""
|
||||
property string passwordInput: ""
|
||||
property bool showPasswordPrompt: false
|
||||
property string connectingSsid: ""
|
||||
property string connectStatus: ""
|
||||
property string connectStatusSsid: ""
|
||||
property string connectError: ""
|
||||
property string connectSecurity: ""
|
||||
property var pendingConnect: null
|
||||
property string detectedInterface: ""
|
||||
property string actionPanelSsid: ""
|
||||
|
||||
function replaceQuickshell(ssid: string): string {
|
||||
const newName = ssid.replace("quickshell-", "");
|
||||
|
||||
if (!ssid.startsWith("quickshell-")) {
|
||||
return newName;
|
||||
}
|
||||
|
||||
if (wifiLogic.networks && newName in wifiLogic.networks) {
|
||||
console.log(`Quickshell ${newName} already exists, deleting old profile`)
|
||||
deleteProfileProcess.connName = ssid;
|
||||
deleteProfileProcess.running = true;
|
||||
}
|
||||
|
||||
console.log(`Changing from ${ssid} to ${newName}`)
|
||||
renameConnectionProcess.oldName = ssid;
|
||||
renameConnectionProcess.newName = newName;
|
||||
renameConnectionProcess.running = true;
|
||||
|
||||
return newName;
|
||||
}
|
||||
|
||||
function disconnectNetwork(ssid) {
|
||||
const profileName = ssid;
|
||||
disconnectProfileProcess.connectionName = profileName;
|
||||
disconnectProfileProcess.running = true;
|
||||
}
|
||||
function refreshNetworks() {
|
||||
existingNetwork.running = true;
|
||||
}
|
||||
function showAt() {
|
||||
wifiPanelModal.visible = true;
|
||||
wifiLogic.refreshNetworks();
|
||||
}
|
||||
function connectNetwork(ssid, security) {
|
||||
wifiLogic.pendingConnect = {
|
||||
ssid: ssid,
|
||||
security: security,
|
||||
password: ""
|
||||
};
|
||||
wifiLogic.doConnect();
|
||||
}
|
||||
function submitPassword() {
|
||||
wifiLogic.pendingConnect = {
|
||||
ssid: wifiLogic.passwordPromptSsid,
|
||||
security: wifiLogic.connectSecurity,
|
||||
password: wifiLogic.passwordInput
|
||||
};
|
||||
wifiLogic.doConnect();
|
||||
}
|
||||
function doConnect() {
|
||||
const params = wifiLogic.pendingConnect;
|
||||
if (!params)
|
||||
return;
|
||||
|
||||
wifiLogic.connectingSsid = params.ssid;
|
||||
|
||||
|
||||
const targetNetwork = wifiLogic.networks[params.ssid];
|
||||
|
||||
|
||||
if (targetNetwork && targetNetwork.existing) {
|
||||
|
||||
upConnectionProcess.profileName = params.ssid;
|
||||
upConnectionProcess.running = true;
|
||||
wifiLogic.pendingConnect = null;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (params.security && params.security !== "--") {
|
||||
getInterfaceProcess.running = true;
|
||||
return;
|
||||
}
|
||||
connectProcess.security = params.security;
|
||||
connectProcess.ssid = params.ssid;
|
||||
connectProcess.password = params.password;
|
||||
connectProcess.running = true;
|
||||
wifiLogic.pendingConnect = null;
|
||||
}
|
||||
function isSecured(security) {
|
||||
return security && security.trim() !== "" && security.trim() !== "--";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Process {
|
||||
id: disconnectProfileProcess
|
||||
property string connectionName: ""
|
||||
running: false
|
||||
command: ["nmcli", "connection", "down", connectionName]
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
wifiLogic.refreshNetworks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process to rename a connection
|
||||
Process {
|
||||
id: renameConnectionProcess
|
||||
running: false
|
||||
property string oldName: ""
|
||||
property string newName: ""
|
||||
command: ["nmcli", "connection", "modify", oldName, "connection.id", newName]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
console.log("Successfully renamed connection '" +
|
||||
renameConnectionProcess.oldName + "' to '" +
|
||||
renameConnectionProcess.newName + "'");
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim() !== "" && !text.toLowerCase().includes("warning")) {
|
||||
console.error("Error renaming connection:", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Process to rename a connection
|
||||
Process {
|
||||
id: deleteProfileProcess
|
||||
running: false
|
||||
property string connName: ""
|
||||
command: ["nmcli", "connection", "delete", `'${connName}'`]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
console.log("Deleted connection '" + deleteProfileProcess.connName + "'");
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
console.error("Error deleting connection '" + deleteProfileProcess.connName + "':", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Process {
|
||||
id: connectProcess
|
||||
property string ssid: ""
|
||||
property string password: ""
|
||||
property string security: ""
|
||||
running: false
|
||||
onStarted: {
|
||||
refreshIndicator.running = true;
|
||||
}
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
refreshIndicator.running = false;
|
||||
}
|
||||
command: {
|
||||
if (password) {
|
||||
return ["nmcli", "device", "wifi", "connect", `'${ssid}'`, "password", password];
|
||||
} else {
|
||||
return ["nmcli", "device", "wifi", "connect", `'${ssid}'`];
|
||||
}
|
||||
}
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
wifiLogic.connectingSsid = "";
|
||||
wifiLogic.showPasswordPrompt = false;
|
||||
wifiLogic.passwordPromptSsid = "";
|
||||
wifiLogic.passwordInput = "";
|
||||
wifiLogic.connectStatus = "success";
|
||||
wifiLogic.connectStatusSsid = connectProcess.ssid;
|
||||
wifiLogic.connectError = "";
|
||||
wifiLogic.refreshNetworks();
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
wifiLogic.connectingSsid = "";
|
||||
wifiLogic.showPasswordPrompt = false;
|
||||
wifiLogic.passwordPromptSsid = "";
|
||||
wifiLogic.passwordInput = "";
|
||||
wifiLogic.connectStatus = "error";
|
||||
wifiLogic.connectStatusSsid = connectProcess.ssid;
|
||||
wifiLogic.connectError = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Process {
|
||||
id: getInterfaceProcess
|
||||
running: false
|
||||
command: ["nmcli", "-t", "-f", "DEVICE,TYPE,STATE", "device"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
var lines = text.split("\n");
|
||||
for (var i = 0; i < lines.length; ++i) {
|
||||
var parts = lines[i].split(":");
|
||||
if (parts[1] === "wifi" && parts[2] !== "unavailable") {
|
||||
wifiLogic.detectedInterface = parts[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (wifiLogic.detectedInterface) {
|
||||
var params = wifiLogic.pendingConnect;
|
||||
addConnectionProcess.ifname = wifiLogic.detectedInterface;
|
||||
addConnectionProcess.ssid = params.ssid;
|
||||
addConnectionProcess.password = params.password;
|
||||
addConnectionProcess.profileName = params.ssid;
|
||||
addConnectionProcess.security = params.security;
|
||||
addConnectionProcess.running = true;
|
||||
} else {
|
||||
wifiLogic.connectStatus = "error";
|
||||
wifiLogic.connectStatusSsid = wifiLogic.pendingConnect.ssid;
|
||||
wifiLogic.connectError = "No Wi-Fi interface found.";
|
||||
wifiLogic.connectingSsid = "";
|
||||
wifiLogic.pendingConnect = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Process {
|
||||
id: addConnectionProcess
|
||||
property string ifname: ""
|
||||
property string ssid: ""
|
||||
property string password: ""
|
||||
property string profileName: ""
|
||||
property string security: ""
|
||||
running: false
|
||||
command: {
|
||||
var cmd = ["nmcli", "connection", "add", "type", "wifi", "ifname", ifname, "con-name", profileName, "ssid", ssid];
|
||||
if (security && security !== "--") {
|
||||
cmd.push("wifi-sec.key-mgmt");
|
||||
cmd.push("wpa-psk");
|
||||
cmd.push("wifi-sec.psk");
|
||||
cmd.push(password);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
upConnectionProcess.profileName = addConnectionProcess.profileName;
|
||||
upConnectionProcess.running = true;
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
upConnectionProcess.profileName = addConnectionProcess.profileName;
|
||||
upConnectionProcess.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Process {
|
||||
id: upConnectionProcess
|
||||
property string profileName: ""
|
||||
running: false
|
||||
command: ["nmcli", "connection", "up", "id", profileName]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
wifiLogic.connectingSsid = "";
|
||||
wifiLogic.showPasswordPrompt = false;
|
||||
wifiLogic.passwordPromptSsid = "";
|
||||
wifiLogic.passwordInput = "";
|
||||
wifiLogic.connectStatus = "success";
|
||||
wifiLogic.connectStatusSsid = wifiLogic.pendingConnect ? wifiLogic.pendingConnect.ssid : "";
|
||||
wifiLogic.connectError = "";
|
||||
wifiLogic.refreshNetworks();
|
||||
wifiLogic.pendingConnect = null;
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
wifiLogic.connectingSsid = "";
|
||||
wifiLogic.showPasswordPrompt = false;
|
||||
wifiLogic.passwordPromptSsid = "";
|
||||
wifiLogic.passwordInput = "";
|
||||
wifiLogic.connectStatus = "error";
|
||||
wifiLogic.connectStatusSsid = wifiLogic.pendingConnect ? wifiLogic.pendingConnect.ssid : "";
|
||||
wifiLogic.connectError = text;
|
||||
wifiLogic.pendingConnect = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: wifiButton
|
||||
width: 36
|
||||
height: 36
|
||||
radius: 18
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
color: wifiButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "wifi"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
color: wifiButtonArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wifiButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: wifiLogic.showAt()
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: wifiPanelModal
|
||||
implicitWidth: 480
|
||||
implicitHeight: 780
|
||||
visible: false
|
||||
color: "transparent"
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.right: 0
|
||||
margins.top: 0
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
Component.onCompleted: {
|
||||
wifiLogic.refreshNetworks();
|
||||
}
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 20
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 32
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 20
|
||||
Layout.preferredHeight: 48
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Text {
|
||||
text: "wifi"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 32
|
||||
color: Theme.accentPrimary
|
||||
}
|
||||
Text {
|
||||
text: "Wi-Fi"
|
||||
font.pixelSize: 26
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Spinner {
|
||||
id: refreshIndicator
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: false
|
||||
running: false
|
||||
color: Theme.accentPrimary
|
||||
size: 22
|
||||
}
|
||||
IconButton {
|
||||
id: refreshButton
|
||||
icon: "refresh"
|
||||
onClicked: wifiLogic.refreshNetworks()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
implicitWidth: 36
|
||||
implicitHeight: 36
|
||||
radius: 18
|
||||
color: closeButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "close"
|
||||
font.family: closeButtonArea.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
|
||||
font.pixelSize: 20
|
||||
color: closeButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
}
|
||||
MouseArea {
|
||||
id: closeButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: wifiPanelModal.visible = false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.12
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 640
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.margins: 0
|
||||
color: Theme.surfaceVariant
|
||||
radius: 18
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
Rectangle {
|
||||
id: bg
|
||||
anchors.fill: parent
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 12
|
||||
border.width: 1
|
||||
border.color: Theme.surfaceVariant
|
||||
z: 0
|
||||
}
|
||||
Rectangle {
|
||||
id: header
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: listContainer
|
||||
anchors.top: header.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 24
|
||||
color: "transparent"
|
||||
clip: true
|
||||
ListView {
|
||||
id: networkListView
|
||||
anchors.fill: parent
|
||||
spacing: 4
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
model: wifiLogic.networks ? Object.values(wifiLogic.networks) : null
|
||||
delegate: Item {
|
||||
id: networkEntry
|
||||
|
||||
required property var modelData
|
||||
property var signalIcon: wifiPanel.signalIcon
|
||||
|
||||
width: parent.width
|
||||
height: (modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt ? 102 : 42) + (modelData.ssid === wifiLogic.actionPanelSsid ? 60 : 0)
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 42
|
||||
radius: 8
|
||||
color: modelData.connected ? Qt.rgba(Theme.accentPrimary.r, Theme.accentPrimary.g, Theme.accentPrimary.b, 0.44) : (networkMouseArea.containsMouse || (modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt) ? Theme.highlight : "transparent")
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
spacing: 12
|
||||
Text {
|
||||
text: signalIcon(modelData.signal)
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 20
|
||||
color: networkMouseArea.containsMouse || (modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt) ? Theme.backgroundPrimary : (modelData.connected ? Theme.accentPrimary : Theme.textSecondary)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 6
|
||||
Text {
|
||||
text: modelData.ssid || "Unknown Network"
|
||||
color: networkMouseArea.containsMouse || (modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt) ? Theme.backgroundPrimary : (modelData.connected ? Theme.accentPrimary : Theme.textPrimary)
|
||||
font.pixelSize: 14
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
Item {
|
||||
width: 22
|
||||
height: 22
|
||||
visible: wifiLogic.connectStatusSsid === modelData.ssid && wifiLogic.connectStatus !== ""
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 2
|
||||
Text {
|
||||
visible: wifiLogic.connectStatus === "success"
|
||||
text: "check_circle"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 18
|
||||
color: "#43a047"
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
Text {
|
||||
visible: wifiLogic.connectStatus === "error"
|
||||
text: "error"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 18
|
||||
color: Theme.error
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: modelData.security && modelData.security !== "--" ? modelData.security : "Open"
|
||||
color: networkMouseArea.containsMouse || (modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt) ? Theme.backgroundPrimary : (modelData.connected ? Theme.accentPrimary : Theme.textSecondary)
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
Text {
|
||||
visible: wifiLogic.connectStatusSsid === modelData.ssid && wifiLogic.connectStatus === "error" && wifiLogic.connectError.length > 0
|
||||
text: wifiLogic.connectError
|
||||
color: Theme.error
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
Text {
|
||||
visible: modelData.connected
|
||||
text: "connected"
|
||||
color: networkMouseArea.containsMouse || (modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt) ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
font.pixelSize: 11
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
Spinner {
|
||||
visible: wifiLogic.connectingSsid === modelData.ssid
|
||||
running: wifiLogic.connectingSsid === modelData.ssid
|
||||
color: Theme.accentPrimary
|
||||
anchors.centerIn: parent
|
||||
size: 22
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: networkMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
|
||||
if (wifiLogic.actionPanelSsid === modelData.ssid) {
|
||||
wifiLogic.actionPanelSsid = ""; // Close if already open
|
||||
} else {
|
||||
wifiLogic.actionPanelSsid = modelData.ssid; // Open for this network
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
visible: modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 60
|
||||
radius: 8
|
||||
color: "transparent"
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.leftMargin: 32
|
||||
Layout.rightMargin: 32
|
||||
z: 2
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 10
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 8
|
||||
color: "transparent"
|
||||
border.color: passwordField.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
TextInput {
|
||||
id: passwordField
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
text: wifiLogic.passwordInput
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
clip: true
|
||||
focus: true
|
||||
selectByMouse: true
|
||||
activeFocusOnTab: true
|
||||
inputMethodHints: Qt.ImhNone
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: wifiLogic.passwordInput = text
|
||||
onAccepted: wifiLogic.submitPassword()
|
||||
MouseArea {
|
||||
id: passwordMouseArea
|
||||
anchors.fill: parent
|
||||
onClicked: passwordField.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 80
|
||||
Layout.preferredHeight: 36
|
||||
radius: 18
|
||||
color: Theme.accentPrimary
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 0
|
||||
opacity: 1.0
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: wifiLogic.submitPassword()
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onEntered: parent.color = Qt.darker(Theme.accentPrimary, 1.1)
|
||||
onExited: parent.color = Theme.accentPrimary
|
||||
}
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Connect"
|
||||
color: Theme.backgroundPrimary
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: modelData.ssid === wifiLogic.actionPanelSsid
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 60
|
||||
radius: 8
|
||||
color: "transparent"
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.leftMargin: 32
|
||||
Layout.rightMargin: 32
|
||||
z: 2
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 10
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
visible: wifiLogic.isSecured(modelData.security) && !modelData.connected && !modelData.existing
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 8
|
||||
color: "transparent"
|
||||
border.color: actionPanelPasswordField.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
TextInput {
|
||||
id: actionPanelPasswordField
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
clip: true
|
||||
selectByMouse: true
|
||||
activeFocusOnTab: true
|
||||
inputMethodHints: Qt.ImhNone
|
||||
echoMode: TextInput.Password
|
||||
onAccepted: {
|
||||
|
||||
wifiLogic.pendingConnect = {
|
||||
ssid: modelData.ssid,
|
||||
security: modelData.security,
|
||||
password: text
|
||||
};
|
||||
wifiLogic.doConnect();
|
||||
|
||||
wifiLogic.actionPanelSsid = ""; // Close the panel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 80
|
||||
Layout.preferredHeight: 36
|
||||
radius: 18
|
||||
color: modelData.connected ? Theme.error : Theme.accentPrimary
|
||||
border.color: modelData.connected ? Theme.error : Theme.accentPrimary
|
||||
border.width: 0
|
||||
opacity: 1.0
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
|
||||
wifiLogic.disconnectNetwork(modelData.ssid);
|
||||
} else {
|
||||
|
||||
if (wifiLogic.isSecured(modelData.security) && !modelData.existing) {
|
||||
|
||||
if (actionPanelPasswordField.text.length > 0) {
|
||||
wifiLogic.pendingConnect = {
|
||||
ssid: modelData.ssid,
|
||||
security: modelData.security,
|
||||
password: actionPanelPasswordField.text
|
||||
};
|
||||
wifiLogic.doConnect();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
wifiLogic.connectNetwork(modelData.ssid, modelData.security);
|
||||
}
|
||||
}
|
||||
wifiLogic.actionPanelSsid = ""; // Close the panel
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onEntered: parent.color = modelData.connected ? Qt.darker(Theme.error, 1.1) : Qt.darker(Theme.accentPrimary, 1.1)
|
||||
onExited: parent.color = modelData.connected ? Theme.error : Theme.accentPrimary
|
||||
}
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.connected ? "wifi_off" : "check"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 20
|
||||
color: Theme.backgroundPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue