Merge branch 'dev'
This commit is contained in:
commit
d2d993d621
70 changed files with 8102 additions and 2264 deletions
|
|
@ -8,7 +8,9 @@ import qs.Components
|
|||
|
||||
PanelWindow {
|
||||
id: taskbarWindow
|
||||
visible: Settings.settings.showDock
|
||||
visible: Settings.settings.showDock &&
|
||||
(Settings.settings.dockMonitors.includes(modelData.name) ||
|
||||
(Settings.settings.dockMonitors.length === 0))
|
||||
screen: (typeof modelData !== 'undefined' ? modelData : null)
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
anchors.bottom: true
|
||||
|
|
@ -245,7 +247,7 @@ PanelWindow {
|
|||
contextMenuVisible = false;
|
||||
contextMenuTarget = null;
|
||||
contextMenuToplevel = null;
|
||||
hidden = true; // Hide dock when context menu closes by clicking outside
|
||||
hidden = true; // Hide dock when context menu closes
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +283,7 @@ PanelWindow {
|
|||
spacing: 4
|
||||
width: parent.width
|
||||
|
||||
// Close
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
|
|
@ -300,7 +302,7 @@ PanelWindow {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "close"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 14
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
|
|
@ -308,7 +310,7 @@ PanelWindow {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Close"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
}
|
||||
|
|
@ -322,7 +324,7 @@ PanelWindow {
|
|||
onClicked: {
|
||||
if (contextMenuToplevel?.close) contextMenuToplevel.close();
|
||||
contextMenuVisible = false;
|
||||
hidden = true; // Hide the dock here as well
|
||||
hidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import qs.Components
|
|||
import qs.Settings
|
||||
|
||||
Item {
|
||||
// Test mode
|
||||
|
||||
property bool testMode: false
|
||||
property int testPercent: 49
|
||||
property bool testCharging: true
|
||||
|
|
@ -21,7 +21,7 @@ Item {
|
|||
height: row.height
|
||||
visible: testMode || (isReady && battery.isLaptopBattery)
|
||||
|
||||
// Choose icon based on charge and charging state
|
||||
|
||||
function batteryIcon() {
|
||||
if (!show)
|
||||
return "";
|
||||
|
|
@ -32,7 +32,7 @@ Item {
|
|||
if (percent >= 95)
|
||||
return "battery_android_full";
|
||||
|
||||
// Hardcoded battery symbols
|
||||
|
||||
if (percent >= 85)
|
||||
return "battery_android_6";
|
||||
if (percent >= 70)
|
||||
|
|
@ -58,7 +58,7 @@ Item {
|
|||
Text {
|
||||
text: batteryIcon()
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 28
|
||||
font.pixelSize: 28 * Theme.scale(Screen)
|
||||
color: charging ? Theme.accentPrimary : Theme.textSecondary
|
||||
verticalAlignment: Text.AlignVBottom
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ Item {
|
|||
Text {
|
||||
text: Math.round(percent) + "%"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 18
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
verticalAlignment: Text.AlignVBottom
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,21 +152,21 @@ WlSessionLock {
|
|||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 30
|
||||
width: Math.min(parent.width * 0.8, 400)
|
||||
width: Math.min(parent.width * 0.8, 400 * Theme.scale(Screen))
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
width: 80
|
||||
height: 80
|
||||
radius: 40
|
||||
width: 80 * Theme.scale(Screen)
|
||||
height: 80 * Theme.scale(Screen)
|
||||
radius: 40 * Theme.scale(Screen)
|
||||
color: Theme.accentPrimary
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: 40
|
||||
radius: 40 * Theme.scale(Screen)
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 3
|
||||
border.width: 3 * Theme.scale(Screen)
|
||||
z: 2
|
||||
}
|
||||
|
||||
|
|
@ -183,28 +183,28 @@ WlSessionLock {
|
|||
Layout.alignment: Qt.AlignHCenter
|
||||
text: Quickshell.env("USER")
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 24
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 50
|
||||
radius: 25
|
||||
height: 50 * Theme.scale(Screen)
|
||||
radius: 25 * Theme.scale(Screen)
|
||||
color: Theme.surface
|
||||
opacity: passwordInput.activeFocus ? 0.8 : 0.3
|
||||
border.color: passwordInput.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 2
|
||||
border.width: 2 * Theme.scale(Screen)
|
||||
|
||||
TextInput {
|
||||
id: passwordInput
|
||||
anchors.fill: parent
|
||||
anchors.margins: 15
|
||||
anchors.margins: 15 * Theme.scale(Screen)
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 16
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
echoMode: TextInput.Password
|
||||
passwordCharacter: "●"
|
||||
|
|
@ -218,7 +218,7 @@ WlSessionLock {
|
|||
text: "Enter password..."
|
||||
color: Theme.textSecondary
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 16
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
visible: !passwordInput.text && !passwordInput.activeFocus
|
||||
}
|
||||
|
||||
|
|
@ -238,9 +238,9 @@ WlSessionLock {
|
|||
id: errorMessageRect
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
width: parent.width * 0.8
|
||||
height: 44
|
||||
height: 44 * Theme.scale(Screen)
|
||||
color: Theme.overlay
|
||||
radius: 20
|
||||
radius: 20 * Theme.scale(Screen)
|
||||
visible: lock.errorMessage !== ""
|
||||
|
||||
Text {
|
||||
|
|
@ -248,7 +248,7 @@ WlSessionLock {
|
|||
text: lock.errorMessage
|
||||
color: Theme.error
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
opacity: 1
|
||||
visible: lock.errorMessage !== ""
|
||||
}
|
||||
|
|
@ -256,13 +256,13 @@ WlSessionLock {
|
|||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
width: 120
|
||||
height: 44
|
||||
radius: 20
|
||||
width: 120 * Theme.scale(Screen)
|
||||
height: 44 * Theme.scale(Screen)
|
||||
radius: 20 * Theme.scale(Screen)
|
||||
opacity: unlockButtonArea.containsMouse ? 0.8 : 0.5
|
||||
color: unlockButtonArea.containsMouse ? Theme.accentPrimary : Theme.surface
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 2
|
||||
border.width: 2 * Theme.scale(Screen)
|
||||
enabled: !lock.authenticating
|
||||
|
||||
Text {
|
||||
|
|
@ -270,7 +270,7 @@ WlSessionLock {
|
|||
anchors.centerIn: parent
|
||||
text: lock.authenticating ? "..." : "Unlock"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 16
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: unlockButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
}
|
||||
|
|
@ -294,37 +294,13 @@ WlSessionLock {
|
|||
}
|
||||
}
|
||||
|
||||
Corners {
|
||||
id: topRightCorner
|
||||
position: "bottomleft"
|
||||
size: 1.3
|
||||
fillColor: (Theme.backgroundPrimary !== undefined && Theme.backgroundPrimary !== null) ? Theme.backgroundPrimary : "#222"
|
||||
offsetX: screen.width / 2 + 38
|
||||
offsetY: 0
|
||||
anchors.top: parent.top
|
||||
visible: Settings.settings.showCorners
|
||||
z: 50
|
||||
}
|
||||
|
||||
Corners {
|
||||
id: topLeftCorner
|
||||
position: "bottomright"
|
||||
size: 1.3
|
||||
fillColor: (Theme.backgroundPrimary !== undefined && Theme.backgroundPrimary !== null) ? Theme.backgroundPrimary : "#222"
|
||||
offsetX: -Screen.width / 2 - 38
|
||||
offsetY: 0
|
||||
anchors.top: parent.top
|
||||
visible: Settings.settings.showCorners
|
||||
z: 51
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: infoColumn.width + 32
|
||||
height: infoColumn.height + 8
|
||||
width: infoColumn.width + 32 * Theme.scale(Screen)
|
||||
height: infoColumn.height + 8 * Theme.scale(Screen)
|
||||
color: (Theme.backgroundPrimary !== undefined && Theme.backgroundPrimary !== null) ? Theme.backgroundPrimary : "#222"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
bottomLeftRadius: 20
|
||||
bottomRightRadius: 20
|
||||
bottomLeftRadius: 20 * Theme.scale(Screen)
|
||||
bottomRightRadius: 20 * Theme.scale(Screen)
|
||||
|
||||
ColumnLayout {
|
||||
id: infoColumn
|
||||
|
|
@ -338,7 +314,7 @@ WlSessionLock {
|
|||
id: timeText
|
||||
text: Qt.formatDateTime(new Date(), "HH:mm")
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 48
|
||||
font.pixelSize: 48 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
|
@ -348,7 +324,7 @@ WlSessionLock {
|
|||
id: dateText
|
||||
text: Qt.formatDateTime(new Date(), "dddd, MMMM d")
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 16
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
opacity: 0.8
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
|
@ -364,7 +340,7 @@ WlSessionLock {
|
|||
Text {
|
||||
text: weatherData && weatherData.current_weather ? materialSymbolForCode(weatherData.current_weather.weathercode) : "cloud"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 28
|
||||
font.pixelSize: 28 * Theme.scale(Screen)
|
||||
color: Theme.accentPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
|
@ -372,7 +348,7 @@ WlSessionLock {
|
|||
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: 18
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
|
@ -383,7 +359,7 @@ WlSessionLock {
|
|||
color: Theme.error
|
||||
visible: weatherError !== ""
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 10
|
||||
font.pixelSize: 10 * Theme.scale(Screen)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
|
@ -425,12 +401,12 @@ WlSessionLock {
|
|||
spacing: 12
|
||||
|
||||
Rectangle {
|
||||
width: 48
|
||||
height: 48
|
||||
radius: 24
|
||||
width: 48 * Theme.scale(Screen)
|
||||
height: 48 * Theme.scale(Screen)
|
||||
radius: 24 * Theme.scale(Screen)
|
||||
color: shutdownArea.containsMouse ? Theme.error : "transparent"
|
||||
border.color: Theme.error
|
||||
border.width: 1
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
MouseArea {
|
||||
id: shutdownArea
|
||||
|
|
@ -445,18 +421,18 @@ WlSessionLock {
|
|||
anchors.centerIn: parent
|
||||
text: "power_settings_new"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
color: shutdownArea.containsMouse ? Theme.onAccent : Theme.error
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 48
|
||||
height: 48
|
||||
radius: 24
|
||||
width: 48 * Theme.scale(Screen)
|
||||
height: 48 * Theme.scale(Screen)
|
||||
radius: 24 * Theme.scale(Screen)
|
||||
color: rebootArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
MouseArea {
|
||||
id: rebootArea
|
||||
|
|
@ -471,18 +447,18 @@ WlSessionLock {
|
|||
anchors.centerIn: parent
|
||||
text: "refresh"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
color: rebootArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 48
|
||||
height: 48
|
||||
radius: 24
|
||||
width: 48 * Theme.scale(Screen)
|
||||
height: 48 * Theme.scale(Screen)
|
||||
radius: 24 * Theme.scale(Screen)
|
||||
color: logoutArea.containsMouse ? Theme.accentSecondary : "transparent"
|
||||
border.color: Theme.accentSecondary
|
||||
border.width: 1
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
MouseArea {
|
||||
id: logoutArea
|
||||
|
|
@ -497,7 +473,7 @@ WlSessionLock {
|
|||
anchors.centerIn: parent
|
||||
text: "exit_to_app"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
color: logoutArea.containsMouse ? Theme.onAccent : Theme.accentSecondary
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,61 +1,31 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Settings
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
|
||||
// The popup window
|
||||
PanelWithOverlay {
|
||||
id: notificationHistoryWin
|
||||
|
||||
property string historyFilePath: Settings.settingsDir + "notification_history.json"
|
||||
property bool hasUnread: notificationHistoryWinRect.hasUnread && !notificationHistoryWinRect.visible
|
||||
function addToHistory(notification) { notificationHistoryWinRect.addToHistory(notification) }
|
||||
|
||||
function addToHistory(notification) {
|
||||
notificationHistoryWinRect.addToHistory(notification);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: notificationHistoryWinRect
|
||||
implicitWidth: 400
|
||||
|
||||
property int maxPopupHeight: 800
|
||||
property int minPopupHeight: 210
|
||||
property int contentHeight: headerRow.height + historyList.contentHeight + 56
|
||||
implicitHeight: Math.max(Math.min(contentHeight, maxPopupHeight), minPopupHeight)
|
||||
visible: parent.visible
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 4
|
||||
anchors.rightMargin: 4
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 20
|
||||
|
||||
property int maxHistory: 100
|
||||
property bool hasUnread: false
|
||||
|
||||
signal unreadChanged(bool hasUnread)
|
||||
|
||||
ListModel {
|
||||
id: historyModel
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: historyFileView
|
||||
path: notificationHistoryWin.historyFilePath
|
||||
blockLoading: true
|
||||
printErrors: true
|
||||
watchChanges: true
|
||||
|
||||
JsonAdapter {
|
||||
id: historyAdapter
|
||||
property var notifications: []
|
||||
}
|
||||
|
||||
onFileChanged: historyFileView.reload()
|
||||
onLoaded: notificationHistoryWinRect.loadHistory()
|
||||
onLoadFailed: function (error) {
|
||||
historyAdapter.notifications = [];
|
||||
historyFileView.writeAdapter();
|
||||
}
|
||||
Component.onCompleted: if (path)
|
||||
reload()
|
||||
}
|
||||
|
||||
function updateHasUnread() {
|
||||
var unread = false;
|
||||
for (let i = 0; i < historyModel.count; ++i) {
|
||||
|
|
@ -80,9 +50,11 @@ PanelWithOverlay {
|
|||
if (typeof n === 'object' && n !== null) {
|
||||
if (n.read === undefined)
|
||||
n.read = false;
|
||||
|
||||
// Mark as read if window is open
|
||||
if (notificationHistoryWinRect.visible)
|
||||
n.read = true;
|
||||
|
||||
historyModel.append(n);
|
||||
}
|
||||
}
|
||||
|
|
@ -95,19 +67,19 @@ PanelWithOverlay {
|
|||
const count = Math.min(historyModel.count, maxHistory);
|
||||
for (let i = 0; i < count; ++i) {
|
||||
let obj = historyModel.get(i);
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
if (typeof obj === 'object' && obj !== null)
|
||||
historyArray.push({
|
||||
id: obj.id,
|
||||
appName: obj.appName,
|
||||
summary: obj.summary,
|
||||
body: obj.body,
|
||||
timestamp: obj.timestamp,
|
||||
read: obj.read === undefined ? false : obj.read
|
||||
"id": obj.id,
|
||||
"appName": obj.appName,
|
||||
"summary": obj.summary,
|
||||
"body": obj.body,
|
||||
"timestamp": obj.timestamp,
|
||||
"read": obj.read === undefined ? false : obj.read
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
historyAdapter.notifications = historyArray;
|
||||
Qt.callLater(function () {
|
||||
Qt.callLater(function() {
|
||||
historyFileView.writeAdapter();
|
||||
});
|
||||
updateHasUnread();
|
||||
|
|
@ -116,12 +88,12 @@ PanelWithOverlay {
|
|||
function addToHistory(notification) {
|
||||
if (!notification.id)
|
||||
notification.id = Date.now();
|
||||
|
||||
if (!notification.timestamp)
|
||||
notification.timestamp = new Date().toISOString();
|
||||
|
||||
// Mark as read if window is open
|
||||
notification.read = visible;
|
||||
|
||||
// Remove duplicate by id
|
||||
for (let i = 0; i < historyModel.count; ++i) {
|
||||
if (historyModel.get(i).id === notification.id) {
|
||||
|
|
@ -129,11 +101,10 @@ PanelWithOverlay {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
historyModel.insert(0, notification);
|
||||
|
||||
if (historyModel.count > maxHistory)
|
||||
historyModel.remove(maxHistory);
|
||||
|
||||
saveHistory();
|
||||
}
|
||||
|
||||
|
|
@ -146,6 +117,7 @@ PanelWithOverlay {
|
|||
function formatTimestamp(ts) {
|
||||
if (!ts)
|
||||
return "";
|
||||
|
||||
var date = typeof ts === "number" ? new Date(ts) : new Date(Date.parse(ts));
|
||||
var y = date.getFullYear();
|
||||
var m = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
|
|
@ -155,6 +127,15 @@ PanelWithOverlay {
|
|||
return `${y}-${m}-${d} ${h}:${min}`;
|
||||
}
|
||||
|
||||
implicitWidth: 400
|
||||
implicitHeight: Math.max(Math.min(contentHeight, maxPopupHeight), minPopupHeight)
|
||||
visible: parent.visible
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 4
|
||||
anchors.rightMargin: 4
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 20
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
// Mark all as read when popup is opened
|
||||
|
|
@ -167,9 +148,46 @@ PanelWithOverlay {
|
|||
}
|
||||
if (changed)
|
||||
saveHistory();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent closing when clicking in the panel bg
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: historyModel
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: historyFileView
|
||||
|
||||
path: notificationHistoryWin.historyFilePath
|
||||
blockLoading: true
|
||||
printErrors: true
|
||||
watchChanges: true
|
||||
onFileChanged: historyFileView.reload()
|
||||
onLoaded: notificationHistoryWinRect.loadHistory()
|
||||
onLoadFailed: function(error) {
|
||||
historyAdapter.notifications = [];
|
||||
historyFileView.writeAdapter();
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (path) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
JsonAdapter {
|
||||
id: historyAdapter
|
||||
|
||||
property var notifications: []
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: notificationHistoryWinRect.width
|
||||
height: notificationHistoryWinRect.height
|
||||
|
|
@ -184,6 +202,7 @@ PanelWithOverlay {
|
|||
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
|
||||
spacing: 4
|
||||
anchors.topMargin: 4
|
||||
anchors.left: parent.left
|
||||
|
|
@ -193,6 +212,7 @@ PanelWithOverlay {
|
|||
Layout.preferredHeight: 52
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
|
||||
Text {
|
||||
text: "Notification History"
|
||||
font.pixelSize: 18
|
||||
|
|
@ -200,11 +220,14 @@ PanelWithOverlay {
|
|||
color: Theme.textPrimary
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clearAllButton
|
||||
|
||||
width: 90
|
||||
height: 32
|
||||
radius: 16
|
||||
|
|
@ -212,9 +235,11 @@ PanelWithOverlay {
|
|||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
|
||||
Text {
|
||||
text: "delete_sweep"
|
||||
font.family: "Material Symbols Outlined"
|
||||
|
|
@ -222,6 +247,7 @@ PanelWithOverlay {
|
|||
color: clearAllMouseArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Clear"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
|
|
@ -229,15 +255,20 @@ PanelWithOverlay {
|
|||
font.bold: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clearAllMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: notificationHistoryWinRect.clearHistory()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
|
@ -261,29 +292,36 @@ PanelWithOverlay {
|
|||
radius: 20
|
||||
z: 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: listContainer
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 12
|
||||
anchors.bottomMargin: 12
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
ListView {
|
||||
id: historyList
|
||||
|
||||
width: parent.width
|
||||
height: Math.min(contentHeight, parent.height)
|
||||
spacing: 12
|
||||
model: historyModel.count > 0 ? historyModel : placeholderModel
|
||||
clip: true
|
||||
|
||||
delegate: Item {
|
||||
width: parent.width
|
||||
height: notificationCard.implicitHeight + 12
|
||||
|
||||
Rectangle {
|
||||
id: notificationCard
|
||||
|
||||
width: parent.width - 24
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.backgroundPrimary
|
||||
|
|
@ -292,16 +330,22 @@ PanelWithOverlay {
|
|||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 0
|
||||
implicitHeight: contentColumn.implicitHeight + 20
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 14
|
||||
spacing: 6
|
||||
|
||||
RowLayout {
|
||||
id: headerRow2
|
||||
|
||||
spacing: 8
|
||||
|
||||
Rectangle {
|
||||
id: iconBackground
|
||||
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 20
|
||||
|
|
@ -309,6 +353,7 @@ PanelWithOverlay {
|
|||
border.color: Qt.darker(Theme.accentPrimary, 1.2)
|
||||
border.width: 1.2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: model.appName ? model.appName.charAt(0).toUpperCase() : "?"
|
||||
|
|
@ -317,11 +362,15 @@ PanelWithOverlay {
|
|||
font.bold: true
|
||||
color: Theme.backgroundPrimary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
id: appInfoColumn
|
||||
|
||||
spacing: 0
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Text {
|
||||
text: model.appName || "No Notifications"
|
||||
font.bold: true
|
||||
|
|
@ -330,6 +379,7 @@ PanelWithOverlay {
|
|||
font.pixelSize: Theme.fontSizeSmall
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: !model.isPlaceholder
|
||||
text: model.timestamp ? notificationHistoryWinRect.formatTimestamp(model.timestamp) : ""
|
||||
|
|
@ -338,11 +388,15 @@ PanelWithOverlay {
|
|||
font.pixelSize: Theme.fontSizeCaption
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
text: model.summary || (model.isPlaceholder ? "You're all caught up!" : "")
|
||||
color: Theme.textSecondary
|
||||
|
|
@ -351,6 +405,7 @@ PanelWithOverlay {
|
|||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Text {
|
||||
text: model.body || (model.isPlaceholder ? "No notifications to show." : "")
|
||||
color: Theme.textDisabled
|
||||
|
|
@ -359,12 +414,19 @@ PanelWithOverlay {
|
|||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
|
@ -375,14 +437,20 @@ PanelWithOverlay {
|
|||
|
||||
ListModel {
|
||||
id: placeholderModel
|
||||
|
||||
ListElement {
|
||||
appName: ""
|
||||
summary: ""
|
||||
body: ""
|
||||
isPlaceholder: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,25 +8,49 @@ Item {
|
|||
id: root
|
||||
width: 22; height: 22
|
||||
property bool isSilence: false
|
||||
property var shell: null
|
||||
|
||||
// Process for executing CLI commands
|
||||
Process {
|
||||
id: rightClickProcess
|
||||
command: ["qs","ipc", "call", "globalIPC", "toggleNotificationPopup"]
|
||||
}
|
||||
|
||||
// Bell icon/button
|
||||
// Timer to check when NotificationHistory is loaded
|
||||
Timer {
|
||||
id: checkHistoryTimer
|
||||
interval: 50
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (shell && shell.notificationHistoryWin) {
|
||||
shell.notificationHistoryWin.visible = true;
|
||||
checkHistoryTimer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: bell
|
||||
width: 22; height: 22
|
||||
Text {
|
||||
id: bellText
|
||||
anchors.centerIn: parent
|
||||
text: notificationHistoryWin.hasUnread ? "notifications_unread" : "notifications"
|
||||
text: {
|
||||
if (shell && shell.notificationHistoryWin && shell.notificationHistoryWin.hasUnread) {
|
||||
return "notifications_unread";
|
||||
} else {
|
||||
return "notifications";
|
||||
}
|
||||
}
|
||||
font.family: mouseAreaBell.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
font.weight: notificationHistoryWin.hasUnread ? Font.Bold : Font.Normal
|
||||
color: mouseAreaBell.containsMouse ? Theme.accentPrimary : (notificationHistoryWin.hasUnread ? Theme.accentPrimary : Theme.textDisabled)
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
font.weight: {
|
||||
if (shell && shell.notificationHistoryWin && shell.notificationHistoryWin.hasUnread) {
|
||||
return Font.Bold;
|
||||
} else {
|
||||
return Font.Normal;
|
||||
}
|
||||
}
|
||||
color: mouseAreaBell.containsMouse ? Theme.accentPrimary : (shell && shell.notificationHistoryWin && shell.notificationHistoryWin.hasUnread ? Theme.accentPrimary : Theme.textDisabled)
|
||||
}
|
||||
MouseArea {
|
||||
id: mouseAreaBell
|
||||
|
|
@ -42,10 +66,18 @@ Item {
|
|||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton){
|
||||
notificationHistoryWin.visible = !notificationHistoryWin.visible
|
||||
return;
|
||||
if (shell) {
|
||||
if (!shell.notificationHistoryWin) {
|
||||
// Use the shell function to load notification history
|
||||
shell.loadNotificationHistory();
|
||||
checkHistoryTimer.start();
|
||||
} else {
|
||||
// Already loaded, just toggle visibility
|
||||
shell.notificationHistoryWin.visible = !shell.notificationHistoryWin.visible;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
onEntered: notificationTooltip.tooltipVisible = true
|
||||
onExited: notificationTooltip.tooltipVisible = false
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ PanelWindow {
|
|||
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.top: -20 // keep as you want
|
||||
margins.top: -20
|
||||
margins.right: 6
|
||||
|
||||
property var notifications: []
|
||||
|
|
@ -52,7 +52,7 @@ PanelWindow {
|
|||
anchors.right: parent.right
|
||||
spacing: window.spacing
|
||||
width: parent.width
|
||||
clip: false // prevent clipping during animation
|
||||
clip: false // Prevent clipping during animation
|
||||
|
||||
Repeater {
|
||||
model: notifications
|
||||
|
|
|
|||
|
|
@ -4,310 +4,389 @@ import Quickshell
|
|||
import Quickshell.Widgets
|
||||
import qs.Settings
|
||||
|
||||
PanelWindow {
|
||||
id: window
|
||||
implicitWidth: 350
|
||||
implicitHeight: notificationColumn.implicitHeight
|
||||
color: "transparent"
|
||||
visible: notificationsVisible && notificationModel.count > 0
|
||||
screen: Quickshell.primaryScreen !== undefined ? Quickshell.primaryScreen : null
|
||||
focusable: false
|
||||
// Main container that manages multiple notification popups for different monitors
|
||||
Item {
|
||||
id: notificationManager
|
||||
anchors.fill: parent
|
||||
|
||||
property bool barVisible: true
|
||||
// Get list of available monitors/screens
|
||||
property var monitors: Quickshell.screens || []
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("[NotificationPopup] Initialized with", monitors.length, "monitors");
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
console.log("[NotificationPopup] Monitor", i, ":", monitors[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
// Global visibility state for all notification popups
|
||||
property bool notificationsVisible: true
|
||||
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.top: 6
|
||||
margins.right: 6
|
||||
|
||||
ListModel {
|
||||
id: notificationModel
|
||||
}
|
||||
|
||||
property int maxVisible: 5
|
||||
property int spacing: 5
|
||||
|
||||
|
||||
function togglePopup(): void {
|
||||
console.log("[NotificationPopup] Current state: " + notificationsVisible);
|
||||
console.log("[NotificationManager] Current state: " + notificationsVisible);
|
||||
notificationsVisible = !notificationsVisible;
|
||||
console.log("[NotificationPopup] New state: " + notificationsVisible);
|
||||
console.log("[NotificationManager] New state: " + notificationsVisible);
|
||||
}
|
||||
|
||||
function addNotification(notification) {
|
||||
notificationModel.insert(0, {
|
||||
id: notification.id,
|
||||
appName: notification.appName || "Notification",
|
||||
summary: notification.summary || "",
|
||||
body: notification.body || "",
|
||||
urgency: notification.urgency || 0,
|
||||
rawNotification: notification,
|
||||
appeared: false,
|
||||
dismissed: false
|
||||
});
|
||||
|
||||
while (notificationModel.count > maxVisible) {
|
||||
notificationModel.remove(notificationModel.count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
function dismissNotificationById(id) {
|
||||
for (var i = 0; i < notificationModel.count; i++) {
|
||||
if (notificationModel.get(i).id === id) {
|
||||
dismissNotificationByIndex(i);
|
||||
break;
|
||||
|
||||
function addNotification(notification): void {
|
||||
console.log("[NotificationPopup] Adding notification to popup manager");
|
||||
// Add notification to all monitor popups
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let child = children[i];
|
||||
if (child.addNotification) {
|
||||
child.addNotification(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dismissNotificationByIndex(index) {
|
||||
if (index >= 0 && index < notificationModel.count) {
|
||||
var notif = notificationModel.get(index);
|
||||
if (!notif.dismissed) {
|
||||
notificationModel.set(index, {
|
||||
id: notif.id,
|
||||
appName: notif.appName,
|
||||
summary: notif.summary,
|
||||
body: notif.body,
|
||||
rawNotification: notif.rawNotification,
|
||||
appeared: notif.appeared,
|
||||
dismissed: true
|
||||
});
|
||||
// Create a notification popup for each monitor
|
||||
Repeater {
|
||||
model: notificationManager.monitors
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
|
||||
// Make addNotification accessible from the Item level
|
||||
function addNotification(notification) {
|
||||
if (panelWindow) {
|
||||
panelWindow.addNotification(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: panelWindow
|
||||
implicitWidth: 350
|
||||
implicitHeight: Math.max(notificationColumn.height, 0)
|
||||
color: "transparent"
|
||||
visible: notificationManager.notificationsVisible && notificationModel.count > 0 && shouldShowOnThisMonitor
|
||||
screen: modelData
|
||||
focusable: false
|
||||
|
||||
Column {
|
||||
id: notificationColumn
|
||||
anchors.right: parent.right
|
||||
spacing: window.spacing
|
||||
width: parent.width
|
||||
clip: false
|
||||
property bool barVisible: true
|
||||
property bool notificationsVisible: notificationManager.notificationsVisible
|
||||
|
||||
// Check if this monitor should show notifications - make it reactive to settings changes
|
||||
property bool shouldShowOnThisMonitor: {
|
||||
let notificationMonitors = Settings.settings.notificationMonitors || [];
|
||||
let currentScreenName = modelData ? modelData.name : "";
|
||||
// Show notifications on all monitors if notificationMonitors is empty or contains "*"
|
||||
let shouldShow = notificationMonitors.length === 0 ||
|
||||
notificationMonitors.includes("*") ||
|
||||
notificationMonitors.includes(currentScreenName);
|
||||
console.log("[NotificationPopup] Monitor", currentScreenName, "should show:", shouldShow, "monitors:", JSON.stringify(notificationMonitors));
|
||||
return shouldShow;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: notificationRepeater
|
||||
model: notificationModel
|
||||
// Watch for changes in notification monitors setting
|
||||
Connections {
|
||||
target: Settings.settings
|
||||
function onNotificationMonitorsChanged() {
|
||||
// Settings changed, visibility will update automatically
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: notificationDelegate
|
||||
width: parent.width
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 20
|
||||
border.color: model.urgency == 2 ? Theme.warning : Theme.outline
|
||||
border.width: 1
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.top: 6
|
||||
margins.right: 6
|
||||
|
||||
property bool appeared: model.appeared
|
||||
property bool dismissed: model.dismissed
|
||||
property var rawNotification: model.rawNotification
|
||||
ListModel {
|
||||
id: notificationModel
|
||||
}
|
||||
|
||||
x: appeared ? 0 : width
|
||||
opacity: dismissed ? 0 : 1
|
||||
height: dismissed ? 0 : contentRow.height + 20
|
||||
property int maxVisible: 5
|
||||
property int spacing: 5
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
width: parent.width - 20
|
||||
function addNotification(notification) {
|
||||
console.log("[NotificationPopup] Adding notification to monitor popup:", notification.appName);
|
||||
notificationModel.insert(0, {
|
||||
id: notification.id,
|
||||
appName: notification.appName || "Notification",
|
||||
summary: notification.summary || "",
|
||||
body: notification.body || "",
|
||||
urgency: notification.urgency || 0,
|
||||
rawNotification: notification,
|
||||
appeared: false,
|
||||
dismissed: false
|
||||
});
|
||||
|
||||
// Circular Icon container with border
|
||||
Rectangle {
|
||||
id: iconBackground
|
||||
width: 36
|
||||
height: 36
|
||||
radius: width / 2 // Circular
|
||||
color: Theme.accentPrimary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
border.color: Qt.darker(Theme.accentPrimary, 1.2)
|
||||
border.width: 1.5
|
||||
while (notificationModel.count > maxVisible) {
|
||||
notificationModel.remove(notificationModel.count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Get all possible icon sources from notification
|
||||
property var iconSources: [rawNotification?.image || "", rawNotification?.appIcon || "", rawNotification?.icon || ""]
|
||||
|
||||
// Try to load notification icon
|
||||
IconImage {
|
||||
id: iconImage
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
asynchronous: true
|
||||
backer.fillMode: Image.PreserveAspectFit
|
||||
source: {
|
||||
for (var i = 0; i < iconBackground.iconSources.length; i++) {
|
||||
var icon = iconBackground.iconSources[i];
|
||||
if (!icon)
|
||||
continue;
|
||||
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=");
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
|
||||
if (icon.startsWith('/')) {
|
||||
return "file://" + icon;
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
visible: status === Image.Ready && source.toString() !== ""
|
||||
function dismissNotificationById(id) {
|
||||
for (var i = 0; i < notificationModel.count; i++) {
|
||||
if (notificationModel.get(i).id === id) {
|
||||
dismissNotificationByIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to first letter of app name
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !iconImage.visible
|
||||
text: model.appName ? model.appName.charAt(0).toUpperCase() : "?"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
font.bold: true
|
||||
function dismissNotificationByIndex(index) {
|
||||
if (index >= 0 && index < notificationModel.count) {
|
||||
var notif = notificationModel.get(index);
|
||||
if (!notif.dismissed) {
|
||||
notificationModel.set(index, {
|
||||
id: notif.id,
|
||||
appName: notif.appName,
|
||||
summary: notif.summary,
|
||||
body: notif.body,
|
||||
rawNotification: notif.rawNotification,
|
||||
appeared: notif.appeared,
|
||||
dismissed: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: notificationColumn
|
||||
anchors.right: parent.right
|
||||
spacing: panelWindow.spacing
|
||||
width: parent.width
|
||||
clip: false
|
||||
|
||||
Repeater {
|
||||
id: notificationRepeater
|
||||
model: notificationModel
|
||||
|
||||
delegate: Rectangle {
|
||||
id: notificationDelegate
|
||||
width: parent.width
|
||||
color: Theme.backgroundPrimary
|
||||
}
|
||||
}
|
||||
radius: 20
|
||||
border.color: model.urgency == 2 ? Theme.warning : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
width: contentRow.width - iconBackground.width - 10
|
||||
spacing: 5
|
||||
property bool appeared: model.appeared
|
||||
property bool dismissed: model.dismissed
|
||||
property var rawNotification: model.rawNotification
|
||||
|
||||
Text {
|
||||
text: model.appName
|
||||
width: parent.width
|
||||
color: Theme.textPrimary
|
||||
font.family: Theme.fontFamily
|
||||
font.bold: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Text {
|
||||
text: model.summary
|
||||
width: parent.width
|
||||
color: "#eeeeee"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
wrapMode: Text.Wrap
|
||||
visible: text !== ""
|
||||
}
|
||||
Text {
|
||||
text: model.body
|
||||
width: parent.width
|
||||
color: "#cccccc"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
wrapMode: Text.Wrap
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
x: appeared ? 0 : width
|
||||
opacity: dismissed ? 0 : 1
|
||||
height: dismissed ? 0 : Math.max(contentRow.height, 60) + 20
|
||||
|
||||
Timer {
|
||||
interval: 4000
|
||||
running: !dismissed
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
dismissAnimation.start();
|
||||
if (rawNotification)
|
||||
rawNotification.expire();
|
||||
}
|
||||
}
|
||||
Row {
|
||||
id: contentRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
width: parent.width - 20
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
dismissAnimation.start();
|
||||
if (rawNotification)
|
||||
rawNotification.dismiss();
|
||||
}
|
||||
}
|
||||
// Circular Icon container with border
|
||||
Rectangle {
|
||||
id: iconBackground
|
||||
width: 36
|
||||
height: 36
|
||||
radius: width / 2
|
||||
color: Theme.accentPrimary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
border.color: Qt.darker(Theme.accentPrimary, 1.2)
|
||||
border.width: 1.5
|
||||
|
||||
ParallelAnimation {
|
||||
id: dismissAnimation
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "opacity"
|
||||
to: 0
|
||||
duration: 150
|
||||
}
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "height"
|
||||
to: 0
|
||||
duration: 150
|
||||
}
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "x"
|
||||
to: width
|
||||
duration: 150
|
||||
easing.type: Easing.InQuad
|
||||
}
|
||||
onFinished: {
|
||||
for (let i = 0; i < notificationModel.count; i++) {
|
||||
if (notificationModel.get(i).id === notificationDelegate.id) {
|
||||
notificationModel.remove(i);
|
||||
break;
|
||||
// Priority order for notification icons: image > appIcon > icon
|
||||
property var iconSources: [rawNotification?.image || "", rawNotification?.appIcon || "", rawNotification?.icon || ""]
|
||||
|
||||
// Load notification icon with fallback handling
|
||||
IconImage {
|
||||
id: iconImage
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
asynchronous: true
|
||||
backer.fillMode: Image.PreserveAspectFit
|
||||
source: {
|
||||
// Try each icon source in priority order
|
||||
for (var i = 0; i < iconBackground.iconSources.length; i++) {
|
||||
var icon = iconBackground.iconSources[i];
|
||||
if (!icon)
|
||||
continue;
|
||||
|
||||
// Handle special path format from some notifications
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=");
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
|
||||
// Handle absolute file paths
|
||||
if (icon.startsWith('/')) {
|
||||
return "file://" + icon;
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
visible: status === Image.Ready && source.toString() !== ""
|
||||
}
|
||||
|
||||
// Fallback: show first letter of app name when no icon available
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !iconImage.visible
|
||||
text: model.appName ? model.appName.charAt(0).toUpperCase() : "?"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
font.bold: true
|
||||
color: Theme.backgroundPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: contentRow.width - iconBackground.width - 10
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
text: model.appName
|
||||
width: parent.width
|
||||
color: Theme.textPrimary
|
||||
font.family: Theme.fontFamily
|
||||
font.bold: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Text {
|
||||
text: model.summary
|
||||
width: parent.width
|
||||
color: "#eeeeee"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
wrapMode: Text.Wrap
|
||||
visible: text !== ""
|
||||
}
|
||||
Text {
|
||||
text: model.body
|
||||
width: parent.width
|
||||
color: "#cccccc"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
wrapMode: Text.Wrap
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 4000
|
||||
running: !dismissed
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
dismissAnimation.start();
|
||||
if (rawNotification)
|
||||
rawNotification.expire();
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
dismissAnimation.start();
|
||||
if (rawNotification)
|
||||
rawNotification.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
id: dismissAnimation
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "opacity"
|
||||
to: 0
|
||||
duration: 150
|
||||
}
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "height"
|
||||
to: 0
|
||||
duration: 150
|
||||
}
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "x"
|
||||
to: width
|
||||
duration: 150
|
||||
easing.type: Easing.InQuad
|
||||
}
|
||||
onFinished: {
|
||||
for (let i = 0; i < notificationModel.count; i++) {
|
||||
if (notificationModel.get(i).id === notificationDelegate.id) {
|
||||
notificationModel.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
id: appearAnimation
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "opacity"
|
||||
to: 1
|
||||
duration: 150
|
||||
}
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "height"
|
||||
to: Math.max(contentRow.height, 60) + 20
|
||||
duration: 150
|
||||
}
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "x"
|
||||
to: 0
|
||||
duration: 150
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: appearTimer
|
||||
interval: 10
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
appearAnimation.start();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!appeared) {
|
||||
opacity = 0;
|
||||
height = 0;
|
||||
x = width;
|
||||
// Small delay to ensure contentRow has proper height
|
||||
appearTimer.start();
|
||||
for (let i = 0; i < notificationModel.count; i++) {
|
||||
if (notificationModel.get(i).id === notificationDelegate.id) {
|
||||
var oldItem = notificationModel.get(i);
|
||||
notificationModel.set(i, {
|
||||
id: oldItem.id,
|
||||
appName: oldItem.appName,
|
||||
summary: oldItem.summary,
|
||||
body: oldItem.body,
|
||||
rawNotification: oldItem.rawNotification,
|
||||
appeared: true,
|
||||
read: oldItem.read,
|
||||
dismissed: oldItem.dismissed
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
id: appearAnimation
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "opacity"
|
||||
to: 1
|
||||
duration: 150
|
||||
}
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "height"
|
||||
to: contentRow.height + 20
|
||||
duration: 150
|
||||
}
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "x"
|
||||
to: 0
|
||||
duration: 150
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!appeared) {
|
||||
opacity = 0;
|
||||
height = 0;
|
||||
x = width;
|
||||
appearAnimation.start();
|
||||
for (let i = 0; i < notificationModel.count; i++) {
|
||||
if (notificationModel.get(i).id === notificationDelegate.id) {
|
||||
var oldItem = notificationModel.get(i);
|
||||
notificationModel.set(i, {
|
||||
id: oldItem.id,
|
||||
appName: oldItem.appName,
|
||||
summary: oldItem.summary,
|
||||
body: oldItem.body,
|
||||
rawNotification: oldItem.rawNotification,
|
||||
appeared: true,
|
||||
read: oldItem.read,
|
||||
dismissed: oldItem.dismissed
|
||||
});
|
||||
break;
|
||||
}
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onScreensChanged() {
|
||||
if (panelWindow.screen) {
|
||||
x = panelWindow.screen.width - panelWindow.width - 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onScreensChanged() {
|
||||
if (window.screen) {
|
||||
x = window.screen.width - width - 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,15 +34,15 @@ ShellRoot {
|
|||
cache: true
|
||||
smooth: true
|
||||
mipmap: false
|
||||
visible: wallpaperSource !== "" // Show the original for FastBlur input
|
||||
visible: wallpaperSource !== ""
|
||||
}
|
||||
MultiEffect {
|
||||
id: overviewBgBlur
|
||||
anchors.fill: parent
|
||||
source: bgImage
|
||||
blurEnabled: true
|
||||
blur: 0.48 // controls blur strength (0 to 1)
|
||||
blurMax: 128 // max blur radius in pixels
|
||||
blur: 0.48
|
||||
blurMax: 128
|
||||
}
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
|
|
|||
500
Widgets/SettingsWindow/SettingsWindow.qml
Normal file
500
Widgets/SettingsWindow/SettingsWindow.qml
Normal file
|
|
@ -0,0 +1,500 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
import qs.Widgets.SettingsWindow.Tabs
|
||||
import qs.Widgets.SettingsWindow.Tabs.Components
|
||||
|
||||
PanelWithOverlay {
|
||||
id: panelMain
|
||||
|
||||
property int activeTabIndex: 0
|
||||
|
||||
// Function to show wallpaper selector
|
||||
function showWallpaperSelector() {
|
||||
if (wallpaperSelector)
|
||||
wallpaperSelector.show();
|
||||
|
||||
}
|
||||
|
||||
// Function to show settings window
|
||||
function showSettings() {
|
||||
show();
|
||||
}
|
||||
|
||||
// Function to load component for a specific tab
|
||||
function loadComponentForTab(tabIndex) {
|
||||
const componentMap = {
|
||||
"0": generalSettings,
|
||||
"1": barSettings,
|
||||
"2": timeWeatherSettings,
|
||||
"3": recordingSettings,
|
||||
"4": networkSettings,
|
||||
"5": displaySettings,
|
||||
"6": wallpaperSettings,
|
||||
"7": miscSettings,
|
||||
"8": aboutSettings
|
||||
};
|
||||
const tabNames = ["General", "Bar", "Time & Weather", "Screen Recorder", "Network", "Display", "Wallpaper", "Misc", "About"];
|
||||
if (componentMap[tabIndex]) {
|
||||
settingsLoader.sourceComponent = componentMap[tabIndex];
|
||||
if (tabName)
|
||||
tabName.text = tabNames[tabIndex];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
// Handle activeTabIndex changes
|
||||
onActiveTabIndexChanged: {
|
||||
if (activeTabIndex >= 0 && activeTabIndex <= 8)
|
||||
loadComponentForTab(activeTabIndex);
|
||||
|
||||
}
|
||||
// Add safety checks for component loading
|
||||
Component.onCompleted: {
|
||||
// Ensure we start with a valid tab
|
||||
if (activeTabIndex < 0 || activeTabIndex > 8)
|
||||
activeTabIndex = 0;
|
||||
|
||||
}
|
||||
// Cleanup when window is hidden
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
// Reset to default tab when hiding to prevent state issues
|
||||
activeTabIndex = 0;
|
||||
if (tabName)
|
||||
tabName.text = "General";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: generalSettings
|
||||
|
||||
General {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: barSettings
|
||||
|
||||
Bar {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: timeWeatherSettings
|
||||
|
||||
TimeWeather {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: recordingSettings
|
||||
|
||||
ScreenRecorder {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: networkSettings
|
||||
|
||||
Network {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: miscSettings
|
||||
|
||||
Misc {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: aboutSettings
|
||||
|
||||
About {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: displaySettings
|
||||
|
||||
Display {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: wallpaperSettings
|
||||
|
||||
Wallpaper {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: settingsWindowRect
|
||||
|
||||
implicitWidth: Quickshell.screens.length > 0 ? Math.min(Quickshell.screens[0].width * 2 / 3, 1200) * Theme.scale(Screen) : 600 * Theme.scale(Screen)
|
||||
implicitHeight: Quickshell.screens.length > 0 ? Math.min(Quickshell.screens[0].height * 2 / 3, 800) * Theme.scale(Screen) : 400 * Theme.scale(Screen)
|
||||
visible: parent.visible
|
||||
color: "transparent"
|
||||
// Center the settings window on screen
|
||||
anchors.centerIn: parent
|
||||
|
||||
// Prevent closing when clicking in the panel bg
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
// Background rectangle
|
||||
Rectangle {
|
||||
id: background
|
||||
|
||||
color: Theme.backgroundPrimary
|
||||
anchors.fill: parent
|
||||
radius: 20 * Theme.scale(Screen)
|
||||
border.color: Theme.outline
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
MultiEffect {
|
||||
source: background
|
||||
anchors.fill: background
|
||||
shadowEnabled: true
|
||||
shadowColor: Theme.shadow
|
||||
shadowOpacity: 0.3
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 2
|
||||
shadowBlur: 12
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: settings
|
||||
clip: true
|
||||
|
||||
color: Theme.backgroundPrimary
|
||||
topRightRadius: 20 * Theme.scale(Screen)
|
||||
bottomRightRadius: 20 * Theme.scale(Screen)
|
||||
|
||||
anchors {
|
||||
left: tabs.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
margins: 12
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: headerArea
|
||||
|
||||
height: 48 * Theme.scale(Screen)
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: 16
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 12 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
id: tabName
|
||||
|
||||
text: wallpaperSelector.visible ? "Select Wallpaper" : (activeTabIndex === 0 ? "General" : activeTabIndex === 1 ? "Bar" : activeTabIndex === 2 ? "Time & Weather" : activeTabIndex === 3 ? "Screen Recorder" : activeTabIndex === 4 ? "Network" : activeTabIndex === 5 ? "Display" : activeTabIndex === 6 ? "Wallpaper" : activeTabIndex === 7 ? "Misc" : activeTabIndex === 8 ? "About" : "General")
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Wallpaper Selection Button (only visible on Wallpaper tab)
|
||||
Rectangle {
|
||||
width: 160 * Theme.scale(Screen)
|
||||
height: 32 * Theme.scale(Screen)
|
||||
radius: 16 * Theme.scale(Screen)
|
||||
color: wallpaperButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
visible: activeTabIndex === 6 // Wallpaper tab index
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 6 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
text: "image"
|
||||
font.family: wallpaperButtonArea.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: wallpaperButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Select Wallpaper"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: wallpaperButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wallpaperButtonArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
// Show the wallpaper selector
|
||||
wallpaperSelector.show();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32 * Theme.scale(Screen)
|
||||
height: 32 * Theme.scale(Screen)
|
||||
radius: 16 * Theme.scale(Screen)
|
||||
color: closeButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "close"
|
||||
font.family: closeButtonArea.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
color: closeButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeButtonArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
// If wallpaper selector is open, close it instead of the settings window
|
||||
if (wallpaperSelector.visible) {
|
||||
wallpaperSelector.hide();
|
||||
} else {
|
||||
panelMain.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1 * Theme.scale(Screen)
|
||||
color: Theme.outline
|
||||
opacity: 0.3
|
||||
|
||||
anchors {
|
||||
top: headerArea.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: 16
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: settingsContainer
|
||||
|
||||
anchors {
|
||||
top: headerArea.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
topMargin: 32
|
||||
}
|
||||
|
||||
// Simplified single loader approach
|
||||
Loader {
|
||||
id: settingsLoader
|
||||
|
||||
anchors.fill: parent
|
||||
sourceComponent: generalSettings
|
||||
active: true
|
||||
}
|
||||
|
||||
// Wallpaper Selector Component - positioned as overlay
|
||||
WallpaperSelector {
|
||||
id: wallpaperSelector
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: tabs
|
||||
|
||||
color: Theme.surface
|
||||
width: parent.width * 0.25
|
||||
height: settingsWindowRect.height
|
||||
topLeftRadius: 20 * Theme.scale(Screen)
|
||||
bottomLeftRadius: 20 * Theme.scale(Screen)
|
||||
border.color: Theme.outline
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 0 * Theme.scale(Screen)
|
||||
topPadding: 8 * Theme.scale(Screen)
|
||||
bottomPadding: 8 * Theme.scale(Screen)
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
model: [{
|
||||
"icon": "tune",
|
||||
"text": "General"
|
||||
}, {
|
||||
"icon": "space_dashboard",
|
||||
"text": "Bar"
|
||||
}, {
|
||||
"icon": "schedule",
|
||||
"text": "Time & Weather"
|
||||
}, {
|
||||
"icon": "photo_camera",
|
||||
"text": "Screen Recorder"
|
||||
}, {
|
||||
"icon": "wifi",
|
||||
"text": "Network"
|
||||
}, {
|
||||
"icon": "monitor",
|
||||
"text": "Display"
|
||||
}, {
|
||||
"icon": "wallpaper",
|
||||
"text": "Wallpaper"
|
||||
}, {
|
||||
"icon": "settings_suggest",
|
||||
"text": "Misc"
|
||||
}, {
|
||||
"icon": "info",
|
||||
"text": "About"
|
||||
}]
|
||||
|
||||
delegate: Rectangle {
|
||||
width: tabs.width
|
||||
height: 48 * Theme.scale(Screen)
|
||||
color: "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 8 * Theme.scale(Screen)
|
||||
|
||||
Rectangle {
|
||||
id: activeIndicator
|
||||
|
||||
Layout.leftMargin: 8 * Theme.scale(Screen)
|
||||
Layout.preferredWidth: 3 * Theme.scale(Screen)
|
||||
Layout.preferredHeight: 24 * Theme.scale(Screen)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
radius: 2 * Theme.scale(Screen)
|
||||
color: Theme.accentPrimary
|
||||
opacity: index === activeTabIndex ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
id: icon
|
||||
|
||||
text: modelData.icon
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
color: index === activeTabIndex ? Theme.accentPrimary : Theme.textPrimary
|
||||
opacity: index === activeTabIndex ? 1 : 0.8
|
||||
Layout.leftMargin: 20 * Theme.scale(Screen)
|
||||
Layout.preferredWidth: 24 * Theme.scale(Screen)
|
||||
Layout.preferredHeight: 24 * Theme.scale(Screen)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.variableAxes: { "wght": (Font.Normal + Font.Bold) / 2.0 }
|
||||
}
|
||||
|
||||
Label {
|
||||
id: label
|
||||
|
||||
text: modelData.text
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: index === activeTabIndex ? Theme.accentPrimary : (tabMouseArea.containsMouse ? Theme.accentPrimary : Theme.textSecondary)
|
||||
font.weight: index === activeTabIndex ? Font.DemiBold : (tabMouseArea.containsMouse ? Font.DemiBold : Font.Normal)
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 24 * Theme.scale(Screen)
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.leftMargin: 4 * Theme.scale(Screen)
|
||||
Layout.rightMargin: 16 * Theme.scale(Screen)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: tabMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
activeTabIndex = index;
|
||||
loadComponentForTab(index);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1 * Theme.scale(Screen)
|
||||
color: Theme.outline
|
||||
opacity: 0.6
|
||||
visible: index < (repeater.count - 1)
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
441
Widgets/SettingsWindow/Tabs/About.qml
Normal file
441
Widgets/SettingsWindow/Tabs/About.qml
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property string latestVersion: "Unknown"
|
||||
property string currentVersion: "Unknown"
|
||||
property var contributors: []
|
||||
property string githubDataPath: Settings.settingsDir + "github_data.json"
|
||||
|
||||
function loadFromFile() {
|
||||
const now = Date.now();
|
||||
const data = githubData;
|
||||
if (!data.timestamp || (now - data.timestamp > 3.6e+06)) {
|
||||
console.log("[About] Cache expired or missing, fetching new data from GitHub...");
|
||||
fetchFromGitHub();
|
||||
return ;
|
||||
}
|
||||
console.log("[About] Loading cached GitHub data (age: " + Math.round((now - data.timestamp) / 60000) + " minutes)");
|
||||
if (data.version)
|
||||
root.latestVersion = data.version;
|
||||
|
||||
if (data.contributors)
|
||||
root.contributors = data.contributors;
|
||||
|
||||
}
|
||||
|
||||
function fetchFromGitHub() {
|
||||
versionProcess.running = true;
|
||||
contributorsProcess.running = true;
|
||||
}
|
||||
|
||||
function saveData() {
|
||||
githubData.timestamp = Date.now();
|
||||
Qt.callLater(() => {
|
||||
githubDataFile.writeAdapter();
|
||||
});
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
|
||||
Process {
|
||||
id: currentVersionProcess
|
||||
|
||||
command: ["sh", "-c", "cd " + Quickshell.shellDir + " && git describe --tags --abbrev=0 2>/dev/null || echo 'Unknown'"]
|
||||
Component.onCompleted: {
|
||||
running = true;
|
||||
}
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const version = text.trim();
|
||||
if (version && version !== "Unknown") {
|
||||
root.currentVersion = version;
|
||||
} else {
|
||||
currentVersionProcess.command = ["sh", "-c", "cd " + Quickshell.shellDir + " && cat package.json 2>/dev/null | grep '\"version\"' | cut -d'\"' -f4 || echo 'Unknown'"];
|
||||
currentVersionProcess.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: githubDataFile
|
||||
|
||||
path: root.githubDataPath
|
||||
blockLoading: true
|
||||
printErrors: true
|
||||
watchChanges: true
|
||||
onFileChanged: githubDataFile.reload()
|
||||
onLoaded: loadFromFile()
|
||||
onLoadFailed: function(error) {
|
||||
console.log("GitHub data file doesn't exist yet, creating it...");
|
||||
githubData.version = "Unknown";
|
||||
githubData.contributors = [];
|
||||
githubData.timestamp = 0;
|
||||
githubDataFile.writeAdapter();
|
||||
fetchFromGitHub();
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (path)
|
||||
reload();
|
||||
|
||||
}
|
||||
|
||||
JsonAdapter {
|
||||
id: githubData
|
||||
|
||||
property string version: "Unknown"
|
||||
property var contributors: []
|
||||
property double timestamp: 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Process {
|
||||
id: versionProcess
|
||||
|
||||
command: ["curl", "-s", "https://api.github.com/repos/Ly-sec/Noctalia/releases/latest"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const data = JSON.parse(text);
|
||||
if (data.tag_name) {
|
||||
const version = data.tag_name;
|
||||
githubData.version = version;
|
||||
root.latestVersion = version;
|
||||
console.log("[About] Latest version fetched from GitHub:", version);
|
||||
} else {
|
||||
console.log("No tag_name in GitHub response");
|
||||
}
|
||||
saveData();
|
||||
} catch (e) {
|
||||
console.error("Failed to parse version:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Process {
|
||||
id: contributorsProcess
|
||||
|
||||
command: ["curl", "-s", "https://api.github.com/repos/Ly-sec/Noctalia/contributors?per_page=100"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const data = JSON.parse(text);
|
||||
githubData.contributors = data || [];
|
||||
root.contributors = githubData.contributors;
|
||||
console.log("[About] Contributors data fetched from GitHub:", githubData.contributors.length, "contributors");
|
||||
saveData();
|
||||
} catch (e) {
|
||||
console.error("Failed to parse contributors:", e);
|
||||
root.contributors = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: 16
|
||||
rightPadding: 12
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
text: "Noctalia: quiet by design"
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.bottomMargin: 8 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "It may just be another quickshell setup but it won't get in your way."
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.bottomMargin: 16 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
columns: 2
|
||||
rowSpacing: 4
|
||||
columnSpacing: 8
|
||||
|
||||
Text {
|
||||
text: "Latest Version:"
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.latestVersion
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Installed Version:"
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.currentVersion
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.topMargin: 8
|
||||
Layout.preferredWidth: updateText.implicitWidth + 46
|
||||
Layout.preferredHeight: 32
|
||||
radius: 20
|
||||
color: updateArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
visible: {
|
||||
if (root.currentVersion === "Unknown" || root.latestVersion === "Unknown")
|
||||
return false;
|
||||
|
||||
const latest = root.latestVersion.replace("v", "").split(".");
|
||||
const current = root.currentVersion.replace("v", "").split(".");
|
||||
for (let i = 0; i < Math.max(latest.length, current.length); i++) {
|
||||
const l = parseInt(latest[i] || "0");
|
||||
const c = parseInt(current[i] || "0");
|
||||
if (l > c)
|
||||
return true;
|
||||
|
||||
if (l < c)
|
||||
return false;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "system_update"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
color: updateArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
id: updateText
|
||||
|
||||
text: "Download latest release"
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
color: updateArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: updateArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://github.com/Ly-sec/Noctalia/releases/latest"]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 26
|
||||
Layout.bottomMargin: 18
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.3
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: 32
|
||||
Layout.rightMargin: 32
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
spacing: 16
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "Contributors"
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "(" + root.contributors.length + ")"
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: contributorsGrid
|
||||
|
||||
Layout.leftMargin: 32
|
||||
Layout.rightMargin: 32
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
width: 200 * 3
|
||||
height: 300
|
||||
cellWidth: 200
|
||||
cellHeight: 100
|
||||
model: root.contributors
|
||||
|
||||
delegate: Rectangle {
|
||||
width: contributorsGrid.cellWidth - 8
|
||||
height: contributorsGrid.cellHeight - 4
|
||||
radius: 20
|
||||
color: contributorArea.containsMouse ? Theme.highlight : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 12
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
|
||||
Image {
|
||||
id: avatarImage
|
||||
|
||||
anchors.fill: parent
|
||||
source: modelData.avatar_url || ""
|
||||
sourceSize: Qt.size(80, 80)
|
||||
visible: false
|
||||
mipmap: true
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
cache: true
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
source: avatarImage
|
||||
maskEnabled: true
|
||||
maskSource: mask
|
||||
}
|
||||
|
||||
Item {
|
||||
id: mask
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: avatarImage.width / 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "person"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
color: contributorArea.containsMouse ? Theme.backgroundPrimary : Theme.textPrimary
|
||||
visible: !avatarImage.source || avatarImage.status !== Image.Ready
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: modelData.login || "Unknown"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: contributorArea.containsMouse ? Theme.backgroundPrimary : Theme.textPrimary
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: (modelData.contributions || 0) + " commits"
|
||||
font.pixelSize: 11 * Theme.scale(Screen)
|
||||
color: contributorArea.containsMouse ? Theme.backgroundPrimary : Theme.textSecondary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: contributorArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData.html_url)
|
||||
Quickshell.execDetached(["xdg-open", modelData.html_url]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
86
Widgets/SettingsWindow/Tabs/Bar.qml
Normal file
86
Widgets/SettingsWindow/Tabs/Bar.qml
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: 16
|
||||
rightPadding: 12
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
text: "Elements"
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 16 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "Show Active Window"
|
||||
description: "Display the title of the currently focused window below the bar"
|
||||
value: Settings.settings.showActiveWindow
|
||||
onToggled: function() {
|
||||
Settings.settings.showActiveWindow = !Settings.settings.showActiveWindow;
|
||||
}
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "Show Active Window Icon"
|
||||
description: "Display the icon of the currently focused window"
|
||||
value: Settings.settings.showActiveWindowIcon
|
||||
onToggled: function() {
|
||||
Settings.settings.showActiveWindowIcon = !Settings.settings.showActiveWindowIcon;
|
||||
}
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "Show System Info"
|
||||
description: "Display system information (CPU, RAM, Temperature)"
|
||||
value: Settings.settings.showSystemInfoInBar
|
||||
onToggled: function() {
|
||||
Settings.settings.showSystemInfoInBar = !Settings.settings.showSystemInfoInBar;
|
||||
}
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "Show Taskbar"
|
||||
description: "Display a taskbar showing currently open windows"
|
||||
value: Settings.settings.showTaskbar
|
||||
onToggled: function() {
|
||||
Settings.settings.showTaskbar = !Settings.settings.showTaskbar;
|
||||
}
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "Show Media"
|
||||
description: "Display media controls and information"
|
||||
value: Settings.settings.showMediaInBar
|
||||
onToggled: function() {
|
||||
Settings.settings.showMediaInBar = !Settings.settings.showMediaInBar;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
97
Widgets/SettingsWindow/Tabs/Components/UnitSelector.qml
Normal file
97
Widgets/SettingsWindow/Tabs/Components/UnitSelector.qml
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
width: 64 * Theme.scale(Screen)
|
||||
height: 32 * Theme.scale(Screen)
|
||||
radius: 16 * Theme.scale(Screen)
|
||||
color: Theme.surfaceVariant
|
||||
border.color: Theme.outline
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
property bool useFahrenheit: Settings.settings.useFahrenheit
|
||||
|
||||
Rectangle {
|
||||
id: slider
|
||||
width: parent.width / 2 - 4 * Theme.scale(Screen)
|
||||
height: parent.height - 4 * Theme.scale(Screen)
|
||||
radius: 14 * Theme.scale(Screen)
|
||||
color: Theme.accentPrimary
|
||||
x: 2 + (useFahrenheit ? parent.width / 2 : 0)
|
||||
y: 2
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
|
||||
Item {
|
||||
width: parent.width / 2
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "°C"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: !useFahrenheit
|
||||
color: !useFahrenheit ? Theme.onAccent : Theme.textPrimary
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 200 }
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (useFahrenheit) {
|
||||
Settings.settings.useFahrenheit = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
width: parent.width / 2
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "°F"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: useFahrenheit
|
||||
color: useFahrenheit ? Theme.onAccent : Theme.textPrimary
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 200 }
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!useFahrenheit) {
|
||||
Settings.settings.useFahrenheit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
171
Widgets/SettingsWindow/Tabs/Components/WallpaperSelector.qml
Normal file
171
Widgets/SettingsWindow/Tabs/Components/WallpaperSelector.qml
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
import qs.Settings
|
||||
|
||||
Rectangle {
|
||||
id: wallpaperOverlay
|
||||
focus: true
|
||||
|
||||
// Function to show the overlay and load wallpapers
|
||||
function show() {
|
||||
// Ensure wallpapers are loaded
|
||||
WallpaperManager.loadWallpapers();
|
||||
wallpaperOverlay.visible = true;
|
||||
wallpaperOverlay.forceActiveFocus();
|
||||
}
|
||||
|
||||
// Function to hide the overlay
|
||||
function hide() {
|
||||
wallpaperOverlay.visible = false;
|
||||
}
|
||||
|
||||
color: Theme.backgroundPrimary
|
||||
visible: false
|
||||
z: 1000
|
||||
|
||||
// Handle escape key to close
|
||||
Keys.onPressed: function(event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
wallpaperOverlay.hide();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Click outside to close
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
wallpaperOverlay.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Content area that stops event propagation
|
||||
MouseArea {
|
||||
// Stop event propagation
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 24
|
||||
onClicked: {
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
|
||||
|
||||
// Wallpaper Grid
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
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 * Theme.scale(Screen), (parent.width / 3) - 12 * Theme.scale(Screen))
|
||||
cellHeight: cellWidth * 0.6
|
||||
model: WallpaperManager.wallpaperList
|
||||
cacheBuffer: 64
|
||||
leftMargin: 8
|
||||
rightMargin: 8
|
||||
topMargin: 8
|
||||
bottomMargin: 8
|
||||
|
||||
delegate: Item {
|
||||
width: wallpaperGrid.cellWidth - 8 * Theme.scale(Screen)
|
||||
height: wallpaperGrid.cellHeight - 8 * Theme.scale(Screen)
|
||||
|
||||
Rectangle {
|
||||
id: wallpaperItem
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 3
|
||||
color: Theme.surface
|
||||
radius: 12 * Theme.scale(Screen)
|
||||
border.color: Settings.settings.currentWallpaper === modelData ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 2 * Theme.scale(Screen)
|
||||
|
||||
Image {
|
||||
id: wallpaperImage
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
source: modelData
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
asynchronous: true
|
||||
cache: true
|
||||
smooth: true
|
||||
mipmap: true
|
||||
sourceSize.width: Math.min(width, 480 * Theme.scale(Screen))
|
||||
sourceSize.height: Math.min(height, 270 * Theme.scale(Screen))
|
||||
opacity: (wallpaperImage.status == Image.Ready) ? 1 : 0
|
||||
// Apply circular mask for rounded corners
|
||||
layer.enabled: true
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskSource: mask
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: mask
|
||||
|
||||
anchors.fill: wallpaperImage
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
width: wallpaperImage.width
|
||||
height: wallpaperImage.height
|
||||
radius: 12 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
WallpaperManager.changeWallpaper(modelData);
|
||||
wallpaperOverlay.hide();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
373
Widgets/SettingsWindow/Tabs/Display.qml
Normal file
373
Widgets/SettingsWindow/Tabs/Display.qml
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// Get list of available monitors/screens
|
||||
property var monitors: Quickshell.screens || []
|
||||
|
||||
// Sorted monitors by name
|
||||
property var sortedMonitors: {
|
||||
let sorted = [...monitors];
|
||||
sorted.sort((a, b) => {
|
||||
let nameA = a.name || "Unknown";
|
||||
let nameB = b.name || "Unknown";
|
||||
return nameA.localeCompare(nameB);
|
||||
});
|
||||
return sorted;
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: 16
|
||||
rightPadding: 12
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
text: "Monitor Selection"
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 16 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "Bar Monitors"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Select which monitors to display the top panel/bar on"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Repeater {
|
||||
model: root.sortedMonitors
|
||||
delegate: Rectangle {
|
||||
id: barCheckbox
|
||||
property bool isChecked: false
|
||||
|
||||
Component.onCompleted: {
|
||||
// Initialize checkbox state from settings
|
||||
let monitors = Settings.settings.barMonitors || [];
|
||||
isChecked = monitors.includes(modelData.name);
|
||||
}
|
||||
|
||||
width: checkboxContent.implicitWidth + 16
|
||||
height: 32
|
||||
radius: 16
|
||||
color: isChecked ? Theme.accentPrimary : Theme.surfaceVariant
|
||||
border.color: isChecked ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
RowLayout {
|
||||
id: checkboxContent
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
|
||||
Text {
|
||||
text: barCheckbox.isChecked ? "check" : ""
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
color: barCheckbox.isChecked ? Theme.onAccent : Theme.textSecondary
|
||||
visible: barCheckbox.isChecked
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.name || "Unknown"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: barCheckbox.isChecked ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
isChecked = !isChecked;
|
||||
|
||||
// Update settings array when checkbox is toggled
|
||||
let monitors = Settings.settings.barMonitors || [];
|
||||
monitors = [...monitors]; // Create copy to trigger reactivity
|
||||
|
||||
if (isChecked) {
|
||||
if (!monitors.includes(modelData.name)) {
|
||||
monitors.push(modelData.name);
|
||||
}
|
||||
} else {
|
||||
monitors = monitors.filter(name => name !== modelData.name);
|
||||
}
|
||||
|
||||
Settings.settings.barMonitors = monitors;
|
||||
console.log("Bar monitors updated:", JSON.stringify(monitors));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "Dock Monitors"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Select which monitors to display the application dock on"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Repeater {
|
||||
model: root.sortedMonitors
|
||||
delegate: Rectangle {
|
||||
id: dockCheckbox
|
||||
property bool isChecked: false
|
||||
|
||||
Component.onCompleted: {
|
||||
// Initialize with current settings
|
||||
let monitors = Settings.settings.dockMonitors || [];
|
||||
isChecked = monitors.includes(modelData.name);
|
||||
}
|
||||
|
||||
width: checkboxContent.implicitWidth + 16
|
||||
height: 32
|
||||
radius: 16
|
||||
color: isChecked ? Theme.accentPrimary : Theme.surfaceVariant
|
||||
border.color: isChecked ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
RowLayout {
|
||||
id: checkboxContent
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
|
||||
Text {
|
||||
text: dockCheckbox.isChecked ? "check" : ""
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
color: dockCheckbox.isChecked ? Theme.onAccent : Theme.textSecondary
|
||||
visible: dockCheckbox.isChecked
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.name || "Unknown"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: dockCheckbox.isChecked ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
// Toggle state immediately for UI responsiveness
|
||||
isChecked = !isChecked;
|
||||
|
||||
// Update settings
|
||||
let monitors = Settings.settings.dockMonitors || [];
|
||||
monitors = [...monitors]; // Copy array
|
||||
|
||||
if (isChecked) {
|
||||
// Add to array if not already there
|
||||
if (!monitors.includes(modelData.name)) {
|
||||
monitors.push(modelData.name);
|
||||
}
|
||||
} else {
|
||||
// Remove from array
|
||||
monitors = monitors.filter(name => name !== modelData.name);
|
||||
}
|
||||
|
||||
Settings.settings.dockMonitors = monitors;
|
||||
console.log("Dock monitors updated:", JSON.stringify(monitors));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "Notification Monitors"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Select which monitors to display system notifications on"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Repeater {
|
||||
model: root.sortedMonitors
|
||||
delegate: Rectangle {
|
||||
id: notificationCheckbox
|
||||
property bool isChecked: false
|
||||
|
||||
Component.onCompleted: {
|
||||
// Initialize with current settings
|
||||
let monitors = Settings.settings.notificationMonitors || [];
|
||||
isChecked = monitors.includes(modelData.name);
|
||||
}
|
||||
|
||||
width: checkboxContent.implicitWidth + 16
|
||||
height: 32
|
||||
radius: 16
|
||||
color: isChecked ? Theme.accentPrimary : Theme.surfaceVariant
|
||||
border.color: isChecked ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
RowLayout {
|
||||
id: checkboxContent
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
|
||||
Text {
|
||||
text: notificationCheckbox.isChecked ? "check" : ""
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
color: notificationCheckbox.isChecked ? Theme.onAccent : Theme.textSecondary
|
||||
visible: notificationCheckbox.isChecked
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.name || "Unknown"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: notificationCheckbox.isChecked ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
// Toggle state immediately for UI responsiveness
|
||||
isChecked = !isChecked;
|
||||
|
||||
// Update settings
|
||||
let monitors = Settings.settings.notificationMonitors || [];
|
||||
monitors = [...monitors]; // Copy array
|
||||
|
||||
if (isChecked) {
|
||||
// Add to array if not already there
|
||||
if (!monitors.includes(modelData.name)) {
|
||||
monitors.push(modelData.name);
|
||||
}
|
||||
} else {
|
||||
// Remove from array
|
||||
monitors = monitors.filter(name => name !== modelData.name);
|
||||
}
|
||||
|
||||
Settings.settings.notificationMonitors = monitors;
|
||||
console.log("Notification monitors updated:", JSON.stringify(monitors));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
166
Widgets/SettingsWindow/Tabs/General.qml
Normal file
166
Widgets/SettingsWindow/Tabs/General.qml
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: 16
|
||||
rightPadding: 12
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
text: "Profile"
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 16 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Profile Image"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 4 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Your profile picture displayed in various places throughout the shell"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 4
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 8 * Theme.scale(Screen)
|
||||
Layout.fillWidth: true
|
||||
|
||||
Rectangle {
|
||||
width: 48 * Theme.scale(Screen)
|
||||
height: 48 * Theme.scale(Screen)
|
||||
radius: 24 * Theme.scale(Screen)
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: 24 * Theme.scale(Screen)
|
||||
border.color: profileImageInput.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 2 * Theme.scale(Screen)
|
||||
z: 2
|
||||
}
|
||||
|
||||
Avatar {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40 * Theme.scale(Screen)
|
||||
radius: 16 * Theme.scale(Screen)
|
||||
color: Theme.surfaceVariant
|
||||
border.color: profileImageInput.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
TextInput {
|
||||
id: profileImageInput
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12 * Theme.scale(Screen)
|
||||
anchors.rightMargin: 12 * Theme.scale(Screen)
|
||||
anchors.topMargin: 6 * Theme.scale(Screen)
|
||||
anchors.bottomMargin: 6 * Theme.scale(Screen)
|
||||
text: Settings.settings.profileImage
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
clip: true
|
||||
selectByMouse: true
|
||||
activeFocusOnTab: true
|
||||
inputMethodHints: Qt.ImhUrlCharactersOnly
|
||||
onTextChanged: {
|
||||
Settings.settings.profileImage = text;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.IBeamCursor
|
||||
onClicked: profileImageInput.forceActiveFocus()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 26 * Theme.scale(Screen)
|
||||
Layout.bottomMargin: 18 * Theme.scale(Screen)
|
||||
height: 1 * Theme.scale(Screen)
|
||||
color: Theme.outline
|
||||
opacity: 0.3
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "User Interface"
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 16 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "Show Corners"
|
||||
description: "Display rounded corners on the edge of the screen"
|
||||
value: Settings.settings.showCorners
|
||||
onToggled: function() {
|
||||
Settings.settings.showCorners = !Settings.settings.showCorners;
|
||||
}
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "Show Dock"
|
||||
description: "Display a dock at the bottom of the screen for quick access to applications"
|
||||
value: Settings.settings.showDock
|
||||
onToggled: function() {
|
||||
Settings.settings.showDock = !Settings.settings.showDock;
|
||||
}
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "Dim Desktop"
|
||||
description: "Dim the desktop when panels or menus are open"
|
||||
value: Settings.settings.dimPanels
|
||||
onToggled: function() {
|
||||
Settings.settings.dimPanels = !Settings.settings.dimPanels;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
148
Widgets/SettingsWindow/Tabs/Misc.qml
Normal file
148
Widgets/SettingsWindow/Tabs/Misc.qml
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: 16
|
||||
rightPadding: 12
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
text: "Media"
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 16 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "Visualizer Type"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Choose the style of the audio visualizer"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 4
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: visualizerTypeComboBox
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
model: ["radial", "fire", "diamond"]
|
||||
currentIndex: model.indexOf(Settings.settings.visualizerType)
|
||||
onActivated: {
|
||||
Settings.settings.visualizerType = model[index];
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 120
|
||||
implicitHeight: 40
|
||||
color: Theme.surfaceVariant
|
||||
border.color: visualizerTypeComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
leftPadding: 12
|
||||
rightPadding: visualizerTypeComboBox.indicator.width + visualizerTypeComboBox.spacing
|
||||
text: visualizerTypeComboBox.displayText.charAt(0).toUpperCase() + visualizerTypeComboBox.displayText.slice(1)
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
indicator: Text {
|
||||
x: visualizerTypeComboBox.width - width - 12
|
||||
y: visualizerTypeComboBox.topPadding + (visualizerTypeComboBox.availableHeight - height) / 2
|
||||
text: "arrow_drop_down"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: visualizerTypeComboBox.height
|
||||
width: visualizerTypeComboBox.width
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
padding: 8
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: visualizerTypeComboBox.popup.visible ? visualizerTypeComboBox.delegateModel : null
|
||||
currentIndex: visualizerTypeComboBox.highlightedIndex
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surfaceVariant
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: visualizerTypeComboBox.width
|
||||
highlighted: visualizerTypeComboBox.highlightedIndex === index
|
||||
|
||||
contentItem: Text {
|
||||
text: modelData.charAt(0).toUpperCase() + modelData.slice(1)
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
86
Widgets/SettingsWindow/Tabs/Network.qml
Normal file
86
Widgets/SettingsWindow/Tabs/Network.qml
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: 16
|
||||
rightPadding: 12
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
text: "Wi-Fi"
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 16 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "Enable Wi-Fi"
|
||||
description: "Turn Wi-Fi radio on or off"
|
||||
value: Settings.settings.wifiEnabled
|
||||
onToggled: function() {
|
||||
Settings.settings.wifiEnabled = !Settings.settings.wifiEnabled;
|
||||
Quickshell.execDetached(["nmcli", "radio", "wifi", Settings.settings.wifiEnabled ? "on" : "off"]);
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 26
|
||||
Layout.bottomMargin: 18
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.3
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Bluetooth"
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 16 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "Enable Bluetooth"
|
||||
description: "Turn Bluetooth radio on or off"
|
||||
value: Settings.settings.bluetoothEnabled
|
||||
onToggled: function() {
|
||||
if (Bluetooth.defaultAdapter) {
|
||||
Settings.settings.bluetoothEnabled = !Settings.settings.bluetoothEnabled;
|
||||
Bluetooth.defaultAdapter.enabled = Settings.settings.bluetoothEnabled;
|
||||
if (Bluetooth.defaultAdapter.enabled)
|
||||
Bluetooth.defaultAdapter.discovering = true;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
19
Widgets/SettingsWindow/Tabs/Record.qml
Normal file
19
Widgets/SettingsWindow/Tabs/Record.qml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Settings
|
||||
import qs.Components
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 24
|
||||
|
||||
Text {
|
||||
text: "Coming soon..."
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.topMargin: 32
|
||||
}
|
||||
}
|
||||
812
Widgets/SettingsWindow/Tabs/ScreenRecorder.qml
Normal file
812
Widgets/SettingsWindow/Tabs/ScreenRecorder.qml
Normal file
|
|
@ -0,0 +1,812 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: 16
|
||||
rightPadding: 12
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 0
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
// Text {
|
||||
// text: "Screen Recording"
|
||||
// font.pixelSize: 18 * Theme.scale(Screen)
|
||||
// font.bold: true
|
||||
// color: Theme.textPrimary
|
||||
// Layout.bottomMargin: 8
|
||||
// }
|
||||
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "Output Directory"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Directory where screen recordings will be saved"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
Layout.bottomMargin: 4
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
radius: 16
|
||||
color: Theme.surfaceVariant
|
||||
border.color: videoPathInput.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
TextInput {
|
||||
id: videoPathInput
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
anchors.topMargin: 6
|
||||
anchors.bottomMargin: 6
|
||||
text: Settings.settings.videoPath !== undefined ? Settings.settings.videoPath : ""
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
clip: true
|
||||
selectByMouse: true
|
||||
activeFocusOnTab: true
|
||||
inputMethodHints: Qt.ImhUrlCharactersOnly
|
||||
onTextChanged: {
|
||||
Settings.settings.videoPath = text;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.IBeamCursor
|
||||
onClicked: videoPathInput.forceActiveFocus()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
Text {
|
||||
text: "Frame Rate"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Target frame rate for screen recordings (default: 60)"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
Layout.bottomMargin: 4
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: frameRateSpinBox
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
from: 24
|
||||
to: 144
|
||||
value: Settings.settings.recordingFrameRate || 60
|
||||
stepSize: 1
|
||||
onValueChanged: {
|
||||
Settings.settings.recordingFrameRate = value;
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 120
|
||||
implicitHeight: 40
|
||||
color: Theme.surfaceVariant
|
||||
border.color: frameRateSpinBox.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
contentItem: TextInput {
|
||||
text: frameRateSpinBox.textFromValue(frameRateSpinBox.value, frameRateSpinBox.locale)
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
selectionColor: Theme.accentPrimary
|
||||
selectedTextColor: Theme.onAccent
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
readOnly: false
|
||||
selectByMouse: true
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
onTextChanged: {
|
||||
var newValue = parseInt(text);
|
||||
if (!isNaN(newValue) && newValue >= frameRateSpinBox.from && newValue <= frameRateSpinBox.to)
|
||||
frameRateSpinBox.value = newValue;
|
||||
|
||||
}
|
||||
onEditingFinished: {
|
||||
var newValue = parseInt(text);
|
||||
if (isNaN(newValue) || newValue < frameRateSpinBox.from || newValue > frameRateSpinBox.to)
|
||||
text = frameRateSpinBox.textFromValue(frameRateSpinBox.value, frameRateSpinBox.locale);
|
||||
|
||||
}
|
||||
|
||||
validator: IntValidator {
|
||||
bottom: frameRateSpinBox.from
|
||||
top: frameRateSpinBox.to
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
up.indicator: Rectangle {
|
||||
x: parent.width - width
|
||||
height: parent.height
|
||||
width: height
|
||||
color: "transparent"
|
||||
radius: 16
|
||||
|
||||
Text {
|
||||
text: "add"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 20 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
down.indicator: Rectangle {
|
||||
x: 0
|
||||
height: parent.height
|
||||
width: height
|
||||
color: "transparent"
|
||||
radius: 16
|
||||
|
||||
Text {
|
||||
text: "remove"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 20 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
Text {
|
||||
text: "Audio Source"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Audio source to capture during recording"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
Layout.bottomMargin: 4
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: audioSourceComboBox
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
model: ["default_output", "default_input", "both"]
|
||||
currentIndex: model.indexOf(Settings.settings.recordingAudioSource || "default_output")
|
||||
onActivated: {
|
||||
Settings.settings.recordingAudioSource = model[index];
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 120
|
||||
implicitHeight: 40
|
||||
color: Theme.surfaceVariant
|
||||
border.color: audioSourceComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
leftPadding: 12
|
||||
rightPadding: audioSourceComboBox.indicator.width + audioSourceComboBox.spacing
|
||||
text: {
|
||||
switch (audioSourceComboBox.currentText) {
|
||||
case "default_output":
|
||||
return "System Audio";
|
||||
case "default_input":
|
||||
return "Microphone";
|
||||
case "both":
|
||||
return "System Audio + Microphone";
|
||||
default:
|
||||
return audioSourceComboBox.currentText;
|
||||
}
|
||||
}
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
indicator: Text {
|
||||
x: audioSourceComboBox.width - width - 12
|
||||
y: audioSourceComboBox.topPadding + (audioSourceComboBox.availableHeight - height) / 2
|
||||
text: "arrow_drop_down"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: audioSourceComboBox.height
|
||||
width: audioSourceComboBox.width
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
padding: 1
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: audioSourceComboBox.popup.visible ? audioSourceComboBox.delegateModel : null
|
||||
currentIndex: audioSourceComboBox.highlightedIndex
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surfaceVariant
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: audioSourceComboBox.width
|
||||
highlighted: audioSourceComboBox.highlightedIndex === index
|
||||
|
||||
contentItem: Text {
|
||||
text: {
|
||||
switch (modelData) {
|
||||
case "default_output":
|
||||
return "System Audio";
|
||||
case "default_input":
|
||||
return "Microphone";
|
||||
case "both":
|
||||
return "System Audio + Microphone";
|
||||
default:
|
||||
return modelData;
|
||||
}
|
||||
}
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
Text {
|
||||
text: "Video Quality"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Higher quality results in larger file sizes"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
Layout.bottomMargin: 4
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: qualityComboBox
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
model: ["medium", "high", "very_high", "ultra"]
|
||||
currentIndex: model.indexOf(Settings.settings.recordingQuality || "very_high")
|
||||
onActivated: {
|
||||
Settings.settings.recordingQuality = model[index];
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 120
|
||||
implicitHeight: 40
|
||||
color: Theme.surfaceVariant
|
||||
border.color: qualityComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
leftPadding: 12
|
||||
rightPadding: qualityComboBox.indicator.width + qualityComboBox.spacing
|
||||
text: {
|
||||
switch (qualityComboBox.currentText) {
|
||||
case "medium":
|
||||
return "Medium";
|
||||
case "high":
|
||||
return "High";
|
||||
case "very_high":
|
||||
return "Very High";
|
||||
case "ultra":
|
||||
return "Ultra";
|
||||
default:
|
||||
return qualityComboBox.currentText;
|
||||
}
|
||||
}
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
indicator: Text {
|
||||
x: qualityComboBox.width - width - 12
|
||||
y: qualityComboBox.topPadding + (qualityComboBox.availableHeight - height) / 2
|
||||
text: "arrow_drop_down"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: qualityComboBox.height
|
||||
width: qualityComboBox.width
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
padding: 1
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: qualityComboBox.popup.visible ? qualityComboBox.delegateModel : null
|
||||
currentIndex: qualityComboBox.highlightedIndex
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surfaceVariant
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: qualityComboBox.width
|
||||
highlighted: qualityComboBox.highlightedIndex === index
|
||||
|
||||
contentItem: Text {
|
||||
text: {
|
||||
switch (modelData) {
|
||||
case "medium":
|
||||
return "Medium";
|
||||
case "high":
|
||||
return "High";
|
||||
case "very_high":
|
||||
return "Very High";
|
||||
case "ultra":
|
||||
return "Ultra";
|
||||
default:
|
||||
return modelData;
|
||||
}
|
||||
}
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
Text {
|
||||
text: "Video Codec"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Different codecs offer different compression and compatibility"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
Layout.bottomMargin: 4
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: codecComboBox
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
model: ["h264", "hevc", "av1", "vp8", "vp9"]
|
||||
currentIndex: model.indexOf(Settings.settings.recordingCodec || "h264")
|
||||
onActivated: {
|
||||
Settings.settings.recordingCodec = model[index];
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 120
|
||||
implicitHeight: 40
|
||||
color: Theme.surfaceVariant
|
||||
border.color: codecComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
leftPadding: 12
|
||||
rightPadding: codecComboBox.indicator.width + codecComboBox.spacing
|
||||
text: codecComboBox.currentText.toUpperCase()
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
indicator: Text {
|
||||
x: codecComboBox.width - width - 12
|
||||
y: codecComboBox.topPadding + (codecComboBox.availableHeight - height) / 2
|
||||
text: "arrow_drop_down"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: codecComboBox.height
|
||||
width: codecComboBox.width
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
padding: 1
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: codecComboBox.popup.visible ? codecComboBox.delegateModel : null
|
||||
currentIndex: codecComboBox.highlightedIndex
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surfaceVariant
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: codecComboBox.width
|
||||
highlighted: codecComboBox.highlightedIndex === index
|
||||
|
||||
contentItem: Text {
|
||||
text: modelData.toUpperCase()
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
Text {
|
||||
text: "Audio Codec"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Opus is recommended for best performance and smallest audio size"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
Layout.bottomMargin: 4
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: audioCodecComboBox
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
model: ["opus", "aac"]
|
||||
currentIndex: model.indexOf(Settings.settings.audioCodec || "opus")
|
||||
onActivated: {
|
||||
Settings.settings.audioCodec = model[index];
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 120
|
||||
implicitHeight: 40
|
||||
color: Theme.surfaceVariant
|
||||
border.color: audioCodecComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
leftPadding: 12
|
||||
rightPadding: audioCodecComboBox.indicator.width + audioCodecComboBox.spacing
|
||||
text: audioCodecComboBox.currentText.toUpperCase()
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
indicator: Text {
|
||||
x: audioCodecComboBox.width - width - 12
|
||||
y: audioCodecComboBox.topPadding + (audioCodecComboBox.availableHeight - height) / 2
|
||||
text: "arrow_drop_down"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: audioCodecComboBox.height
|
||||
width: audioCodecComboBox.width
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
padding: 1
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: audioCodecComboBox.popup.visible ? audioCodecComboBox.delegateModel : null
|
||||
currentIndex: audioCodecComboBox.highlightedIndex
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surfaceVariant
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: audioCodecComboBox.width
|
||||
highlighted: audioCodecComboBox.highlightedIndex === index
|
||||
|
||||
contentItem: Text {
|
||||
text: modelData.toUpperCase()
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
Text {
|
||||
text: "Color Range"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Limited is recommended for better compatibility"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
Layout.bottomMargin: 4
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: colorRangeComboBox
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
model: ["limited", "full"]
|
||||
currentIndex: model.indexOf(Settings.settings.colorRange || "limited")
|
||||
onActivated: {
|
||||
Settings.settings.colorRange = model[index];
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 120
|
||||
implicitHeight: 40
|
||||
color: Theme.surfaceVariant
|
||||
border.color: colorRangeComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
leftPadding: 12
|
||||
rightPadding: colorRangeComboBox.indicator.width + colorRangeComboBox.spacing
|
||||
text: colorRangeComboBox.currentText.charAt(0).toUpperCase() + colorRangeComboBox.currentText.slice(1)
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
indicator: Text {
|
||||
x: colorRangeComboBox.width - width - 12
|
||||
y: colorRangeComboBox.topPadding + (colorRangeComboBox.availableHeight - height) / 2
|
||||
text: "arrow_drop_down"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: colorRangeComboBox.height
|
||||
width: colorRangeComboBox.width
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
padding: 1
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: colorRangeComboBox.popup.visible ? colorRangeComboBox.delegateModel : null
|
||||
currentIndex: colorRangeComboBox.highlightedIndex
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surfaceVariant
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: colorRangeComboBox.width
|
||||
highlighted: colorRangeComboBox.highlightedIndex === index
|
||||
|
||||
contentItem: Text {
|
||||
text: modelData.charAt(0).toUpperCase() + modelData.slice(1)
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "Show Cursor"
|
||||
description: "Record mouse cursor in the video"
|
||||
value: Settings.settings.showCursor
|
||||
onToggled: function() {
|
||||
Settings.settings.showCursor = !Settings.settings.showCursor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 24
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
176
Widgets/SettingsWindow/Tabs/TimeWeather.qml
Normal file
176
Widgets/SettingsWindow/Tabs/TimeWeather.qml
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
import qs.Widgets.SettingsWindow.Tabs.Components
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: 16
|
||||
rightPadding: 12
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
text: "Time"
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 16 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "Use 12 Hour Clock"
|
||||
description: "Display time in 12-hour format (e.g., 2:30 PM) instead of 24-hour format"
|
||||
value: Settings.settings.use12HourClock
|
||||
onToggled: function() {
|
||||
Settings.settings.use12HourClock = !Settings.settings.use12HourClock;
|
||||
}
|
||||
}
|
||||
|
||||
ToggleOption {
|
||||
label: "US Style Date"
|
||||
description: "Display dates in MM/DD/YYYY format instead of DD/MM/YYYY"
|
||||
value: Settings.settings.reverseDayMonth
|
||||
onToggled: function() {
|
||||
Settings.settings.reverseDayMonth = !Settings.settings.reverseDayMonth;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 26
|
||||
Layout.bottomMargin: 18
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.3
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Weather"
|
||||
font.pixelSize: 18 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 16 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 8 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
text: "City"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Your city name for weather information"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
radius: 16
|
||||
color: Theme.surfaceVariant
|
||||
border.color: cityInput.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
TextInput {
|
||||
id: cityInput
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
anchors.topMargin: 6
|
||||
anchors.bottomMargin: 6
|
||||
text: Settings.settings.weatherCity
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
clip: true
|
||||
focus: true
|
||||
selectByMouse: true
|
||||
activeFocusOnTab: true
|
||||
inputMethodHints: Qt.ImhNone
|
||||
onTextChanged: {
|
||||
Settings.settings.weatherCity = text;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.IBeamCursor
|
||||
onClicked: {
|
||||
cityInput.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "Temperature Unit"
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Choose between Celsius and Fahrenheit"
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
UnitSelector {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
670
Widgets/SettingsWindow/Tabs/Wallpaper.qml
Normal file
670
Widgets/SettingsWindow/Tabs/Wallpaper.qml
Normal file
|
|
@ -0,0 +1,670 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
import qs.Settings
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: 16
|
||||
rightPadding: 12
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 0
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "Wallpaper Settings"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 8
|
||||
}
|
||||
|
||||
// Wallpaper Settings Category
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
// Wallpaper Folder
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "Wallpaper Folder"
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Path to your wallpaper folder"
|
||||
font.pixelSize: 12
|
||||
color: Theme.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
radius: 16
|
||||
color: Theme.surfaceVariant
|
||||
border.color: folderInput.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
TextInput {
|
||||
id: folderInput
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
anchors.topMargin: 6
|
||||
anchors.bottomMargin: 6
|
||||
text: Settings.settings.wallpaperFolder !== undefined ? Settings.settings.wallpaperFolder : ""
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
clip: true
|
||||
selectByMouse: true
|
||||
activeFocusOnTab: true
|
||||
inputMethodHints: Qt.ImhUrlCharactersOnly
|
||||
onTextChanged: {
|
||||
Settings.settings.wallpaperFolder = text;
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.IBeamCursor
|
||||
onClicked: folderInput.forceActiveFocus()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 26
|
||||
Layout.bottomMargin: 18
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.3
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "Automation"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 8
|
||||
}
|
||||
|
||||
// Random Wallpaper
|
||||
ToggleOption {
|
||||
label: "Random Wallpaper"
|
||||
description: "Automatically select random wallpapers from the folder"
|
||||
value: Settings.settings.randomWallpaper
|
||||
onToggled: function() {
|
||||
Settings.settings.randomWallpaper = !Settings.settings.randomWallpaper;
|
||||
}
|
||||
}
|
||||
|
||||
// Use Wallpaper Theme
|
||||
ToggleOption {
|
||||
label: "Use Wallpaper Theme"
|
||||
description: "Automatically adjust theme colors based on wallpaper"
|
||||
value: Settings.settings.useWallpaperTheme
|
||||
onToggled: function() {
|
||||
Settings.settings.useWallpaperTheme = !Settings.settings.useWallpaperTheme;
|
||||
}
|
||||
}
|
||||
|
||||
// Wallpaper Interval
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
Text {
|
||||
text: "Wallpaper Interval"
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "How often to change wallpapers automatically (in seconds)"
|
||||
font.pixelSize: 12
|
||||
color: Theme.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: Settings.settings.wallpaperInterval + " seconds"
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: intervalSlider
|
||||
|
||||
Layout.fillWidth: true
|
||||
from: 10
|
||||
to: 900
|
||||
stepSize: 10
|
||||
value: Settings.settings.wallpaperInterval
|
||||
snapMode: Slider.SnapAlways
|
||||
onMoved: {
|
||||
Settings.settings.wallpaperInterval = Math.round(value);
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
x: intervalSlider.leftPadding
|
||||
y: intervalSlider.topPadding + intervalSlider.availableHeight / 2 - height / 2
|
||||
implicitWidth: 200
|
||||
implicitHeight: 4
|
||||
width: intervalSlider.availableWidth
|
||||
height: implicitHeight
|
||||
radius: 2
|
||||
color: Theme.surfaceVariant
|
||||
|
||||
Rectangle {
|
||||
width: intervalSlider.visualPosition * parent.width
|
||||
height: parent.height
|
||||
color: Theme.accentPrimary
|
||||
radius: 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
handle: Rectangle {
|
||||
x: intervalSlider.leftPadding + intervalSlider.visualPosition * (intervalSlider.availableWidth - width)
|
||||
y: intervalSlider.topPadding + intervalSlider.availableHeight / 2 - height / 2
|
||||
implicitWidth: 20
|
||||
implicitHeight: 20
|
||||
radius: 10
|
||||
color: intervalSlider.pressed ? Theme.surfaceVariant : Theme.surface
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 26
|
||||
Layout.bottomMargin: 18
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.3
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "SWWW"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.bottomMargin: 8
|
||||
}
|
||||
|
||||
// Use SWWW
|
||||
ToggleOption {
|
||||
label: "Use SWWW"
|
||||
description: "Use SWWW daemon for advanced wallpaper management"
|
||||
value: Settings.settings.useSWWW
|
||||
onToggled: function() {
|
||||
Settings.settings.useSWWW = !Settings.settings.useSWWW;
|
||||
}
|
||||
}
|
||||
|
||||
// SWWW Settings (only visible when useSWWW is enabled)
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
visible: Settings.settings.useSWWW
|
||||
|
||||
// Resize Mode
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "Resize Mode"
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "How SWWW should resize wallpapers to fit the screen"
|
||||
font.pixelSize: 12
|
||||
color: Theme.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
radius: 16
|
||||
color: Theme.surfaceVariant
|
||||
border.color: resizeComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
ComboBox {
|
||||
id: resizeComboBox
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
anchors.topMargin: 6
|
||||
anchors.bottomMargin: 6
|
||||
model: ["no", "crop", "fit", "stretch"]
|
||||
currentIndex: model.indexOf(Settings.settings.wallpaperResize)
|
||||
onActivated: {
|
||||
Settings.settings.wallpaperResize = model[index];
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: resizeComboBox.displayText
|
||||
font: resizeComboBox.font
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: resizeComboBox.height
|
||||
width: resizeComboBox.width
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
padding: 1
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: resizeComboBox.popup.visible ? resizeComboBox.delegateModel : null
|
||||
currentIndex: resizeComboBox.highlightedIndex
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surface
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
radius: 8
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: resizeComboBox.width
|
||||
highlighted: resizeComboBox.highlightedIndex === index
|
||||
|
||||
contentItem: Text {
|
||||
text: modelData
|
||||
color: Theme.textPrimary
|
||||
font: resizeComboBox.font
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.highlighted ? Theme.accentPrimary : "transparent"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Transition Type
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
Text {
|
||||
text: "Transition Type"
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Animation type when switching between wallpapers"
|
||||
font.pixelSize: 12
|
||||
color: Theme.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
radius: 16
|
||||
color: Theme.surfaceVariant
|
||||
border.color: transitionTypeComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
ComboBox {
|
||||
id: transitionTypeComboBox
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
anchors.topMargin: 6
|
||||
anchors.bottomMargin: 6
|
||||
model: ["none", "simple", "fade", "left", "right", "top", "bottom", "wipe", "wave", "grow", "center", "any", "outer", "random"]
|
||||
currentIndex: model.indexOf(Settings.settings.transitionType)
|
||||
onActivated: {
|
||||
Settings.settings.transitionType = model[index];
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: transitionTypeComboBox.displayText
|
||||
font: transitionTypeComboBox.font
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: transitionTypeComboBox.height
|
||||
width: transitionTypeComboBox.width
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
padding: 1
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: transitionTypeComboBox.popup.visible ? transitionTypeComboBox.delegateModel : null
|
||||
currentIndex: transitionTypeComboBox.highlightedIndex
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surface
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
radius: 8
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: transitionTypeComboBox.width
|
||||
highlighted: transitionTypeComboBox.highlightedIndex === index
|
||||
|
||||
contentItem: Text {
|
||||
text: modelData
|
||||
color: Theme.textPrimary
|
||||
font: transitionTypeComboBox.font
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.highlighted ? Theme.accentPrimary : "transparent"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Transition FPS
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
Text {
|
||||
text: "Transition FPS"
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Frames per second for transition animations"
|
||||
font.pixelSize: 12
|
||||
color: Theme.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: Settings.settings.transitionFps + " FPS"
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: fpsSlider
|
||||
|
||||
Layout.fillWidth: true
|
||||
from: 30
|
||||
to: 500
|
||||
stepSize: 5
|
||||
value: Settings.settings.transitionFps
|
||||
snapMode: Slider.SnapAlways
|
||||
onMoved: {
|
||||
Settings.settings.transitionFps = Math.round(value);
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
x: fpsSlider.leftPadding
|
||||
y: fpsSlider.topPadding + fpsSlider.availableHeight / 2 - height / 2
|
||||
implicitWidth: 200
|
||||
implicitHeight: 4
|
||||
width: fpsSlider.availableWidth
|
||||
height: implicitHeight
|
||||
radius: 2
|
||||
color: Theme.surfaceVariant
|
||||
|
||||
Rectangle {
|
||||
width: fpsSlider.visualPosition * parent.width
|
||||
height: parent.height
|
||||
color: Theme.accentPrimary
|
||||
radius: 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
handle: Rectangle {
|
||||
x: fpsSlider.leftPadding + fpsSlider.visualPosition * (fpsSlider.availableWidth - width)
|
||||
y: fpsSlider.topPadding + fpsSlider.availableHeight / 2 - height / 2
|
||||
implicitWidth: 20
|
||||
implicitHeight: 20
|
||||
radius: 10
|
||||
color: fpsSlider.pressed ? Theme.surfaceVariant : Theme.surface
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Transition Duration
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
Text {
|
||||
text: "Transition Duration"
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Duration of transition animations in seconds"
|
||||
font.pixelSize: 12
|
||||
color: Theme.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: Settings.settings.transitionDuration.toFixed(3) + " seconds"
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: durationSlider
|
||||
|
||||
Layout.fillWidth: true
|
||||
from: 0.25
|
||||
to: 10
|
||||
stepSize: 0.05
|
||||
value: Settings.settings.transitionDuration
|
||||
snapMode: Slider.SnapAlways
|
||||
onMoved: {
|
||||
Settings.settings.transitionDuration = value;
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
x: durationSlider.leftPadding
|
||||
y: durationSlider.topPadding + durationSlider.availableHeight / 2 - height / 2
|
||||
implicitWidth: 200
|
||||
implicitHeight: 4
|
||||
width: durationSlider.availableWidth
|
||||
height: implicitHeight
|
||||
radius: 2
|
||||
color: Theme.surfaceVariant
|
||||
|
||||
Rectangle {
|
||||
width: durationSlider.visualPosition * parent.width
|
||||
height: parent.height
|
||||
color: Theme.accentPrimary
|
||||
radius: 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
handle: Rectangle {
|
||||
x: durationSlider.leftPadding + durationSlider.visualPosition * (durationSlider.availableWidth - width)
|
||||
y: durationSlider.topPadding + durationSlider.availableHeight / 2 - height / 2
|
||||
implicitWidth: 20
|
||||
implicitHeight: 20
|
||||
radius: 10
|
||||
color: durationSlider.pressed ? Theme.surfaceVariant : Theme.surface
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ Item {
|
|||
id: root
|
||||
property alias panel: bluetoothPanelModal
|
||||
|
||||
// For showing error/status messages
|
||||
|
||||
property string statusMessage: ""
|
||||
property bool statusPopupVisible: false
|
||||
|
||||
|
|
@ -145,7 +145,7 @@ Item {
|
|||
opacity: 0.12
|
||||
}
|
||||
|
||||
// Content area (centered, in a card)
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 640
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Settings
|
||||
import qs.Widgets.Sidebar.Panel
|
||||
|
||||
Item {
|
||||
id: buttonRoot
|
||||
|
|
@ -45,7 +44,7 @@ Item {
|
|||
id: iconText
|
||||
text: "dashboard"
|
||||
font.family: isActive ? "Material Symbols Rounded" : "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: sidebarPopup.visible ? Theme.accentPrimary : Theme.textPrimary
|
||||
anchors.centerIn: parent
|
||||
z: 1
|
||||
|
|
@ -8,15 +8,15 @@ import qs.Services
|
|||
|
||||
Rectangle {
|
||||
id: musicCard
|
||||
width: 360
|
||||
height: 250
|
||||
width: 360 * Theme.scale(Screen)
|
||||
height: 250 * Theme.scale(Screen)
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
anchors.fill: parent
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
radius: 18 * Theme.scale(Screen)
|
||||
|
||||
// Show fallback UI if no player is available
|
||||
Item {
|
||||
|
|
@ -26,12 +26,12 @@ Rectangle {
|
|||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 16
|
||||
spacing: 16 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
text: "music_note"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeHeader
|
||||
font.pixelSize: Theme.fontSizeHeader * Theme.scale(Screen)
|
||||
color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.3)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ Rectangle {
|
|||
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
|
||||
font.pixelSize: Theme.fontSizeSmall * Theme.scale(Screen)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
|
@ -49,45 +49,45 @@ Rectangle {
|
|||
// Main player UI
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18
|
||||
spacing: 12
|
||||
anchors.margins: 18 * Theme.scale(Screen)
|
||||
spacing: 12 * Theme.scale(Screen)
|
||||
visible: !!MusicManager.currentPlayer
|
||||
|
||||
// Player selector
|
||||
ComboBox {
|
||||
id: playerSelector
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredHeight: 40 * Theme.scale(Screen)
|
||||
visible: MusicManager.getAvailablePlayers().length > 1
|
||||
model: MusicManager.getAvailablePlayers()
|
||||
textRole: "identity"
|
||||
currentIndex: MusicManager.selectedPlayerIndex
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 120
|
||||
implicitHeight: 40
|
||||
implicitWidth: 120 * Theme.scale(Screen)
|
||||
implicitHeight: 40 * Theme.scale(Screen)
|
||||
color: Theme.surfaceVariant
|
||||
border.color: playerSelector.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
radius: 16 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
leftPadding: 12
|
||||
leftPadding: 12 * Theme.scale(Screen)
|
||||
rightPadding: playerSelector.indicator.width + playerSelector.spacing
|
||||
text: playerSelector.displayText
|
||||
font.pixelSize: 13
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
indicator: Text {
|
||||
x: playerSelector.width - width - 12
|
||||
x: playerSelector.width - width - 12 * Theme.scale(Screen)
|
||||
y: playerSelector.topPadding + (playerSelector.availableHeight - height) / 2
|
||||
text: "arrow_drop_down"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24
|
||||
font.pixelSize: 24 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ Rectangle {
|
|||
y: playerSelector.height
|
||||
width: playerSelector.width
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
padding: 1
|
||||
padding: 1 * Theme.scale(Screen)
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
|
|
@ -109,8 +109,8 @@ Rectangle {
|
|||
background: Rectangle {
|
||||
color: Theme.surfaceVariant
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
radius: 16 * Theme.scale(Screen)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ Rectangle {
|
|||
width: playerSelector.width
|
||||
contentItem: Text {
|
||||
text: modelData.identity
|
||||
font.pixelSize: 13
|
||||
font.pixelSize: 13 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
|
|
@ -136,57 +136,57 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Album art and spectrum
|
||||
// Album art with spectrum visualizer
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
spacing: 12 * Theme.scale(Screen)
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Album art with spectrum
|
||||
// Album art container with circular spectrum overlay
|
||||
Item {
|
||||
id: albumArtContainer
|
||||
width: 96
|
||||
height: 96 // enough for spectrum and art (will adjust if needed)
|
||||
width: 96 * Theme.scale(Screen)
|
||||
height: 96 * Theme.scale(Screen) // enough for spectrum and art (will adjust if needed)
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
|
||||
// Spectrum visualizer
|
||||
// Circular spectrum visualizer around album art
|
||||
CircularSpectrum {
|
||||
id: spectrum
|
||||
values: MusicManager.cavaValues
|
||||
anchors.centerIn: parent
|
||||
innerRadius: 30 // just outside 60x60 album art
|
||||
outerRadius: 48 // how far bars extend
|
||||
innerRadius: 30 * Theme.scale(Screen) // Position just outside 60x60 album art
|
||||
outerRadius: 48 * Theme.scale(Screen) // Extend bars outward from album art
|
||||
fillColor: Theme.accentPrimary
|
||||
strokeColor: Theme.accentPrimary
|
||||
strokeWidth: 0
|
||||
strokeWidth: 0 * Theme.scale(Screen)
|
||||
z: 0
|
||||
}
|
||||
|
||||
// Album art image
|
||||
Rectangle {
|
||||
id: albumArtwork
|
||||
width: 60
|
||||
height: 60
|
||||
width: 60 * Theme.scale(Screen)
|
||||
height: 60 * Theme.scale(Screen)
|
||||
anchors.centerIn: parent
|
||||
radius: 30 // circle
|
||||
radius: 30 * Theme.scale(Screen) // 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
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
Image {
|
||||
id: albumArt
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
anchors.margins: 2 * Theme.scale(Screen)
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
mipmap: true
|
||||
cache: false
|
||||
asynchronous: true
|
||||
sourceSize.width: 60
|
||||
sourceSize.height: 60
|
||||
sourceSize.width: 60 * Theme.scale(Screen)
|
||||
sourceSize.height: 60 * Theme.scale(Screen)
|
||||
source: MusicManager.trackArtUrl
|
||||
visible: source.toString() !== ""
|
||||
|
||||
// Rounded corners using layer
|
||||
// Apply circular mask for rounded corners
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
|
|
@ -208,12 +208,12 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Fallback icon
|
||||
// Fallback icon when no album art available
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "album"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
font.pixelSize: Theme.fontSizeBody * Theme.scale(Screen)
|
||||
color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.4)
|
||||
visible: !albumArt.visible
|
||||
}
|
||||
|
|
@ -223,13 +223,13 @@ Rectangle {
|
|||
// Track metadata
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
spacing: 4 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
text: MusicManager.trackTitle
|
||||
color: Theme.textPrimary
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.fontSizeSmall * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
|
|
@ -241,7 +241,7 @@ Rectangle {
|
|||
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
|
||||
font.pixelSize: Theme.fontSizeCaption * Theme.scale(Screen)
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
|
@ -250,7 +250,7 @@ Rectangle {
|
|||
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
|
||||
font.pixelSize: Theme.fontSizeCaption * Theme.scale(Screen)
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
|
@ -261,8 +261,8 @@ Rectangle {
|
|||
Rectangle {
|
||||
id: progressBarBackground
|
||||
width: parent.width
|
||||
height: 6
|
||||
radius: 3
|
||||
height: 6 * Theme.scale(Screen)
|
||||
radius: 3 * Theme.scale(Screen)
|
||||
color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.15)
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
|
@ -290,12 +290,12 @@ Rectangle {
|
|||
// Interactive progress handle
|
||||
Rectangle {
|
||||
id: progressHandle
|
||||
width: 12
|
||||
height: 12
|
||||
radius: 6
|
||||
width: 12 * Theme.scale(Screen)
|
||||
height: 12 * Theme.scale(Screen)
|
||||
radius: 6 * Theme.scale(Screen)
|
||||
color: Theme.accentPrimary
|
||||
border.color: Qt.lighter(Theme.accentPrimary, 1.3)
|
||||
border.width: 1
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
x: Math.max(0, Math.min(parent.width - width, progressFill.width - width / 2))
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
|
@ -334,18 +334,18 @@ Rectangle {
|
|||
|
||||
// Media controls
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
spacing: 4 * Theme.scale(Screen)
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
// Previous button
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
width: 28 * Theme.scale(Screen)
|
||||
height: 28 * Theme.scale(Screen)
|
||||
radius: 14 * Theme.scale(Screen)
|
||||
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
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
MouseArea {
|
||||
id: previousButton
|
||||
|
|
@ -360,19 +360,19 @@ Rectangle {
|
|||
anchors.centerIn: parent
|
||||
text: "skip_previous"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
font.pixelSize: Theme.fontSizeCaption * Theme.scale(Screen)
|
||||
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
|
||||
width: 36 * Theme.scale(Screen)
|
||||
height: 36 * Theme.scale(Screen)
|
||||
radius: 18 * Theme.scale(Screen)
|
||||
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
|
||||
border.width: 2 * Theme.scale(Screen)
|
||||
|
||||
MouseArea {
|
||||
id: playButton
|
||||
|
|
@ -387,19 +387,19 @@ Rectangle {
|
|||
anchors.centerIn: parent
|
||||
text: MusicManager.isPlaying ? "pause" : "play_arrow"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
font.pixelSize: Theme.fontSizeBody * Theme.scale(Screen)
|
||||
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
|
||||
width: 28 * Theme.scale(Screen)
|
||||
height: 28 * Theme.scale(Screen)
|
||||
radius: 14 * Theme.scale(Screen)
|
||||
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
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
MouseArea {
|
||||
id: nextButton
|
||||
|
|
@ -414,7 +414,7 @@ Rectangle {
|
|||
anchors.centerIn: parent
|
||||
text: "skip_next"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
font.pixelSize: Theme.fontSizeCaption * Theme.scale(Screen)
|
||||
color: nextButton.enabled ? Theme.accentPrimary : Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.3)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,13 +3,15 @@ import QtQuick.Layouts
|
|||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import qs.Settings
|
||||
import qs.Widgets.Sidebar.Config
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
import qs.Widgets.SettingsWindow
|
||||
|
||||
PanelWithOverlay {
|
||||
id: sidebarPopup
|
||||
|
||||
property var shell: null
|
||||
|
||||
function showAt() {
|
||||
sidebarPopupRect.showAt();
|
||||
}
|
||||
|
|
@ -26,18 +28,44 @@ PanelWithOverlay {
|
|||
sidebarPopupRect.hidePopup();
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: sidebarPopupRect
|
||||
implicitWidth: 500
|
||||
implicitHeight: 800
|
||||
visible: parent.visible
|
||||
color: "transparent"
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
// 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();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
// Access the shell's SettingsWindow instead of creating a new one
|
||||
|
||||
id: sidebarPopupRect
|
||||
|
||||
// Animation properties
|
||||
property real slideOffset: width
|
||||
property bool isAnimating: false
|
||||
property int leftPadding: 20 * Theme.scale(Screen)
|
||||
property int bottomPadding: 20 * Theme.scale(Screen)
|
||||
// Recording properties
|
||||
property bool isRecording: false
|
||||
|
||||
Process {
|
||||
id: checkRecordingProcess
|
||||
command: ["pgrep", "-f", "gpu-screen-recorder.*portal"]
|
||||
onExited: function(exitCode, exitStatus) {
|
||||
var isActuallyRecording = exitCode === 0
|
||||
if (isRecording && !isActuallyRecording) {
|
||||
isRecording = isActuallyRecording
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkRecordingStatus() {
|
||||
if (isRecording) {
|
||||
checkRecordingProcess.running = true
|
||||
}
|
||||
}
|
||||
|
||||
function showAt() {
|
||||
if (!sidebarPopup.visible) {
|
||||
|
|
@ -48,26 +76,18 @@ PanelWithOverlay {
|
|||
slideAnim.running = true;
|
||||
if (weather)
|
||||
weather.startWeatherFetch();
|
||||
|
||||
if (systemWidget)
|
||||
systemWidget.panelVisible = true;
|
||||
if (quickAccessWidget)
|
||||
quickAccessWidget.panelVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
function hidePopup() {
|
||||
if (sidebarPopupRect.settingsModal && sidebarPopupRect.settingsModal.visible) {
|
||||
sidebarPopupRect.settingsModal.visible = false;
|
||||
}
|
||||
if (wallpaperPanel && wallpaperPanel.visible) {
|
||||
wallpaperPanel.visible = false;
|
||||
}
|
||||
if (sidebarPopupRect.wifiPanelModal && sidebarPopupRect.wifiPanelModal.visible) {
|
||||
sidebarPopupRect.wifiPanelModal.visible = false;
|
||||
}
|
||||
if (sidebarPopupRect.bluetoothPanelModal && sidebarPopupRect.bluetoothPanelModal.visible) {
|
||||
sidebarPopupRect.bluetoothPanelModal.visible = false;
|
||||
}
|
||||
if (shell && shell.settingsWindow && shell.settingsWindow.visible)
|
||||
shell.settingsWindow.visible = false;
|
||||
|
||||
|
||||
|
||||
if (sidebarPopup.visible) {
|
||||
slideAnim.from = 0;
|
||||
slideAnim.to = width;
|
||||
|
|
@ -75,81 +95,129 @@ PanelWithOverlay {
|
|||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
implicitWidth: 500 * Theme.scale(Screen)
|
||||
implicitHeight: 700 * Theme.scale(Screen)
|
||||
visible: parent.visible
|
||||
color: "transparent"
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
// Clean up processes on destruction
|
||||
Component.onDestruction: {
|
||||
if (isRecording)
|
||||
stopRecording();
|
||||
|
||||
}
|
||||
|
||||
// Prevent closing when clicking in the panel bg
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: slideAnim
|
||||
|
||||
target: sidebarPopupRect
|
||||
property: "slideOffset"
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
|
||||
onStopped: {
|
||||
if (sidebarPopupRect.slideOffset === sidebarPopupRect.width) {
|
||||
sidebarPopup.visible = false;
|
||||
// Stop monitoring and background tasks when hidden
|
||||
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
|
||||
bottomLeftRadius: 20 * Theme.scale(Screen)
|
||||
z: 0
|
||||
|
||||
Behavior on x {
|
||||
enabled: !sidebarPopupRect.isAnimating
|
||||
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
property alias settingsModal: settingsModal
|
||||
property alias wifiPanelModal: wifiPanel.panel
|
||||
property alias bluetoothPanelModal: bluetoothPanel.panel
|
||||
SettingsModal {
|
||||
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Keys.onEscapePressed: sidebarPopupRect.hidePopup()
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 16
|
||||
anchors.margins: 20 * Theme.scale(Screen)
|
||||
spacing: 4 * Theme.scale(Screen)
|
||||
|
||||
System {
|
||||
PowerMenu {
|
||||
id: systemWidget
|
||||
settingsModal: settingsModal
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
z: 3
|
||||
}
|
||||
|
|
@ -162,7 +230,7 @@ PanelWithOverlay {
|
|||
|
||||
// Music and System Monitor row
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
spacing: 12 * Theme.scale(Screen)
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
|
|
@ -174,241 +242,147 @@ PanelWithOverlay {
|
|||
id: systemMonitor
|
||||
z: 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Power profile, Wifi and Bluetooth row
|
||||
// Power profile, Record and Wallpaper row
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.preferredHeight: 80
|
||||
spacing: 16
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: 10 * Theme.scale(Screen)
|
||||
Layout.preferredHeight: 80 * Theme.scale(Screen)
|
||||
z: 3
|
||||
|
||||
PowerProfile {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.preferredHeight: 80
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: 80 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
// Network card containing Wifi and Bluetooth
|
||||
// Record and Wallpaper card
|
||||
Rectangle {
|
||||
Layout.preferredHeight: 80
|
||||
Layout.preferredWidth: 140
|
||||
Layout.preferredHeight: 80 * Theme.scale(Screen)
|
||||
Layout.preferredWidth: 140 * Theme.scale(Screen)
|
||||
Layout.fillWidth: false
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
radius: 18 * Theme.scale(Screen)
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 20
|
||||
spacing: 20 * Theme.scale(Screen)
|
||||
|
||||
// Wifi button
|
||||
// Record button
|
||||
Rectangle {
|
||||
id: wifiButton
|
||||
width: 36
|
||||
height: 36
|
||||
radius: 18
|
||||
id: recordButton
|
||||
|
||||
width: 36 * Theme.scale(Screen)
|
||||
height: 36 * Theme.scale(Screen)
|
||||
radius: 18 * Theme.scale(Screen)
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
color: wifiButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
color: sidebarPopupRect.isRecording ? Theme.accentPrimary :
|
||||
(recordButtonArea.containsMouse ? Theme.accentPrimary : "transparent")
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "wifi"
|
||||
text: "photo_camera"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
color: wifiButtonArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
font.pixelSize: 22 * Theme.scale(Screen)
|
||||
color: sidebarPopupRect.isRecording || recordButtonArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wifiButtonArea
|
||||
id: recordButtonArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: wifiPanel.showAt()
|
||||
onClicked: {
|
||||
if (sidebarPopupRect.isRecording) {
|
||||
sidebarPopupRect.stopRecording();
|
||||
sidebarPopup.dismiss();
|
||||
} else {
|
||||
sidebarPopupRect.startRecording();
|
||||
sidebarPopup.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledTooltip {
|
||||
text: "Wifi"
|
||||
targetItem: wifiButtonArea
|
||||
tooltipVisible: wifiButtonArea.containsMouse
|
||||
text: sidebarPopupRect.isRecording ? "Stop Recording" : "Start Recording"
|
||||
targetItem: recordButtonArea
|
||||
tooltipVisible: recordButtonArea.containsMouse
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Bluetooth button
|
||||
// Wallpaper button
|
||||
Rectangle {
|
||||
id: bluetoothButton
|
||||
width: 36
|
||||
height: 36
|
||||
radius: 18
|
||||
id: wallpaperButton
|
||||
|
||||
width: 36 * Theme.scale(Screen)
|
||||
height: 36 * Theme.scale(Screen)
|
||||
radius: 18 * Theme.scale(Screen)
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
color: bluetoothButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
color: wallpaperButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "bluetooth"
|
||||
text: "image"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
color: bluetoothButtonArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
font.pixelSize: 22 * Theme.scale(Screen)
|
||||
color: wallpaperButtonArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: bluetoothButtonArea
|
||||
id: wallpaperButtonArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: bluetoothPanel.showAt()
|
||||
onClicked: {
|
||||
if (typeof settingsModal !== 'undefined' && settingsModal && settingsModal.openSettings) {
|
||||
settingsModal.openSettings(6);
|
||||
sidebarPopup.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledTooltip {
|
||||
text: "Bluetooth"
|
||||
targetItem: bluetoothButtonArea
|
||||
tooltipVisible: bluetoothButtonArea.containsMouse
|
||||
text: "Wallpaper"
|
||||
targetItem: wallpaperButtonArea
|
||||
tooltipVisible: wallpaperButtonArea.containsMouse
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Hidden panel components for modal functionality
|
||||
WifiPanel {
|
||||
id: wifiPanel
|
||||
visible: false
|
||||
}
|
||||
BluetoothPanel {
|
||||
id: bluetoothPanel
|
||||
visible: false
|
||||
}
|
||||
|
||||
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: {
|
||||
settingsModal.visible = true;
|
||||
}
|
||||
onWallpaperRequested: {
|
||||
wallpaperPanel.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 60 -a default_output -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();
|
||||
}
|
||||
}
|
||||
|
||||
Corners {
|
||||
id: sidebarCornerLeft
|
||||
position: "bottomright"
|
||||
size: 1.1
|
||||
fillColor: Theme.backgroundPrimary
|
||||
anchors.top: mainRectangle.top
|
||||
offsetX: -447 + sidebarPopupRect.slideOffset
|
||||
offsetY: 0
|
||||
visible: Settings.settings.showCorners
|
||||
|
||||
Behavior on offsetX {
|
||||
Behavior on x {
|
||||
enabled: !sidebarPopupRect.isAnimating
|
||||
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Corners {
|
||||
id: sidebarCornerBottom
|
||||
position: "bottomright"
|
||||
size: 1.1
|
||||
fillColor: Theme.backgroundPrimary
|
||||
offsetX: 33 + sidebarPopupRect.slideOffset
|
||||
offsetY: 46
|
||||
visible: Settings.settings.showCorners
|
||||
|
||||
Behavior on offsetX {
|
||||
enabled: !sidebarPopupRect.isAnimating
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WallpaperPanel {
|
||||
id: wallpaperPanel
|
||||
Component.onCompleted: {
|
||||
if (parent) {
|
||||
anchors.top = parent.top;
|
||||
anchors.right = parent.right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,396 +1,23 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
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
|
||||
|
||||
// User info row
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
// Profile image
|
||||
Rectangle {
|
||||
width: 48
|
||||
height: 48
|
||||
radius: 24
|
||||
color: Theme.accentPrimary
|
||||
|
||||
// Border
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: 24
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 2
|
||||
z: 2
|
||||
}
|
||||
|
||||
Avatar {}
|
||||
}
|
||||
|
||||
// User info text
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// System menu button
|
||||
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
|
||||
// System menu popup
|
||||
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
|
||||
|
||||
// Position below system button
|
||||
anchors.rightMargin: 32
|
||||
anchors.topMargin: systemButton.y + systemButton.height + 48
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 4
|
||||
|
||||
// Lock button
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Suspend button
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reboot button
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logout button
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown button
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Properties
|
||||
property string uptimeText: "--:--"
|
||||
|
||||
// Process to get uptime
|
||||
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
|
||||
}
|
||||
property bool panelVisible: false
|
||||
property var settingsModal: null
|
||||
|
||||
Process {
|
||||
id: logoutProcess
|
||||
|
|
@ -399,14 +26,12 @@ Rectangle {
|
|||
}
|
||||
|
||||
function logout() {
|
||||
if (WorkspaceManager.isNiri) {
|
||||
if (WorkspaceManager.isNiri)
|
||||
logoutProcessNiri.running = true;
|
||||
} else if (WorkspaceManager.isHyprland) {
|
||||
else if (WorkspaceManager.isHyprland)
|
||||
logoutProcessHyprland.running = true;
|
||||
} else {
|
||||
// fallback or error
|
||||
else
|
||||
console.warn("No supported compositor detected for logout");
|
||||
}
|
||||
}
|
||||
|
||||
function suspend() {
|
||||
|
|
@ -421,33 +46,479 @@ Rectangle {
|
|||
rebootProcess.running = true;
|
||||
}
|
||||
|
||||
property bool panelVisible: false
|
||||
|
||||
// Trigger initial update when panel becomes visible
|
||||
onPanelVisibleChanged: {
|
||||
if (panelVisible) {
|
||||
updateSystemInfo();
|
||||
}
|
||||
function updateSystemInfo() {
|
||||
uptimeProcess.running = true;
|
||||
}
|
||||
|
||||
width: 440 * Theme.scale(Screen)
|
||||
height: 80 * Theme.scale(Screen)
|
||||
color: "transparent"
|
||||
anchors.horizontalCenterOffset: -2
|
||||
onPanelVisibleChanged: {
|
||||
if (panelVisible)
|
||||
updateSystemInfo();
|
||||
|
||||
}
|
||||
Component.onCompleted: {
|
||||
uptimeProcess.running = true;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
|
||||
anchors.fill: parent
|
||||
color: Theme.surface
|
||||
radius: 18 * Theme.scale(Screen)
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18 * Theme.scale(Screen)
|
||||
spacing: 12 * Theme.scale(Screen)
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12 * Theme.scale(Screen)
|
||||
|
||||
Rectangle {
|
||||
width: 48 * Theme.scale(Screen)
|
||||
height: 48 * Theme.scale(Screen)
|
||||
radius: 24 * Theme.scale(Screen)
|
||||
color: Theme.accentPrimary
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: 24 * Theme.scale(Screen)
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 2 * Theme.scale(Screen)
|
||||
z: 2
|
||||
}
|
||||
|
||||
Avatar {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4 * Theme.scale(Screen)
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: Quickshell.env("USER")
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "System Uptime: " + uptimeText
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: settingsButton
|
||||
|
||||
width: 32 * Theme.scale(Screen)
|
||||
height: 32 * Theme.scale(Screen)
|
||||
radius: 16 * Theme.scale(Screen)
|
||||
color: settingsButtonArea.containsMouse || settingsButtonArea.pressed ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: -1
|
||||
text: "settings"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: settingsButtonArea.containsMouse || settingsButtonArea.pressed ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: settingsButtonArea
|
||||
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
if (typeof settingsModal !== 'undefined' && settingsModal && settingsModal.openSettings)
|
||||
settingsModal.openSettings();
|
||||
}
|
||||
}
|
||||
|
||||
StyledTooltip {
|
||||
id: settingsTooltip
|
||||
|
||||
text: "Settings"
|
||||
targetItem: settingsButton
|
||||
tooltipVisible: settingsButtonArea.containsMouse
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: systemButton
|
||||
|
||||
width: 32 * Theme.scale(Screen)
|
||||
height: 32 * Theme.scale(Screen)
|
||||
radius: 16 * Theme.scale(Screen)
|
||||
color: systemButtonArea.containsMouse || systemButtonArea.pressed ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "power_settings_new"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
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: "Power Menu"
|
||||
targetItem: systemButton
|
||||
tooltipVisible: systemButtonArea.containsMouse
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PanelWithOverlay {
|
||||
id: systemMenu
|
||||
|
||||
anchors.top: systemButton.bottom
|
||||
anchors.right: systemButton.right
|
||||
|
||||
Rectangle {
|
||||
width: 160 * Theme.scale(Screen)
|
||||
height: 220 * Theme.scale(Screen)
|
||||
color: Theme.surface
|
||||
radius: 8 * Theme.scale(Screen)
|
||||
border.color: Theme.outline
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
visible: true
|
||||
z: 9999
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 32 * Theme.scale(Screen)
|
||||
anchors.topMargin: systemButton.y + systemButton.height + 48 * Theme.scale(Screen)
|
||||
|
||||
// Prevent closing when clicking in the panel bg
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8 * Theme.scale(Screen)
|
||||
spacing: 4 * Theme.scale(Screen)
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36 * Theme.scale(Screen)
|
||||
radius: 6 * Theme.scale(Screen)
|
||||
color: lockButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12 * Theme.scale(Screen)
|
||||
spacing: 8 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
text: "lock_outline"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: lockButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Lock Screen"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
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 * Theme.scale(Screen)
|
||||
radius: 6 * Theme.scale(Screen)
|
||||
color: suspendButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12 * Theme.scale(Screen)
|
||||
spacing: 8 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
text: "bedtime"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: suspendButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Suspend"
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
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 * Theme.scale(Screen)
|
||||
radius: 6 * Theme.scale(Screen)
|
||||
color: rebootButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12 * Theme.scale(Screen)
|
||||
spacing: 8 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
text: "refresh"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: rebootButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Reboot"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
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 * Theme.scale(Screen)
|
||||
radius: 6 * Theme.scale(Screen)
|
||||
color: logoutButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12 * Theme.scale(Screen)
|
||||
spacing: 8 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
text: "exit_to_app"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: logoutButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Logout"
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
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 * Theme.scale(Screen)
|
||||
radius: 6 * Theme.scale(Screen)
|
||||
color: shutdownButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12 * Theme.scale(Screen)
|
||||
spacing: 8 * Theme.scale(Screen)
|
||||
|
||||
Text {
|
||||
text: "power_settings_new"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16 * Theme.scale(Screen)
|
||||
color: shutdownButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Shutdown"
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Timer to update uptime - only runs when panel is visible
|
||||
Timer {
|
||||
interval: 60000 // Update every minute
|
||||
interval: 60000
|
||||
repeat: true
|
||||
running: panelVisible
|
||||
onTriggered: updateSystemInfo()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
uptimeProcess.running = true;
|
||||
}
|
||||
|
||||
function updateSystemInfo() {
|
||||
uptimeProcess.running = true;
|
||||
}
|
||||
|
||||
// Add lockscreen instance (hidden by default)
|
||||
LockScreen {
|
||||
id: lockScreen
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,22 +7,22 @@ import qs.Components
|
|||
|
||||
Rectangle {
|
||||
id: card
|
||||
width: 200
|
||||
height: 70
|
||||
width: 200 * Theme.scale(Screen)
|
||||
height: 70 * Theme.scale(Screen)
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
radius: 18 * Theme.scale(Screen)
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 20
|
||||
spacing: 20 * Theme.scale(Screen)
|
||||
|
||||
|
||||
// Performance
|
||||
Rectangle {
|
||||
width: 36; height: 36
|
||||
radius: 18
|
||||
width: 36 * Theme.scale(Screen); height: 36 * Theme.scale(Screen)
|
||||
radius: 18 * Theme.scale(Screen)
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
color: (typeof PowerProfiles !== 'undefined' && PowerProfiles.profile === PowerProfile.Performance)
|
||||
? Theme.accentPrimary
|
||||
: (perfMouseArea.containsMouse ? Theme.accentPrimary : "transparent")
|
||||
|
|
@ -33,7 +33,7 @@ Rectangle {
|
|||
anchors.centerIn: parent
|
||||
text: "speed"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
font.pixelSize: 22 * Theme.scale(Screen)
|
||||
color: (typeof PowerProfiles !== 'undefined' && PowerProfiles.profile === PowerProfile.Performance) || perfMouseArea.containsMouse
|
||||
? Theme.backgroundPrimary
|
||||
: Theme.accentPrimary
|
||||
|
|
@ -63,12 +63,12 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Balanced
|
||||
|
||||
Rectangle {
|
||||
width: 36; height: 36
|
||||
radius: 18
|
||||
width: 36 * Theme.scale(Screen); height: 36 * Theme.scale(Screen)
|
||||
radius: 18 * Theme.scale(Screen)
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
color: (typeof PowerProfiles !== 'undefined' && PowerProfiles.profile === PowerProfile.Balanced)
|
||||
? Theme.accentPrimary
|
||||
: (balMouseArea.containsMouse ? Theme.accentPrimary : "transparent")
|
||||
|
|
@ -79,7 +79,7 @@ Rectangle {
|
|||
anchors.centerIn: parent
|
||||
text: "balance"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
font.pixelSize: 22 * Theme.scale(Screen)
|
||||
color: (typeof PowerProfiles !== 'undefined' && PowerProfiles.profile === PowerProfile.Balanced) || balMouseArea.containsMouse
|
||||
? Theme.backgroundPrimary
|
||||
: Theme.accentPrimary
|
||||
|
|
@ -109,12 +109,12 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Power Saver
|
||||
|
||||
Rectangle {
|
||||
width: 36; height: 36
|
||||
radius: 18
|
||||
width: 36 * Theme.scale(Screen); height: 36 * Theme.scale(Screen)
|
||||
radius: 18 * Theme.scale(Screen)
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
border.width: 1 * Theme.scale(Screen)
|
||||
color: (typeof PowerProfiles !== 'undefined' && PowerProfiles.profile === PowerProfile.PowerSaver)
|
||||
? Theme.accentPrimary
|
||||
: (saveMouseArea.containsMouse ? Theme.accentPrimary : "transparent")
|
||||
|
|
@ -125,7 +125,7 @@ Rectangle {
|
|||
anchors.centerIn: parent
|
||||
text: "eco"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
font.pixelSize: 22 * Theme.scale(Screen)
|
||||
color: (typeof PowerProfiles !== 'undefined' && PowerProfiles.profile === PowerProfile.PowerSaver) || saveMouseArea.containsMouse
|
||||
? Theme.backgroundPrimary
|
||||
: Theme.accentPrimary
|
||||
94
Widgets/SidePanel/SettingsIcon.qml
Normal file
94
Widgets/SidePanel/SettingsIcon.qml
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
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 * Theme.scale(Screen)
|
||||
implicitHeight: 780 * Theme.scale(Screen)
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
sidebarPopup.dismiss();
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
81
Widgets/SidePanel/SettingsModal.qml
Normal file
81
Widgets/SidePanel/SettingsModal.qml
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
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 * Theme.scale(Screen)
|
||||
implicitHeight: 780 * Theme.scale(Screen)
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,35 +8,37 @@ import qs.Settings
|
|||
|
||||
Rectangle {
|
||||
id: systemMonitor
|
||||
width: 70
|
||||
height: 250
|
||||
width: 70 * Theme.scale(Screen)
|
||||
height: 250 * Theme.scale(Screen)
|
||||
color: "transparent"
|
||||
|
||||
// Track visibility state for panel integration
|
||||
property bool isVisible: false
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
anchors.fill: parent
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
radius: 18 * Theme.scale(Screen)
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 12
|
||||
anchors.margins: 8 * Theme.scale(Screen)
|
||||
spacing: 12 * Theme.scale(Screen)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
// CPU Usage
|
||||
|
||||
// CPU usage indicator with circular progress bar
|
||||
Item {
|
||||
width: 50; height: 50
|
||||
width: 50 * Theme.scale(Screen); height: 50 * Theme.scale(Screen)
|
||||
CircularProgressBar {
|
||||
id: cpuBar
|
||||
progress: Sysinfo.cpuUsage / 100
|
||||
size: 50
|
||||
strokeWidth: 4
|
||||
size: 50 * Theme.scale(Screen)
|
||||
strokeWidth: 4 * Theme.scale(Screen)
|
||||
hasNotch: true
|
||||
notchIcon: "speed"
|
||||
notchIconSize: 14
|
||||
notchIconSize: 14 * Theme.scale(Screen)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
MouseArea {
|
||||
|
|
@ -55,18 +57,19 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Cpu Temp
|
||||
|
||||
// CPU temperature indicator with circular progress bar
|
||||
Item {
|
||||
width: 50; height: 50
|
||||
width: 50 * Theme.scale(Screen); height: 50 * Theme.scale(Screen)
|
||||
CircularProgressBar {
|
||||
id: tempBar
|
||||
progress: Sysinfo.cpuTemp / 100
|
||||
size: 50
|
||||
strokeWidth: 4
|
||||
size: 50 * Theme.scale(Screen)
|
||||
strokeWidth: 4 * Theme.scale(Screen)
|
||||
hasNotch: true
|
||||
units: "°C"
|
||||
notchIcon: "thermometer"
|
||||
notchIconSize: 14
|
||||
notchIconSize: 14 * Theme.scale(Screen)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
MouseArea {
|
||||
|
|
@ -85,17 +88,18 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Memory Usage
|
||||
|
||||
// Memory usage indicator with circular progress bar
|
||||
Item {
|
||||
width: 50; height: 50
|
||||
width: 50 * Theme.scale(Screen); height: 50 * Theme.scale(Screen)
|
||||
CircularProgressBar {
|
||||
id: memBar
|
||||
progress: Sysinfo.memoryUsagePer / 100
|
||||
size: 50
|
||||
strokeWidth: 4
|
||||
size: 50 * Theme.scale(Screen)
|
||||
strokeWidth: 4 * Theme.scale(Screen)
|
||||
hasNotch: true
|
||||
notchIcon: "memory"
|
||||
notchIconSize: 14
|
||||
notchIconSize: 14 * Theme.scale(Screen)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
MouseArea {
|
||||
|
|
@ -114,17 +118,18 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Disk Usage
|
||||
|
||||
// Disk usage indicator with circular progress bar
|
||||
Item {
|
||||
width: 50; height: 50
|
||||
width: 50 * Theme.scale(Screen); height: 50 * Theme.scale(Screen)
|
||||
CircularProgressBar {
|
||||
id: diskBar
|
||||
progress: Sysinfo.diskUsage / 100
|
||||
size: 50
|
||||
strokeWidth: 4
|
||||
size: 50 * Theme.scale(Screen)
|
||||
strokeWidth: 4 * Theme.scale(Screen)
|
||||
hasNotch: true
|
||||
notchIcon: "storage"
|
||||
notchIconSize: 14
|
||||
notchIconSize: 14 * Theme.scale(Screen)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
MouseArea {
|
||||
|
|
@ -2,12 +2,12 @@ import QtQuick
|
|||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import qs.Settings
|
||||
import "../../../Helpers/Weather.js" as WeatherHelper
|
||||
import "../../Helpers/Weather.js" as WeatherHelper
|
||||
|
||||
Rectangle {
|
||||
id: weatherRoot
|
||||
width: 440
|
||||
height: 180
|
||||
width: 440 * Theme.scale(Screen)
|
||||
height: 180 * Theme.scale(Screen)
|
||||
color: "transparent"
|
||||
anchors.horizontalCenterOffset: -2
|
||||
|
||||
|
|
@ -15,6 +15,20 @@ Rectangle {
|
|||
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) {
|
||||
|
|
@ -23,20 +37,42 @@ Rectangle {
|
|||
}
|
||||
|
||||
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
|
||||
fetchCityWeather()
|
||||
// Force refresh when panel opens, regardless of time check
|
||||
lastFetchTime = 0;
|
||||
fetchCityWeather();
|
||||
}
|
||||
|
||||
function stopWeatherFetch() {
|
||||
|
|
@ -47,81 +83,90 @@ Rectangle {
|
|||
id: card
|
||||
anchors.fill: parent
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
radius: 18 * Theme.scale(Screen)
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18
|
||||
spacing: 12
|
||||
anchors.margins: 18 * Theme.scale(Screen)
|
||||
spacing: 12 * Theme.scale(Screen)
|
||||
|
||||
// Current weather row
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
spacing: 12 * Theme.scale(Screen)
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Weather icon and basic info section
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
Layout.preferredWidth: 140
|
||||
|
||||
// Weather icon
|
||||
RowLayout {
|
||||
spacing: 12 * Theme.scale(Screen)
|
||||
Layout.preferredWidth: 140 * Theme.scale(Screen)
|
||||
|
||||
|
||||
Text {
|
||||
id: weatherIcon
|
||||
text: weatherData && weatherData.current_weather ? materialSymbolForCode(weatherData.current_weather.weathercode) : "cloud"
|
||||
text: isLoading ? "sync" : (weatherData && weatherData.current_weather ? materialSymbolForCode(weatherData.current_weather.weathercode) : "cloud")
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 28
|
||||
font.pixelSize: 28 * Theme.scale(Screen)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Theme.accentPrimary
|
||||
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
|
||||
spacing: 2 * Theme.scale(Screen)
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
spacing: 4 * Theme.scale(Screen)
|
||||
Text {
|
||||
text: city
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14
|
||||
font.pixelSize: 14 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
Text {
|
||||
text: weatherData && weatherData.timezone_abbreviation ? `(${weatherData.timezone_abbreviation})` : ""
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 10
|
||||
font.pixelSize: 10 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
leftPadding: 2
|
||||
leftPadding: 2 * Theme.scale(Screen)
|
||||
}
|
||||
}
|
||||
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.pixelSize: 24 * Theme.scale(Screen)
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
// Spacer to push content to the right
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
// Separator line
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
height: 1 * Theme.scale(Screen)
|
||||
color: Qt.rgba(Theme.textSecondary.g, Theme.textSecondary.g, Theme.textSecondary.b, 0.12)
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 2
|
||||
Layout.bottomMargin: 2
|
||||
Layout.topMargin: 2 * Theme.scale(Screen)
|
||||
Layout.bottomMargin: 2 * Theme.scale(Screen)
|
||||
}
|
||||
|
||||
// 5-day forecast row
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
spacing: 12 * Theme.scale(Screen)
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: weatherData && weatherData.daily && weatherData.daily.time
|
||||
|
|
@ -129,31 +174,31 @@ Rectangle {
|
|||
Repeater {
|
||||
model: weatherData && weatherData.daily && weatherData.daily.time ? 5 : 0
|
||||
delegate: ColumnLayout {
|
||||
spacing: 2
|
||||
spacing: 2 * Theme.scale(Screen)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Text {
|
||||
// Day of the week (e.g., Mon)
|
||||
|
||||
text: Qt.formatDateTime(new Date(weatherData.daily.time[index]), "ddd")
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 12
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textSecondary
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
Text {
|
||||
// Material Symbol icon
|
||||
|
||||
text: materialSymbolForCode(weatherData.daily.weathercode[index])
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
font.pixelSize: 22 * Theme.scale(Screen)
|
||||
color: Theme.accentPrimary
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
Text {
|
||||
// High/low temp
|
||||
|
||||
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
|
||||
font.pixelSize: 12 * Theme.scale(Screen)
|
||||
color: Theme.textPrimary
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
|
@ -162,29 +207,29 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Error message
|
||||
|
||||
Text {
|
||||
text: errorString
|
||||
color: Theme.error
|
||||
visible: errorString !== ""
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 10
|
||||
font.pixelSize: 10 * Theme.scale(Screen)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Weather code to Material Symbol ligature mapping
|
||||
|
||||
function materialSymbolForCode(code) {
|
||||
if (code === 0) return "sunny"; // Clear
|
||||
if (code === 1 || code === 2) return "partly_cloudy_day"; // Mainly clear/partly cloudy
|
||||
if (code === 3) return "cloud"; // Overcast
|
||||
if (code >= 45 && code <= 48) return "foggy"; // Fog
|
||||
if (code >= 51 && code <= 67) return "rainy"; // Drizzle
|
||||
if (code >= 71 && code <= 77) return "weather_snowy"; // Snow
|
||||
if (code >= 80 && code <= 82) return "rainy"; // Rain showers
|
||||
if (code >= 95 && code <= 99) return "thunderstorm"; // Thunderstorm
|
||||
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) {
|
||||
|
|
@ -204,19 +204,19 @@ Item {
|
|||
|
||||
wifiLogic.connectingSsid = params.ssid;
|
||||
|
||||
// Find the target network in our networks data
|
||||
|
||||
const targetNetwork = wifiLogic.networks[params.ssid];
|
||||
|
||||
// Check if profile already exists using existing field
|
||||
|
||||
if (targetNetwork && targetNetwork.existing) {
|
||||
// Profile exists, just bring it up (no password prompt)
|
||||
|
||||
upConnectionProcess.profileName = params.ssid;
|
||||
upConnectionProcess.running = true;
|
||||
wifiLogic.pendingConnect = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// No existing profile, proceed with normal connection flow
|
||||
|
||||
if (params.security && params.security !== "--") {
|
||||
getInterfaceProcess.running = true;
|
||||
return;
|
||||
|
|
@ -232,7 +232,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Disconnect, delete profile, refresh
|
||||
|
||||
Process {
|
||||
id: disconnectProfileProcess
|
||||
property string connectionName: ""
|
||||
|
|
@ -291,7 +291,7 @@ Item {
|
|||
}
|
||||
|
||||
|
||||
// Handles connecting to a Wi-Fi network, with or without password
|
||||
|
||||
Process {
|
||||
id: connectProcess
|
||||
property string ssid: ""
|
||||
|
|
@ -336,7 +336,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Finds the correct Wi-Fi interface for connection
|
||||
|
||||
Process {
|
||||
id: getInterfaceProcess
|
||||
running: false
|
||||
|
|
@ -370,7 +370,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Adds a new Wi-Fi connection profile
|
||||
|
||||
Process {
|
||||
id: addConnectionProcess
|
||||
property string ifname: ""
|
||||
|
|
@ -403,7 +403,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Brings up the new connection profile and finalizes connection state
|
||||
|
||||
Process {
|
||||
id: upConnectionProcess
|
||||
property string profileName: ""
|
||||
|
|
@ -436,7 +436,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Wifi button (no background card)
|
||||
|
||||
Rectangle {
|
||||
id: wifiButton
|
||||
width: 36
|
||||
|
|
@ -516,8 +516,8 @@ Item {
|
|||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: false
|
||||
running: false
|
||||
color: Theme.accentPrimary // Assuming Spinner supports color property
|
||||
size: 22 // Based on the existing Spinner usage
|
||||
color: Theme.accentPrimary
|
||||
size: 22
|
||||
}
|
||||
IconButton {
|
||||
id: refreshButton
|
||||
|
|
@ -704,7 +704,7 @@ Item {
|
|||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
// Toggle the action panel for this network
|
||||
|
||||
if (wifiLogic.actionPanelSsid === modelData.ssid) {
|
||||
wifiLogic.actionPanelSsid = ""; // Close if already open
|
||||
} else {
|
||||
|
|
@ -791,7 +791,7 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Action panel for network connection controls
|
||||
|
||||
Rectangle {
|
||||
visible: modelData.ssid === wifiLogic.actionPanelSsid
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -806,7 +806,7 @@ Item {
|
|||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 10
|
||||
// Password field for new secured networks
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
|
|
@ -830,7 +830,7 @@ Item {
|
|||
inputMethodHints: Qt.ImhNone
|
||||
echoMode: TextInput.Password
|
||||
onAccepted: {
|
||||
// Connect with the entered password
|
||||
|
||||
wifiLogic.pendingConnect = {
|
||||
ssid: modelData.ssid,
|
||||
security: modelData.security,
|
||||
|
|
@ -843,7 +843,7 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Connect/Disconnect button
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 80
|
||||
Layout.preferredHeight: 36
|
||||
|
|
@ -861,12 +861,12 @@ Item {
|
|||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
// Disconnect from network
|
||||
|
||||
wifiLogic.disconnectNetwork(modelData.ssid);
|
||||
} else {
|
||||
// For secured networks, check if we need password
|
||||
|
||||
if (wifiLogic.isSecured(modelData.security) && !modelData.existing) {
|
||||
// If password field is visible and has content, use it
|
||||
|
||||
if (actionPanelPasswordField.text.length > 0) {
|
||||
wifiLogic.pendingConnect = {
|
||||
ssid: modelData.ssid,
|
||||
|
|
@ -875,10 +875,9 @@ Item {
|
|||
};
|
||||
wifiLogic.doConnect();
|
||||
}
|
||||
// For new networks without password entered, we might want to show an error or handle differently
|
||||
// For now, we'll just close the panel
|
||||
|
||||
} else {
|
||||
// Connect to open network
|
||||
|
||||
wifiLogic.connectNetwork(modelData.ssid, modelData.security);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import qs.Settings
|
||||
|
||||
ColumnLayout {
|
||||
property alias title: headerText.text
|
||||
property bool expanded: false // Hidden by default
|
||||
default property alias content: contentItem.children
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 44
|
||||
radius: 12
|
||||
color: Theme.surface
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 2
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 8
|
||||
Item { width: 2 }
|
||||
Text {
|
||||
id: headerText
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
Rectangle {
|
||||
width: 32; height: 32
|
||||
color: "transparent"
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: expanded ? "expand_less" : "expand_more"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
color: Theme.accentPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: expanded = !expanded
|
||||
}
|
||||
}
|
||||
Item { height: 8 }
|
||||
ColumnLayout {
|
||||
id: contentItem
|
||||
Layout.fillWidth: true
|
||||
visible: expanded
|
||||
spacing: 0
|
||||
}
|
||||
}
|
||||
|
|
@ -1,275 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Settings
|
||||
|
||||
Rectangle {
|
||||
id: weatherSettingsCard
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 320
|
||||
color: Theme.surface
|
||||
radius: 18
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18
|
||||
spacing: 12
|
||||
|
||||
// Weather Settings Header
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Text {
|
||||
text: "wb_sunny"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 20
|
||||
color: Theme.accentPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Weather Settings"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
// Weather City Setting
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "City"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
radius: 16
|
||||
color: Theme.surfaceVariant
|
||||
border.color: cityInput.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
TextInput {
|
||||
id: cityInput
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
anchors.topMargin: 6
|
||||
anchors.bottomMargin: 6
|
||||
text: Settings.settings.weatherCity
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
clip: true
|
||||
focus: true
|
||||
selectByMouse: true
|
||||
activeFocusOnTab: true
|
||||
inputMethodHints: Qt.ImhNone
|
||||
|
||||
onTextChanged: {
|
||||
Settings.settings.weatherCity = text;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.IBeamCursor
|
||||
onClicked: {
|
||||
cityInput.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Temperature Unit Setting
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "Temperature Unit"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Custom Material 3 Switch
|
||||
Rectangle {
|
||||
id: customSwitch
|
||||
width: 52
|
||||
height: 32
|
||||
radius: 16
|
||||
color: Theme.accentPrimary
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 2
|
||||
|
||||
Rectangle {
|
||||
id: thumb
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: Theme.surface
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
y: 2
|
||||
x: Settings.settings.useFahrenheit ? customSwitch.width - width - 2 : 2
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: Settings.settings.useFahrenheit ? "\u00b0F" : "\u00b0C"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 12
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Settings.settings.useFahrenheit = !Settings.settings.useFahrenheit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Random Wallpaper Setting
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
Text {
|
||||
text: "Use 12 Hour Clock"
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Custom Material 3 Switch
|
||||
Rectangle {
|
||||
id: use12HourClockSwitch
|
||||
width: 52
|
||||
height: 32
|
||||
radius: 16
|
||||
color: Settings.settings.use12HourClock ? Theme.accentPrimary : Theme.surfaceVariant
|
||||
border.color: Settings.settings.use12HourClock ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 2
|
||||
|
||||
Rectangle {
|
||||
id: randomWallpaperThumb
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: Theme.surface
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
y: 2
|
||||
x: Settings.settings.use12HourClock ? use12HourClockSwitch.width - width - 2 : 2
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Settings.settings.use12HourClock = !Settings.settings.use12HourClock;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse Day Month Setting
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
Text {
|
||||
text: "US Style Date"
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Custom Material 3 Switch
|
||||
Rectangle {
|
||||
id: reverseDayMonthSwitch
|
||||
width: 52
|
||||
height: 32
|
||||
radius: 16
|
||||
color: Settings.settings.reverseDayMonth ? Theme.accentPrimary : Theme.surfaceVariant
|
||||
border.color: Settings.settings.reverseDayMonth ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 2
|
||||
|
||||
Rectangle {
|
||||
id: reverseDayMonthThumb
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: Theme.surface
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
y: 2
|
||||
x: Settings.settings.reverseDayMonth ? reverseDayMonthSwitch.width - width - 2 : 2
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Settings.settings.reverseDayMonth = !Settings.settings.reverseDayMonth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue