mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-14 09:42:10 -04:00
replace qmlformat with a better tool
still not perfect, but well - what can ya do
This commit is contained in:
@@ -1,28 +1,26 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property int durShort: 200
|
readonly property int durShort: 200
|
||||||
readonly property int durMed: 450
|
readonly property int durMed: 450
|
||||||
readonly property int durLong: 600
|
readonly property int durLong: 600
|
||||||
|
|
||||||
readonly property int slidePx: 80
|
readonly property int slidePx: 80
|
||||||
|
|
||||||
readonly property var emphasized: [
|
readonly property var emphasized: [0.05, 0.00, 0.133333, 0.06, 0.166667, 0.40, 0.208333, 0.82, 0.25, 1.00, 1.00, 1.00]
|
||||||
0.05, 0.00, 0.133333, 0.06, 0.166667, 0.40,
|
|
||||||
0.208333, 0.82, 0.25, 1.00, 1.00, 1.00
|
|
||||||
]
|
|
||||||
|
|
||||||
readonly property var emphasizedDecel: [ 0.05, 0.70, 0.10, 1.00, 1.00, 1.00 ]
|
readonly property var emphasizedDecel: [0.05, 0.70, 0.10, 1.00, 1.00, 1.00]
|
||||||
|
|
||||||
readonly property var emphasizedAccel: [ 0.30, 0.00, 0.80, 0.15, 1.00, 1.00 ]
|
readonly property var emphasizedAccel: [0.30, 0.00, 0.80, 0.15, 1.00, 1.00]
|
||||||
|
|
||||||
readonly property var standard: [ 0.20, 0.00, 0.00, 1.00, 1.00, 1.00 ]
|
readonly property var standard: [0.20, 0.00, 0.00, 1.00, 1.00, 1.00]
|
||||||
readonly property var standardDecel: [ 0.00, 0.00, 0.00, 1.00, 1.00, 1.00 ]
|
readonly property var standardDecel: [0.00, 0.00, 0.00, 1.00, 1.00, 1.00]
|
||||||
readonly property var standardAccel: [ 0.30, 0.00, 1.00, 1.00, 1.00, 1.00 ]
|
readonly property var standardAccel: [0.30, 0.00, 1.00, 1.00, 1.00, 1.00]
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtCore
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
@@ -8,118 +9,123 @@ import Quickshell.Io
|
|||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
|
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var appUsageRanking: {}
|
property var appUsageRanking: {
|
||||||
|
|
||||||
Component.onCompleted: {
|
}
|
||||||
loadSettings();
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
loadSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSettings() {
|
||||||
|
parseSettings(settingsFile.text())
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSettings(content) {
|
||||||
|
try {
|
||||||
|
if (content && content.trim()) {
|
||||||
|
var settings = JSON.parse(content)
|
||||||
|
appUsageRanking = settings.appUsageRanking || {}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSettings() {
|
||||||
|
settingsFile.setText(JSON.stringify({
|
||||||
|
"appUsageRanking": appUsageRanking
|
||||||
|
}, null, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAppUsage(app) {
|
||||||
|
if (!app)
|
||||||
|
return
|
||||||
|
|
||||||
|
var appId = app.id || (app.execString || app.exec || "")
|
||||||
|
if (!appId)
|
||||||
|
return
|
||||||
|
|
||||||
|
var currentRanking = Object.assign({}, appUsageRanking)
|
||||||
|
|
||||||
|
if (currentRanking[appId]) {
|
||||||
|
currentRanking[appId].usageCount = (currentRanking[appId].usageCount
|
||||||
|
|| 1) + 1
|
||||||
|
currentRanking[appId].lastUsed = Date.now()
|
||||||
|
currentRanking[appId].icon = app.icon || currentRanking[appId].icon
|
||||||
|
|| "application-x-executable"
|
||||||
|
currentRanking[appId].name = app.name || currentRanking[appId].name || ""
|
||||||
|
} else {
|
||||||
|
currentRanking[appId] = {
|
||||||
|
"name": app.name || "",
|
||||||
|
"exec": app.execString || app.exec || "",
|
||||||
|
"icon": app.icon || "application-x-executable",
|
||||||
|
"comment": app.comment || "",
|
||||||
|
"usageCount": 1,
|
||||||
|
"lastUsed": Date.now()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSettings() {
|
appUsageRanking = currentRanking
|
||||||
parseSettings(settingsFile.text());
|
saveSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAppUsageRanking() {
|
||||||
|
return appUsageRanking
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRankedApps() {
|
||||||
|
var apps = []
|
||||||
|
for (var appId in appUsageRanking) {
|
||||||
|
var appData = appUsageRanking[appId]
|
||||||
|
apps.push({
|
||||||
|
"id": appId,
|
||||||
|
"name": appData.name,
|
||||||
|
"exec": appData.exec,
|
||||||
|
"icon": appData.icon,
|
||||||
|
"comment": appData.comment,
|
||||||
|
"usageCount": appData.usageCount,
|
||||||
|
"lastUsed": appData.lastUsed
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSettings(content) {
|
return apps.sort(function (a, b) {
|
||||||
try {
|
if (a.usageCount !== b.usageCount)
|
||||||
if (content && content.trim()) {
|
return b.usageCount - a.usageCount
|
||||||
var settings = JSON.parse(content);
|
return a.name.localeCompare(b.name)
|
||||||
appUsageRanking = settings.appUsageRanking || {};
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
}
|
function cleanupAppUsageRanking(availableAppIds) {
|
||||||
|
var currentRanking = Object.assign({}, appUsageRanking)
|
||||||
|
var hasChanges = false
|
||||||
|
|
||||||
|
for (var appId in currentRanking) {
|
||||||
|
if (availableAppIds.indexOf(appId) === -1) {
|
||||||
|
delete currentRanking[appId]
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveSettings() {
|
if (hasChanges) {
|
||||||
settingsFile.setText(JSON.stringify({
|
appUsageRanking = currentRanking
|
||||||
"appUsageRanking": appUsageRanking
|
saveSettings()
|
||||||
}, null, 2));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function addAppUsage(app) {
|
FileView {
|
||||||
if (!app)
|
id: settingsFile
|
||||||
return;
|
|
||||||
|
|
||||||
var appId = app.id || (app.execString || app.exec || "");
|
path: StandardPaths.writableLocation(
|
||||||
if (!appId)
|
StandardPaths.GenericStateLocation) + "/DankMaterialShell/appusage.json"
|
||||||
return;
|
blockLoading: true
|
||||||
|
blockWrites: true
|
||||||
var currentRanking = Object.assign({}, appUsageRanking);
|
watchChanges: true
|
||||||
|
onLoaded: {
|
||||||
if (currentRanking[appId]) {
|
parseSettings(settingsFile.text())
|
||||||
currentRanking[appId].usageCount = (currentRanking[appId].usageCount || 1) + 1;
|
|
||||||
currentRanking[appId].lastUsed = Date.now();
|
|
||||||
currentRanking[appId].icon = app.icon || currentRanking[appId].icon || "application-x-executable";
|
|
||||||
currentRanking[appId].name = app.name || currentRanking[appId].name || "";
|
|
||||||
} else {
|
|
||||||
currentRanking[appId] = {
|
|
||||||
"name": app.name || "",
|
|
||||||
"exec": app.execString || app.exec || "",
|
|
||||||
"icon": app.icon || "application-x-executable",
|
|
||||||
"comment": app.comment || "",
|
|
||||||
"usageCount": 1,
|
|
||||||
"lastUsed": Date.now()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
appUsageRanking = currentRanking;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAppUsageRanking() {
|
|
||||||
return appUsageRanking;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRankedApps() {
|
|
||||||
var apps = [];
|
|
||||||
for (var appId in appUsageRanking) {
|
|
||||||
var appData = appUsageRanking[appId];
|
|
||||||
apps.push({
|
|
||||||
id: appId,
|
|
||||||
name: appData.name,
|
|
||||||
exec: appData.exec,
|
|
||||||
icon: appData.icon,
|
|
||||||
comment: appData.comment,
|
|
||||||
usageCount: appData.usageCount,
|
|
||||||
lastUsed: appData.lastUsed
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return apps.sort(function(a, b) {
|
|
||||||
if (a.usageCount !== b.usageCount)
|
|
||||||
return b.usageCount - a.usageCount;
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanupAppUsageRanking(availableAppIds) {
|
|
||||||
var currentRanking = Object.assign({}, appUsageRanking);
|
|
||||||
var hasChanges = false;
|
|
||||||
|
|
||||||
for (var appId in currentRanking) {
|
|
||||||
if (availableAppIds.indexOf(appId) === -1) {
|
|
||||||
delete currentRanking[appId];
|
|
||||||
hasChanges = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasChanges) {
|
|
||||||
appUsageRanking = currentRanking;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: settingsFile
|
|
||||||
|
|
||||||
path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/appusage.json"
|
|
||||||
blockLoading: true
|
|
||||||
blockWrites: true
|
|
||||||
watchChanges: true
|
|
||||||
onLoaded: {
|
|
||||||
parseSettings(settingsFile.text());
|
|
||||||
}
|
|
||||||
onLoadFailed: (error) => {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
onLoadFailed: error => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,65 +1,67 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property Rounding rounding: Rounding {}
|
readonly property Rounding rounding: Rounding {}
|
||||||
readonly property Spacing spacing: Spacing {}
|
readonly property Spacing spacing: Spacing {}
|
||||||
readonly property FontSize fontSize: FontSize {}
|
readonly property FontSize fontSize: FontSize {}
|
||||||
readonly property Anim anim: Anim {}
|
readonly property Anim anim: Anim {}
|
||||||
|
|
||||||
component Rounding: QtObject {
|
component Rounding: QtObject {
|
||||||
readonly property int small: 8
|
readonly property int small: 8
|
||||||
readonly property int normal: 12
|
readonly property int normal: 12
|
||||||
readonly property int large: 16
|
readonly property int large: 16
|
||||||
readonly property int extraLarge: 24
|
readonly property int extraLarge: 24
|
||||||
readonly property int full: 1000
|
readonly property int full: 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
component Spacing: QtObject {
|
component Spacing: QtObject {
|
||||||
readonly property int small: 4
|
readonly property int small: 4
|
||||||
readonly property int normal: 8
|
readonly property int normal: 8
|
||||||
readonly property int large: 12
|
readonly property int large: 12
|
||||||
readonly property int extraLarge: 16
|
readonly property int extraLarge: 16
|
||||||
readonly property int huge: 24
|
readonly property int huge: 24
|
||||||
}
|
}
|
||||||
|
|
||||||
component FontSize: QtObject {
|
component FontSize: QtObject {
|
||||||
readonly property int small: 12
|
readonly property int small: 12
|
||||||
readonly property int normal: 14
|
readonly property int normal: 14
|
||||||
readonly property int large: 16
|
readonly property int large: 16
|
||||||
readonly property int extraLarge: 20
|
readonly property int extraLarge: 20
|
||||||
readonly property int huge: 24
|
readonly property int huge: 24
|
||||||
}
|
}
|
||||||
|
|
||||||
component AnimCurves: QtObject {
|
component AnimCurves: QtObject {
|
||||||
readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
|
readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
|
||||||
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
|
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
|
||||||
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
|
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
|
||||||
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
|
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1
|
||||||
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
|
/ 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
|
||||||
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
|
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
|
||||||
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
|
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
|
||||||
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
|
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
|
||||||
readonly property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
|
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
|
||||||
}
|
readonly property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
|
||||||
|
}
|
||||||
|
|
||||||
component AnimDurations: QtObject {
|
component AnimDurations: QtObject {
|
||||||
readonly property int quick: 150
|
readonly property int quick: 150
|
||||||
readonly property int normal: 300
|
readonly property int normal: 300
|
||||||
readonly property int slow: 500
|
readonly property int slow: 500
|
||||||
readonly property int extraSlow: 1000
|
readonly property int extraSlow: 1000
|
||||||
readonly property int expressiveFastSpatial: 350
|
readonly property int expressiveFastSpatial: 350
|
||||||
readonly property int expressiveDefaultSpatial: 500
|
readonly property int expressiveDefaultSpatial: 500
|
||||||
readonly property int expressiveEffects: 200
|
readonly property int expressiveEffects: 200
|
||||||
}
|
}
|
||||||
|
|
||||||
component Anim: QtObject {
|
component Anim: QtObject {
|
||||||
readonly property AnimCurves curves: AnimCurves {}
|
readonly property AnimCurves curves: AnimCurves {}
|
||||||
readonly property AnimDurations durations: AnimDurations {}
|
readonly property AnimDurations durations: AnimDurations {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,39 +3,43 @@ import qs.Common
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// Clear all image cache
|
// Clear all image cache
|
||||||
function clearImageCache() {
|
function clearImageCache() {
|
||||||
Quickshell.execDetached(["rm", "-rf", Paths.stringify(Paths.imagecache)]);
|
Quickshell.execDetached(["rm", "-rf", Paths.stringify(Paths.imagecache)])
|
||||||
Paths.mkdir(Paths.imagecache);
|
Paths.mkdir(Paths.imagecache)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear cache older than specified minutes
|
// Clear cache older than specified minutes
|
||||||
function clearOldCache(ageInMinutes) {
|
function clearOldCache(ageInMinutes) {
|
||||||
Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"]);
|
Quickshell.execDetached(
|
||||||
}
|
["find", Paths.stringify(
|
||||||
|
Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"])
|
||||||
|
}
|
||||||
|
|
||||||
// Clear cache for specific size
|
// Clear cache for specific size
|
||||||
function clearCacheForSize(size) {
|
function clearCacheForSize(size) {
|
||||||
Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"]);
|
Quickshell.execDetached(
|
||||||
}
|
["find", Paths.stringify(
|
||||||
|
Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"])
|
||||||
// Get cache size in MB
|
}
|
||||||
function getCacheSize(callback) {
|
|
||||||
var process = Qt.createQmlObject(`
|
|
||||||
import Quickshell.Io
|
|
||||||
Process {
|
|
||||||
command: ["du", "-sm", "${Paths.stringify(Paths.imagecache)}"]
|
|
||||||
running: true
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
var sizeMB = parseInt(text.split("\\t")[0]) || 0
|
|
||||||
callback(sizeMB)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, root);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Get cache size in MB
|
||||||
|
function getCacheSize(callback) {
|
||||||
|
var process = Qt.createQmlObject(`
|
||||||
|
import Quickshell.Io
|
||||||
|
Process {
|
||||||
|
command: ["du", "-sm", "${Paths.stringify(
|
||||||
|
Paths.imagecache)}"]
|
||||||
|
running: true
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
var sizeMB = parseInt(text.split("\\t")[0]) || 0
|
||||||
|
callback(sizeMB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, root)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import Qt.labs.platform
|
import Qt.labs.platform
|
||||||
import QtQuick
|
import QtQuick
|
||||||
@@ -9,347 +10,376 @@ import qs.Services
|
|||||||
import qs.Common
|
import qs.Common
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
|
readonly property string _homeUrl: StandardPaths.writableLocation(
|
||||||
readonly property string homeDir: _homeUrl.startsWith("file://") ? _homeUrl.substring(7) : _homeUrl
|
StandardPaths.HomeLocation)
|
||||||
readonly property string _configUrl: StandardPaths.writableLocation(StandardPaths.ConfigLocation)
|
readonly property string homeDir: _homeUrl.startsWith(
|
||||||
readonly property string configDir: _configUrl.startsWith("file://") ? _configUrl.substring(7) : _configUrl
|
"file://") ? _homeUrl.substring(
|
||||||
readonly property string shellDir: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Common/", "")
|
7) : _homeUrl
|
||||||
readonly property string wallpaperPath: SessionData.wallpaperPath
|
readonly property string _configUrl: StandardPaths.writableLocation(
|
||||||
property bool matugenAvailable: false
|
StandardPaths.ConfigLocation)
|
||||||
property bool gtkThemingEnabled: false
|
readonly property string configDir: _configUrl.startsWith(
|
||||||
property bool qtThemingEnabled: false
|
"file://") ? _configUrl.substring(
|
||||||
property bool systemThemeGenerationInProgress: false
|
7) : _configUrl
|
||||||
property string matugenJson: ""
|
readonly property string shellDir: Qt.resolvedUrl(".").toString().replace(
|
||||||
property var matugenColors: ({})
|
"file://", "").replace("/Common/", "")
|
||||||
property bool extractionRequested: false
|
readonly property string wallpaperPath: SessionData.wallpaperPath
|
||||||
property int colorUpdateTrigger: 0
|
property bool matugenAvailable: false
|
||||||
property string lastWallpaperTimestamp: ""
|
property bool gtkThemingEnabled: false
|
||||||
property color primary: getMatugenColor("primary", "#42a5f5")
|
property bool qtThemingEnabled: false
|
||||||
property color secondary: getMatugenColor("secondary", "#8ab4f8")
|
property bool systemThemeGenerationInProgress: false
|
||||||
property color tertiary: getMatugenColor("tertiary", "#bb86fc")
|
property string matugenJson: ""
|
||||||
property color tertiaryContainer: getMatugenColor("tertiary_container", "#3700b3")
|
property var matugenColors: ({})
|
||||||
property color error: getMatugenColor("error", "#cf6679")
|
property bool extractionRequested: false
|
||||||
property color inversePrimary: getMatugenColor("inverse_primary", "#6200ea")
|
property int colorUpdateTrigger: 0
|
||||||
property color bg: getMatugenColor("background", "#1a1c1e")
|
property string lastWallpaperTimestamp: ""
|
||||||
property color surface: getMatugenColor("surface", "#1a1c1e")
|
property color primary: getMatugenColor("primary", "#42a5f5")
|
||||||
property color surfaceContainer: getMatugenColor("surface_container", "#1e2023")
|
property color secondary: getMatugenColor("secondary", "#8ab4f8")
|
||||||
property color surfaceContainerHigh: getMatugenColor("surface_container_high", "#292b2f")
|
property color tertiary: getMatugenColor("tertiary", "#bb86fc")
|
||||||
property color surfaceVariant: getMatugenColor("surface_variant", "#44464f")
|
property color tertiaryContainer: getMatugenColor("tertiary_container",
|
||||||
property color surfaceText: getMatugenColor("on_background", "#e3e8ef")
|
"#3700b3")
|
||||||
property color primaryText: getMatugenColor("on_primary", "#ffffff")
|
property color error: getMatugenColor("error", "#cf6679")
|
||||||
property color surfaceVariantText: getMatugenColor("on_surface_variant", "#c4c7c5")
|
property color inversePrimary: getMatugenColor("inverse_primary", "#6200ea")
|
||||||
property color primaryContainer: getMatugenColor("primary_container", "#1976d2")
|
property color bg: getMatugenColor("background", "#1a1c1e")
|
||||||
property color surfaceTint: getMatugenColor("surface_tint", "#8ab4f8")
|
property color surface: getMatugenColor("surface", "#1a1c1e")
|
||||||
property color outline: getMatugenColor("outline", "#8e918f")
|
property color surfaceContainer: getMatugenColor("surface_container",
|
||||||
property color accentHi: primary
|
"#1e2023")
|
||||||
property color accentLo: secondary
|
property color surfaceContainerHigh: getMatugenColor(
|
||||||
|
"surface_container_high", "#292b2f")
|
||||||
|
property color surfaceVariant: getMatugenColor("surface_variant", "#44464f")
|
||||||
|
property color surfaceText: getMatugenColor("on_background", "#e3e8ef")
|
||||||
|
property color primaryText: getMatugenColor("on_primary", "#ffffff")
|
||||||
|
property color surfaceVariantText: getMatugenColor("on_surface_variant",
|
||||||
|
"#c4c7c5")
|
||||||
|
property color primaryContainer: getMatugenColor("primary_container",
|
||||||
|
"#1976d2")
|
||||||
|
property color surfaceTint: getMatugenColor("surface_tint", "#8ab4f8")
|
||||||
|
property color outline: getMatugenColor("outline", "#8e918f")
|
||||||
|
property color accentHi: primary
|
||||||
|
property color accentLo: secondary
|
||||||
|
|
||||||
signal colorsUpdated
|
signal colorsUpdated
|
||||||
|
|
||||||
function onLightModeChanged() {
|
function onLightModeChanged() {
|
||||||
if (matugenColors && Object.keys(matugenColors).length > 0) {
|
if (matugenColors && Object.keys(matugenColors).length > 0) {
|
||||||
colorUpdateTrigger++;
|
colorUpdateTrigger++
|
||||||
colorsUpdated();
|
colorsUpdated()
|
||||||
|
|
||||||
if (typeof Theme !== "undefined" && Theme.isDynamicTheme) {
|
if (typeof Theme !== "undefined" && Theme.isDynamicTheme) {
|
||||||
generateSystemThemes();
|
generateSystemThemes()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractColors() {
|
||||||
|
extractionRequested = true
|
||||||
|
if (matugenAvailable)
|
||||||
|
fileChecker.running = true
|
||||||
|
else
|
||||||
|
matugenCheck.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMatugenColor(path, fallback) {
|
||||||
|
colorUpdateTrigger
|
||||||
|
const colorMode = (typeof SessionData !== "undefined"
|
||||||
|
&& SessionData.isLightMode) ? "light" : "dark"
|
||||||
|
let cur = matugenColors && matugenColors.colors
|
||||||
|
&& matugenColors.colors[colorMode]
|
||||||
|
for (const part of path.split(".")) {
|
||||||
|
if (!cur || typeof cur !== "object" || !(part in cur))
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
cur = cur[part]
|
||||||
|
}
|
||||||
|
return cur || fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
function isColorDark(c) {
|
||||||
|
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
matugenCheck.running = true
|
||||||
|
checkGtkThemingAvailability()
|
||||||
|
checkQtThemingAvailability()
|
||||||
|
if (typeof SessionData !== "undefined")
|
||||||
|
SessionData.isLightModeChanged.connect(root.onLightModeChanged)
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: matugenCheck
|
||||||
|
|
||||||
|
command: ["which", "matugen"]
|
||||||
|
onExited: code => {
|
||||||
|
matugenAvailable = (code === 0)
|
||||||
|
if (!matugenAvailable) {
|
||||||
|
ToastService.wallpaperErrorStatus = "matugen_missing"
|
||||||
|
ToastService.showWarning("matugen not found - dynamic theming disabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (extractionRequested) {
|
||||||
|
fileChecker.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: fileChecker
|
||||||
|
|
||||||
|
command: ["test", "-r", wallpaperPath]
|
||||||
|
onExited: code => {
|
||||||
|
if (code === 0) {
|
||||||
|
matugenProcess.running = true
|
||||||
|
} else {
|
||||||
|
ToastService.wallpaperErrorStatus = "error"
|
||||||
|
ToastService.showError("Wallpaper processing failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: matugenProcess
|
||||||
|
|
||||||
|
command: ["matugen", "-v", "image", wallpaperPath, "--json", "hex"]
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: matugenCollector
|
||||||
|
|
||||||
|
onStreamFinished: {
|
||||||
|
const out = matugenCollector.text
|
||||||
|
if (!out.length) {
|
||||||
|
ToastService.wallpaperErrorStatus = "error"
|
||||||
|
ToastService.showError("Wallpaper Processing Failed")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
try {
|
||||||
|
root.matugenJson = out
|
||||||
function extractColors() {
|
root.matugenColors = JSON.parse(out)
|
||||||
extractionRequested = true;
|
root.colorsUpdated()
|
||||||
if (matugenAvailable)
|
generateAppConfigs()
|
||||||
fileChecker.running = true;
|
ToastService.clearWallpaperError()
|
||||||
else
|
} catch (e) {
|
||||||
matugenCheck.running = true;
|
ToastService.wallpaperErrorStatus = "error"
|
||||||
}
|
ToastService.showError("Wallpaper Processing Failed")
|
||||||
|
|
||||||
function getMatugenColor(path, fallback) {
|
|
||||||
colorUpdateTrigger;
|
|
||||||
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark";
|
|
||||||
let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode];
|
|
||||||
for (const part of path.split(".")) {
|
|
||||||
if (!cur || typeof cur !== "object" || !(part in cur))
|
|
||||||
return fallback;
|
|
||||||
|
|
||||||
cur = cur[part];
|
|
||||||
}
|
}
|
||||||
return cur || fallback;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isColorDark(c) {
|
stderr: StdioCollector {
|
||||||
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5;
|
id: matugenErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateAppConfigs() {
|
||||||
|
if (!matugenColors || !matugenColors.colors) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
generateNiriConfig()
|
||||||
matugenCheck.running = true;
|
generateGhosttyConfig()
|
||||||
checkGtkThemingAvailability();
|
|
||||||
checkQtThemingAvailability();
|
if (gtkThemingEnabled && typeof SettingsData !== "undefined"
|
||||||
if (typeof SessionData !== "undefined")
|
&& SettingsData.gtkThemingEnabled) {
|
||||||
SessionData.isLightModeChanged.connect(root.onLightModeChanged);
|
generateSystemThemes()
|
||||||
|
} else if (qtThemingEnabled && typeof SettingsData !== "undefined"
|
||||||
|
&& SettingsData.qtThemingEnabled) {
|
||||||
|
generateSystemThemes()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
function generateNiriConfig() {
|
||||||
id: matugenCheck
|
var dark = matugenColors.colors.dark
|
||||||
|
if (!dark)
|
||||||
|
return
|
||||||
|
|
||||||
command: ["which", "matugen"]
|
var bg = dark.background || "#1a1c1e"
|
||||||
onExited: code => {
|
var primary = dark.primary || "#42a5f5"
|
||||||
matugenAvailable = (code === 0);
|
var secondary = dark.secondary || "#8ab4f8"
|
||||||
if (!matugenAvailable) {
|
var inverse = dark.inverse_primary || "#6200ea"
|
||||||
ToastService.wallpaperErrorStatus = "matugen_missing";
|
|
||||||
ToastService.showWarning("matugen not found - dynamic theming disabled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (extractionRequested) {
|
|
||||||
fileChecker.running = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
var content = `layout {
|
||||||
id: fileChecker
|
|
||||||
|
|
||||||
command: ["test", "-r", wallpaperPath]
|
|
||||||
onExited: code => {
|
|
||||||
if (code === 0) {
|
|
||||||
matugenProcess.running = true;
|
|
||||||
} else {
|
|
||||||
ToastService.wallpaperErrorStatus = "error";
|
|
||||||
ToastService.showError("Wallpaper processing failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: matugenProcess
|
|
||||||
|
|
||||||
command: ["matugen", "-v", "image", wallpaperPath, "--json", "hex"]
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
id: matugenCollector
|
|
||||||
|
|
||||||
onStreamFinished: {
|
|
||||||
const out = matugenCollector.text;
|
|
||||||
if (!out.length) {
|
|
||||||
ToastService.wallpaperErrorStatus = "error";
|
|
||||||
ToastService.showError("Wallpaper Processing Failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
root.matugenJson = out;
|
|
||||||
root.matugenColors = JSON.parse(out);
|
|
||||||
root.colorsUpdated();
|
|
||||||
generateAppConfigs();
|
|
||||||
ToastService.clearWallpaperError();
|
|
||||||
} catch (e) {
|
|
||||||
ToastService.wallpaperErrorStatus = "error";
|
|
||||||
ToastService.showError("Wallpaper Processing Failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stderr: StdioCollector {
|
|
||||||
id: matugenErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateAppConfigs() {
|
|
||||||
if (!matugenColors || !matugenColors.colors) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
generateNiriConfig();
|
|
||||||
generateGhosttyConfig();
|
|
||||||
|
|
||||||
if (gtkThemingEnabled && typeof SettingsData !== "undefined" && SettingsData.gtkThemingEnabled) {
|
|
||||||
generateSystemThemes();
|
|
||||||
} else if (qtThemingEnabled && typeof SettingsData !== "undefined" && SettingsData.qtThemingEnabled) {
|
|
||||||
generateSystemThemes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateNiriConfig() {
|
|
||||||
var dark = matugenColors.colors.dark;
|
|
||||||
if (!dark)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var bg = dark.background || "#1a1c1e";
|
|
||||||
var primary = dark.primary || "#42a5f5";
|
|
||||||
var secondary = dark.secondary || "#8ab4f8";
|
|
||||||
var inverse = dark.inverse_primary || "#6200ea";
|
|
||||||
|
|
||||||
var content = `layout {
|
|
||||||
border {
|
border {
|
||||||
active-color "${primary}"
|
active-color "${primary}"
|
||||||
inactive-color "${secondary}"
|
inactive-color "${secondary}"
|
||||||
}
|
}
|
||||||
focus-ring {
|
focus-ring {
|
||||||
active-color "${inverse}"
|
active-color "${inverse}"
|
||||||
}
|
}
|
||||||
background-color "${bg}"
|
background-color "${bg}"
|
||||||
}`;
|
}`
|
||||||
|
|
||||||
Quickshell.execDetached(["bash", "-c", `echo '${content}' > niri-colors.generated.kdl`]);
|
Quickshell.execDetached(
|
||||||
|
["bash", "-c", `echo '${content}' > niri-colors.generated.kdl`])
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateGhosttyConfig() {
|
||||||
|
var dark = matugenColors.colors.dark
|
||||||
|
var light = matugenColors.colors.light
|
||||||
|
if (!dark || !light)
|
||||||
|
return
|
||||||
|
|
||||||
|
var bg = dark.background || "#1a1c1e"
|
||||||
|
var fg = dark.on_background || "#e3e8ef"
|
||||||
|
var primary = dark.primary || "#42a5f5"
|
||||||
|
var secondary = dark.secondary || "#8ab4f8"
|
||||||
|
var tertiary = dark.tertiary || "#bb86fc"
|
||||||
|
var tertiary_ctr = dark.tertiary_container || "#3700b3"
|
||||||
|
var error = dark.error || "#cf6679"
|
||||||
|
var inverse = dark.inverse_primary || "#6200ea"
|
||||||
|
|
||||||
|
var bg_b = light.background || "#fef7ff"
|
||||||
|
var fg_b = light.on_background || "#1d1b20"
|
||||||
|
var primary_b = light.primary || "#1976d2"
|
||||||
|
var secondary_b = light.secondary || "#1565c0"
|
||||||
|
var tertiary_b = light.tertiary || "#7b1fa2"
|
||||||
|
var tertiary_ctr_b = light.tertiary_container || "#e1bee7"
|
||||||
|
var error_b = light.error || "#b00020"
|
||||||
|
var inverse_b = light.inverse_primary || "#bb86fc"
|
||||||
|
|
||||||
|
var content = `background = ${bg}
|
||||||
|
foreground = ${fg}
|
||||||
|
cursor-color = ${inverse}
|
||||||
|
selection-background = ${secondary}
|
||||||
|
selection-foreground = #ffffff
|
||||||
|
palette = 0=${bg}
|
||||||
|
palette = 1=${error}
|
||||||
|
palette = 2=${tertiary}
|
||||||
|
palette = 3=${secondary}
|
||||||
|
palette = 4=${primary}
|
||||||
|
palette = 5=${tertiary_ctr}
|
||||||
|
palette = 6=${inverse}
|
||||||
|
palette = 7=${fg}
|
||||||
|
palette = 8=${bg_b}
|
||||||
|
palette = 9=${error_b}
|
||||||
|
palette = 10=${tertiary_b}
|
||||||
|
palette = 11=${secondary_b}
|
||||||
|
palette = 12=${primary_b}
|
||||||
|
palette = 13=${tertiary_ctr_b}
|
||||||
|
palette = 14=${inverse_b}
|
||||||
|
palette = 15=${fg_b}`
|
||||||
|
|
||||||
|
var ghosttyConfigDir = configDir + "/ghostty"
|
||||||
|
var ghosttyConfigPath = ghosttyConfigDir + "/config-dankcolors"
|
||||||
|
|
||||||
|
Quickshell.execDetached(
|
||||||
|
["bash", "-c", `mkdir -p '${ghosttyConfigDir}' && echo '${content}' > '${ghosttyConfigPath}'`])
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkGtkThemingAvailability() {
|
||||||
|
gtkAvailabilityChecker.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkQtThemingAvailability() {
|
||||||
|
qtAvailabilityChecker.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSystemThemes() {
|
||||||
|
if (systemThemeGenerationInProgress) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateGhosttyConfig() {
|
if (!matugenAvailable) {
|
||||||
var dark = matugenColors.colors.dark;
|
return
|
||||||
var light = matugenColors.colors.light;
|
|
||||||
if (!dark || !light)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var bg = dark.background || "#1a1c1e";
|
|
||||||
var fg = dark.on_background || "#e3e8ef";
|
|
||||||
var primary = dark.primary || "#42a5f5";
|
|
||||||
var secondary = dark.secondary || "#8ab4f8";
|
|
||||||
var tertiary = dark.tertiary || "#bb86fc";
|
|
||||||
var tertiary_ctr = dark.tertiary_container || "#3700b3";
|
|
||||||
var error = dark.error || "#cf6679";
|
|
||||||
var inverse = dark.inverse_primary || "#6200ea";
|
|
||||||
|
|
||||||
var bg_b = light.background || "#fef7ff";
|
|
||||||
var fg_b = light.on_background || "#1d1b20";
|
|
||||||
var primary_b = light.primary || "#1976d2";
|
|
||||||
var secondary_b = light.secondary || "#1565c0";
|
|
||||||
var tertiary_b = light.tertiary || "#7b1fa2";
|
|
||||||
var tertiary_ctr_b = light.tertiary_container || "#e1bee7";
|
|
||||||
var error_b = light.error || "#b00020";
|
|
||||||
var inverse_b = light.inverse_primary || "#bb86fc";
|
|
||||||
|
|
||||||
var content = `background = ${bg}
|
|
||||||
foreground = ${fg}
|
|
||||||
cursor-color = ${inverse}
|
|
||||||
selection-background = ${secondary}
|
|
||||||
selection-foreground = #ffffff
|
|
||||||
palette = 0=${bg}
|
|
||||||
palette = 1=${error}
|
|
||||||
palette = 2=${tertiary}
|
|
||||||
palette = 3=${secondary}
|
|
||||||
palette = 4=${primary}
|
|
||||||
palette = 5=${tertiary_ctr}
|
|
||||||
palette = 6=${inverse}
|
|
||||||
palette = 7=${fg}
|
|
||||||
palette = 8=${bg_b}
|
|
||||||
palette = 9=${error_b}
|
|
||||||
palette = 10=${tertiary_b}
|
|
||||||
palette = 11=${secondary_b}
|
|
||||||
palette = 12=${primary_b}
|
|
||||||
palette = 13=${tertiary_ctr_b}
|
|
||||||
palette = 14=${inverse_b}
|
|
||||||
palette = 15=${fg_b}`;
|
|
||||||
|
|
||||||
var ghosttyConfigDir = configDir + "/ghostty";
|
|
||||||
var ghosttyConfigPath = ghosttyConfigDir + "/config-dankcolors";
|
|
||||||
|
|
||||||
Quickshell.execDetached(["bash", "-c", `mkdir -p '${ghosttyConfigDir}' && echo '${content}' > '${ghosttyConfigPath}'`]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkGtkThemingAvailability() {
|
if (!wallpaperPath || wallpaperPath === "") {
|
||||||
gtkAvailabilityChecker.running = true;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkQtThemingAvailability() {
|
const isLight = (typeof SessionData !== "undefined"
|
||||||
qtAvailabilityChecker.running = true;
|
&& SessionData.isLightMode) ? "true" : "false"
|
||||||
|
const iconTheme = (typeof SettingsData !== "undefined"
|
||||||
|
&& SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
|
||||||
|
const gtkTheming = (typeof SettingsData !== "undefined"
|
||||||
|
&& SettingsData.gtkThemingEnabled) ? "true" : "false"
|
||||||
|
const qtTheming = (typeof SettingsData !== "undefined"
|
||||||
|
&& SettingsData.qtThemingEnabled) ? "true" : "false"
|
||||||
|
|
||||||
|
systemThemeGenerationInProgress = true
|
||||||
|
systemThemeGenerator.command = [shellDir + "/generate-themes.sh", wallpaperPath, shellDir, configDir, "generate", isLight, iconTheme, gtkTheming, qtTheming]
|
||||||
|
systemThemeGenerator.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreSystemThemes() {
|
||||||
|
const shellDir = root.shellDir
|
||||||
|
if (!shellDir) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateSystemThemes() {
|
const isLight = (typeof SessionData !== "undefined"
|
||||||
if (systemThemeGenerationInProgress) {
|
&& SessionData.isLightMode) ? "true" : "false"
|
||||||
return;
|
const iconTheme = (typeof SettingsData !== "undefined"
|
||||||
}
|
&& SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
|
||||||
|
const gtkTheming = (typeof SettingsData !== "undefined"
|
||||||
|
&& SettingsData.gtkThemingEnabled) ? "true" : "false"
|
||||||
|
const qtTheming = (typeof SettingsData !== "undefined"
|
||||||
|
&& SettingsData.qtThemingEnabled) ? "true" : "false"
|
||||||
|
|
||||||
if (!matugenAvailable) {
|
systemThemeRestoreProcess.command = [shellDir + "/generate-themes.sh", "", shellDir, configDir, "restore", isLight, iconTheme, gtkTheming, qtTheming]
|
||||||
return;
|
systemThemeRestoreProcess.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wallpaperPath || wallpaperPath === "") {
|
Process {
|
||||||
return;
|
id: gtkAvailabilityChecker
|
||||||
}
|
command: ["bash", "-c", "command -v gsettings >/dev/null && [ -d "
|
||||||
|
+ configDir + "/gtk-3.0 -o -d " + configDir + "/gtk-4.0 ]"]
|
||||||
|
running: false
|
||||||
|
onExited: exitCode => {
|
||||||
|
gtkThemingEnabled = (exitCode === 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false";
|
Process {
|
||||||
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default";
|
id: qtAvailabilityChecker
|
||||||
const gtkTheming = (typeof SettingsData !== "undefined" && SettingsData.gtkThemingEnabled) ? "true" : "false";
|
command: ["bash", "-c", "command -v qt5ct >/dev/null || command -v qt6ct >/dev/null"]
|
||||||
const qtTheming = (typeof SettingsData !== "undefined" && SettingsData.qtThemingEnabled) ? "true" : "false";
|
running: false
|
||||||
|
onExited: exitCode => {
|
||||||
|
qtThemingEnabled = (exitCode === 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
systemThemeGenerationInProgress = true;
|
Process {
|
||||||
systemThemeGenerator.command = [shellDir + "/generate-themes.sh", wallpaperPath, shellDir, configDir, "generate", isLight, iconTheme, gtkTheming, qtTheming];
|
id: systemThemeGenerator
|
||||||
systemThemeGenerator.running = true;
|
running: false
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: systemThemeStdout
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreSystemThemes() {
|
stderr: StdioCollector {
|
||||||
const shellDir = root.shellDir;
|
id: systemThemeStderr
|
||||||
if (!shellDir) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false";
|
|
||||||
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default";
|
|
||||||
const gtkTheming = (typeof SettingsData !== "undefined" && SettingsData.gtkThemingEnabled) ? "true" : "false";
|
|
||||||
const qtTheming = (typeof SettingsData !== "undefined" && SettingsData.qtThemingEnabled) ? "true" : "false";
|
|
||||||
|
|
||||||
systemThemeRestoreProcess.command = [shellDir + "/generate-themes.sh", "", shellDir, configDir, "restore", isLight, iconTheme, gtkTheming, qtTheming];
|
|
||||||
systemThemeRestoreProcess.running = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
onExited: exitCode => {
|
||||||
id: gtkAvailabilityChecker
|
systemThemeGenerationInProgress = false
|
||||||
command: ["bash", "-c", "command -v gsettings >/dev/null && [ -d " + configDir + "/gtk-3.0 -o -d " + configDir + "/gtk-4.0 ]"]
|
|
||||||
running: false
|
if (exitCode !== 0) {
|
||||||
onExited: exitCode => {
|
ToastService.showError(
|
||||||
gtkThemingEnabled = (exitCode === 0);
|
"Failed to generate system themes: " + systemThemeStderr.text)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: systemThemeRestoreProcess
|
||||||
|
running: false
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: restoreThemeStdout
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
stderr: StdioCollector {
|
||||||
id: qtAvailabilityChecker
|
id: restoreThemeStderr
|
||||||
command: ["bash", "-c", "command -v qt5ct >/dev/null || command -v qt6ct >/dev/null"]
|
|
||||||
running: false
|
|
||||||
onExited: exitCode => {
|
|
||||||
qtThemingEnabled = (exitCode === 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
onExited: exitCode => {
|
||||||
id: systemThemeGenerator
|
if (exitCode === 0) {
|
||||||
running: false
|
ToastService.showInfo("System themes restored to default")
|
||||||
|
} else {
|
||||||
stdout: StdioCollector {
|
ToastService.showWarning(
|
||||||
id: systemThemeStdout
|
"Failed to restore system themes: " + restoreThemeStderr.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
stderr: StdioCollector {
|
|
||||||
id: systemThemeStderr
|
|
||||||
}
|
|
||||||
|
|
||||||
onExited: exitCode => {
|
|
||||||
systemThemeGenerationInProgress = false;
|
|
||||||
|
|
||||||
if (exitCode !== 0) {
|
|
||||||
ToastService.showError("Failed to generate system themes: " + systemThemeStderr.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: systemThemeRestoreProcess
|
|
||||||
running: false
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
id: restoreThemeStdout
|
|
||||||
}
|
|
||||||
|
|
||||||
stderr: StdioCollector {
|
|
||||||
id: restoreThemeStderr
|
|
||||||
}
|
|
||||||
|
|
||||||
onExited: exitCode => {
|
|
||||||
if (exitCode === 0) {
|
|
||||||
ToastService.showInfo("System themes restored to default");
|
|
||||||
} else {
|
|
||||||
ToastService.showWarning("Failed to restore system themes: " + restoreThemeStderr.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,39 +4,45 @@ import Quickshell
|
|||||||
import QtCore
|
import QtCore
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property url home: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
readonly property url home: StandardPaths.standardLocations(
|
||||||
readonly property url pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
|
StandardPaths.HomeLocation)[0]
|
||||||
|
readonly property url pictures: StandardPaths.standardLocations(
|
||||||
|
StandardPaths.PicturesLocation)[0]
|
||||||
|
|
||||||
readonly property url data: `${StandardPaths.standardLocations(StandardPaths.GenericDataLocation)[0]}/DankMaterialShell`
|
readonly property url data: `${StandardPaths.standardLocations(
|
||||||
readonly property url state: `${StandardPaths.standardLocations(StandardPaths.GenericStateLocation)[0]}/DankMaterialShell`
|
StandardPaths.GenericDataLocation)[0]}/DankMaterialShell`
|
||||||
readonly property url cache: `${StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]}/DankMaterialShell`
|
readonly property url state: `${StandardPaths.standardLocations(
|
||||||
readonly property url config: `${StandardPaths.standardLocations(StandardPaths.GenericConfigLocation)[0]}/DankMaterialShell`
|
StandardPaths.GenericStateLocation)[0]}/DankMaterialShell`
|
||||||
|
readonly property url cache: `${StandardPaths.standardLocations(
|
||||||
|
StandardPaths.GenericCacheLocation)[0]}/DankMaterialShell`
|
||||||
|
readonly property url config: `${StandardPaths.standardLocations(
|
||||||
|
StandardPaths.GenericConfigLocation)[0]}/DankMaterialShell`
|
||||||
|
|
||||||
readonly property url imagecache: `${cache}/imagecache`
|
readonly property url imagecache: `${cache}/imagecache`
|
||||||
|
|
||||||
function stringify(path: url): string {
|
function stringify(path: url): string {
|
||||||
return path.toString().replace(/%20/g, " ");
|
return path.toString().replace(/%20/g, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandTilde(path: string): string {
|
function expandTilde(path: string): string {
|
||||||
return strip(path.replace("~", stringify(root.home)));
|
return strip(path.replace("~", stringify(root.home)))
|
||||||
}
|
}
|
||||||
|
|
||||||
function shortenHome(path: string): string {
|
function shortenHome(path: string): string {
|
||||||
return path.replace(strip(root.home), "~");
|
return path.replace(strip(root.home), "~")
|
||||||
}
|
}
|
||||||
|
|
||||||
function strip(path: url): string {
|
function strip(path: url): string {
|
||||||
return stringify(path).replace("file://", "");
|
return stringify(path).replace("file://", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkdir(path: url): void {
|
function mkdir(path: url): void {
|
||||||
Quickshell.execDetached(["mkdir", "-p", strip(path)]);
|
Quickshell.execDetached(["mkdir", "-p", strip(path)])
|
||||||
}
|
}
|
||||||
|
|
||||||
function copy(from: url, to: url): void {
|
function copy(from: url, to: url): void {
|
||||||
Quickshell.execDetached(["cp", strip(from), strip(to)]);
|
Quickshell.execDetached(["cp", strip(from), strip(to)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,8 @@ import QtQuick
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
required property Singleton service
|
required property Singleton service
|
||||||
|
|
||||||
Component.onCompleted: service.refCount++
|
Component.onCompleted: service.refCount++
|
||||||
Component.onDestruction: service.refCount--
|
Component.onDestruction: service.refCount--
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtCore
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
@@ -8,169 +9,176 @@ import Quickshell.Io
|
|||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
|
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool isLightMode: false
|
property bool isLightMode: false
|
||||||
property string wallpaperPath: ""
|
property string wallpaperPath: ""
|
||||||
property string wallpaperLastPath: ""
|
property string wallpaperLastPath: ""
|
||||||
property string profileLastPath: ""
|
property string profileLastPath: ""
|
||||||
property bool doNotDisturb: false
|
property bool doNotDisturb: false
|
||||||
property var pinnedApps: []
|
property var pinnedApps: []
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
loadSettings();
|
loadSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSettings() {
|
||||||
|
parseSettings(settingsFile.text())
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSettings(content) {
|
||||||
|
try {
|
||||||
|
if (content && content.trim()) {
|
||||||
|
var settings = JSON.parse(content)
|
||||||
|
isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false
|
||||||
|
wallpaperPath = settings.wallpaperPath !== undefined ? settings.wallpaperPath : ""
|
||||||
|
wallpaperLastPath = settings.wallpaperLastPath
|
||||||
|
!== undefined ? settings.wallpaperLastPath : ""
|
||||||
|
profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : ""
|
||||||
|
doNotDisturb = settings.doNotDisturb !== undefined ? settings.doNotDisturb : false
|
||||||
|
pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : []
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSettings() {
|
||||||
|
settingsFile.setText(JSON.stringify({
|
||||||
|
"isLightMode": isLightMode,
|
||||||
|
"wallpaperPath": wallpaperPath,
|
||||||
|
"wallpaperLastPath": wallpaperLastPath,
|
||||||
|
"profileLastPath": profileLastPath,
|
||||||
|
"doNotDisturb": doNotDisturb,
|
||||||
|
"pinnedApps": pinnedApps
|
||||||
|
}, null, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLightMode(lightMode) {
|
||||||
|
isLightMode = lightMode
|
||||||
|
saveSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDoNotDisturb(enabled) {
|
||||||
|
doNotDisturb = enabled
|
||||||
|
saveSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWallpaperPath(path) {
|
||||||
|
wallpaperPath = path
|
||||||
|
saveSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWallpaper(imagePath) {
|
||||||
|
wallpaperPath = imagePath
|
||||||
|
saveSettings()
|
||||||
|
|
||||||
|
if (typeof Colors !== "undefined" && typeof SettingsData !== "undefined"
|
||||||
|
&& SettingsData.wallpaperDynamicTheming) {
|
||||||
|
Colors.extractColors()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWallpaperLastPath(path) {
|
||||||
|
wallpaperLastPath = path
|
||||||
|
saveSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProfileLastPath(path) {
|
||||||
|
profileLastPath = path
|
||||||
|
saveSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPinnedApps(apps) {
|
||||||
|
pinnedApps = apps
|
||||||
|
saveSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPinnedApp(appId) {
|
||||||
|
if (!appId)
|
||||||
|
return
|
||||||
|
var currentPinned = [...pinnedApps]
|
||||||
|
if (currentPinned.indexOf(appId) === -1) {
|
||||||
|
currentPinned.push(appId)
|
||||||
|
setPinnedApps(currentPinned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePinnedApp(appId) {
|
||||||
|
if (!appId)
|
||||||
|
return
|
||||||
|
var currentPinned = pinnedApps.filter(id => id !== appId)
|
||||||
|
setPinnedApps(currentPinned)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPinnedApp(appId) {
|
||||||
|
return appId && pinnedApps.indexOf(appId) !== -1
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: settingsFile
|
||||||
|
|
||||||
|
path: StandardPaths.writableLocation(
|
||||||
|
StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
|
||||||
|
blockLoading: true
|
||||||
|
blockWrites: true
|
||||||
|
watchChanges: true
|
||||||
|
onLoaded: {
|
||||||
|
parseSettings(settingsFile.text())
|
||||||
|
}
|
||||||
|
onLoadFailed: error => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
target: "wallpaper"
|
||||||
|
|
||||||
|
function get(): string {
|
||||||
|
return root.wallpaperPath || ""
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSettings() {
|
function set(path: string): string {
|
||||||
parseSettings(settingsFile.text());
|
if (!path) {
|
||||||
|
return "ERROR: No path provided"
|
||||||
|
}
|
||||||
|
|
||||||
|
var absolutePath = path.startsWith(
|
||||||
|
"/") ? path : StandardPaths.writableLocation(
|
||||||
|
StandardPaths.HomeLocation) + "/" + path
|
||||||
|
|
||||||
|
try {
|
||||||
|
root.setWallpaper(absolutePath)
|
||||||
|
return "SUCCESS: Wallpaper set to " + absolutePath
|
||||||
|
} catch (e) {
|
||||||
|
return "ERROR: Failed to set wallpaper: " + e.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSettings(content) {
|
function clear(): string {
|
||||||
try {
|
root.setWallpaper("")
|
||||||
if (content && content.trim()) {
|
return "SUCCESS: Wallpaper cleared"
|
||||||
var settings = JSON.parse(content);
|
}
|
||||||
isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false;
|
}
|
||||||
wallpaperPath = settings.wallpaperPath !== undefined ? settings.wallpaperPath : "";
|
|
||||||
wallpaperLastPath = settings.wallpaperLastPath !== undefined ? settings.wallpaperLastPath : "";
|
IpcHandler {
|
||||||
profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : "";
|
target: "theme"
|
||||||
doNotDisturb = settings.doNotDisturb !== undefined ? settings.doNotDisturb : false;
|
|
||||||
pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : [];
|
function toggle(): string {
|
||||||
}
|
root.setLightMode(!root.isLightMode)
|
||||||
} catch (e) {
|
return root.isLightMode ? "light" : "dark"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveSettings() {
|
function light(): string {
|
||||||
settingsFile.setText(JSON.stringify({
|
root.setLightMode(true)
|
||||||
"isLightMode": isLightMode,
|
return "light"
|
||||||
"wallpaperPath": wallpaperPath,
|
|
||||||
"wallpaperLastPath": wallpaperLastPath,
|
|
||||||
"profileLastPath": profileLastPath,
|
|
||||||
"doNotDisturb": doNotDisturb,
|
|
||||||
"pinnedApps": pinnedApps
|
|
||||||
}, null, 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLightMode(lightMode) {
|
function dark(): string {
|
||||||
isLightMode = lightMode;
|
root.setLightMode(false)
|
||||||
saveSettings();
|
return "dark"
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDoNotDisturb(enabled) {
|
function getMode(): string {
|
||||||
doNotDisturb = enabled;
|
return root.isLightMode ? "light" : "dark"
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWallpaperPath(path) {
|
|
||||||
wallpaperPath = path;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWallpaper(imagePath) {
|
|
||||||
wallpaperPath = imagePath;
|
|
||||||
saveSettings();
|
|
||||||
|
|
||||||
if (typeof Colors !== "undefined" && typeof SettingsData !== "undefined" && SettingsData.wallpaperDynamicTheming) {
|
|
||||||
Colors.extractColors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWallpaperLastPath(path) {
|
|
||||||
wallpaperLastPath = path;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setProfileLastPath(path) {
|
|
||||||
profileLastPath = path;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setPinnedApps(apps) {
|
|
||||||
pinnedApps = apps;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function addPinnedApp(appId) {
|
|
||||||
if (!appId) return;
|
|
||||||
var currentPinned = [...pinnedApps];
|
|
||||||
if (currentPinned.indexOf(appId) === -1) {
|
|
||||||
currentPinned.push(appId);
|
|
||||||
setPinnedApps(currentPinned);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removePinnedApp(appId) {
|
|
||||||
if (!appId) return;
|
|
||||||
var currentPinned = pinnedApps.filter(id => id !== appId);
|
|
||||||
setPinnedApps(currentPinned);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPinnedApp(appId) {
|
|
||||||
return appId && pinnedApps.indexOf(appId) !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: settingsFile
|
|
||||||
|
|
||||||
path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
|
|
||||||
blockLoading: true
|
|
||||||
blockWrites: true
|
|
||||||
watchChanges: true
|
|
||||||
onLoaded: {
|
|
||||||
parseSettings(settingsFile.text());
|
|
||||||
}
|
|
||||||
onLoadFailed: (error) => {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IpcHandler {
|
|
||||||
target: "wallpaper"
|
|
||||||
|
|
||||||
function get(): string {
|
|
||||||
return root.wallpaperPath || ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function set(path: string): string {
|
|
||||||
if (!path) {
|
|
||||||
return "ERROR: No path provided"
|
|
||||||
}
|
|
||||||
|
|
||||||
var absolutePath = path.startsWith("/") ? path : StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/" + path
|
|
||||||
|
|
||||||
try {
|
|
||||||
root.setWallpaper(absolutePath)
|
|
||||||
return "SUCCESS: Wallpaper set to " + absolutePath
|
|
||||||
} catch (e) {
|
|
||||||
return "ERROR: Failed to set wallpaper: " + e.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear(): string {
|
|
||||||
root.setWallpaper("")
|
|
||||||
return "SUCCESS: Wallpaper cleared"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IpcHandler {
|
|
||||||
target: "theme"
|
|
||||||
|
|
||||||
function toggle(): string {
|
|
||||||
root.setLightMode(!root.isLightMode)
|
|
||||||
return root.isLightMode ? "light" : "dark"
|
|
||||||
}
|
|
||||||
|
|
||||||
function light(): string {
|
|
||||||
root.setLightMode(true)
|
|
||||||
return "light"
|
|
||||||
}
|
|
||||||
|
|
||||||
function dark(): string {
|
|
||||||
root.setLightMode(false)
|
|
||||||
return "dark"
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMode(): string {
|
|
||||||
return root.isLightMode ? "light" : "dark"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
1167
Common/Theme.qml
1167
Common/Theme.qml
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,208 +5,204 @@ import Quickshell.Wayland
|
|||||||
import qs.Common
|
import qs.Common
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias content: contentLoader.sourceComponent
|
property alias content: contentLoader.sourceComponent
|
||||||
property real width: 400
|
property real width: 400
|
||||||
property real height: 300
|
property real height: 300
|
||||||
readonly property real screenWidth: screen ? screen.width : 1920
|
readonly property real screenWidth: screen ? screen.width : 1920
|
||||||
readonly property real screenHeight: screen ? screen.height : 1080
|
readonly property real screenHeight: screen ? screen.height : 1080
|
||||||
property bool showBackground: true
|
property bool showBackground: true
|
||||||
property real backgroundOpacity: 0.5
|
property real backgroundOpacity: 0.5
|
||||||
property string positioning: "center"
|
property string positioning: "center"
|
||||||
property point customPosition: Qt.point(0, 0)
|
property point customPosition: Qt.point(0, 0)
|
||||||
property string keyboardFocus: "ondemand"
|
property string keyboardFocus: "ondemand"
|
||||||
property bool closeOnEscapeKey: true
|
property bool closeOnEscapeKey: true
|
||||||
property bool closeOnBackgroundClick: true
|
property bool closeOnBackgroundClick: true
|
||||||
property string animationType: "scale"
|
property string animationType: "scale"
|
||||||
property int animationDuration: Theme.mediumDuration
|
property int animationDuration: Theme.mediumDuration
|
||||||
property var animationEasing: Theme.emphasizedEasing
|
property var animationEasing: Theme.emphasizedEasing
|
||||||
property color backgroundColor: Theme.surfaceContainer
|
property color backgroundColor: Theme.surfaceContainer
|
||||||
property color borderColor: Theme.outlineMedium
|
property color borderColor: Theme.outlineMedium
|
||||||
property real borderWidth: 1
|
property real borderWidth: 1
|
||||||
property real cornerRadius: Theme.cornerRadiusLarge
|
property real cornerRadius: Theme.cornerRadiusLarge
|
||||||
property bool enableShadow: false
|
property bool enableShadow: false
|
||||||
|
|
||||||
signal opened()
|
signal opened
|
||||||
signal dialogClosed()
|
signal dialogClosed
|
||||||
signal backgroundClicked()
|
signal backgroundClicked
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
visible = true;
|
visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
visible = !visible
|
||||||
|
}
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: {
|
||||||
|
switch (root.keyboardFocus) {
|
||||||
|
case "exclusive":
|
||||||
|
return WlrKeyboardFocus.Exclusive
|
||||||
|
case "none":
|
||||||
|
return WlrKeyboardFocus.None
|
||||||
|
default:
|
||||||
|
return WlrKeyboardFocus.OnDemand
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (root.visible) {
|
||||||
|
opened()
|
||||||
|
} else {
|
||||||
|
if (Qt.inputMethod) {
|
||||||
|
Qt.inputMethod.hide()
|
||||||
|
Qt.inputMethod.reset()
|
||||||
|
}
|
||||||
|
dialogClosed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: background
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "black"
|
||||||
|
opacity: root.showBackground ? (root.visible ? root.backgroundOpacity : 0) : 0
|
||||||
|
visible: root.showBackground
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: root.closeOnBackgroundClick
|
||||||
|
onClicked: mouse => {
|
||||||
|
var localPos = mapToItem(contentContainer, mouse.x, mouse.y)
|
||||||
|
if (localPos.x < 0 || localPos.x > contentContainer.width
|
||||||
|
|| localPos.y < 0
|
||||||
|
|| localPos.y > contentContainer.height)
|
||||||
|
root.backgroundClicked()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
Behavior on opacity {
|
||||||
visible = false;
|
NumberAnimation {
|
||||||
|
duration: root.animationDuration
|
||||||
|
easing.type: root.animationEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: contentContainer
|
||||||
|
|
||||||
|
width: root.width
|
||||||
|
height: root.height
|
||||||
|
anchors.centerIn: positioning === "center" ? parent : undefined
|
||||||
|
x: {
|
||||||
|
if (positioning === "top-right")
|
||||||
|
return Math.max(Theme.spacingL,
|
||||||
|
root.screenWidth - width - Theme.spacingL)
|
||||||
|
else if (positioning === "custom")
|
||||||
|
return root.customPosition.x
|
||||||
|
return 0 // Will be overridden by anchors.centerIn when positioning === "center"
|
||||||
|
}
|
||||||
|
y: {
|
||||||
|
if (positioning === "top-right")
|
||||||
|
return Theme.barHeight + Theme.spacingXS
|
||||||
|
else if (positioning === "custom")
|
||||||
|
return root.customPosition.y
|
||||||
|
return 0 // Will be overridden by anchors.centerIn when positioning === "center"
|
||||||
|
}
|
||||||
|
color: root.backgroundColor
|
||||||
|
radius: root.cornerRadius
|
||||||
|
border.color: root.borderColor
|
||||||
|
border.width: root.borderWidth
|
||||||
|
layer.enabled: root.enableShadow
|
||||||
|
opacity: root.visible ? 1 : 0
|
||||||
|
scale: {
|
||||||
|
if (root.animationType === "scale")
|
||||||
|
return root.visible ? 1 : 0.9
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
transform: root.animationType === "slide" ? slideTransform : null
|
||||||
|
|
||||||
|
Translate {
|
||||||
|
id: slideTransform
|
||||||
|
|
||||||
|
x: root.visible ? 0 : 15
|
||||||
|
y: root.visible ? 0 : -30
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle() {
|
Loader {
|
||||||
visible = !visible;
|
id: contentLoader
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
active: true
|
||||||
|
asynchronous: false
|
||||||
}
|
}
|
||||||
|
|
||||||
color: "transparent"
|
Behavior on opacity {
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
NumberAnimation {
|
||||||
WlrLayershell.exclusiveZone: -1
|
duration: root.animationDuration
|
||||||
WlrLayershell.keyboardFocus: {
|
easing.type: root.animationEasing
|
||||||
switch (root.keyboardFocus) {
|
}
|
||||||
case "exclusive":
|
|
||||||
return WlrKeyboardFocus.Exclusive;
|
|
||||||
case "none":
|
|
||||||
return WlrKeyboardFocus.None;
|
|
||||||
default:
|
|
||||||
return WlrKeyboardFocus.OnDemand;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
enabled: root.animationType === "scale"
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animationDuration
|
||||||
|
easing.type: root.animationEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
shadowEnabled: true
|
||||||
|
shadowHorizontalOffset: 0
|
||||||
|
shadowVerticalOffset: 8
|
||||||
|
shadowBlur: 1
|
||||||
|
shadowColor: Theme.shadowStrong
|
||||||
|
shadowOpacity: 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
id: focusScope
|
||||||
|
objectName: "modalFocusScope"
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: root.visible // Only active when the modal is visible
|
||||||
|
focus: root.visible
|
||||||
|
Keys.onEscapePressed: event => {
|
||||||
|
if (root.closeOnEscapeKey) {
|
||||||
|
root.visible = false
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (root.visible) {
|
if (visible) {
|
||||||
opened();
|
Qt.callLater(function () {
|
||||||
} else {
|
focusScope.forceActiveFocus()
|
||||||
if (Qt.inputMethod) {
|
})
|
||||||
Qt.inputMethod.hide();
|
}
|
||||||
Qt.inputMethod.reset();
|
|
||||||
}
|
|
||||||
dialogClosed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
anchors {
|
// Expose the focusScope for external access
|
||||||
top: true
|
property alias modalFocusScope: focusScope
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: background
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
color: "black"
|
|
||||||
opacity: root.showBackground ? (root.visible ? root.backgroundOpacity : 0) : 0
|
|
||||||
visible: root.showBackground
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: root.closeOnBackgroundClick
|
|
||||||
onClicked: (mouse) => {
|
|
||||||
var localPos = mapToItem(contentContainer, mouse.x, mouse.y);
|
|
||||||
if (localPos.x < 0 || localPos.x > contentContainer.width || localPos.y < 0 || localPos.y > contentContainer.height)
|
|
||||||
root.backgroundClicked();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: root.animationDuration
|
|
||||||
easing.type: root.animationEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: contentContainer
|
|
||||||
|
|
||||||
width: root.width
|
|
||||||
height: root.height
|
|
||||||
anchors.centerIn: positioning === "center" ? parent : undefined
|
|
||||||
x: {
|
|
||||||
if (positioning === "top-right")
|
|
||||||
return Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL);
|
|
||||||
else if (positioning === "custom")
|
|
||||||
return root.customPosition.x;
|
|
||||||
return 0; // Will be overridden by anchors.centerIn when positioning === "center"
|
|
||||||
}
|
|
||||||
y: {
|
|
||||||
if (positioning === "top-right")
|
|
||||||
return Theme.barHeight + Theme.spacingXS;
|
|
||||||
else if (positioning === "custom")
|
|
||||||
return root.customPosition.y;
|
|
||||||
return 0; // Will be overridden by anchors.centerIn when positioning === "center"
|
|
||||||
}
|
|
||||||
color: root.backgroundColor
|
|
||||||
radius: root.cornerRadius
|
|
||||||
border.color: root.borderColor
|
|
||||||
border.width: root.borderWidth
|
|
||||||
layer.enabled: root.enableShadow
|
|
||||||
opacity: root.visible ? 1 : 0
|
|
||||||
scale: {
|
|
||||||
if (root.animationType === "scale")
|
|
||||||
return root.visible ? 1 : 0.9;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
transform: root.animationType === "slide" ? slideTransform : null
|
|
||||||
|
|
||||||
Translate {
|
|
||||||
id: slideTransform
|
|
||||||
|
|
||||||
x: root.visible ? 0 : 15
|
|
||||||
y: root.visible ? 0 : -30
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: contentLoader
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
active: true
|
|
||||||
asynchronous: false
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: root.animationDuration
|
|
||||||
easing.type: root.animationEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
enabled: root.animationType === "scale"
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
duration: root.animationDuration
|
|
||||||
easing.type: root.animationEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowHorizontalOffset: 0
|
|
||||||
shadowVerticalOffset: 8
|
|
||||||
shadowBlur: 1
|
|
||||||
shadowColor: Theme.shadowStrong
|
|
||||||
shadowOpacity: 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
FocusScope {
|
|
||||||
id: focusScope
|
|
||||||
objectName: "modalFocusScope"
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: root.visible // Only active when the modal is visible
|
|
||||||
focus: root.visible
|
|
||||||
Keys.onEscapePressed: (event) => {
|
|
||||||
if (root.closeOnEscapeKey) {
|
|
||||||
root.visible = false;
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible) {
|
|
||||||
Qt.callLater(function() {
|
|
||||||
focusScope.forceActiveFocus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expose the focusScope for external access
|
|
||||||
property alias modalFocusScope: focusScope
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
@@ -7,289 +7,300 @@ import Qt.labs.folderlistmodel
|
|||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
DankModal {
|
DankModal {
|
||||||
id: fileBrowserModal
|
id: fileBrowserModal
|
||||||
|
|
||||||
signal fileSelected(string path)
|
signal fileSelected(string path)
|
||||||
|
|
||||||
property string homeDir: StandardPaths.writableLocation(StandardPaths.HomeLocation)
|
property string homeDir: StandardPaths.writableLocation(
|
||||||
property string currentPath: ""
|
StandardPaths.HomeLocation)
|
||||||
property var fileExtensions: ["*.*"]
|
property string currentPath: ""
|
||||||
property string browserTitle: "Select File"
|
property var fileExtensions: ["*.*"]
|
||||||
property string browserIcon: "folder_open"
|
property string browserTitle: "Select File"
|
||||||
property string browserType: "generic" // "wallpaper" or "profile" for last path memory
|
property string browserIcon: "folder_open"
|
||||||
|
property string browserType: "generic" // "wallpaper" or "profile" for last path memory
|
||||||
|
|
||||||
FolderListModel {
|
FolderListModel {
|
||||||
id: folderModel
|
id: folderModel
|
||||||
showDirsFirst: true
|
showDirsFirst: true
|
||||||
showDotAndDotDot: false
|
showDotAndDotDot: false
|
||||||
showHidden: false
|
showHidden: false
|
||||||
nameFilters: fileExtensions
|
nameFilters: fileExtensions
|
||||||
showFiles: true
|
showFiles: true
|
||||||
showDirs: true
|
showDirs: true
|
||||||
folder: currentPath ? "file://" + currentPath : "file://" + homeDir
|
folder: currentPath ? "file://" + currentPath : "file://" + homeDir
|
||||||
|
}
|
||||||
|
|
||||||
|
function isImageFile(fileName) {
|
||||||
|
if (!fileName)
|
||||||
|
return false
|
||||||
|
var ext = fileName.toLowerCase().split('.').pop()
|
||||||
|
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLastPath() {
|
||||||
|
var lastPath = ""
|
||||||
|
if (browserType === "wallpaper") {
|
||||||
|
lastPath = SessionData.wallpaperLastPath
|
||||||
|
} else if (browserType === "profile") {
|
||||||
|
lastPath = SessionData.profileLastPath
|
||||||
}
|
}
|
||||||
|
|
||||||
function isImageFile(fileName) {
|
if (lastPath && lastPath !== "") {
|
||||||
if (!fileName) return false
|
return lastPath
|
||||||
var ext = fileName.toLowerCase().split('.').pop()
|
}
|
||||||
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(ext)
|
return homeDir
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveLastPath(path) {
|
||||||
|
if (browserType === "wallpaper") {
|
||||||
|
SessionData.setWallpaperLastPath(path)
|
||||||
|
} else if (browserType === "profile") {
|
||||||
|
SessionData.setProfileLastPath(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
currentPath = getLastPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
width: 800
|
||||||
|
height: 600
|
||||||
|
keyboardFocus: "ondemand"
|
||||||
|
enableShadow: true
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
onBackgroundClicked: visible = false
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible) {
|
||||||
|
var startPath = getLastPath()
|
||||||
|
currentPath = startPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCurrentPathChanged: {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateUp() {
|
||||||
|
var path = currentPath
|
||||||
|
|
||||||
|
if (path === homeDir) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLastPath() {
|
var lastSlash = path.lastIndexOf('/')
|
||||||
var lastPath = "";
|
if (lastSlash > 0) {
|
||||||
if (browserType === "wallpaper") {
|
var newPath = path.substring(0, lastSlash)
|
||||||
lastPath = SessionData.wallpaperLastPath;
|
if (newPath.length < homeDir.length) {
|
||||||
} else if (browserType === "profile") {
|
currentPath = homeDir
|
||||||
lastPath = SessionData.profileLastPath;
|
saveLastPath(homeDir)
|
||||||
|
} else {
|
||||||
|
currentPath = newPath
|
||||||
|
saveLastPath(newPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateTo(path) {
|
||||||
|
currentPath = path
|
||||||
|
saveLastPath(path) // Save the path when navigating
|
||||||
|
}
|
||||||
|
|
||||||
|
content: Component {
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: 40
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: browserIcon
|
||||||
|
size: Theme.iconSizeLarge
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: browserTitle
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastPath && lastPath !== "") {
|
DankActionButton {
|
||||||
return lastPath;
|
circular: false
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
hoverColor: Theme.errorHover
|
||||||
|
onClicked: fileBrowserModal.visible = false
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
return homeDir;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function saveLastPath(path) {
|
Row {
|
||||||
if (browserType === "wallpaper") {
|
width: parent.width
|
||||||
SessionData.setWallpaperLastPath(path);
|
spacing: Theme.spacingS
|
||||||
} else if (browserType === "profile") {
|
|
||||||
SessionData.setProfileLastPath(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
StyledRect {
|
||||||
currentPath = getLastPath();
|
width: 32
|
||||||
}
|
height: 32
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: mouseArea.containsMouse
|
||||||
|
&& currentPath !== homeDir ? Theme.surfaceVariant : "transparent"
|
||||||
|
opacity: currentPath !== homeDir ? 1.0 : 0.0
|
||||||
|
|
||||||
width: 800
|
DankIcon {
|
||||||
height: 600
|
anchors.centerIn: parent
|
||||||
keyboardFocus: "ondemand"
|
name: "arrow_back"
|
||||||
enableShadow: true
|
size: Theme.iconSizeSmall
|
||||||
visible: false
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
onBackgroundClicked: visible = false
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible) {
|
|
||||||
var startPath = getLastPath();
|
|
||||||
currentPath = startPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onCurrentPathChanged: {
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateUp() {
|
|
||||||
var path = currentPath;
|
|
||||||
|
|
||||||
if (path === homeDir) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastSlash = path.lastIndexOf('/');
|
|
||||||
if (lastSlash > 0) {
|
|
||||||
var newPath = path.substring(0, lastSlash);
|
|
||||||
if (newPath.length < homeDir.length) {
|
|
||||||
currentPath = homeDir;
|
|
||||||
saveLastPath(homeDir);
|
|
||||||
} else {
|
|
||||||
currentPath = newPath;
|
|
||||||
saveLastPath(newPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateTo(path) {
|
|
||||||
currentPath = path;
|
|
||||||
saveLastPath(path); // Save the path when navigating
|
|
||||||
}
|
|
||||||
|
|
||||||
content: Component {
|
|
||||||
Column {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingM
|
hoverEnabled: currentPath !== homeDir
|
||||||
spacing: Theme.spacingS
|
cursorShape: currentPath !== homeDir ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
enabled: currentPath !== homeDir
|
||||||
|
onClicked: navigateUp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: fileBrowserModal.currentPath.replace("file://", "")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
width: parent.width - 40 - Theme.spacingS
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
maximumLineCount: 1
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GridView {
|
||||||
|
id: fileGrid
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - 80
|
||||||
|
clip: true
|
||||||
|
cellWidth: 150
|
||||||
|
cellHeight: 130
|
||||||
|
cacheBuffer: 260
|
||||||
|
|
||||||
|
model: folderModel
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
policy: ScrollBar.AsNeeded
|
||||||
|
}
|
||||||
|
ScrollBar.horizontal: ScrollBar {
|
||||||
|
policy: ScrollBar.AlwaysOff
|
||||||
|
}
|
||||||
|
|
||||||
|
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
||||||
|
interactive: true
|
||||||
|
flickDeceleration: 1500 // Touch only in Qt 6.9+ // Lower = more momentum, longer scrolling
|
||||||
|
maximumFlickVelocity: 2000 // Touch only in Qt 6.9+ // Higher = faster maximum scroll speed
|
||||||
|
boundsBehavior: Flickable.DragAndOvershootBounds
|
||||||
|
boundsMovement: Flickable.FollowBoundsBehavior
|
||||||
|
pressDelay: 0
|
||||||
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
|
||||||
|
delegate: StyledRect {
|
||||||
|
id: delegateRoot
|
||||||
|
|
||||||
|
required property bool fileIsDir
|
||||||
|
required property string filePath
|
||||||
|
required property string fileName
|
||||||
|
required property url fileURL
|
||||||
|
|
||||||
|
width: 140
|
||||||
|
height: 120
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: mouseArea.containsMouse ? Theme.surfaceVariant : "transparent"
|
||||||
|
border.color: Theme.outline
|
||||||
|
border.width: mouseArea.containsMouse ? 1 : 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: 80
|
||||||
height: 40
|
height: 60
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
Row {
|
CachingImage {
|
||||||
spacing: Theme.spacingM
|
anchors.fill: parent
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
imagePath: !delegateRoot.fileIsDir ? delegateRoot.filePath : ""
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
visible: !delegateRoot.fileIsDir && isImageFile(
|
||||||
|
delegateRoot.fileName)
|
||||||
|
maxCacheSize: 80
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: browserIcon
|
anchors.centerIn: parent
|
||||||
size: Theme.iconSizeLarge
|
name: "description"
|
||||||
color: Theme.primary
|
size: Theme.iconSizeLarge
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
color: Theme.primary
|
||||||
}
|
visible: !delegateRoot.fileIsDir && !isImageFile(
|
||||||
|
delegateRoot.fileName)
|
||||||
|
}
|
||||||
|
|
||||||
StyledText {
|
DankIcon {
|
||||||
text: browserTitle
|
anchors.centerIn: parent
|
||||||
font.pixelSize: Theme.fontSizeXLarge
|
name: "folder"
|
||||||
color: Theme.surfaceText
|
size: Theme.iconSizeLarge
|
||||||
font.weight: Font.Medium
|
color: Theme.primary
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
visible: delegateRoot.fileIsDir
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
circular: false
|
|
||||||
iconName: "close"
|
|
||||||
iconSize: Theme.iconSize - 4
|
|
||||||
iconColor: Theme.surfaceText
|
|
||||||
hoverColor: Theme.errorHover
|
|
||||||
onClicked: fileBrowserModal.visible = false
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
StyledText {
|
||||||
width: parent.width
|
text: delegateRoot.fileName || ""
|
||||||
spacing: Theme.spacingS
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
StyledRect {
|
width: 120
|
||||||
width: 32
|
elide: Text.ElideMiddle
|
||||||
height: 32
|
horizontalAlignment: Text.AlignHCenter
|
||||||
radius: Theme.cornerRadius
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
color: mouseArea.containsMouse && currentPath !== homeDir ? Theme.surfaceVariant : "transparent"
|
maximumLineCount: 2
|
||||||
opacity: currentPath !== homeDir ? 1.0 : 0.0
|
wrapMode: Text.WordWrap
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "arrow_back"
|
|
||||||
size: Theme.iconSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: currentPath !== homeDir
|
|
||||||
cursorShape: currentPath !== homeDir ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
enabled: currentPath !== homeDir
|
|
||||||
onClicked: navigateUp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: fileBrowserModal.currentPath.replace("file://", "")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
width: parent.width - 40 - Theme.spacingS
|
|
||||||
elide: Text.ElideMiddle
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
maximumLineCount: 1
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GridView {
|
MouseArea {
|
||||||
id: fileGrid
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
width: parent.width
|
onClicked: {
|
||||||
height: parent.height - 80
|
if (delegateRoot.fileIsDir) {
|
||||||
clip: true
|
navigateTo(delegateRoot.filePath)
|
||||||
cellWidth: 150
|
} else {
|
||||||
cellHeight: 130
|
fileSelected(delegateRoot.filePath)
|
||||||
cacheBuffer: 260
|
}
|
||||||
|
}
|
||||||
model: folderModel
|
}
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
|
|
||||||
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
|
|
||||||
|
|
||||||
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
|
||||||
interactive: true
|
|
||||||
flickDeceleration: 1500 // Touch only in Qt 6.9+ // Lower = more momentum, longer scrolling
|
|
||||||
maximumFlickVelocity: 2000 // Touch only in Qt 6.9+ // Higher = faster maximum scroll speed
|
|
||||||
boundsBehavior: Flickable.DragAndOvershootBounds
|
|
||||||
boundsMovement: Flickable.FollowBoundsBehavior
|
|
||||||
pressDelay: 0
|
|
||||||
flickableDirection: Flickable.VerticalFlick
|
|
||||||
|
|
||||||
delegate: StyledRect {
|
|
||||||
id: delegateRoot
|
|
||||||
|
|
||||||
required property bool fileIsDir
|
|
||||||
required property string filePath
|
|
||||||
required property string fileName
|
|
||||||
required property url fileURL
|
|
||||||
|
|
||||||
width: 140
|
|
||||||
height: 120
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: mouseArea.containsMouse ? Theme.surfaceVariant : "transparent"
|
|
||||||
border.color: Theme.outline
|
|
||||||
border.width: mouseArea.containsMouse ? 1 : 0
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 80
|
|
||||||
height: 60
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
|
|
||||||
CachingImage {
|
|
||||||
anchors.fill: parent
|
|
||||||
imagePath: !delegateRoot.fileIsDir ? delegateRoot.filePath : ""
|
|
||||||
fillMode: Image.PreserveAspectCrop
|
|
||||||
visible: !delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)
|
|
||||||
maxCacheSize: 80
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "description"
|
|
||||||
size: Theme.iconSizeLarge
|
|
||||||
color: Theme.primary
|
|
||||||
visible: !delegateRoot.fileIsDir && !isImageFile(delegateRoot.fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "folder"
|
|
||||||
size: Theme.iconSizeLarge
|
|
||||||
color: Theme.primary
|
|
||||||
visible: delegateRoot.fileIsDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: delegateRoot.fileName || ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: 120
|
|
||||||
elide: Text.ElideMiddle
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
maximumLineCount: 2
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (delegateRoot.fileIsDir) {
|
|
||||||
navigateTo(delegateRoot.filePath);
|
|
||||||
} else {
|
|
||||||
fileSelected(delegateRoot.filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,198 +8,193 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
DankModal {
|
DankModal {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool networkInfoModalVisible: false
|
property bool networkInfoModalVisible: false
|
||||||
property string networkSSID: ""
|
property string networkSSID: ""
|
||||||
property var networkData: null
|
property var networkData: null
|
||||||
property string networkDetails: ""
|
property string networkDetails: ""
|
||||||
|
|
||||||
function showNetworkInfo(ssid, data) {
|
function showNetworkInfo(ssid, data) {
|
||||||
networkSSID = ssid;
|
networkSSID = ssid
|
||||||
networkData = data;
|
networkData = data
|
||||||
networkInfoModalVisible = true;
|
networkInfoModalVisible = true
|
||||||
NetworkService.fetchNetworkInfo(ssid);
|
NetworkService.fetchNetworkInfo(ssid)
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideDialog() {
|
||||||
|
networkInfoModalVisible = false
|
||||||
|
networkSSID = ""
|
||||||
|
networkData = null
|
||||||
|
networkDetails = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: networkInfoModalVisible
|
||||||
|
width: 600
|
||||||
|
height: 500
|
||||||
|
enableShadow: true
|
||||||
|
onBackgroundClicked: {
|
||||||
|
hideDialog()
|
||||||
|
}
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (!visible) {
|
||||||
|
networkSSID = ""
|
||||||
|
networkData = null
|
||||||
|
networkDetails = ""
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function hideDialog() {
|
content: Component {
|
||||||
networkInfoModalVisible = false;
|
Item {
|
||||||
networkSSID = "";
|
anchors.fill: parent
|
||||||
networkData = null;
|
|
||||||
networkDetails = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
visible: networkInfoModalVisible
|
Column {
|
||||||
width: 600
|
anchors.fill: parent
|
||||||
height: 500
|
anchors.margins: Theme.spacingL
|
||||||
enableShadow: true
|
spacing: Theme.spacingL
|
||||||
onBackgroundClicked: {
|
|
||||||
hideDialog();
|
|
||||||
}
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (!visible) {
|
|
||||||
networkSSID = "";
|
|
||||||
networkData = null;
|
|
||||||
networkDetails = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
content: Component {
|
Row {
|
||||||
Item {
|
width: parent.width
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
width: parent.width - 40
|
||||||
anchors.margins: Theme.spacingL
|
spacing: Theme.spacingXS
|
||||||
spacing: Theme.spacingL
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width - 40
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Network Information"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Details for \"" + networkSSID + "\""
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceTextMedium
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
iconName: "close"
|
|
||||||
iconSize: Theme.iconSize - 4
|
|
||||||
iconColor: Theme.surfaceText
|
|
||||||
hoverColor: Theme.errorHover
|
|
||||||
onClicked: {
|
|
||||||
root.hideDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Flickable {
|
|
||||||
width: parent.width
|
|
||||||
height: parent.height - 140
|
|
||||||
clip: true
|
|
||||||
contentWidth: width
|
|
||||||
contentHeight: detailsRect.height
|
|
||||||
|
|
||||||
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
|
||||||
interactive: true
|
|
||||||
flickDeceleration: 1500
|
|
||||||
maximumFlickVelocity: 2000
|
|
||||||
boundsBehavior: Flickable.DragAndOvershootBounds
|
|
||||||
boundsMovement: Flickable.FollowBoundsBehavior
|
|
||||||
pressDelay: 0
|
|
||||||
flickableDirection: Flickable.VerticalFlick
|
|
||||||
|
|
||||||
// Custom wheel handler for Qt 6.9+ responsive mouse wheel scrolling
|
|
||||||
WheelHandler {
|
|
||||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
|
||||||
onWheel: (event) => {
|
|
||||||
let delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y * 1.8 : event.angleDelta.y / 120 * 60
|
|
||||||
let newY = parent.contentY - delta
|
|
||||||
newY = Math.max(0, Math.min(parent.contentHeight - parent.height, newY))
|
|
||||||
parent.contentY = newY
|
|
||||||
event.accepted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: detailsRect
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: Math.max(parent.parent.height, detailsText.contentHeight + Theme.spacingM * 2)
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.surfaceHover
|
|
||||||
border.color: Theme.outlineStrong
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: detailsText
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingM
|
|
||||||
text: NetworkService.networkInfoDetails.replace(/\\n/g, '\n') || "No information available"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
lineHeight: 1.5
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
|
||||||
policy: ScrollBar.AsNeeded
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.horizontal: ScrollBar {
|
|
||||||
policy: ScrollBar.AlwaysOff
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: 40
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: Math.max(70, closeText.contentWidth + Theme.spacingM * 2)
|
|
||||||
height: 36
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: closeArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: closeText
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "Close"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.background
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: closeArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
root.hideDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Network Information"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Details for \"" + networkSSID + "\""
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
hoverColor: Theme.errorHover
|
||||||
|
onClicked: {
|
||||||
|
root.hideDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
Flickable {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - 140
|
||||||
|
clip: true
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: detailsRect.height
|
||||||
|
|
||||||
|
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
||||||
|
interactive: true
|
||||||
|
flickDeceleration: 1500
|
||||||
|
maximumFlickVelocity: 2000
|
||||||
|
boundsBehavior: Flickable.DragAndOvershootBounds
|
||||||
|
boundsMovement: Flickable.FollowBoundsBehavior
|
||||||
|
pressDelay: 0
|
||||||
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
|
||||||
|
// Custom wheel handler for Qt 6.9+ responsive mouse wheel scrolling
|
||||||
|
WheelHandler {
|
||||||
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||||
|
onWheel: event => {
|
||||||
|
let delta = event.pixelDelta.y
|
||||||
|
!== 0 ? event.pixelDelta.y * 1.8 : event.angleDelta.y / 120 * 60
|
||||||
|
let newY = parent.contentY - delta
|
||||||
|
newY = Math.max(0, Math.min(
|
||||||
|
parent.contentHeight - parent.height,
|
||||||
|
newY))
|
||||||
|
parent.contentY = newY
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: detailsRect
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: Math.max(parent.parent.height,
|
||||||
|
detailsText.contentHeight + Theme.spacingM * 2)
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceHover
|
||||||
|
border.color: Theme.outlineStrong
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: detailsText
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
text: NetworkService.networkInfoDetails.replace(/\\n/g, '\n')
|
||||||
|
|| "No information available"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
lineHeight: 1.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
policy: ScrollBar.AsNeeded
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.horizontal: ScrollBar {
|
||||||
|
policy: ScrollBar.AlwaysOff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: 40
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: Math.max(70, closeText.contentWidth + Theme.spacingM * 2)
|
||||||
|
height: 36
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: closeArea.containsMouse ? Qt.darker(Theme.primary,
|
||||||
|
1.1) : Theme.primary
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: closeText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "Close"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.background
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: closeArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
root.hideDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,158 +7,154 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
DankModal {
|
DankModal {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool powerConfirmVisible: false
|
property bool powerConfirmVisible: false
|
||||||
property string powerConfirmAction: ""
|
property string powerConfirmAction: ""
|
||||||
property string powerConfirmTitle: ""
|
property string powerConfirmTitle: ""
|
||||||
property string powerConfirmMessage: ""
|
property string powerConfirmMessage: ""
|
||||||
|
|
||||||
function executePowerAction(action) {
|
function executePowerAction(action) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "logout":
|
case "logout":
|
||||||
NiriService.quit();
|
NiriService.quit()
|
||||||
break;
|
break
|
||||||
case "suspend":
|
case "suspend":
|
||||||
Quickshell.execDetached(["systemctl", "suspend"]);
|
Quickshell.execDetached(["systemctl", "suspend"])
|
||||||
break;
|
break
|
||||||
case "reboot":
|
case "reboot":
|
||||||
Quickshell.execDetached(["systemctl", "reboot"]);
|
Quickshell.execDetached(["systemctl", "reboot"])
|
||||||
break;
|
break
|
||||||
case "poweroff":
|
case "poweroff":
|
||||||
Quickshell.execDetached(["systemctl", "poweroff"]);
|
Quickshell.execDetached(["systemctl", "poweroff"])
|
||||||
break;
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: powerConfirmVisible
|
||||||
|
width: 350
|
||||||
|
height: 160
|
||||||
|
keyboardFocus: "ondemand"
|
||||||
|
enableShadow: false
|
||||||
|
onBackgroundClicked: {
|
||||||
|
powerConfirmVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
content: Component {
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: powerConfirmTitle
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: {
|
||||||
|
switch (powerConfirmAction) {
|
||||||
|
case "poweroff":
|
||||||
|
return Theme.error
|
||||||
|
case "reboot":
|
||||||
|
return Theme.warning
|
||||||
|
default:
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
font.weight: Font.Medium
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
visible: powerConfirmVisible
|
StyledText {
|
||||||
width: 350
|
text: powerConfirmMessage
|
||||||
height: 160
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
keyboardFocus: "ondemand"
|
color: Theme.surfaceText
|
||||||
enableShadow: false
|
width: parent.width
|
||||||
onBackgroundClicked: {
|
horizontalAlignment: Text.AlignHCenter
|
||||||
powerConfirmVisible = false;
|
wrapMode: Text.WordWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
content: Component {
|
|
||||||
Item {
|
Item {
|
||||||
anchors.fill: parent
|
height: Theme.spacingS
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Row {
|
||||||
anchors.centerIn: parent
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
width: parent.width - Theme.spacingM * 2
|
spacing: Theme.spacingM
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
StyledText {
|
Rectangle {
|
||||||
text: powerConfirmTitle
|
width: 120
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
height: 40
|
||||||
color: {
|
radius: Theme.cornerRadius
|
||||||
switch (powerConfirmAction) {
|
color: cancelButton.containsMouse ? Theme.surfaceTextPressed : Theme.surfaceVariantAlpha
|
||||||
case "poweroff":
|
|
||||||
return Theme.error;
|
|
||||||
case "reboot":
|
|
||||||
return Theme.warning;
|
|
||||||
default:
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
font.weight: Font.Medium
|
|
||||||
width: parent.width
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: powerConfirmMessage
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
height: Theme.spacingS
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 120
|
|
||||||
height: 40
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: cancelButton.containsMouse ? Theme.surfaceTextPressed : Theme.surfaceVariantAlpha
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Cancel"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: cancelButton
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
powerConfirmVisible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 120
|
|
||||||
height: 40
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: {
|
|
||||||
let baseColor;
|
|
||||||
switch (powerConfirmAction) {
|
|
||||||
case "poweroff":
|
|
||||||
baseColor = Theme.error;
|
|
||||||
break;
|
|
||||||
case "reboot":
|
|
||||||
baseColor = Theme.warning;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
baseColor = Theme.primary;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return confirmButton.containsMouse ? Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) : baseColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Confirm"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.primaryText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: confirmButton
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
powerConfirmVisible = false;
|
|
||||||
executePowerAction(powerConfirmAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Cancel"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cancelButton
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
powerConfirmVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 120
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
let baseColor
|
||||||
|
switch (powerConfirmAction) {
|
||||||
|
case "poweroff":
|
||||||
|
baseColor = Theme.error
|
||||||
|
break
|
||||||
|
case "reboot":
|
||||||
|
baseColor = Theme.warning
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
baseColor = Theme.primary
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return confirmButton.containsMouse ? Qt.rgba(baseColor.r,
|
||||||
|
baseColor.g,
|
||||||
|
baseColor.b,
|
||||||
|
0.9) : baseColor
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Confirm"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.primaryText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: confirmButton
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
powerConfirmVisible = false
|
||||||
|
executePowerAction(powerConfirmAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,306 +12,277 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
DankModal {
|
DankModal {
|
||||||
id: processListModal
|
id: processListModal
|
||||||
|
|
||||||
property int currentTab: 0
|
property int currentTab: 0
|
||||||
property var tabNames: ["Processes", "Performance", "System"]
|
property var tabNames: ["Processes", "Performance", "System"]
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
processListModal.visible = true;
|
processListModal.visible = true
|
||||||
UserInfoService.getUptime();
|
UserInfoService.getUptime()
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
processListModal.visible = false
|
||||||
|
if (processContextMenu.visible)
|
||||||
|
processContextMenu.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
if (processListModal.visible)
|
||||||
|
hide()
|
||||||
|
else
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
|
||||||
|
width: 900
|
||||||
|
height: 680
|
||||||
|
visible: false
|
||||||
|
keyboardFocus: "exclusive"
|
||||||
|
backgroundColor: Theme.popupBackground()
|
||||||
|
cornerRadius: Theme.cornerRadiusXLarge
|
||||||
|
enableShadow: true
|
||||||
|
onBackgroundClicked: hide()
|
||||||
|
|
||||||
|
Ref {
|
||||||
|
service: SysMonitorService
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: processesTabComponent
|
||||||
|
|
||||||
|
ProcessesTab {
|
||||||
|
contextMenu: processContextMenu
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function hide() {
|
Component {
|
||||||
processListModal.visible = false;
|
id: performanceTabComponent
|
||||||
if (processContextMenu.visible)
|
|
||||||
processContextMenu.close();
|
|
||||||
|
|
||||||
}
|
PerformanceTab {}
|
||||||
|
}
|
||||||
|
|
||||||
function toggle() {
|
Component {
|
||||||
if (processListModal.visible)
|
id: systemTabComponent
|
||||||
hide();
|
|
||||||
else
|
|
||||||
show();
|
|
||||||
}
|
|
||||||
|
|
||||||
width: 900
|
SystemTab {}
|
||||||
height: 680
|
}
|
||||||
visible: false
|
|
||||||
keyboardFocus: "exclusive"
|
|
||||||
backgroundColor: Theme.popupBackground()
|
|
||||||
cornerRadius: Theme.cornerRadiusXLarge
|
|
||||||
enableShadow: true
|
|
||||||
onBackgroundClicked: hide()
|
|
||||||
|
|
||||||
Ref {
|
ProcessContextMenu {
|
||||||
service: SysMonitorService
|
id: processContextMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
content: Component {
|
||||||
id: processesTabComponent
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
focus: true
|
||||||
|
Keys.onPressed: function (event) {
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
processListModal.hide()
|
||||||
|
event.accepted = true
|
||||||
|
} else if (event.key === Qt.Key_1) {
|
||||||
|
currentTab = 0
|
||||||
|
event.accepted = true
|
||||||
|
} else if (event.key === Qt.Key_2) {
|
||||||
|
currentTab = 1
|
||||||
|
event.accepted = true
|
||||||
|
} else if (event.key === Qt.Key_3) {
|
||||||
|
currentTab = 2
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ProcessesTab {
|
ColumnLayout {
|
||||||
contextMenu: processContextMenu
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 40
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "System Monitor"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge + 4
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
circular: false
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
hoverColor: Theme.errorHover
|
||||||
|
onClicked: processListModal.hide()
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 52
|
||||||
|
color: Theme.surfaceSelected
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
border.color: Theme.outlineLight
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
Component {
|
Row {
|
||||||
id: performanceTabComponent
|
|
||||||
|
|
||||||
PerformanceTab {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: systemTabComponent
|
|
||||||
|
|
||||||
SystemTab {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessContextMenu {
|
|
||||||
id: processContextMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
content: Component {
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
anchors.margins: 4
|
||||||
Keys.onPressed: function(event) {
|
spacing: 2
|
||||||
if (event.key === Qt.Key_Escape) {
|
|
||||||
processListModal.hide();
|
Repeater {
|
||||||
event.accepted = true;
|
model: tabNames
|
||||||
} else if (event.key === Qt.Key_1) {
|
|
||||||
currentTab = 0;
|
Rectangle {
|
||||||
event.accepted = true;
|
width: (parent.width - (tabNames.length - 1) * 2) / tabNames.length
|
||||||
} else if (event.key === Qt.Key_2) {
|
height: 44
|
||||||
currentTab = 1;
|
radius: Theme.cornerRadiusLarge
|
||||||
event.accepted = true;
|
color: currentTab === index ? Theme.primaryPressed : (tabMouseArea.containsMouse ? Theme.primaryHoverLight : "transparent")
|
||||||
} else if (event.key === Qt.Key_3) {
|
border.color: currentTab === index ? Theme.primary : "transparent"
|
||||||
currentTab = 2;
|
border.width: currentTab === index ? 1 : 0
|
||||||
event.accepted = true;
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
return "list_alt"
|
||||||
|
case 1:
|
||||||
|
return "analytics"
|
||||||
|
case 2:
|
||||||
|
return "settings"
|
||||||
|
default:
|
||||||
|
return "tab"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: currentTab === index ? Theme.primary : Theme.surfaceText
|
||||||
|
opacity: currentTab === index ? 1 : 0.7
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: currentTab === index ? Theme.primary : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.verticalCenterOffset: -1
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: tabMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
currentTab = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on border.color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingL
|
|
||||||
spacing: Theme.spacingL
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
height: 40
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "System Monitor"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge + 4
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
circular: false
|
|
||||||
iconName: "close"
|
|
||||||
iconSize: Theme.iconSize - 4
|
|
||||||
iconColor: Theme.surfaceText
|
|
||||||
hoverColor: Theme.errorHover
|
|
||||||
onClicked: processListModal.hide()
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
height: 52
|
|
||||||
color: Theme.surfaceSelected
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
border.color: Theme.outlineLight
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 4
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: tabNames
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: (parent.width - (tabNames.length - 1) * 2) / tabNames.length
|
|
||||||
height: 44
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: currentTab === index ? Theme.primaryPressed : (tabMouseArea.containsMouse ? Theme.primaryHoverLight : "transparent")
|
|
||||||
border.color: currentTab === index ? Theme.primary : "transparent"
|
|
||||||
border.width: currentTab === index ? 1 : 0
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return "list_alt";
|
|
||||||
case 1:
|
|
||||||
return "analytics";
|
|
||||||
case 2:
|
|
||||||
return "settings";
|
|
||||||
default:
|
|
||||||
return "tab";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: currentTab === index ? Theme.primary : Theme.surfaceText
|
|
||||||
opacity: currentTab === index ? 1 : 0.7
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: currentTab === index ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.verticalCenterOffset: -1
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: tabMouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
currentTab = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on border.color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Theme.surfaceLight
|
|
||||||
border.color: Theme.outlineLight
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: processesTab
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
active: currentTab === 0
|
|
||||||
visible: currentTab === 0
|
|
||||||
opacity: currentTab === 0 ? 1 : 0
|
|
||||||
sourceComponent: processesTabComponent
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: performanceTab
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
active: currentTab === 1
|
|
||||||
visible: currentTab === 1
|
|
||||||
opacity: currentTab === 1 ? 1 : 0
|
|
||||||
sourceComponent: performanceTabComponent
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: systemTab
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
active: currentTab === 2
|
|
||||||
visible: currentTab === 2
|
|
||||||
opacity: currentTab === 2 ? 1 : 0
|
|
||||||
sourceComponent: systemTabComponent
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Theme.surfaceLight
|
||||||
|
border.color: Theme.outlineLight
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: processesTab
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
active: currentTab === 0
|
||||||
|
visible: currentTab === 0
|
||||||
|
opacity: currentTab === 0 ? 1 : 0
|
||||||
|
sourceComponent: processesTabComponent
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: performanceTab
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
active: currentTab === 1
|
||||||
|
visible: currentTab === 1
|
||||||
|
opacity: currentTab === 1 ? 1 : 0
|
||||||
|
sourceComponent: performanceTabComponent
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: systemTab
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
active: currentTab === 2
|
||||||
|
visible: currentTab === 2
|
||||||
|
opacity: currentTab === 2 ? 1 : 0
|
||||||
|
sourceComponent: systemTabComponent
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import QtQuick
|
import QtQuick
|
||||||
@@ -8,183 +8,190 @@ import qs.Modules.Settings
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
DankModal {
|
DankModal {
|
||||||
id: settingsModal
|
id: settingsModal
|
||||||
|
|
||||||
signal closingModal()
|
signal closingModal
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
settingsModal.visible = true;
|
settingsModal.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
settingsModal.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
if (settingsModal.visible)
|
||||||
|
hide()
|
||||||
|
else
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
|
||||||
|
width: 750
|
||||||
|
height: 750
|
||||||
|
visible: false
|
||||||
|
keyboardFocus: "ondemand"
|
||||||
|
onBackgroundClicked: hide()
|
||||||
|
|
||||||
|
content: Component {
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
focus: true
|
||||||
|
Keys.onPressed: function (event) {
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
settingsModal.hide()
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "settings"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Settings"
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width - 175 // Spacer to push close button to the right
|
||||||
|
height: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
circular: false
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
hoverColor: Theme.errorHover
|
||||||
|
onClicked: settingsModal.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - 50
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
DankTabBar {
|
||||||
|
id: settingsTabBar
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
model: [{
|
||||||
|
"text": "Personalization",
|
||||||
|
"icon": "person"
|
||||||
|
}, {
|
||||||
|
"text": "Time & Weather",
|
||||||
|
"icon": "schedule"
|
||||||
|
}, {
|
||||||
|
"text": "Widgets",
|
||||||
|
"icon": "widgets"
|
||||||
|
}, {
|
||||||
|
"text": "Launcher",
|
||||||
|
"icon": "apps"
|
||||||
|
}, {
|
||||||
|
"text": "Appearance",
|
||||||
|
"icon": "palette"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - settingsTabBar.height
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: settingsTabBar.currentIndex === 0
|
||||||
|
visible: active
|
||||||
|
asynchronous: false
|
||||||
|
sourceComponent: Component {
|
||||||
|
PersonalizationTab {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: settingsTabBar.currentIndex === 1
|
||||||
|
visible: active
|
||||||
|
asynchronous: true
|
||||||
|
sourceComponent: Component {
|
||||||
|
TimeWeatherTab {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: settingsTabBar.currentIndex === 2
|
||||||
|
visible: active
|
||||||
|
asynchronous: true
|
||||||
|
sourceComponent: Component {
|
||||||
|
WidgetsTab {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: settingsTabBar.currentIndex === 3
|
||||||
|
visible: active
|
||||||
|
asynchronous: true
|
||||||
|
sourceComponent: Component {
|
||||||
|
LauncherTab {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: settingsTabBar.currentIndex === 4
|
||||||
|
visible: active
|
||||||
|
asynchronous: false
|
||||||
|
sourceComponent: Component {
|
||||||
|
AppearanceTab {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
function open() {
|
||||||
|
settingsModal.show()
|
||||||
|
return "SETTINGS_OPEN_SUCCESS"
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function close() {
|
||||||
settingsModal.visible = false;
|
settingsModal.hide()
|
||||||
|
return "SETTINGS_CLOSE_SUCCESS"
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
if (settingsModal.visible)
|
settingsModal.toggle()
|
||||||
hide();
|
return "SETTINGS_TOGGLE_SUCCESS"
|
||||||
else
|
|
||||||
show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
width: 750
|
target: "settings"
|
||||||
height: 750
|
}
|
||||||
visible: false
|
|
||||||
keyboardFocus: "ondemand"
|
|
||||||
onBackgroundClicked: hide()
|
|
||||||
|
|
||||||
content: Component {
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
focus: true
|
|
||||||
Keys.onPressed: function(event) {
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
|
||||||
settingsModal.hide();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingM
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "settings"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Settings"
|
|
||||||
font.pixelSize: Theme.fontSizeXLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width - 175 // Spacer to push close button to the right
|
|
||||||
height: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
circular: false
|
|
||||||
iconName: "close"
|
|
||||||
iconSize: Theme.iconSize - 4
|
|
||||||
iconColor: Theme.surfaceText
|
|
||||||
hoverColor: Theme.errorHover
|
|
||||||
onClicked: settingsModal.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
height: parent.height - 50
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
DankTabBar {
|
|
||||||
id: settingsTabBar
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
model: [
|
|
||||||
{ text: "Personalization", icon: "person" },
|
|
||||||
{ text: "Time & Weather", icon: "schedule" },
|
|
||||||
{ text: "Widgets", icon: "widgets" },
|
|
||||||
{ text: "Launcher", icon: "apps" },
|
|
||||||
{ text: "Appearance", icon: "palette" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: parent.height - settingsTabBar.height
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingL
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
anchors.fill: parent
|
|
||||||
active: settingsTabBar.currentIndex === 0
|
|
||||||
visible: active
|
|
||||||
asynchronous: false
|
|
||||||
sourceComponent: Component {
|
|
||||||
PersonalizationTab {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
anchors.fill: parent
|
|
||||||
active: settingsTabBar.currentIndex === 1
|
|
||||||
visible: active
|
|
||||||
asynchronous: true
|
|
||||||
sourceComponent: Component {
|
|
||||||
TimeWeatherTab {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
anchors.fill: parent
|
|
||||||
active: settingsTabBar.currentIndex === 2
|
|
||||||
visible: active
|
|
||||||
asynchronous: true
|
|
||||||
sourceComponent: Component {
|
|
||||||
WidgetsTab {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
anchors.fill: parent
|
|
||||||
active: settingsTabBar.currentIndex === 3
|
|
||||||
visible: active
|
|
||||||
asynchronous: true
|
|
||||||
sourceComponent: Component {
|
|
||||||
LauncherTab {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
anchors.fill: parent
|
|
||||||
active: settingsTabBar.currentIndex === 4
|
|
||||||
visible: active
|
|
||||||
asynchronous: false
|
|
||||||
sourceComponent: Component {
|
|
||||||
AppearanceTab {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
IpcHandler {
|
|
||||||
function open() {
|
|
||||||
settingsModal.show();
|
|
||||||
return "SETTINGS_OPEN_SUCCESS";
|
|
||||||
}
|
|
||||||
|
|
||||||
function close() {
|
|
||||||
settingsModal.hide();
|
|
||||||
return "SETTINGS_CLOSE_SUCCESS";
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle() {
|
|
||||||
settingsModal.toggle();
|
|
||||||
return "SETTINGS_TOGGLE_SUCCESS";
|
|
||||||
}
|
|
||||||
|
|
||||||
target: "settings"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,277 +5,266 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
DankModal {
|
DankModal {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool wifiPasswordModalVisible: false
|
property bool wifiPasswordModalVisible: false
|
||||||
property string wifiPasswordSSID: ""
|
property string wifiPasswordSSID: ""
|
||||||
property string wifiPasswordInput: ""
|
property string wifiPasswordInput: ""
|
||||||
|
|
||||||
visible: wifiPasswordModalVisible
|
visible: wifiPasswordModalVisible
|
||||||
width: 420
|
width: 420
|
||||||
height: 230
|
height: 230
|
||||||
keyboardFocus: "exclusive"
|
keyboardFocus: "exclusive"
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (!visible)
|
if (!visible)
|
||||||
wifiPasswordInput = "";
|
wifiPasswordInput = ""
|
||||||
|
}
|
||||||
|
onBackgroundClicked: {
|
||||||
|
wifiPasswordModalVisible = false
|
||||||
|
wifiPasswordInput = ""
|
||||||
|
}
|
||||||
|
|
||||||
}
|
Connections {
|
||||||
onBackgroundClicked: {
|
function onPasswordDialogShouldReopenChanged() {
|
||||||
wifiPasswordModalVisible = false;
|
if (NetworkService.passwordDialogShouldReopen
|
||||||
wifiPasswordInput = "";
|
&& NetworkService.connectingSSID !== "") {
|
||||||
|
wifiPasswordSSID = NetworkService.connectingSSID
|
||||||
|
wifiPasswordInput = ""
|
||||||
|
wifiPasswordModalVisible = true
|
||||||
|
NetworkService.passwordDialogShouldReopen = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
target: NetworkService
|
||||||
function onPasswordDialogShouldReopenChanged() {
|
}
|
||||||
if (NetworkService.passwordDialogShouldReopen && NetworkService.connectingSSID !== "") {
|
|
||||||
wifiPasswordSSID = NetworkService.connectingSSID;
|
content: Component {
|
||||||
wifiPasswordInput = "";
|
FocusScope {
|
||||||
wifiPasswordModalVisible = true;
|
anchors.fill: parent
|
||||||
NetworkService.passwordDialogShouldReopen = false;
|
focus: true
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - 40
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Connect to Wi-Fi"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Enter password for \"" + wifiPasswordSSID + "\""
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
hoverColor: Theme.errorHover
|
||||||
|
onClicked: {
|
||||||
|
wifiPasswordModalVisible = false
|
||||||
|
wifiPasswordInput = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target: NetworkService
|
Rectangle {
|
||||||
}
|
width: parent.width
|
||||||
|
height: 50
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceHover
|
||||||
|
border.color: passwordInput.activeFocus ? Theme.primary : Theme.outlineStrong
|
||||||
|
border.width: passwordInput.activeFocus ? 2 : 1
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: passwordInput
|
||||||
|
|
||||||
content: Component {
|
|
||||||
FocusScope {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
textColor: Theme.surfaceText
|
||||||
|
text: wifiPasswordInput
|
||||||
|
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
|
||||||
|
placeholderText: "Enter password"
|
||||||
|
backgroundColor: "transparent"
|
||||||
focus: true
|
focus: true
|
||||||
|
onTextEdited: {
|
||||||
Column {
|
wifiPasswordInput = text
|
||||||
anchors.centerIn: parent
|
}
|
||||||
width: parent.width - Theme.spacingM * 2
|
onAccepted: {
|
||||||
spacing: Theme.spacingM
|
NetworkService.connectToWifiWithPassword(wifiPasswordSSID,
|
||||||
|
passwordInput.text)
|
||||||
Row {
|
wifiPasswordModalVisible = false
|
||||||
width: parent.width
|
wifiPasswordInput = ""
|
||||||
|
passwordInput.text = ""
|
||||||
Column {
|
|
||||||
width: parent.width - 40
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Connect to Wi-Fi"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Enter password for \"" + wifiPasswordSSID + "\""
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceTextMedium
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
iconName: "close"
|
|
||||||
iconSize: Theme.iconSize - 4
|
|
||||||
iconColor: Theme.surfaceText
|
|
||||||
hoverColor: Theme.errorHover
|
|
||||||
onClicked: {
|
|
||||||
wifiPasswordModalVisible = false;
|
|
||||||
wifiPasswordInput = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 50
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.surfaceHover
|
|
||||||
border.color: passwordInput.activeFocus ? Theme.primary : Theme.outlineStrong
|
|
||||||
border.width: passwordInput.activeFocus ? 2 : 1
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: passwordInput
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
textColor: Theme.surfaceText
|
|
||||||
text: wifiPasswordInput
|
|
||||||
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
|
|
||||||
placeholderText: "Enter password"
|
|
||||||
backgroundColor: "transparent"
|
|
||||||
focus: true
|
|
||||||
onTextEdited: {
|
|
||||||
wifiPasswordInput = text;
|
|
||||||
}
|
|
||||||
onAccepted: {
|
|
||||||
NetworkService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text);
|
|
||||||
wifiPasswordModalVisible = false;
|
|
||||||
wifiPasswordInput = "";
|
|
||||||
passwordInput.text = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: focusTimer
|
|
||||||
interval: 50
|
|
||||||
onTriggered: passwordInput.forceActiveFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
focusTimer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onOpened() {
|
|
||||||
focusTimer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onVisibleChanged() {
|
|
||||||
if (root.visible) {
|
|
||||||
focusTimer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
target: root
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: showPasswordCheckbox
|
|
||||||
|
|
||||||
property bool checked: false
|
|
||||||
|
|
||||||
width: 20
|
|
||||||
height: 20
|
|
||||||
radius: 4
|
|
||||||
color: checked ? Theme.primary : "transparent"
|
|
||||||
border.color: checked ? Theme.primary : Theme.outlineButton
|
|
||||||
border.width: 2
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "check"
|
|
||||||
size: 12
|
|
||||||
color: Theme.background
|
|
||||||
visible: parent.checked
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
showPasswordCheckbox.checked = !showPasswordCheckbox.checked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Show password"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: 40
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
|
|
||||||
height: 36
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
|
|
||||||
border.color: Theme.surfaceVariantAlpha
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: cancelText
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "Cancel"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: cancelArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
wifiPasswordModalVisible = false;
|
|
||||||
wifiPasswordInput = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: Math.max(80, connectText.contentWidth + Theme.spacingM * 2)
|
|
||||||
height: 36
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
|
||||||
enabled: passwordInput.text.length > 0
|
|
||||||
opacity: enabled ? 1 : 0.5
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: connectText
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "Connect"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.background
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: connectArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
enabled: parent.enabled
|
|
||||||
onClicked: {
|
|
||||||
NetworkService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text);
|
|
||||||
wifiPasswordModalVisible = false;
|
|
||||||
wifiPasswordInput = "";
|
|
||||||
passwordInput.text = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
Timer {
|
||||||
}
|
id: focusTimer
|
||||||
|
interval: 50
|
||||||
|
onTriggered: passwordInput.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
focusTimer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onOpened() {
|
||||||
|
focusTimer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onVisibleChanged() {
|
||||||
|
if (root.visible) {
|
||||||
|
focusTimer.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target: root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: showPasswordCheckbox
|
||||||
|
|
||||||
|
property bool checked: false
|
||||||
|
|
||||||
|
width: 20
|
||||||
|
height: 20
|
||||||
|
radius: 4
|
||||||
|
color: checked ? Theme.primary : "transparent"
|
||||||
|
border.color: checked ? Theme.primary : Theme.outlineButton
|
||||||
|
border.width: 2
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "check"
|
||||||
|
size: 12
|
||||||
|
color: Theme.background
|
||||||
|
visible: parent.checked
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
showPasswordCheckbox.checked = !showPasswordCheckbox.checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Show password"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: 40
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
|
||||||
|
height: 36
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
|
||||||
|
border.color: Theme.surfaceVariantAlpha
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: cancelText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "Cancel"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cancelArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
wifiPasswordModalVisible = false
|
||||||
|
wifiPasswordInput = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Math.max(80, connectText.contentWidth + Theme.spacingM * 2)
|
||||||
|
height: 36
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: connectArea.containsMouse ? Qt.darker(Theme.primary,
|
||||||
|
1.1) : Theme.primary
|
||||||
|
enabled: passwordInput.text.length > 0
|
||||||
|
opacity: enabled ? 1 : 0.5
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: connectText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "Connect"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.background
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: connectArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
enabled: parent.enabled
|
||||||
|
onClicked: {
|
||||||
|
NetworkService.connectToWifiWithPassword(wifiPasswordSSID,
|
||||||
|
passwordInput.text)
|
||||||
|
wifiPasswordModalVisible = false
|
||||||
|
wifiPasswordInput = ""
|
||||||
|
passwordInput.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,180 +6,185 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string searchQuery: ""
|
property string searchQuery: ""
|
||||||
property string selectedCategory: "All"
|
property string selectedCategory: "All"
|
||||||
property string viewMode: "list" // "list" or "grid"
|
property string viewMode: "list" // "list" or "grid"
|
||||||
property int selectedIndex: 0
|
property int selectedIndex: 0
|
||||||
property int maxResults: 50
|
property int maxResults: 50
|
||||||
property int gridColumns: 4
|
property int gridColumns: 4
|
||||||
property bool debounceSearch: true
|
property bool debounceSearch: true
|
||||||
property int debounceInterval: 50
|
property int debounceInterval: 50
|
||||||
property bool keyboardNavigationActive: false
|
property bool keyboardNavigationActive: false
|
||||||
property var categories: {
|
property var categories: {
|
||||||
var allCategories = AppSearchService.getAllCategories().filter((cat) => {
|
var allCategories = AppSearchService.getAllCategories().filter(cat => {
|
||||||
return cat !== "Education" && cat !== "Science";
|
return cat !== "Education"
|
||||||
});
|
&& cat !== "Science"
|
||||||
var result = ["All"];
|
})
|
||||||
return result.concat(allCategories.filter((cat) => {
|
var result = ["All"]
|
||||||
return cat !== "All";
|
return result.concat(allCategories.filter(cat => {
|
||||||
}));
|
return cat !== "All"
|
||||||
}
|
}))
|
||||||
property var categoryIcons: categories.map((category) => {
|
}
|
||||||
return AppSearchService.getCategoryIcon(category);
|
property var categoryIcons: categories.map(category => {
|
||||||
})
|
return AppSearchService.getCategoryIcon(
|
||||||
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
|
category)
|
||||||
property alias model: filteredModel
|
})
|
||||||
property var _watchApplications: AppSearchService.applications
|
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
|
||||||
|
property alias model: filteredModel
|
||||||
|
property var _watchApplications: AppSearchService.applications
|
||||||
|
|
||||||
signal appLaunched(var app)
|
signal appLaunched(var app)
|
||||||
signal categorySelected(string category)
|
signal categorySelected(string category)
|
||||||
signal viewModeSelected(string mode)
|
signal viewModeSelected(string mode)
|
||||||
|
|
||||||
function updateFilteredModel() {
|
function updateFilteredModel() {
|
||||||
filteredModel.clear();
|
filteredModel.clear()
|
||||||
selectedIndex = 0;
|
selectedIndex = 0
|
||||||
keyboardNavigationActive = false;
|
keyboardNavigationActive = false
|
||||||
var apps = [];
|
var apps = []
|
||||||
if (searchQuery.length === 0) {
|
if (searchQuery.length === 0) {
|
||||||
if (selectedCategory === "All") {
|
if (selectedCategory === "All") {
|
||||||
apps = AppSearchService.applications || [];
|
apps = AppSearchService.applications || []
|
||||||
} else {
|
} else {
|
||||||
var categoryApps = AppSearchService.getAppsInCategory(selectedCategory);
|
var categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
|
||||||
apps = categoryApps.slice(0, maxResults);
|
apps = categoryApps.slice(0, maxResults)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedCategory === "All") {
|
||||||
|
apps = AppSearchService.searchApplications(searchQuery)
|
||||||
|
} else {
|
||||||
|
var categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
|
||||||
|
if (categoryApps.length > 0) {
|
||||||
|
var allSearchResults = AppSearchService.searchApplications(
|
||||||
|
searchQuery)
|
||||||
|
var categoryNames = new Set(categoryApps.map(app => {
|
||||||
|
return app.name
|
||||||
|
}))
|
||||||
|
apps = allSearchResults.filter(searchApp => {
|
||||||
|
return categoryNames.has(
|
||||||
|
searchApp.name)
|
||||||
|
}).slice(0, maxResults)
|
||||||
} else {
|
} else {
|
||||||
if (selectedCategory === "All") {
|
apps = []
|
||||||
apps = AppSearchService.searchApplications(searchQuery);
|
|
||||||
} else {
|
|
||||||
var categoryApps = AppSearchService.getAppsInCategory(selectedCategory);
|
|
||||||
if (categoryApps.length > 0) {
|
|
||||||
var allSearchResults = AppSearchService.searchApplications(searchQuery);
|
|
||||||
var categoryNames = new Set(categoryApps.map((app) => {
|
|
||||||
return app.name;
|
|
||||||
}));
|
|
||||||
apps = allSearchResults.filter((searchApp) => {
|
|
||||||
return categoryNames.has(searchApp.name);
|
|
||||||
}).slice(0, maxResults);
|
|
||||||
} else {
|
|
||||||
apps = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (searchQuery.length === 0)
|
}
|
||||||
apps = apps.sort(function(a, b) {
|
|
||||||
var aId = a.id || (a.execString || a.exec || "");
|
|
||||||
var bId = b.id || (b.execString || b.exec || "");
|
|
||||||
var aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0;
|
|
||||||
var bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0;
|
|
||||||
if (aUsage !== bUsage)
|
|
||||||
return bUsage - aUsage;
|
|
||||||
|
|
||||||
return (a.name || "").localeCompare(b.name || "");
|
|
||||||
});
|
|
||||||
|
|
||||||
apps.forEach((app) => {
|
|
||||||
if (app)
|
|
||||||
filteredModel.append({
|
|
||||||
"name": app.name || "",
|
|
||||||
"exec": app.execString || "",
|
|
||||||
"icon": app.icon || "application-x-executable",
|
|
||||||
"comment": app.comment || "",
|
|
||||||
"categories": app.categories || [],
|
|
||||||
"desktopEntry": app
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
if (searchQuery.length === 0)
|
||||||
|
apps = apps.sort(function (a, b) {
|
||||||
|
var aId = a.id || (a.execString || a.exec || "")
|
||||||
|
var bId = b.id || (b.execString || b.exec || "")
|
||||||
|
var aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0
|
||||||
|
var bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0
|
||||||
|
if (aUsage !== bUsage)
|
||||||
|
return bUsage - aUsage
|
||||||
|
|
||||||
function selectNext() {
|
return (a.name || "").localeCompare(b.name || "")
|
||||||
if (filteredModel.count > 0) {
|
})
|
||||||
keyboardNavigationActive = true;
|
|
||||||
if (viewMode === "grid") {
|
apps.forEach(app => {
|
||||||
var newIndex = Math.min(selectedIndex + gridColumns, filteredModel.count - 1);
|
if (app)
|
||||||
selectedIndex = newIndex;
|
filteredModel.append({
|
||||||
} else {
|
"name": app.name || "",
|
||||||
selectedIndex = (selectedIndex + 1) % filteredModel.count;
|
"exec": app.execString || "",
|
||||||
}
|
"icon": app.icon
|
||||||
}
|
|| "application-x-executable",
|
||||||
|
"comment": app.comment || "",
|
||||||
|
"categories": app.categories || [],
|
||||||
|
"desktopEntry": app
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectNext() {
|
||||||
|
if (filteredModel.count > 0) {
|
||||||
|
keyboardNavigationActive = true
|
||||||
|
if (viewMode === "grid") {
|
||||||
|
var newIndex = Math.min(selectedIndex + gridColumns,
|
||||||
|
filteredModel.count - 1)
|
||||||
|
selectedIndex = newIndex
|
||||||
|
} else {
|
||||||
|
selectedIndex = (selectedIndex + 1) % filteredModel.count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function selectPrevious() {
|
function selectPrevious() {
|
||||||
if (filteredModel.count > 0) {
|
if (filteredModel.count > 0) {
|
||||||
keyboardNavigationActive = true;
|
keyboardNavigationActive = true
|
||||||
if (viewMode === "grid") {
|
if (viewMode === "grid") {
|
||||||
var newIndex = Math.max(selectedIndex - gridColumns, 0);
|
var newIndex = Math.max(selectedIndex - gridColumns, 0)
|
||||||
selectedIndex = newIndex;
|
selectedIndex = newIndex
|
||||||
} else {
|
} else {
|
||||||
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1;
|
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function selectNextInRow() {
|
function selectNextInRow() {
|
||||||
if (filteredModel.count > 0 && viewMode === "grid") {
|
if (filteredModel.count > 0 && viewMode === "grid") {
|
||||||
keyboardNavigationActive = true;
|
keyboardNavigationActive = true
|
||||||
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1);
|
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function selectPreviousInRow() {
|
function selectPreviousInRow() {
|
||||||
if (filteredModel.count > 0 && viewMode === "grid") {
|
if (filteredModel.count > 0 && viewMode === "grid") {
|
||||||
keyboardNavigationActive = true;
|
keyboardNavigationActive = true
|
||||||
selectedIndex = Math.max(selectedIndex - 1, 0);
|
selectedIndex = Math.max(selectedIndex - 1, 0)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function launchSelected() {
|
function launchSelected() {
|
||||||
if (filteredModel.count > 0 && selectedIndex >= 0 && selectedIndex < filteredModel.count) {
|
if (filteredModel.count > 0 && selectedIndex >= 0
|
||||||
var selectedApp = filteredModel.get(selectedIndex);
|
&& selectedIndex < filteredModel.count) {
|
||||||
launchApp(selectedApp);
|
var selectedApp = filteredModel.get(selectedIndex)
|
||||||
}
|
launchApp(selectedApp)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function launchApp(appData) {
|
function launchApp(appData) {
|
||||||
if (!appData)
|
if (!appData)
|
||||||
return ;
|
return
|
||||||
|
|
||||||
appData.desktopEntry.execute();
|
appData.desktopEntry.execute()
|
||||||
appLaunched(appData);
|
appLaunched(appData)
|
||||||
AppUsageHistoryData.addAppUsage(appData.desktopEntry);
|
AppUsageHistoryData.addAppUsage(appData.desktopEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCategory(category) {
|
function setCategory(category) {
|
||||||
selectedCategory = category;
|
selectedCategory = category
|
||||||
categorySelected(category);
|
categorySelected(category)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setViewMode(mode) {
|
function setViewMode(mode) {
|
||||||
viewMode = mode;
|
viewMode = mode
|
||||||
viewModeSelected(mode);
|
viewModeSelected(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSearchQueryChanged: {
|
onSearchQueryChanged: {
|
||||||
if (debounceSearch)
|
if (debounceSearch)
|
||||||
searchDebounceTimer.restart();
|
searchDebounceTimer.restart()
|
||||||
else
|
else
|
||||||
updateFilteredModel();
|
updateFilteredModel()
|
||||||
}
|
}
|
||||||
onSelectedCategoryChanged: updateFilteredModel()
|
onSelectedCategoryChanged: updateFilteredModel()
|
||||||
onAppUsageRankingChanged: updateFilteredModel()
|
onAppUsageRankingChanged: updateFilteredModel()
|
||||||
on_WatchApplicationsChanged: updateFilteredModel()
|
on_WatchApplicationsChanged: updateFilteredModel()
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
updateFilteredModel();
|
updateFilteredModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
ListModel {
|
ListModel {
|
||||||
id: filteredModel
|
id: filteredModel
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: searchDebounceTimer
|
id: searchDebounceTimer
|
||||||
|
|
||||||
interval: root.debounceInterval
|
|
||||||
repeat: false
|
|
||||||
onTriggered: updateFilteredModel()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
interval: root.debounceInterval
|
||||||
|
repeat: false
|
||||||
|
onTriggered: updateFilteredModel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,147 +4,151 @@ import qs.Common
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var categories: []
|
property var categories: []
|
||||||
property string selectedCategory: "All"
|
property string selectedCategory: "All"
|
||||||
property bool compact: false // For different layout styles
|
property bool compact: false // For different layout styles
|
||||||
|
|
||||||
signal categorySelected(string category)
|
signal categorySelected(string category)
|
||||||
|
|
||||||
height: compact ? 36 : (72 + Theme.spacingS) // Single row vs two rows
|
height: compact ? 36 : (72 + Theme.spacingS) // Single row vs two rows
|
||||||
|
|
||||||
|
Row {
|
||||||
|
visible: compact
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: categories.slice(0, Math.min(categories.length,
|
||||||
|
8)) // Limit for space
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
height: 36
|
||||||
|
width: (parent.width - (Math.min(categories.length,
|
||||||
|
8) - 1) * Theme.spacingS) / Math.min(
|
||||||
|
categories.length, 8)
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: selectedCategory === modelData ? Theme.primary : "transparent"
|
||||||
|
border.color: selectedCategory === modelData ? "transparent" : Qt.rgba(
|
||||||
|
Theme.outline.r,
|
||||||
|
Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.3)
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: modelData
|
||||||
|
color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
selectedCategory = modelData
|
||||||
|
categorySelected(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
visible: !compact
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
visible: compact
|
property var topRowCategories: ["All", "Development", "Graphics", "Games"]
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Repeater {
|
width: parent.width
|
||||||
model: categories.slice(0, Math.min(categories.length, 8)) // Limit for space
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Rectangle {
|
Repeater {
|
||||||
height: 36
|
model: parent.topRowCategories.filter(cat => {
|
||||||
width: (parent.width - (Math.min(categories.length, 8) - 1) * Theme.spacingS) / Math.min(categories.length, 8)
|
return categories.includes(cat)
|
||||||
radius: Theme.cornerRadiusLarge
|
})
|
||||||
color: selectedCategory === modelData ? Theme.primary : "transparent"
|
|
||||||
border.color: selectedCategory === modelData ? "transparent" : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
|
||||||
|
|
||||||
StyledText {
|
Rectangle {
|
||||||
anchors.centerIn: parent
|
height: 36
|
||||||
text: modelData
|
width: (parent.width - (parent.topRowCategories.length - 1)
|
||||||
color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText
|
* Theme.spacingS) / parent.topRowCategories.length
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
radius: Theme.cornerRadiusLarge
|
||||||
font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal
|
color: selectedCategory === modelData ? Theme.primary : "transparent"
|
||||||
elide: Text.ElideRight
|
border.color: selectedCategory === modelData ? "transparent" : Qt.rgba(
|
||||||
}
|
Theme.outline.r,
|
||||||
|
Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.3)
|
||||||
|
|
||||||
MouseArea {
|
StyledText {
|
||||||
anchors.fill: parent
|
anchors.centerIn: parent
|
||||||
hoverEnabled: true
|
text: modelData
|
||||||
cursorShape: Qt.PointingHandCursor
|
color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText
|
||||||
onClicked: {
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
selectedCategory = modelData;
|
font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal
|
||||||
categorySelected(modelData);
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
selectedCategory = modelData
|
||||||
|
categorySelected(modelData)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Row {
|
||||||
visible: !compact
|
property var bottomRowCategories: ["Internet", "Media", "Office", "Settings", "System"]
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Row {
|
width: parent.width
|
||||||
property var topRowCategories: ["All", "Development", "Graphics", "Games"]
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
width: parent.width
|
Repeater {
|
||||||
spacing: Theme.spacingS
|
model: parent.bottomRowCategories.filter(cat => {
|
||||||
|
return categories.includes(
|
||||||
|
cat)
|
||||||
|
})
|
||||||
|
|
||||||
Repeater {
|
Rectangle {
|
||||||
model: parent.topRowCategories.filter((cat) => {
|
height: 36
|
||||||
return categories.includes(cat);
|
width: (parent.width - (parent.bottomRowCategories.length - 1)
|
||||||
})
|
* Theme.spacingS) / parent.bottomRowCategories.length
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: selectedCategory === modelData ? Theme.primary : "transparent"
|
||||||
|
border.color: selectedCategory === modelData ? "transparent" : Qt.rgba(
|
||||||
|
Theme.outline.r,
|
||||||
|
Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.3)
|
||||||
|
|
||||||
Rectangle {
|
StyledText {
|
||||||
height: 36
|
anchors.centerIn: parent
|
||||||
width: (parent.width - (parent.topRowCategories.length - 1) * Theme.spacingS) / parent.topRowCategories.length
|
text: modelData
|
||||||
radius: Theme.cornerRadiusLarge
|
color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText
|
||||||
color: selectedCategory === modelData ? Theme.primary : "transparent"
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
border.color: selectedCategory === modelData ? "transparent" : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal
|
||||||
|
elide: Text.ElideRight
|
||||||
StyledText {
|
}
|
||||||
anchors.centerIn: parent
|
|
||||||
text: modelData
|
|
||||||
color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
selectedCategory = modelData;
|
|
||||||
categorySelected(modelData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
selectedCategory = modelData
|
||||||
|
categorySelected(modelData)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Row {
|
|
||||||
property var bottomRowCategories: ["Internet", "Media", "Office", "Settings", "System"]
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: parent.bottomRowCategories.filter((cat) => {
|
|
||||||
return categories.includes(cat);
|
|
||||||
})
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
height: 36
|
|
||||||
width: (parent.width - (parent.bottomRowCategories.length - 1) * Theme.spacingS) / parent.bottomRowCategories.length
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: selectedCategory === modelData ? Theme.primary : "transparent"
|
|
||||||
border.color: selectedCategory === modelData ? "transparent" : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: modelData
|
|
||||||
color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
selectedCategory = modelData;
|
|
||||||
categorySelected(modelData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,202 +8,204 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var modelData
|
property var modelData
|
||||||
property bool brightnessPopupVisible: false
|
property bool brightnessPopupVisible: false
|
||||||
property var brightnessDebounceTimer
|
property var brightnessDebounceTimer
|
||||||
|
|
||||||
brightnessDebounceTimer: Timer {
|
brightnessDebounceTimer: Timer {
|
||||||
property int pendingValue: 0
|
property int pendingValue: 0
|
||||||
|
|
||||||
interval: BrightnessService.ddcAvailable ? 500 : 50
|
interval: BrightnessService.ddcAvailable ? 500 : 50
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
BrightnessService.setBrightnessInternal(pendingValue);
|
BrightnessService.setBrightnessInternal(pendingValue)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
root.brightnessPopupVisible = true
|
||||||
|
hideTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetHideTimer() {
|
||||||
|
if (root.brightnessPopupVisible)
|
||||||
|
hideTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
screen: modelData
|
||||||
|
visible: brightnessPopupVisible
|
||||||
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: hideTimer
|
||||||
|
|
||||||
|
interval: 3000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (!brightnessPopup.containsMouse)
|
||||||
|
root.brightnessPopupVisible = false
|
||||||
|
else
|
||||||
|
hideTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onBrightnessChanged() {
|
||||||
|
root.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
function show() {
|
target: BrightnessService
|
||||||
root.brightnessPopupVisible = true;
|
}
|
||||||
hideTimer.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetHideTimer() {
|
Rectangle {
|
||||||
if (root.brightnessPopupVisible)
|
id: brightnessPopup
|
||||||
hideTimer.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
screen: modelData
|
property bool containsMouse: popupMouseArea.containsMouse
|
||||||
visible: brightnessPopupVisible
|
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
anchors {
|
width: Math.min(260, Screen.width - Theme.spacingM * 2)
|
||||||
top: true
|
height: brightnessContent.height + Theme.spacingS * 2
|
||||||
left: true
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
right: true
|
anchors.bottom: parent.bottom
|
||||||
bottom: true
|
anchors.bottomMargin: Theme.spacingM
|
||||||
}
|
color: Theme.popupBackground()
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
opacity: root.brightnessPopupVisible ? 1 : 0
|
||||||
|
scale: root.brightnessPopupVisible ? 1 : 0.9
|
||||||
|
layer.enabled: true
|
||||||
|
|
||||||
Timer {
|
Column {
|
||||||
id: hideTimer
|
id: brightnessContent
|
||||||
|
|
||||||
interval: 3000
|
anchors.centerIn: parent
|
||||||
repeat: false
|
width: parent.width - Theme.spacingS * 2
|
||||||
onTriggered: {
|
spacing: Theme.spacingXS
|
||||||
if (!brightnessPopup.containsMouse)
|
|
||||||
root.brightnessPopupVisible = false;
|
|
||||||
else
|
|
||||||
hideTimer.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Item {
|
||||||
function onBrightnessChanged() {
|
property int gap: Theme.spacingS
|
||||||
root.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
target: BrightnessService
|
width: parent.width
|
||||||
}
|
height: 40
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: brightnessPopup
|
width: Theme.iconSize
|
||||||
|
height: Theme.iconSize
|
||||||
property bool containsMouse: popupMouseArea.containsMouse
|
radius: Theme.iconSize / 2
|
||||||
|
color: "transparent"
|
||||||
width: Math.min(260, Screen.width - Theme.spacingM * 2)
|
x: parent.gap
|
||||||
height: brightnessContent.height + Theme.spacingS * 2
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: Theme.spacingM
|
|
||||||
color: Theme.popupBackground()
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
opacity: root.brightnessPopupVisible ? 1 : 0
|
|
||||||
scale: root.brightnessPopupVisible ? 1 : 0.9
|
|
||||||
layer.enabled: true
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: brightnessContent
|
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width - Theme.spacingS * 2
|
name: BrightnessService.brightnessLevel
|
||||||
spacing: Theme.spacingXS
|
< 30 ? "brightness_low" : BrightnessService.brightnessLevel
|
||||||
|
< 70 ? "brightness_medium" : "brightness_high"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
DankSlider {
|
||||||
property int gap: Theme.spacingS
|
id: brightnessSlider
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width - Theme.iconSize - parent.gap * 3
|
||||||
height: 40
|
height: 40
|
||||||
|
x: parent.gap * 2 + Theme.iconSize
|
||||||
Rectangle {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: Theme.iconSize
|
minimum: 1
|
||||||
height: Theme.iconSize
|
maximum: 100
|
||||||
radius: Theme.iconSize / 2
|
enabled: BrightnessService.brightnessAvailable
|
||||||
color: "transparent"
|
showValue: true
|
||||||
x: parent.gap
|
unit: "%"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
Component.onCompleted: {
|
||||||
|
if (BrightnessService.brightnessAvailable)
|
||||||
DankIcon {
|
value = BrightnessService.brightnessLevel
|
||||||
anchors.centerIn: parent
|
}
|
||||||
name: BrightnessService.brightnessLevel < 30 ? "brightness_low" :
|
onSliderValueChanged: function (newValue) {
|
||||||
BrightnessService.brightnessLevel < 70 ? "brightness_medium" : "brightness_high"
|
if (BrightnessService.brightnessAvailable) {
|
||||||
size: Theme.iconSize
|
brightnessDebounceTimer.pendingValue = newValue
|
||||||
color: Theme.primary
|
brightnessDebounceTimer.restart()
|
||||||
}
|
root.resetHideTimer()
|
||||||
}
|
|
||||||
|
|
||||||
DankSlider {
|
|
||||||
id: brightnessSlider
|
|
||||||
|
|
||||||
width: parent.width - Theme.iconSize - parent.gap * 3
|
|
||||||
height: 40
|
|
||||||
x: parent.gap * 2 + Theme.iconSize
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
minimum: 1
|
|
||||||
maximum: 100
|
|
||||||
enabled: BrightnessService.brightnessAvailable
|
|
||||||
showValue: true
|
|
||||||
unit: "%"
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (BrightnessService.brightnessAvailable)
|
|
||||||
value = BrightnessService.brightnessLevel;
|
|
||||||
}
|
|
||||||
onSliderValueChanged: function(newValue) {
|
|
||||||
if (BrightnessService.brightnessAvailable) {
|
|
||||||
brightnessDebounceTimer.pendingValue = newValue;
|
|
||||||
brightnessDebounceTimer.restart();
|
|
||||||
root.resetHideTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onSliderDragFinished: function(finalValue) {
|
|
||||||
if (BrightnessService.brightnessAvailable) {
|
|
||||||
brightnessDebounceTimer.stop();
|
|
||||||
BrightnessService.setBrightnessInternal(finalValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onBrightnessChanged() {
|
|
||||||
brightnessSlider.value = BrightnessService.brightnessLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
target: BrightnessService
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onSliderDragFinished: function (finalValue) {
|
||||||
MouseArea {
|
if (BrightnessService.brightnessAvailable) {
|
||||||
id: popupMouseArea
|
brightnessDebounceTimer.stop()
|
||||||
|
BrightnessService.setBrightnessInternal(finalValue)
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
acceptedButtons: Qt.NoButton
|
|
||||||
propagateComposedEvents: true
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowHorizontalOffset: 0
|
|
||||||
shadowVerticalOffset: 4
|
|
||||||
shadowBlur: 0.8
|
|
||||||
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
|
||||||
shadowOpacity: 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
transform: Translate {
|
|
||||||
y: root.brightnessPopupVisible ? 0 : 20
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scale {
|
Connections {
|
||||||
NumberAnimation {
|
function onBrightnessChanged() {
|
||||||
duration: Theme.mediumDuration
|
brightnessSlider.value = BrightnessService.brightnessLevel
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on transform {
|
target: BrightnessService
|
||||||
PropertyAnimation {
|
}
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mask: Region {
|
MouseArea {
|
||||||
item: brightnessPopup
|
id: popupMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
propagateComposedEvents: true
|
||||||
|
z: -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
shadowEnabled: true
|
||||||
|
shadowHorizontalOffset: 0
|
||||||
|
shadowVerticalOffset: 4
|
||||||
|
shadowBlur: 0.8
|
||||||
|
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
||||||
|
shadowOpacity: 0.3
|
||||||
|
}
|
||||||
|
|
||||||
|
transform: Translate {
|
||||||
|
y: root.brightnessPopupVisible ? 0 : 20
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on transform {
|
||||||
|
PropertyAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: brightnessPopup
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,264 +6,267 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: calendarGrid
|
id: calendarGrid
|
||||||
|
|
||||||
property date displayDate: new Date()
|
property date displayDate: new Date()
|
||||||
property date selectedDate: new Date()
|
property date selectedDate: new Date()
|
||||||
|
|
||||||
function loadEventsForMonth() {
|
function loadEventsForMonth() {
|
||||||
if (!CalendarService || !CalendarService.khalAvailable)
|
if (!CalendarService || !CalendarService.khalAvailable)
|
||||||
return ;
|
return
|
||||||
|
|
||||||
let firstDay = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1);
|
let firstDay = new Date(displayDate.getFullYear(),
|
||||||
let dayOfWeek = firstDay.getDay();
|
displayDate.getMonth(), 1)
|
||||||
let startDate = new Date(firstDay);
|
let dayOfWeek = firstDay.getDay()
|
||||||
startDate.setDate(startDate.getDate() - dayOfWeek - 7); // Extra week padding
|
let startDate = new Date(firstDay)
|
||||||
let lastDay = new Date(displayDate.getFullYear(), displayDate.getMonth() + 1, 0);
|
startDate.setDate(startDate.getDate() - dayOfWeek - 7) // Extra week padding
|
||||||
let endDate = new Date(lastDay);
|
let lastDay = new Date(displayDate.getFullYear(),
|
||||||
endDate.setDate(endDate.getDate() + (6 - lastDay.getDay()) + 7); // Extra week padding
|
displayDate.getMonth() + 1, 0)
|
||||||
CalendarService.loadEvents(startDate, endDate);
|
let endDate = new Date(lastDay)
|
||||||
|
endDate.setDate(endDate.getDate() + (6 - lastDay.getDay(
|
||||||
|
)) + 7) // Extra week padding
|
||||||
|
CalendarService.loadEvents(startDate, endDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
onDisplayDateChanged: {
|
||||||
|
loadEventsForMonth()
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
loadEventsForMonth()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onKhalAvailableChanged() {
|
||||||
|
if (CalendarService && CalendarService.khalAvailable)
|
||||||
|
loadEventsForMonth()
|
||||||
}
|
}
|
||||||
|
|
||||||
spacing: Theme.spacingM
|
target: CalendarService
|
||||||
onDisplayDateChanged: {
|
enabled: CalendarService !== null
|
||||||
loadEventsForMonth();
|
}
|
||||||
}
|
|
||||||
Component.onCompleted: {
|
|
||||||
loadEventsForMonth();
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Row {
|
||||||
function onKhalAvailableChanged() {
|
width: parent.width
|
||||||
if (CalendarService && CalendarService.khalAvailable)
|
height: 40
|
||||||
loadEventsForMonth();
|
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: prevMonthArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "chevron_left"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: prevMonthArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
let newDate = new Date(displayDate)
|
||||||
|
newDate.setMonth(newDate.getMonth() - 1)
|
||||||
|
displayDate = newDate
|
||||||
}
|
}
|
||||||
|
}
|
||||||
target: CalendarService
|
|
||||||
enabled: CalendarService !== null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
StyledText {
|
||||||
width: parent.width
|
width: parent.width - 80
|
||||||
height: 40
|
height: 40
|
||||||
|
text: Qt.formatDate(displayDate, "MMMM yyyy")
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 40
|
width: 40
|
||||||
height: 40
|
height: 40
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: prevMonthArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
color: nextMonthArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "chevron_left"
|
name: "chevron_right"
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: prevMonthArea
|
id: nextMonthArea
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
let newDate = new Date(displayDate);
|
|
||||||
newDate.setMonth(newDate.getMonth() - 1);
|
|
||||||
displayDate = newDate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
let newDate = new Date(displayDate)
|
||||||
|
newDate.setMonth(newDate.getMonth() + 1)
|
||||||
|
displayDate = newDate
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
height: 32
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width / 7
|
||||||
|
height: 32
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
width: parent.width - 80
|
anchors.centerIn: parent
|
||||||
height: 40
|
text: modelData
|
||||||
text: Qt.formatDate(displayDate, "MMMM yyyy")
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
color: Theme.surfaceText
|
Theme.surfaceText.b, 0.6)
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Grid {
|
||||||
|
property date firstDay: {
|
||||||
|
let date = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1)
|
||||||
|
let dayOfWeek = date.getDay()
|
||||||
|
date.setDate(date.getDate() - dayOfWeek)
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: 200 // Fixed height for calendar
|
||||||
|
columns: 7
|
||||||
|
rows: 6
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: 42
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
property date dayDate: {
|
||||||
|
let date = new Date(parent.firstDay)
|
||||||
|
date.setDate(date.getDate() + index)
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
property bool isCurrentMonth: dayDate.getMonth(
|
||||||
|
) === displayDate.getMonth()
|
||||||
|
property bool isToday: dayDate.toDateString(
|
||||||
|
) === new Date().toDateString()
|
||||||
|
property bool isSelected: dayDate.toDateString(
|
||||||
|
) === selectedDate.toDateString()
|
||||||
|
|
||||||
|
width: parent.width / 7
|
||||||
|
height: parent.height / 6
|
||||||
|
color: "transparent"
|
||||||
|
clip: true
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 40
|
anchors.centerIn: parent
|
||||||
height: 40
|
width: parent.width - 4
|
||||||
radius: Theme.cornerRadius
|
height: parent.height - 4
|
||||||
color: nextMonthArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
color: isSelected ? Theme.primary : isToday ? Qt.rgba(
|
||||||
|
Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||||
|
radius: Theme.cornerRadiusSmall
|
||||||
|
clip: true
|
||||||
|
|
||||||
DankIcon {
|
StyledText {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "chevron_right"
|
text: dayDate.getDate()
|
||||||
size: Theme.iconSize
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.primary
|
color: isSelected ? Theme.surface : isToday ? Theme.primary : isCurrentMonth ? Theme.surfaceText : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||||
|
font.weight: isToday || isSelected ? Font.Medium : Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: eventIndicator
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: parent.radius
|
||||||
|
visible: CalendarService && CalendarService.khalAvailable
|
||||||
|
&& CalendarService.hasEventsForDate(dayDate)
|
||||||
|
opacity: {
|
||||||
|
if (isSelected)
|
||||||
|
return 0.9
|
||||||
|
else if (isToday)
|
||||||
|
return 0.8
|
||||||
|
else
|
||||||
|
return 0.6
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
gradient: Gradient {
|
||||||
id: nextMonthArea
|
GradientStop {
|
||||||
|
position: 0.89
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
let newDate = new Date(displayDate);
|
|
||||||
newDate.setMonth(newDate.getMonth() + 1);
|
|
||||||
displayDate = newDate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
height: 32
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width / 7
|
|
||||||
height: 32
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
}
|
||||||
|
|
||||||
StyledText {
|
GradientStop {
|
||||||
anchors.centerIn: parent
|
position: 0.9
|
||||||
text: modelData
|
color: {
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
if (isSelected)
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
return Qt.lighter(Theme.primary, 1.3)
|
||||||
font.weight: Font.Medium
|
else if (isToday)
|
||||||
|
return Theme.primary
|
||||||
|
else
|
||||||
|
return Theme.primary
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientStop {
|
||||||
|
position: 1
|
||||||
|
color: {
|
||||||
|
if (isSelected)
|
||||||
|
return Qt.lighter(Theme.primary, 1.3)
|
||||||
|
else if (isToday)
|
||||||
|
return Theme.primary
|
||||||
|
else
|
||||||
|
return Theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
}
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
Grid {
|
}
|
||||||
property date firstDay: {
|
|
||||||
let date = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1);
|
|
||||||
let dayOfWeek = date.getDay();
|
|
||||||
date.setDate(date.getDate() - dayOfWeek);
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: 200 // Fixed height for calendar
|
|
||||||
columns: 7
|
|
||||||
rows: 6
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: 42
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
property date dayDate: {
|
|
||||||
let date = new Date(parent.firstDay);
|
|
||||||
date.setDate(date.getDate() + index);
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
property bool isCurrentMonth: dayDate.getMonth() === displayDate.getMonth()
|
|
||||||
property bool isToday: dayDate.toDateString() === new Date().toDateString()
|
|
||||||
property bool isSelected: dayDate.toDateString() === selectedDate.toDateString()
|
|
||||||
|
|
||||||
width: parent.width / 7
|
|
||||||
height: parent.height / 6
|
|
||||||
color: "transparent"
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width - 4
|
|
||||||
height: parent.height - 4
|
|
||||||
color: isSelected ? Theme.primary : isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: dayDate.getDate()
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: isSelected ? Theme.surface : isToday ? Theme.primary : isCurrentMonth ? Theme.surfaceText : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
|
||||||
font.weight: isToday || isSelected ? Font.Medium : Font.Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: eventIndicator
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: parent.radius
|
|
||||||
visible: CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)
|
|
||||||
opacity: {
|
|
||||||
if (isSelected)
|
|
||||||
return 0.9;
|
|
||||||
else if (isToday)
|
|
||||||
return 0.8;
|
|
||||||
else
|
|
||||||
return 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
gradient: Gradient {
|
|
||||||
GradientStop {
|
|
||||||
position: 0.89
|
|
||||||
color: "transparent"
|
|
||||||
}
|
|
||||||
|
|
||||||
GradientStop {
|
|
||||||
position: 0.9
|
|
||||||
color: {
|
|
||||||
if (isSelected)
|
|
||||||
return Qt.lighter(Theme.primary, 1.3);
|
|
||||||
else if (isToday)
|
|
||||||
return Theme.primary;
|
|
||||||
else
|
|
||||||
return Theme.primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GradientStop {
|
|
||||||
position: 1
|
|
||||||
color: {
|
|
||||||
if (isSelected)
|
|
||||||
return Qt.lighter(Theme.primary, 1.3);
|
|
||||||
else if (isToday)
|
|
||||||
return Theme.primary;
|
|
||||||
else
|
|
||||||
return Theme.primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: dayArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
selectedDate = dayDate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
MouseArea {
|
||||||
|
id: dayArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
selectedDate = dayDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,304 +10,303 @@ import qs.Modules.CentcomCenter
|
|||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property bool hasActiveMedia: MprisController.activePlayer !== null
|
readonly property bool hasActiveMedia: MprisController.activePlayer !== null
|
||||||
property bool calendarVisible: false
|
property bool calendarVisible: false
|
||||||
property bool internalVisible: false
|
property bool internalVisible: false
|
||||||
property real triggerX: (Screen.width - 480) / 2
|
property real triggerX: (Screen.width - 480) / 2
|
||||||
property real triggerY: Theme.barHeight + 4
|
property real triggerY: Theme.barHeight + 4
|
||||||
property real triggerWidth: 80
|
property real triggerWidth: 80
|
||||||
property string triggerSection: "center"
|
property string triggerSection: "center"
|
||||||
property var triggerScreen: null
|
property var triggerScreen: null
|
||||||
|
|
||||||
function setTriggerPosition(x, y, width, section, screen) {
|
function setTriggerPosition(x, y, width, section, screen) {
|
||||||
triggerX = x;
|
triggerX = x
|
||||||
triggerY = y;
|
triggerY = y
|
||||||
triggerWidth = width;
|
triggerWidth = width
|
||||||
triggerSection = section;
|
triggerSection = section
|
||||||
triggerScreen = screen;
|
triggerScreen = screen
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: internalVisible
|
||||||
|
screen: triggerScreen
|
||||||
|
onCalendarVisibleChanged: {
|
||||||
|
if (calendarVisible) {
|
||||||
|
internalVisible = true
|
||||||
|
Qt.callLater(() => {
|
||||||
|
internalVisible = true
|
||||||
|
calendarGrid.loadEventsForMonth()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
internalVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible && calendarGrid)
|
||||||
|
calendarGrid.loadEventsForMonth()
|
||||||
|
}
|
||||||
|
implicitWidth: 480
|
||||||
|
implicitHeight: 600
|
||||||
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: calendarVisible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: mainContainer
|
||||||
|
|
||||||
|
readonly property real targetWidth: Math.min(
|
||||||
|
(root.screen ? root.screen.width : Screen.width) * 0.9,
|
||||||
|
600)
|
||||||
|
|
||||||
|
function calculateWidth() {
|
||||||
|
let baseWidth = 320
|
||||||
|
if (leftWidgets.hasAnyWidgets)
|
||||||
|
return Math.min(parent.width * 0.9, 600)
|
||||||
|
|
||||||
|
return Math.min(parent.width * 0.7, 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
visible: internalVisible
|
function calculateHeight() {
|
||||||
screen: triggerScreen
|
let contentHeight = Theme.spacingM * 2
|
||||||
onCalendarVisibleChanged: {
|
// margins
|
||||||
if (calendarVisible) {
|
let widgetHeight = 160
|
||||||
internalVisible = true;
|
widgetHeight += 140 + Theme.spacingM
|
||||||
Qt.callLater(() => {
|
let calendarHeight = 300
|
||||||
internalVisible = true;
|
let mainRowHeight = Math.max(widgetHeight, calendarHeight)
|
||||||
calendarGrid.loadEventsForMonth();
|
contentHeight += mainRowHeight + Theme.spacingM
|
||||||
});
|
if (CalendarService && CalendarService.khalAvailable) {
|
||||||
} else {
|
let hasEvents = events.selectedDateEvents
|
||||||
internalVisible = false;
|
&& events.selectedDateEvents.length > 0
|
||||||
}
|
let eventsHeight = hasEvents ? Math.min(
|
||||||
|
300,
|
||||||
|
80 + events.selectedDateEvents.length * 60) : 120
|
||||||
|
contentHeight += eventsHeight
|
||||||
|
} else {
|
||||||
|
contentHeight -= Theme.spacingM
|
||||||
|
}
|
||||||
|
return Math.min(contentHeight, parent.height * 0.9)
|
||||||
}
|
}
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible && calendarGrid)
|
|
||||||
calendarGrid.loadEventsForMonth();
|
|
||||||
|
|
||||||
|
readonly property real calculatedX: {
|
||||||
|
var screenWidth = root.screen ? root.screen.width : Screen.width
|
||||||
|
if (root.triggerSection === "center") {
|
||||||
|
return (screenWidth - targetWidth) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
var centerX = root.triggerX + (root.triggerWidth / 2) - (targetWidth / 2)
|
||||||
|
|
||||||
|
if (centerX >= Theme.spacingM
|
||||||
|
&& centerX + targetWidth <= screenWidth - Theme.spacingM) {
|
||||||
|
return centerX
|
||||||
|
}
|
||||||
|
|
||||||
|
if (centerX < Theme.spacingM) {
|
||||||
|
return Theme.spacingM
|
||||||
|
}
|
||||||
|
|
||||||
|
if (centerX + targetWidth > screenWidth - Theme.spacingM) {
|
||||||
|
return screenWidth - targetWidth - Theme.spacingM
|
||||||
|
}
|
||||||
|
|
||||||
|
return centerX
|
||||||
}
|
}
|
||||||
implicitWidth: 480
|
|
||||||
implicitHeight: 600
|
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: calendarVisible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
anchors {
|
width: targetWidth
|
||||||
top: true
|
height: calculateHeight()
|
||||||
left: true
|
color: Theme.surfaceContainer
|
||||||
right: true
|
radius: Theme.cornerRadiusLarge
|
||||||
bottom: true
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
layer.enabled: true
|
||||||
|
opacity: calendarVisible ? 1 : 0
|
||||||
|
scale: calendarVisible ? 1 : 0.9
|
||||||
|
x: calculatedX
|
||||||
|
y: root.triggerY
|
||||||
|
onOpacityChanged: {
|
||||||
|
if (opacity === 1)
|
||||||
|
Qt.callLater(() => {
|
||||||
|
height = calculateHeight()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onEventsByDateChanged() {
|
||||||
|
if (mainContainer.opacity === 1)
|
||||||
|
mainContainer.height = mainContainer.calculateHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKhalAvailableChanged() {
|
||||||
|
if (mainContainer.opacity === 1)
|
||||||
|
mainContainer.height = mainContainer.calculateHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
target: CalendarService
|
||||||
|
enabled: CalendarService !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onSelectedDateEventsChanged() {
|
||||||
|
if (mainContainer.opacity === 1)
|
||||||
|
mainContainer.height = mainContainer.calculateHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
target: events
|
||||||
|
enabled: events !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: mainContainer
|
anchors.fill: parent
|
||||||
|
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g,
|
||||||
|
Theme.surfaceTint.b, 0.04)
|
||||||
|
radius: parent.radius
|
||||||
|
|
||||||
readonly property real targetWidth: Math.min((root.screen ? root.screen.width : Screen.width) * 0.9, 600)
|
SequentialAnimation on opacity {
|
||||||
|
running: calendarVisible
|
||||||
|
loops: Animation.Infinite
|
||||||
|
|
||||||
function calculateWidth() {
|
NumberAnimation {
|
||||||
let baseWidth = 320;
|
to: 0.08
|
||||||
if (leftWidgets.hasAnyWidgets)
|
duration: Theme.extraLongDuration
|
||||||
return Math.min(parent.width * 0.9, 600);
|
easing.type: Theme.standardEasing
|
||||||
|
|
||||||
return Math.min(parent.width * 0.7, 400);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateHeight() {
|
NumberAnimation {
|
||||||
let contentHeight = Theme.spacingM * 2; // margins
|
to: 0.02
|
||||||
let widgetHeight = 160;
|
duration: Theme.extraLongDuration
|
||||||
widgetHeight += 140 + Theme.spacingM;
|
easing.type: Theme.standardEasing
|
||||||
let calendarHeight = 300;
|
|
||||||
let mainRowHeight = Math.max(widgetHeight, calendarHeight);
|
|
||||||
contentHeight += mainRowHeight + Theme.spacingM;
|
|
||||||
if (CalendarService && CalendarService.khalAvailable) {
|
|
||||||
let hasEvents = events.selectedDateEvents && events.selectedDateEvents.length > 0;
|
|
||||||
let eventsHeight = hasEvents ? Math.min(300, 80 + events.selectedDateEvents.length * 60) : 120;
|
|
||||||
contentHeight += eventsHeight;
|
|
||||||
} else {
|
|
||||||
contentHeight -= Theme.spacingM;
|
|
||||||
}
|
|
||||||
return Math.min(contentHeight, parent.height * 0.9);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
readonly property real calculatedX: {
|
Column {
|
||||||
var screenWidth = root.screen ? root.screen.width : Screen.width;
|
anchors.fill: parent
|
||||||
if (root.triggerSection === "center") {
|
anchors.margins: Theme.spacingM
|
||||||
return (screenWidth - targetWidth) / 2;
|
spacing: Theme.spacingM
|
||||||
}
|
focus: true
|
||||||
|
Keys.onPressed: function (event) {
|
||||||
var centerX = root.triggerX + (root.triggerWidth / 2) - (targetWidth / 2);
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
calendarVisible = false
|
||||||
if (centerX >= Theme.spacingM && centerX + targetWidth <= screenWidth - Theme.spacingM) {
|
event.accepted = true
|
||||||
return centerX;
|
} else {
|
||||||
}
|
// Don't handle other keys - let them bubble up to modals
|
||||||
|
event.accepted = false
|
||||||
if (centerX < Theme.spacingM) {
|
|
||||||
return Theme.spacingM;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (centerX + targetWidth > screenWidth - Theme.spacingM) {
|
|
||||||
return screenWidth - targetWidth - Theme.spacingM;
|
|
||||||
}
|
|
||||||
|
|
||||||
return centerX;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
width: targetWidth
|
Row {
|
||||||
height: calculateHeight()
|
width: parent.width
|
||||||
color: Theme.surfaceContainer
|
height: {
|
||||||
radius: Theme.cornerRadiusLarge
|
let widgetHeight = 160
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
// Media widget
|
||||||
border.width: 1
|
widgetHeight += 140 + Theme.spacingM // Weather/SystemInfo widget with spacing
|
||||||
layer.enabled: true
|
let calendarHeight = 300
|
||||||
opacity: calendarVisible ? 1 : 0
|
// Calendar
|
||||||
scale: calendarVisible ? 1 : 0.9
|
return Math.max(widgetHeight, calendarHeight)
|
||||||
x: calculatedX
|
|
||||||
y: root.triggerY
|
|
||||||
onOpacityChanged: {
|
|
||||||
if (opacity === 1)
|
|
||||||
Qt.callLater(() => {
|
|
||||||
height = calculateHeight();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Connections {
|
Column {
|
||||||
function onEventsByDateChanged() {
|
id: leftWidgets
|
||||||
if (mainContainer.opacity === 1)
|
|
||||||
mainContainer.height = mainContainer.calculateHeight();
|
|
||||||
|
|
||||||
}
|
property bool hasAnyWidgets: true
|
||||||
|
|
||||||
function onKhalAvailableChanged() {
|
width: hasAnyWidgets ? parent.width * 0.42 : 0 // Slightly narrower for better proportions
|
||||||
if (mainContainer.opacity === 1)
|
height: childrenRect.height
|
||||||
mainContainer.height = mainContainer.calculateHeight();
|
spacing: Theme.spacingM
|
||||||
|
visible: hasAnyWidgets
|
||||||
|
anchors.top: parent.top
|
||||||
|
|
||||||
}
|
MediaPlayer {
|
||||||
|
width: parent.width
|
||||||
|
height: 160
|
||||||
|
}
|
||||||
|
|
||||||
target: CalendarService
|
Weather {
|
||||||
enabled: CalendarService !== null
|
width: parent.width
|
||||||
}
|
height: 140
|
||||||
|
visible: SettingsData.weatherEnabled
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
SystemInfo {
|
||||||
function onSelectedDateEventsChanged() {
|
width: parent.width
|
||||||
if (mainContainer.opacity === 1)
|
height: 140
|
||||||
mainContainer.height = mainContainer.calculateHeight();
|
visible: !SettingsData.weatherEnabled
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
target: events
|
|
||||||
enabled: events !== null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
width: leftWidgets.hasAnyWidgets ? parent.width - leftWidgets.width
|
||||||
|
- Theme.spacingM : parent.width
|
||||||
|
height: parent.height
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.2)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
CalendarGrid {
|
||||||
|
id: calendarGrid
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
|
anchors.margins: Theme.spacingS
|
||||||
radius: parent.radius
|
}
|
||||||
|
|
||||||
SequentialAnimation on opacity {
|
|
||||||
running: calendarVisible
|
|
||||||
loops: Animation.Infinite
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
to: 0.08
|
|
||||||
duration: Theme.extraLongDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
to: 0.02
|
|
||||||
duration: Theme.extraLongDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Events {
|
||||||
anchors.fill: parent
|
id: events
|
||||||
anchors.margins: Theme.spacingM
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
focus: true
|
|
||||||
Keys.onPressed: function(event) {
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
|
||||||
calendarVisible = false;
|
|
||||||
event.accepted = true;
|
|
||||||
} else {
|
|
||||||
// Don't handle other keys - let them bubble up to modals
|
|
||||||
event.accepted = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
height: {
|
|
||||||
let widgetHeight = 160; // Media widget
|
|
||||||
widgetHeight += 140 + Theme.spacingM; // Weather/SystemInfo widget with spacing
|
|
||||||
let calendarHeight = 300; // Calendar
|
|
||||||
return Math.max(widgetHeight, calendarHeight);
|
|
||||||
}
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: leftWidgets
|
|
||||||
|
|
||||||
property bool hasAnyWidgets: true
|
|
||||||
|
|
||||||
width: hasAnyWidgets ? parent.width * 0.42 : 0 // Slightly narrower for better proportions
|
|
||||||
height: childrenRect.height
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
visible: hasAnyWidgets
|
|
||||||
anchors.top: parent.top
|
|
||||||
|
|
||||||
MediaPlayer {
|
|
||||||
width: parent.width
|
|
||||||
height: 160
|
|
||||||
}
|
|
||||||
|
|
||||||
Weather {
|
|
||||||
width: parent.width
|
|
||||||
height: 140
|
|
||||||
visible: SettingsData.weatherEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemInfo {
|
|
||||||
width: parent.width
|
|
||||||
height: 140
|
|
||||||
visible: !SettingsData.weatherEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: leftWidgets.hasAnyWidgets ? parent.width - leftWidgets.width - Theme.spacingM : parent.width
|
|
||||||
height: parent.height
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
CalendarGrid {
|
|
||||||
id: calendarGrid
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Events {
|
|
||||||
id: events
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
selectedDate: calendarGrid.selectedDate
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Anims.durMed
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.emphasized
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Anims.durMed
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.emphasized
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowHorizontalOffset: 0
|
|
||||||
shadowVerticalOffset: 4
|
|
||||||
shadowBlur: 0.5
|
|
||||||
shadowColor: Qt.rgba(0, 0, 0, 0.15)
|
|
||||||
shadowOpacity: 0.15
|
|
||||||
}
|
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
selectedDate: calendarGrid.selectedDate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
Behavior on opacity {
|
||||||
anchors.fill: parent
|
NumberAnimation {
|
||||||
z: -1
|
duration: Anims.durMed
|
||||||
enabled: calendarVisible
|
easing.type: Easing.BezierSpline
|
||||||
onClicked: function(mouse) {
|
easing.bezierCurve: Anims.emphasized
|
||||||
var localPos = mapToItem(mainContainer, mouse.x, mouse.y);
|
}
|
||||||
if (localPos.x < 0 || localPos.x > mainContainer.width || localPos.y < 0 || localPos.y > mainContainer.height)
|
|
||||||
calendarVisible = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Anims.durMed
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.emphasized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
shadowEnabled: true
|
||||||
|
shadowHorizontalOffset: 0
|
||||||
|
shadowVerticalOffset: 4
|
||||||
|
shadowBlur: 0.5
|
||||||
|
shadowColor: Qt.rgba(0, 0, 0, 0.15)
|
||||||
|
shadowOpacity: 0.15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
z: -1
|
||||||
|
enabled: calendarVisible
|
||||||
|
onClicked: function (mouse) {
|
||||||
|
var localPos = mapToItem(mainContainer, mouse.x, mouse.y)
|
||||||
|
if (localPos.x < 0 || localPos.x > mainContainer.width || localPos.y < 0
|
||||||
|
|| localPos.y > mainContainer.height)
|
||||||
|
calendarVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,330 +6,340 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: events
|
id: events
|
||||||
|
|
||||||
property date selectedDate: new Date()
|
property date selectedDate: new Date()
|
||||||
property var selectedDateEvents: []
|
property var selectedDateEvents: []
|
||||||
property bool hasEvents: selectedDateEvents && selectedDateEvents.length > 0
|
property bool hasEvents: selectedDateEvents && selectedDateEvents.length > 0
|
||||||
property bool shouldShow: CalendarService && CalendarService.khalAvailable
|
property bool shouldShow: CalendarService && CalendarService.khalAvailable
|
||||||
|
|
||||||
function updateSelectedDateEvents() {
|
function updateSelectedDateEvents() {
|
||||||
if (CalendarService && CalendarService.khalAvailable) {
|
if (CalendarService && CalendarService.khalAvailable) {
|
||||||
let events = CalendarService.getEventsForDate(selectedDate);
|
let events = CalendarService.getEventsForDate(selectedDate)
|
||||||
selectedDateEvents = events;
|
selectedDateEvents = events
|
||||||
} else {
|
} else {
|
||||||
selectedDateEvents = [];
|
selectedDateEvents = []
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectedDateEventsChanged: {
|
||||||
|
eventsList.model = selectedDateEvents
|
||||||
|
}
|
||||||
|
width: parent.width
|
||||||
|
height: shouldShow ? (hasEvents ? Math.min(
|
||||||
|
300,
|
||||||
|
80 + selectedDateEvents.length * 60) : 120) : 0
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.12)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
visible: shouldShow
|
||||||
|
layer.enabled: true
|
||||||
|
Component.onCompleted: {
|
||||||
|
updateSelectedDateEvents()
|
||||||
|
}
|
||||||
|
onSelectedDateChanged: {
|
||||||
|
updateSelectedDateEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onEventsByDateChanged() {
|
||||||
|
updateSelectedDateEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelectedDateEventsChanged: {
|
function onKhalAvailableChanged() {
|
||||||
eventsList.model = selectedDateEvents;
|
updateSelectedDateEvents()
|
||||||
}
|
|
||||||
width: parent.width
|
|
||||||
height: shouldShow ? (hasEvents ? Math.min(300, 80 + selectedDateEvents.length * 60) : 120) : 0
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
visible: shouldShow
|
|
||||||
layer.enabled: true
|
|
||||||
Component.onCompleted: {
|
|
||||||
updateSelectedDateEvents();
|
|
||||||
}
|
|
||||||
onSelectedDateChanged: {
|
|
||||||
updateSelectedDateEvents();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
target: CalendarService
|
||||||
function onEventsByDateChanged() {
|
enabled: CalendarService !== null
|
||||||
updateSelectedDateEvents();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function onKhalAvailableChanged() {
|
Row {
|
||||||
updateSelectedDateEvents();
|
id: headerRow
|
||||||
}
|
|
||||||
|
|
||||||
target: CalendarService
|
anchors.top: parent.top
|
||||||
enabled: CalendarService !== null
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "event"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
StyledText {
|
||||||
id: headerRow
|
text: hasEvents ? (Qt.formatDate(
|
||||||
|
selectedDate,
|
||||||
|
"MMM d") + " • " + (selectedDateEvents.length
|
||||||
|
=== 1 ? "1 event" : selectedDateEvents.length
|
||||||
|
+ " events")) : Qt.formatDate(
|
||||||
|
selectedDate, "MMM d")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: !hasEvents
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "event_busy"
|
||||||
|
size: Theme.iconSize + 8
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.3)
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "No events"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.5)
|
||||||
|
font.weight: Font.Normal
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankListView {
|
||||||
|
id: eventsList
|
||||||
|
|
||||||
|
anchors.top: headerRow.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
anchors.topMargin: Theme.spacingM
|
||||||
|
visible: opacity > 0
|
||||||
|
opacity: hasEvents ? 1 : 0
|
||||||
|
clip: true
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
||||||
|
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
||||||
|
interactive: true
|
||||||
|
flickDeceleration: 1500
|
||||||
|
maximumFlickVelocity: 2000
|
||||||
|
boundsMovement: Flickable.FollowBoundsBehavior
|
||||||
|
pressDelay: 0
|
||||||
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
|
||||||
|
// Custom wheel handler for Qt 6.9+ responsive mouse wheel scrolling
|
||||||
|
WheelHandler {
|
||||||
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||||
|
property real momentum: 0
|
||||||
|
onWheel: event => {
|
||||||
|
if (event.pixelDelta.y !== 0) {
|
||||||
|
// Touchpad with pixel delta
|
||||||
|
momentum = event.pixelDelta.y * 1.8
|
||||||
|
} else {
|
||||||
|
// Mouse wheel with angle delta
|
||||||
|
momentum = (event.angleDelta.y / 120) * (60 * 2.5) // ~2.5 items per wheel step
|
||||||
|
}
|
||||||
|
|
||||||
|
let newY = parent.contentY - momentum
|
||||||
|
newY = Math.max(0,
|
||||||
|
Math.min(parent.contentHeight - parent.height,
|
||||||
|
newY))
|
||||||
|
parent.contentY = newY
|
||||||
|
momentum *= 0.92 // Decay for smooth momentum
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
policy: eventsList.contentHeight
|
||||||
|
> eventsList.height ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: eventsList.width
|
||||||
|
height: eventContent.implicitHeight + Theme.spacingM
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
if (modelData.url && eventMouseArea.containsMouse)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||||
|
Theme.primary.b, 0.12)
|
||||||
|
else if (eventMouseArea.containsMouse)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||||
|
Theme.primary.b, 0.06)
|
||||||
|
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.06)
|
||||||
|
}
|
||||||
|
border.color: {
|
||||||
|
if (modelData.url && eventMouseArea.containsMouse)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||||
|
else if (eventMouseArea.containsMouse)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||||
|
Theme.primary.b, 0.15)
|
||||||
|
return "transparent"
|
||||||
|
}
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 4
|
||||||
|
height: parent.height - 8
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 4
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
radius: 2
|
||||||
|
color: Theme.primary
|
||||||
|
opacity: 0.8
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: eventContent
|
||||||
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.margins: Theme.spacingL
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingS
|
anchors.leftMargin: Theme.spacingL + 4
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
DankIcon {
|
spacing: 6
|
||||||
name: "event"
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: hasEvents ? (Qt.formatDate(selectedDate, "MMM d") + " • " + (selectedDateEvents.length === 1 ? "1 event" : selectedDateEvents.length + " events")) : Qt.formatDate(selectedDate, "MMM d")
|
width: parent.width
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
text: modelData.title
|
||||||
color: Theme.surfaceText
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
font.weight: Font.Medium
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
maximumLineCount: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: Math.max(timeRow.height, locationRow.height)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: timeRow
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
anchors.left: parent.left
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
|
||||||
|
|
||||||
}
|
DankIcon {
|
||||||
|
name: "schedule"
|
||||||
|
size: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
StyledText {
|
||||||
anchors.centerIn: parent
|
text: {
|
||||||
spacing: Theme.spacingXS
|
if (modelData.allDay) {
|
||||||
visible: !hasEvents
|
return "All day"
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "event_busy"
|
|
||||||
size: Theme.iconSize + 8
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "No events"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
|
||||||
font.weight: Font.Normal
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankListView {
|
|
||||||
id: eventsList
|
|
||||||
|
|
||||||
anchors.top: headerRow.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.margins: Theme.spacingL
|
|
||||||
anchors.topMargin: Theme.spacingM
|
|
||||||
visible: opacity > 0
|
|
||||||
opacity: hasEvents ? 1 : 0
|
|
||||||
clip: true
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
|
||||||
interactive: true
|
|
||||||
flickDeceleration: 1500
|
|
||||||
maximumFlickVelocity: 2000
|
|
||||||
boundsMovement: Flickable.FollowBoundsBehavior
|
|
||||||
pressDelay: 0
|
|
||||||
flickableDirection: Flickable.VerticalFlick
|
|
||||||
|
|
||||||
// Custom wheel handler for Qt 6.9+ responsive mouse wheel scrolling
|
|
||||||
WheelHandler {
|
|
||||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
|
||||||
property real momentum: 0
|
|
||||||
onWheel: (event) => {
|
|
||||||
if (event.pixelDelta.y !== 0) {
|
|
||||||
// Touchpad with pixel delta
|
|
||||||
momentum = event.pixelDelta.y * 1.8
|
|
||||||
} else {
|
} else {
|
||||||
// Mouse wheel with angle delta
|
let timeFormat = SettingsData.use24HourClock ? "H:mm" : "h:mm AP"
|
||||||
momentum = (event.angleDelta.y / 120) * (60 * 2.5) // ~2.5 items per wheel step
|
let startTime = Qt.formatTime(modelData.start, timeFormat)
|
||||||
}
|
if (modelData.start.toDateString(
|
||||||
|
) !== modelData.end.toDateString()
|
||||||
|
|| modelData.start.getTime() !== modelData.end.getTime())
|
||||||
|
return startTime + " – " + Qt.formatTime(modelData.end,
|
||||||
|
timeFormat)
|
||||||
|
|
||||||
let newY = parent.contentY - momentum
|
return startTime
|
||||||
newY = Math.max(0, Math.min(parent.contentHeight - parent.height, newY))
|
}
|
||||||
parent.contentY = newY
|
}
|
||||||
momentum *= 0.92 // Decay for smooth momentum
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
event.accepted = true
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
font.weight: Font.Normal
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: locationRow
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: modelData.location !== ""
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "location_on"
|
||||||
|
size: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.location
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
elide: Text.ElideRight
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
maximumLineCount: 1
|
||||||
|
width: Math.min(implicitWidth, 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
MouseArea {
|
||||||
policy: eventsList.contentHeight > eventsList.height ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff
|
id: eventMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: modelData.url ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
enabled: modelData.url !== ""
|
||||||
|
onClicked: {
|
||||||
|
if (modelData.url && modelData.url !== "") {
|
||||||
|
if (Qt.openUrlExternally(modelData.url) === false)
|
||||||
|
console.warn("Failed to open URL: " + modelData.url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on color {
|
||||||
NumberAnimation {
|
ColorAnimation {
|
||||||
duration: Theme.mediumDuration
|
duration: Theme.shortDuration
|
||||||
easing.type: Theme.emphasizedEasing
|
easing.type: Theme.standardEasing
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delegate: Rectangle {
|
Behavior on border.color {
|
||||||
width: eventsList.width
|
ColorAnimation {
|
||||||
height: eventContent.implicitHeight + Theme.spacingM
|
duration: Theme.shortDuration
|
||||||
radius: Theme.cornerRadius
|
easing.type: Theme.standardEasing
|
||||||
color: {
|
|
||||||
if (modelData.url && eventMouseArea.containsMouse)
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
|
||||||
else if (eventMouseArea.containsMouse)
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06);
|
|
||||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.06);
|
|
||||||
}
|
|
||||||
border.color: {
|
|
||||||
if (modelData.url && eventMouseArea.containsMouse)
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
|
|
||||||
else if (eventMouseArea.containsMouse)
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15);
|
|
||||||
return "transparent";
|
|
||||||
}
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 4
|
|
||||||
height: parent.height - 8
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 4
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
radius: 2
|
|
||||||
color: Theme.primary
|
|
||||||
opacity: 0.8
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: eventContent
|
|
||||||
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.leftMargin: Theme.spacingL + 4
|
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
width: parent.width
|
|
||||||
text: modelData.title
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
maximumLineCount: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: Math.max(timeRow.height, locationRow.height)
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: timeRow
|
|
||||||
|
|
||||||
spacing: 4
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "schedule"
|
|
||||||
size: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (modelData.allDay) {
|
|
||||||
return "All day";
|
|
||||||
} else {
|
|
||||||
let timeFormat = SettingsData.use24HourClock ? "H:mm" : "h:mm AP";
|
|
||||||
let startTime = Qt.formatTime(modelData.start, timeFormat);
|
|
||||||
if (modelData.start.toDateString() !== modelData.end.toDateString() || modelData.start.getTime() !== modelData.end.getTime())
|
|
||||||
return startTime + " – " + Qt.formatTime(modelData.end, timeFormat);
|
|
||||||
|
|
||||||
return startTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
font.weight: Font.Normal
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: locationRow
|
|
||||||
|
|
||||||
spacing: 4
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: modelData.location !== ""
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "location_on"
|
|
||||||
size: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.location
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
elide: Text.ElideRight
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
maximumLineCount: 1
|
|
||||||
width: Math.min(implicitWidth, 200)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: eventMouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: modelData.url ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
enabled: modelData.url !== ""
|
|
||||||
onClicked: {
|
|
||||||
if (modelData.url && modelData.url !== "") {
|
|
||||||
if (Qt.openUrlExternally(modelData.url) === false)
|
|
||||||
console.warn("Failed to open URL: " + modelData.url);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on border.color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
layer.effect: MultiEffect {
|
||||||
shadowEnabled: true
|
shadowEnabled: true
|
||||||
shadowHorizontalOffset: 0
|
shadowHorizontalOffset: 0
|
||||||
shadowVerticalOffset: 2
|
shadowVerticalOffset: 2
|
||||||
shadowBlur: 0.25
|
shadowBlur: 0.25
|
||||||
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
||||||
shadowOpacity: 0.1
|
shadowOpacity: 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,435 +8,445 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: mediaPlayer
|
id: mediaPlayer
|
||||||
|
|
||||||
property MprisPlayer activePlayer: MprisController.activePlayer
|
property MprisPlayer activePlayer: MprisController.activePlayer
|
||||||
property string lastValidTitle: ""
|
property string lastValidTitle: ""
|
||||||
property string lastValidArtist: ""
|
property string lastValidArtist: ""
|
||||||
property string lastValidAlbum: ""
|
property string lastValidAlbum: ""
|
||||||
property string lastValidArtUrl: ""
|
property string lastValidArtUrl: ""
|
||||||
property real currentPosition: 0
|
property real currentPosition: 0
|
||||||
|
|
||||||
function ratio() {
|
function ratio() {
|
||||||
return activePlayer && activePlayer.length > 0 ? currentPosition / activePlayer.length : 0;
|
return activePlayer
|
||||||
|
&& activePlayer.length > 0 ? currentPosition / activePlayer.length : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
onActivePlayerChanged: {
|
||||||
|
if (!activePlayer)
|
||||||
|
updateTimer.start()
|
||||||
|
else
|
||||||
|
updateTimer.stop()
|
||||||
|
}
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||||
|
Theme.surfaceContainer.b, 0.4)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
layer.enabled: true
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: updateTimer
|
||||||
|
|
||||||
|
interval: 2000
|
||||||
|
running: {
|
||||||
|
return (!activePlayer)
|
||||||
|
|| (activePlayer
|
||||||
|
&& activePlayer.playbackState === MprisPlaybackState.Playing
|
||||||
|
&& activePlayer.length > 0 && !progressMouseArea.isSeeking)
|
||||||
|
}
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
if (!activePlayer) {
|
||||||
|
lastValidTitle = ""
|
||||||
|
lastValidArtist = ""
|
||||||
|
lastValidAlbum = ""
|
||||||
|
lastValidArtUrl = ""
|
||||||
|
stop() // Stop after clearing cache
|
||||||
|
} else if (activePlayer.playbackState === MprisPlaybackState.Playing
|
||||||
|
&& !progressMouseArea.isSeeking) {
|
||||||
|
currentPosition = activePlayer.position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onPositionChanged() {
|
||||||
|
if (!progressMouseArea.isSeeking)
|
||||||
|
currentPosition = activePlayer.position
|
||||||
}
|
}
|
||||||
|
|
||||||
onActivePlayerChanged: {
|
function onPostTrackChanged() {
|
||||||
if (!activePlayer)
|
currentPosition = activePlayer && activePlayer.position || 0
|
||||||
updateTimer.start();
|
|
||||||
else
|
|
||||||
updateTimer.stop();
|
|
||||||
}
|
}
|
||||||
width: parent.width
|
|
||||||
height: parent.height
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
layer.enabled: true
|
|
||||||
|
|
||||||
Timer {
|
function onTrackTitleChanged() {
|
||||||
id: updateTimer
|
currentPosition = activePlayer && activePlayer.position || 0
|
||||||
|
}
|
||||||
|
|
||||||
interval: 2000
|
target: activePlayer
|
||||||
running: {
|
}
|
||||||
return (!activePlayer) || (activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing && activePlayer.length > 0 && !progressMouseArea.isSeeking);
|
|
||||||
}
|
Item {
|
||||||
repeat: true
|
anchors.fill: parent
|
||||||
onTriggered: {
|
anchors.margins: Theme.spacingS
|
||||||
if (!activePlayer) {
|
|
||||||
lastValidTitle = "";
|
Column {
|
||||||
lastValidArtist = "";
|
anchors.centerIn: parent
|
||||||
lastValidAlbum = "";
|
spacing: Theme.spacingS
|
||||||
lastValidArtUrl = "";
|
visible: (!activePlayer && !lastValidTitle)
|
||||||
stop(); // Stop after clearing cache
|
|| (activePlayer && activePlayer.trackTitle === ""
|
||||||
} else if (activePlayer.playbackState === MprisPlaybackState.Playing && !progressMouseArea.isSeeking) {
|
&& lastValidTitle === "")
|
||||||
currentPosition = activePlayer.position;
|
|
||||||
|
DankIcon {
|
||||||
|
name: "music_note"
|
||||||
|
size: Theme.iconSize + 8
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.5)
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "No Media Playing"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: activePlayer && activePlayer.trackTitle !== ""
|
||||||
|
|| lastValidTitle !== ""
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
height: 60
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 60
|
||||||
|
height: 60
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.3)
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: albumArt
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
source: activePlayer && activePlayer.trackArtUrl
|
||||||
|
|| lastValidArtUrl || ""
|
||||||
|
onSourceChanged: {
|
||||||
|
if (activePlayer && activePlayer.trackArtUrl)
|
||||||
|
lastValidArtUrl = activePlayer.trackArtUrl
|
||||||
|
}
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
smooth: true
|
||||||
|
cache: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: albumArt.status !== Image.Ready
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "album"
|
||||||
|
size: 28
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onPositionChanged() {
|
|
||||||
if (!progressMouseArea.isSeeking)
|
|
||||||
currentPosition = activePlayer.position;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPostTrackChanged() {
|
|
||||||
currentPosition = activePlayer && activePlayer.position || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onTrackTitleChanged() {
|
|
||||||
currentPosition = activePlayer && activePlayer.position || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
target: activePlayer
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.centerIn: parent
|
width: parent.width - 60 - Theme.spacingM
|
||||||
spacing: Theme.spacingS
|
height: parent.height
|
||||||
visible: (!activePlayer && !lastValidTitle) || (activePlayer && activePlayer.trackTitle === "" && lastValidTitle === "")
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: activePlayer && activePlayer.trackTitle || lastValidTitle
|
||||||
|
|| "Unknown Track"
|
||||||
|
onTextChanged: {
|
||||||
|
if (activePlayer && activePlayer.trackTitle)
|
||||||
|
lastValidTitle = activePlayer.trackTitle
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: activePlayer && activePlayer.trackArtist || lastValidArtist
|
||||||
|
|| "Unknown Artist"
|
||||||
|
onTextChanged: {
|
||||||
|
if (activePlayer && activePlayer.trackArtist)
|
||||||
|
lastValidArtist = activePlayer.trackArtist
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.8)
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: activePlayer && activePlayer.trackAlbum
|
||||||
|
|| lastValidAlbum || ""
|
||||||
|
onTextChanged: {
|
||||||
|
if (activePlayer && activePlayer.trackAlbum)
|
||||||
|
lastValidAlbum = activePlayer.trackAlbum
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.6)
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
maximumLineCount: 1
|
||||||
|
visible: text.length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: progressBarContainer
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: 24
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: progressBarBackground
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: 6
|
||||||
|
radius: 3
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.3)
|
||||||
|
visible: activePlayer !== null
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: progressFill
|
||||||
|
|
||||||
|
height: parent.height
|
||||||
|
radius: parent.radius
|
||||||
|
color: Theme.primary
|
||||||
|
width: parent.width * ratio()
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: progressHandle
|
||||||
|
|
||||||
|
width: 12
|
||||||
|
height: 12
|
||||||
|
radius: 6
|
||||||
|
color: Theme.primary
|
||||||
|
border.color: Qt.lighter(Theme.primary, 1.3)
|
||||||
|
border.width: 1
|
||||||
|
x: Math.max(0, Math.min(parent.width - width,
|
||||||
|
progressFill.width - width / 2))
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: activePlayer && activePlayer.length > 0
|
||||||
|
scale: progressMouseArea.containsMouse
|
||||||
|
|| progressMouseArea.pressed ? 1.2 : 1
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: progressMouseArea
|
||||||
|
|
||||||
|
property bool isSeeking: false
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
enabled: activePlayer && activePlayer.length > 0
|
||||||
|
&& activePlayer.canSeek
|
||||||
|
preventStealing: true
|
||||||
|
onPressed: function (mouse) {
|
||||||
|
isSeeking = true
|
||||||
|
if (activePlayer && activePlayer.length > 0) {
|
||||||
|
let ratio = Math.max(0, Math.min(
|
||||||
|
1, mouse.x / progressBarBackground.width))
|
||||||
|
let seekPosition = ratio * activePlayer.length
|
||||||
|
activePlayer.position = seekPosition
|
||||||
|
currentPosition = seekPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onReleased: {
|
||||||
|
isSeeking = false
|
||||||
|
}
|
||||||
|
onPositionChanged: function (mouse) {
|
||||||
|
if (pressed && isSeeking && activePlayer
|
||||||
|
&& activePlayer.length > 0) {
|
||||||
|
let ratio = Math.max(0, Math.min(
|
||||||
|
1, mouse.x / progressBarBackground.width))
|
||||||
|
let seekPosition = ratio * activePlayer.length
|
||||||
|
activePlayer.position = seekPosition
|
||||||
|
currentPosition = seekPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClicked: function (mouse) {
|
||||||
|
if (activePlayer && activePlayer.length > 0) {
|
||||||
|
let ratio = Math.max(0, Math.min(
|
||||||
|
1, mouse.x / progressBarBackground.width))
|
||||||
|
let seekPosition = ratio * activePlayer.length
|
||||||
|
activePlayer.position = seekPosition
|
||||||
|
currentPosition = seekPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: progressGlobalMouseArea
|
||||||
|
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: mediaPlayer.width
|
||||||
|
height: mediaPlayer.height
|
||||||
|
enabled: progressMouseArea.isSeeking
|
||||||
|
visible: false
|
||||||
|
preventStealing: true
|
||||||
|
onPositionChanged: function (mouse) {
|
||||||
|
if (progressMouseArea.isSeeking && activePlayer
|
||||||
|
&& activePlayer.length > 0) {
|
||||||
|
let globalPos = mapToItem(progressBarBackground, mouse.x, mouse.y)
|
||||||
|
let ratio = Math.max(
|
||||||
|
0, Math.min(1, globalPos.x / progressBarBackground.width))
|
||||||
|
let seekPosition = ratio * activePlayer.length
|
||||||
|
activePlayer.position = seekPosition
|
||||||
|
currentPosition = seekPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onReleased: {
|
||||||
|
progressMouseArea.isSeeking = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: 32
|
||||||
|
visible: activePlayer !== null
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
radius: 14
|
||||||
|
color: prevBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r,
|
||||||
|
Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "music_note"
|
anchors.centerIn: parent
|
||||||
size: Theme.iconSize + 8
|
name: "skip_previous"
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
size: 16
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
color: Theme.surfaceText
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
MouseArea {
|
||||||
text: "No Media Playing"
|
id: prevBtnArea
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
anchors.fill: parent
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (!activePlayer)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (currentPosition > 8 && activePlayer.canSeek) {
|
||||||
|
activePlayer.position = 0
|
||||||
|
currentPosition = 0
|
||||||
|
} else {
|
||||||
|
activePlayer.previous()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
radius: 16
|
||||||
|
color: Theme.primary
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: activePlayer && activePlayer.playbackState
|
||||||
|
=== MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
||||||
|
size: 20
|
||||||
|
color: Theme.background
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: activePlayer && activePlayer.togglePlaying()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
radius: 14
|
||||||
|
color: nextBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r,
|
||||||
|
Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "skip_next"
|
||||||
|
size: 16
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: nextBtnArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: activePlayer && activePlayer.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Column {
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
visible: activePlayer && activePlayer.trackTitle !== "" || lastValidTitle !== ""
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
height: 60
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 60
|
|
||||||
height: 60
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Image {
|
|
||||||
id: albumArt
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
source: activePlayer && activePlayer.trackArtUrl || lastValidArtUrl || ""
|
|
||||||
onSourceChanged: {
|
|
||||||
if (activePlayer && activePlayer.trackArtUrl)
|
|
||||||
lastValidArtUrl = activePlayer.trackArtUrl;
|
|
||||||
|
|
||||||
}
|
|
||||||
fillMode: Image.PreserveAspectCrop
|
|
||||||
smooth: true
|
|
||||||
cache: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: albumArt.status !== Image.Ready
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "album"
|
|
||||||
size: 28
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width - 60 - Theme.spacingM
|
|
||||||
height: parent.height
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: activePlayer && activePlayer.trackTitle || lastValidTitle || "Unknown Track"
|
|
||||||
onTextChanged: {
|
|
||||||
if (activePlayer && activePlayer.trackTitle)
|
|
||||||
lastValidTitle = activePlayer.trackTitle;
|
|
||||||
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
maximumLineCount: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: activePlayer && activePlayer.trackArtist || lastValidArtist || "Unknown Artist"
|
|
||||||
onTextChanged: {
|
|
||||||
if (activePlayer && activePlayer.trackArtist)
|
|
||||||
lastValidArtist = activePlayer.trackArtist;
|
|
||||||
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
maximumLineCount: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: activePlayer && activePlayer.trackAlbum || lastValidAlbum || ""
|
|
||||||
onTextChanged: {
|
|
||||||
if (activePlayer && activePlayer.trackAlbum)
|
|
||||||
lastValidAlbum = activePlayer.trackAlbum;
|
|
||||||
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
maximumLineCount: 1
|
|
||||||
visible: text.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: progressBarContainer
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: 24
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: progressBarBackground
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: 6
|
|
||||||
radius: 3
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
|
||||||
visible: activePlayer !== null
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: progressFill
|
|
||||||
|
|
||||||
height: parent.height
|
|
||||||
radius: parent.radius
|
|
||||||
color: Theme.primary
|
|
||||||
width: parent.width * ratio()
|
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 100
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: progressHandle
|
|
||||||
|
|
||||||
width: 12
|
|
||||||
height: 12
|
|
||||||
radius: 6
|
|
||||||
color: Theme.primary
|
|
||||||
border.color: Qt.lighter(Theme.primary, 1.3)
|
|
||||||
border.width: 1
|
|
||||||
x: Math.max(0, Math.min(parent.width - width, progressFill.width - width / 2))
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: activePlayer && activePlayer.length > 0
|
|
||||||
scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 150
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: progressMouseArea
|
|
||||||
|
|
||||||
property bool isSeeking: false
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
enabled: activePlayer && activePlayer.length > 0 && activePlayer.canSeek
|
|
||||||
preventStealing: true
|
|
||||||
onPressed: function(mouse) {
|
|
||||||
isSeeking = true;
|
|
||||||
if (activePlayer && activePlayer.length > 0) {
|
|
||||||
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width));
|
|
||||||
let seekPosition = ratio * activePlayer.length;
|
|
||||||
activePlayer.position = seekPosition;
|
|
||||||
currentPosition = seekPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onReleased: {
|
|
||||||
isSeeking = false;
|
|
||||||
}
|
|
||||||
onPositionChanged: function(mouse) {
|
|
||||||
if (pressed && isSeeking && activePlayer && activePlayer.length > 0) {
|
|
||||||
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width));
|
|
||||||
let seekPosition = ratio * activePlayer.length;
|
|
||||||
activePlayer.position = seekPosition;
|
|
||||||
currentPosition = seekPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onClicked: function(mouse) {
|
|
||||||
if (activePlayer && activePlayer.length > 0) {
|
|
||||||
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width));
|
|
||||||
let seekPosition = ratio * activePlayer.length;
|
|
||||||
activePlayer.position = seekPosition;
|
|
||||||
currentPosition = seekPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: progressGlobalMouseArea
|
|
||||||
|
|
||||||
x: 0
|
|
||||||
y: 0
|
|
||||||
width: mediaPlayer.width
|
|
||||||
height: mediaPlayer.height
|
|
||||||
enabled: progressMouseArea.isSeeking
|
|
||||||
visible: false
|
|
||||||
preventStealing: true
|
|
||||||
onPositionChanged: function(mouse) {
|
|
||||||
if (progressMouseArea.isSeeking && activePlayer && activePlayer.length > 0) {
|
|
||||||
let globalPos = mapToItem(progressBarBackground, mouse.x, mouse.y);
|
|
||||||
let ratio = Math.max(0, Math.min(1, globalPos.x / progressBarBackground.width));
|
|
||||||
let seekPosition = ratio * activePlayer.length;
|
|
||||||
activePlayer.position = seekPosition;
|
|
||||||
currentPosition = seekPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onReleased: {
|
|
||||||
progressMouseArea.isSeeking = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: 32
|
|
||||||
visible: activePlayer !== null
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 28
|
|
||||||
height: 28
|
|
||||||
radius: 14
|
|
||||||
color: prevBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "skip_previous"
|
|
||||||
size: 16
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: prevBtnArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (!activePlayer)
|
|
||||||
return ;
|
|
||||||
|
|
||||||
if (currentPosition > 8 && activePlayer.canSeek) {
|
|
||||||
activePlayer.position = 0;
|
|
||||||
currentPosition = 0;
|
|
||||||
} else {
|
|
||||||
activePlayer.previous();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 32
|
|
||||||
height: 32
|
|
||||||
radius: 16
|
|
||||||
color: Theme.primary
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
|
||||||
size: 20
|
|
||||||
color: Theme.background
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: activePlayer && activePlayer.togglePlaying()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 28
|
|
||||||
height: 28
|
|
||||||
radius: 14
|
|
||||||
color: nextBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "skip_next"
|
|
||||||
size: 16
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: nextBtnArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: activePlayer && activePlayer.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowHorizontalOffset: 0
|
|
||||||
shadowVerticalOffset: 2
|
|
||||||
shadowBlur: 0.5
|
|
||||||
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
|
||||||
shadowOpacity: 0.1
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
shadowEnabled: true
|
||||||
|
shadowHorizontalOffset: 0
|
||||||
|
shadowVerticalOffset: 2
|
||||||
|
shadowBlur: 0.5
|
||||||
|
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
||||||
|
shadowOpacity: 0.1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,133 +4,132 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
radius: Theme.cornerRadiusLarge
|
radius: Theme.cornerRadiusLarge
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4)
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
Theme.surfaceContainer.b, 0.4)
|
||||||
border.width: 1
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
Ref {
|
Ref {
|
||||||
service: SysMonitorService
|
service: SysMonitorService
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
SystemLogo {
|
||||||
|
width: 48
|
||||||
|
height: 48
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - 48 - Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.hostname
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.distribution + " • " + SysMonitorService.architecture
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
width: parent.width
|
||||||
anchors.margins: Theme.spacingM
|
spacing: Theme.spacingXS
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Row {
|
StyledText {
|
||||||
width: parent.width
|
text: "Uptime " + formatUptime(UserInfoService.uptime)
|
||||||
spacing: Theme.spacingM
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
SystemLogo {
|
StyledText {
|
||||||
width: 48
|
text: "Load: " + SysMonitorService.loadAverage
|
||||||
height: 48
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
}
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
StyledText {
|
||||||
width: parent.width - 48 - Theme.spacingM
|
text: SysMonitorService.processCount + " proc, "
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
+ SysMonitorService.threadCount + " threads"
|
||||||
spacing: Theme.spacingXS
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.8)
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StyledText {
|
function formatUptime(uptime) {
|
||||||
text: SysMonitorService.hostname
|
if (!uptime)
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
return "0m"
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
// Parse the uptime string - handle formats like "1 week, 4 days, 3:45" or "4 days, 3:45" or "3:45"
|
||||||
text: SysMonitorService.distribution + " • " + SysMonitorService.architecture
|
var uptimeStr = uptime.toString().trim()
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
// Check for weeks and days - need to add them together
|
||||||
|
var weekMatch = uptimeStr.match(/(\d+)\s+weeks?/)
|
||||||
}
|
var dayMatch = uptimeStr.match(/(\d+)\s+days?/)
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Uptime " + formatUptime(UserInfoService.uptime)
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Load: " + SysMonitorService.loadAverage
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.processCount + " proc, " + SysMonitorService.threadCount + " threads"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (weekMatch) {
|
||||||
|
var weeks = parseInt(weekMatch[1])
|
||||||
|
var totalDays = weeks * 7
|
||||||
|
if (dayMatch) {
|
||||||
|
var days = parseInt(dayMatch[1])
|
||||||
|
totalDays += days
|
||||||
|
}
|
||||||
|
return totalDays + "d"
|
||||||
|
} else if (dayMatch) {
|
||||||
|
var days = parseInt(dayMatch[1])
|
||||||
|
return days + "d"
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatUptime(uptime) {
|
// If it's just hours:minutes, show the largest unit
|
||||||
if (!uptime) return "0m"
|
var timeMatch = uptimeStr.match(/(\d+):(\d+)/)
|
||||||
|
if (timeMatch) {
|
||||||
// Parse the uptime string - handle formats like "1 week, 4 days, 3:45" or "4 days, 3:45" or "3:45"
|
var hours = parseInt(timeMatch[1])
|
||||||
var uptimeStr = uptime.toString().trim()
|
var minutes = parseInt(timeMatch[2])
|
||||||
|
if (hours > 0) {
|
||||||
// Check for weeks and days - need to add them together
|
return hours + "h"
|
||||||
var weekMatch = uptimeStr.match(/(\d+)\s+weeks?/)
|
} else {
|
||||||
var dayMatch = uptimeStr.match(/(\d+)\s+days?/)
|
return minutes + "m"
|
||||||
|
}
|
||||||
if (weekMatch) {
|
|
||||||
var weeks = parseInt(weekMatch[1])
|
|
||||||
var totalDays = weeks * 7
|
|
||||||
if (dayMatch) {
|
|
||||||
var days = parseInt(dayMatch[1])
|
|
||||||
totalDays += days
|
|
||||||
}
|
|
||||||
return totalDays + "d"
|
|
||||||
} else if (dayMatch) {
|
|
||||||
var days = parseInt(dayMatch[1])
|
|
||||||
return days + "d"
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's just hours:minutes, show the largest unit
|
|
||||||
var timeMatch = uptimeStr.match(/(\d+):(\d+)/)
|
|
||||||
if (timeMatch) {
|
|
||||||
var hours = parseInt(timeMatch[1])
|
|
||||||
var minutes = parseInt(timeMatch[2])
|
|
||||||
if (hours > 0) {
|
|
||||||
return hours + "h"
|
|
||||||
} else {
|
|
||||||
return minutes + "m"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback - return as is but truncated
|
|
||||||
return uptimeStr.length > 8 ? uptimeStr.substring(0, 8) + "…" : uptimeStr
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback - return as is but truncated
|
||||||
|
return uptimeStr.length > 8 ? uptimeStr.substring(0, 8) + "…" : uptimeStr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,191 +6,185 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: weather
|
id: weather
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
radius: Theme.cornerRadiusLarge
|
radius: Theme.cornerRadiusLarge
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4)
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
Theme.surfaceContainer.b, 0.4)
|
||||||
border.width: 1
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
layer.enabled: true
|
border.width: 1
|
||||||
|
layer.enabled: true
|
||||||
|
|
||||||
Ref {
|
Ref {
|
||||||
service: WeatherService
|
service: WeatherService
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: !WeatherService.weather.available
|
||||||
|
|| WeatherService.weather.temp === 0
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "cloud_off"
|
||||||
|
size: Theme.iconSize + 8
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.5)
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
StyledText {
|
||||||
|
text: "No Weather Data"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: WeatherService.weather.available
|
||||||
|
&& WeatherService.weather.temp !== 0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: 60
|
||||||
|
|
||||||
|
Row {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingL
|
||||||
visible: !WeatherService.weather.available || WeatherService.weather.temp === 0
|
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "cloud_off"
|
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
||||||
size: Theme.iconSize + 8
|
size: Theme.iconSize + 8
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
color: Theme.primary
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: (SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp)
|
||||||
|
+ "°" + (SettingsData.useFahrenheit ? "F" : "C")
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Light
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (WeatherService.weather.available)
|
||||||
|
SettingsData.setTemperatureUnit(!SettingsData.useFahrenheit)
|
||||||
|
}
|
||||||
|
enabled: WeatherService.weather.available
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: WeatherService.weather.city || ""
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
visible: text.length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Grid {
|
||||||
|
columns: 2
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "humidity_low"
|
||||||
|
size: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "No Weather Data"
|
text: WeatherService.weather.humidity ? WeatherService.weather.humidity + "%" : "--"
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
color: Theme.surfaceText
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "air"
|
||||||
|
size: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
StyledText {
|
||||||
|
text: WeatherService.weather.wind || "--"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Row {
|
||||||
anchors.fill: parent
|
spacing: Theme.spacingXS
|
||||||
anchors.margins: Theme.spacingL
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
visible: WeatherService.weather.available && WeatherService.weather.temp !== 0
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: 60
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingL
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
|
||||||
size: Theme.iconSize + 8
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: (SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + (SettingsData.useFahrenheit ? "F" : "C")
|
|
||||||
font.pixelSize: Theme.fontSizeXLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Light
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (WeatherService.weather.available)
|
|
||||||
SettingsData.setTemperatureUnit(!SettingsData.useFahrenheit);
|
|
||||||
|
|
||||||
}
|
|
||||||
enabled: WeatherService.weather.available
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: WeatherService.weather.city || ""
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
visible: text.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "wb_twilight"
|
||||||
|
size: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Grid {
|
StyledText {
|
||||||
columns: 2
|
text: WeatherService.weather.sunrise || "--"
|
||||||
spacing: Theme.spacingM
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "humidity_low"
|
|
||||||
size: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: WeatherService.weather.humidity ? WeatherService.weather.humidity + "%" : "--"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "air"
|
|
||||||
size: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: WeatherService.weather.wind || "--"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "wb_twilight"
|
|
||||||
size: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: WeatherService.weather.sunrise || "--"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "bedtime"
|
|
||||||
size: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: WeatherService.weather.sunset || "--"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "bedtime"
|
||||||
|
size: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: WeatherService.weather.sunset || "--"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
layer.effect: MultiEffect {
|
||||||
shadowEnabled: true
|
shadowEnabled: true
|
||||||
shadowHorizontalOffset: 0
|
shadowHorizontalOffset: 0
|
||||||
shadowVerticalOffset: 2
|
shadowVerticalOffset: 2
|
||||||
shadowBlur: 0.5
|
shadowBlur: 0.5
|
||||||
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
||||||
shadowOpacity: 0.1
|
shadowOpacity: 0.1
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,142 +9,140 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string currentSinkDisplayName: AudioService.sink ? AudioService.displayName(AudioService.sink) : ""
|
property string currentSinkDisplayName: AudioService.sink ? AudioService.displayName(
|
||||||
|
AudioService.sink) : ""
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Output Device"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
height: 35
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||||
|
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||||
|
Theme.primary.b, 0.3)
|
||||||
|
border.width: 1
|
||||||
|
visible: AudioService.sink !== null
|
||||||
|
|
||||||
StyledText {
|
Row {
|
||||||
text: "Output Device"
|
anchors.left: parent.left
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
anchors.leftMargin: Theme.spacingM
|
||||||
color: Theme.surfaceText
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "check_circle"
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Current: " + (root.currentSinkDisplayName || "None")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.primary
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: {
|
||||||
|
if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values)
|
||||||
|
return []
|
||||||
|
|
||||||
|
let sinks = []
|
||||||
|
for (var i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||||
|
let node = Pipewire.nodes.values[i]
|
||||||
|
if (!node || node.isStream)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ((node.type & PwNodeType.AudioSink) === PwNodeType.AudioSink)
|
||||||
|
sinks.push(node)
|
||||||
|
}
|
||||||
|
return sinks
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 35
|
height: 50
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
color: deviceArea.containsMouse ? Qt.rgba(
|
||||||
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
Theme.primary.r, Theme.primary.g,
|
||||||
border.width: 1
|
Theme.primary.b, 0.08) : (modelData === AudioService.sink ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||||
visible: AudioService.sink !== null
|
border.color: modelData === AudioService.sink ? Theme.primary : "transparent"
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Theme.spacingM
|
anchors.leftMargin: Theme.spacingM
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "check_circle"
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Current: " + (root.currentSinkDisplayName || "None")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.primary
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: {
|
||||||
|
if (modelData.name.includes("bluez"))
|
||||||
|
return "headset"
|
||||||
|
else if (modelData.name.includes("hdmi"))
|
||||||
|
return "tv"
|
||||||
|
else if (modelData.name.includes("usb"))
|
||||||
|
return "headset"
|
||||||
|
else
|
||||||
|
return "speaker"
|
||||||
|
}
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: AudioService.displayName(modelData)
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||||
|
font.weight: modelData === AudioService.sink ? Font.Medium : Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (AudioService.subtitle(modelData.name)
|
||||||
|
&& AudioService.subtitle(modelData.name) !== "")
|
||||||
|
return AudioService.subtitle(
|
||||||
|
modelData.name) + (modelData === AudioService.sink ? " • Selected" : "")
|
||||||
|
else
|
||||||
|
return modelData === AudioService.sink ? "Selected" : ""
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
visible: text !== ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: deviceArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (modelData)
|
||||||
|
Pipewire.preferredDefaultAudioSink = modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Repeater {
|
|
||||||
model: {
|
|
||||||
if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
let sinks = [];
|
|
||||||
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
|
||||||
let node = Pipewire.nodes.values[i];
|
|
||||||
if (!node || node.isStream)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if ((node.type & PwNodeType.AudioSink) === PwNodeType.AudioSink)
|
|
||||||
sinks.push(node);
|
|
||||||
|
|
||||||
}
|
|
||||||
return sinks;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 50
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: deviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData === AudioService.sink ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
|
||||||
border.color: modelData === AudioService.sink ? Theme.primary : "transparent"
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: {
|
|
||||||
if (modelData.name.includes("bluez"))
|
|
||||||
return "headset";
|
|
||||||
else if (modelData.name.includes("hdmi"))
|
|
||||||
return "tv";
|
|
||||||
else if (modelData.name.includes("usb"))
|
|
||||||
return "headset";
|
|
||||||
else
|
|
||||||
return "speaker";
|
|
||||||
}
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
spacing: 2
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: AudioService.displayName(modelData)
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: modelData === AudioService.sink ? Font.Medium : Font.Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (AudioService.subtitle(modelData.name) && AudioService.subtitle(modelData.name) !== "")
|
|
||||||
return AudioService.subtitle(modelData.name) + (modelData === AudioService.sink ? " • Selected" : "");
|
|
||||||
else
|
|
||||||
return modelData === AudioService.sink ? "Selected" : "";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
visible: text !== ""
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: deviceArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (modelData)
|
|
||||||
Pipewire.preferredDefaultAudioSink = modelData;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,140 +9,139 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string currentSourceDisplayName: AudioService.source ? AudioService.displayName(AudioService.source) : ""
|
property string currentSourceDisplayName: AudioService.source ? AudioService.displayName(
|
||||||
|
AudioService.source) : ""
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Input Device"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
height: 35
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||||
|
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||||
|
Theme.primary.b, 0.3)
|
||||||
|
border.width: 1
|
||||||
|
visible: AudioService.source !== null
|
||||||
|
|
||||||
StyledText {
|
Row {
|
||||||
text: "Input Device"
|
anchors.left: parent.left
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
anchors.leftMargin: Theme.spacingM
|
||||||
color: Theme.surfaceText
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "check_circle"
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Current: " + (root.currentSourceDisplayName || "None")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.primary
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: {
|
||||||
|
if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values)
|
||||||
|
return []
|
||||||
|
|
||||||
|
let sources = []
|
||||||
|
for (var i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||||
|
let node = Pipewire.nodes.values[i]
|
||||||
|
if (!node || node.isStream)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ((node.type & PwNodeType.AudioSource) === PwNodeType.AudioSource
|
||||||
|
&& !node.name.includes(".monitor"))
|
||||||
|
sources.push(node)
|
||||||
|
}
|
||||||
|
return sources
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 35
|
height: 50
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
color: sourceArea.containsMouse ? Qt.rgba(
|
||||||
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
Theme.primary.r, Theme.primary.g,
|
||||||
border.width: 1
|
Theme.primary.b, 0.08) : (modelData === AudioService.source ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||||
visible: AudioService.source !== null
|
border.color: modelData === AudioService.source ? Theme.primary : "transparent"
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Theme.spacingM
|
anchors.leftMargin: Theme.spacingM
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "check_circle"
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Current: " + (root.currentSourceDisplayName || "None")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.primary
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: {
|
||||||
|
if (modelData.name.includes("bluez"))
|
||||||
|
return "headset_mic"
|
||||||
|
else if (modelData.name.includes("usb"))
|
||||||
|
return "headset_mic"
|
||||||
|
else
|
||||||
|
return "mic"
|
||||||
|
}
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: AudioService.displayName(modelData)
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
||||||
|
font.weight: modelData === AudioService.source ? Font.Medium : Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (AudioService.subtitle(modelData.name)
|
||||||
|
&& AudioService.subtitle(modelData.name) !== "")
|
||||||
|
return AudioService.subtitle(
|
||||||
|
modelData.name) + (modelData === AudioService.source ? " • Selected" : "")
|
||||||
|
else
|
||||||
|
return modelData === AudioService.source ? "Selected" : ""
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
visible: text !== ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: sourceArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (modelData)
|
||||||
|
Pipewire.preferredDefaultAudioSource = modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Repeater {
|
|
||||||
model: {
|
|
||||||
if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
let sources = [];
|
|
||||||
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
|
||||||
let node = Pipewire.nodes.values[i];
|
|
||||||
if (!node || node.isStream)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if ((node.type & PwNodeType.AudioSource) === PwNodeType.AudioSource && !node.name.includes(".monitor"))
|
|
||||||
sources.push(node);
|
|
||||||
|
|
||||||
}
|
|
||||||
return sources;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 50
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: sourceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData === AudioService.source ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
|
||||||
border.color: modelData === AudioService.source ? Theme.primary : "transparent"
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: {
|
|
||||||
if (modelData.name.includes("bluez"))
|
|
||||||
return "headset_mic";
|
|
||||||
else if (modelData.name.includes("usb"))
|
|
||||||
return "headset_mic";
|
|
||||||
else
|
|
||||||
return "mic";
|
|
||||||
}
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
spacing: 2
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: AudioService.displayName(modelData)
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: modelData === AudioService.source ? Font.Medium : Font.Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (AudioService.subtitle(modelData.name) && AudioService.subtitle(modelData.name) !== "")
|
|
||||||
return AudioService.subtitle(modelData.name) + (modelData === AudioService.source ? " • Selected" : "");
|
|
||||||
else
|
|
||||||
return modelData === AudioService.source ? "Selected" : "";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
visible: text !== ""
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: sourceArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (modelData)
|
|
||||||
Pipewire.preferredDefaultAudioSource = modelData;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,216 +8,222 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property real micLevel: Math.min(100, (AudioService.source && AudioService.source.audio && AudioService.source.audio.volume * 100) || 0)
|
property real micLevel: Math.min(
|
||||||
property bool micMuted: (AudioService.source && AudioService.source.audio && AudioService.source.audio.muted) || false
|
100,
|
||||||
|
(AudioService.source && AudioService.source.audio
|
||||||
|
&& AudioService.source.audio.volume * 100) || 0)
|
||||||
|
property bool micMuted: (AudioService.source && AudioService.source.audio
|
||||||
|
&& AudioService.source.audio.muted) || false
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Microphone Level"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
StyledText {
|
DankIcon {
|
||||||
text: "Microphone Level"
|
name: root.micMuted ? "mic_off" : "mic"
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
size: Theme.iconSize
|
||||||
color: Theme.surfaceText
|
color: root.micMuted ? Theme.error : Theme.surfaceText
|
||||||
font.weight: Font.Medium
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (AudioService.source && AudioService.source.audio)
|
||||||
|
AudioService.source.audio.muted = !AudioService.source.audio.muted
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Item {
|
||||||
|
id: micSliderContainer
|
||||||
|
|
||||||
|
width: parent.width - 80
|
||||||
|
height: 32
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: micSliderTrack
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
height: 8
|
||||||
|
radius: 4
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.3)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
DankIcon {
|
Rectangle {
|
||||||
name: root.micMuted ? "mic_off" : "mic"
|
id: micSliderFill
|
||||||
size: Theme.iconSize
|
|
||||||
color: root.micMuted ? Theme.error : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
MouseArea {
|
width: parent.width * (root.micLevel / 100)
|
||||||
anchors.fill: parent
|
height: parent.height
|
||||||
hoverEnabled: true
|
radius: parent.radius
|
||||||
cursorShape: Qt.PointingHandCursor
|
color: Theme.primary
|
||||||
onClicked: {
|
|
||||||
if (AudioService.source && AudioService.source.audio)
|
|
||||||
AudioService.source.audio.muted = !AudioService.source.audio.muted;
|
|
||||||
|
|
||||||
}
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Anims.durShort
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.standardDecel
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Rectangle {
|
||||||
id: micSliderContainer
|
id: micHandle
|
||||||
|
|
||||||
width: parent.width - 80
|
width: 18
|
||||||
height: 32
|
height: 18
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
radius: 9
|
||||||
|
color: Theme.primary
|
||||||
|
border.color: Qt.lighter(Theme.primary, 1.3)
|
||||||
|
border.width: 2
|
||||||
|
x: Math.max(0, Math.min(parent.width - width,
|
||||||
|
micSliderFill.width - width / 2))
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
scale: micMouseArea.containsMouse || micMouseArea.pressed ? 1.2 : 1
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: micSliderTrack
|
id: micTooltip
|
||||||
|
|
||||||
width: parent.width
|
width: tooltipText.contentWidth + Theme.spacingS * 2
|
||||||
height: 8
|
height: tooltipText.contentHeight + Theme.spacingXS * 2
|
||||||
radius: 4
|
radius: Theme.cornerRadiusSmall
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
color: Theme.surfaceContainer
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
border.color: Theme.outline
|
||||||
|
border.width: 1
|
||||||
|
anchors.bottom: parent.top
|
||||||
|
anchors.bottomMargin: Theme.spacingS
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
visible: (micMouseArea.containsMouse && !root.micMuted)
|
||||||
|
|| micMouseArea.isDragging
|
||||||
|
opacity: visible ? 1 : 0
|
||||||
|
|
||||||
Rectangle {
|
StyledText {
|
||||||
id: micSliderFill
|
id: tooltipText
|
||||||
|
|
||||||
width: parent.width * (root.micLevel / 100)
|
|
||||||
height: parent.height
|
|
||||||
radius: parent.radius
|
|
||||||
color: Theme.primary
|
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Anims.durShort
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.standardDecel
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: micHandle
|
|
||||||
|
|
||||||
width: 18
|
|
||||||
height: 18
|
|
||||||
radius: 9
|
|
||||||
color: Theme.primary
|
|
||||||
border.color: Qt.lighter(Theme.primary, 1.3)
|
|
||||||
border.width: 2
|
|
||||||
x: Math.max(0, Math.min(parent.width - width, micSliderFill.width - width / 2))
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
scale: micMouseArea.containsMouse || micMouseArea.pressed ? 1.2 : 1
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: micTooltip
|
|
||||||
|
|
||||||
width: tooltipText.contentWidth + Theme.spacingS * 2
|
|
||||||
height: tooltipText.contentHeight + Theme.spacingXS * 2
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: Theme.surfaceContainer
|
|
||||||
border.color: Theme.outline
|
|
||||||
border.width: 1
|
|
||||||
anchors.bottom: parent.top
|
|
||||||
anchors.bottomMargin: Theme.spacingS
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
visible: (micMouseArea.containsMouse && !root.micMuted) || micMouseArea.isDragging
|
|
||||||
opacity: visible ? 1 : 0
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: tooltipText
|
|
||||||
|
|
||||||
text: Math.round(root.micLevel) + "%"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Anims.durShort
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.standard
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
text: Math.round(root.micLevel) + "%"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
Behavior on opacity {
|
||||||
id: micMouseArea
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
property bool isDragging: false
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
preventStealing: true
|
|
||||||
onPressed: (mouse) => {
|
|
||||||
isDragging = true;
|
|
||||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
|
||||||
let newMicLevel = Math.round(ratio * 100);
|
|
||||||
if (AudioService.source && AudioService.source.audio) {
|
|
||||||
AudioService.source.audio.muted = false;
|
|
||||||
AudioService.source.audio.volume = newMicLevel / 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onReleased: {
|
|
||||||
isDragging = false;
|
|
||||||
}
|
|
||||||
onPositionChanged: (mouse) => {
|
|
||||||
if (pressed && isDragging) {
|
|
||||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
|
||||||
let newMicLevel = Math.max(0, Math.min(100, Math.round(ratio * 100)));
|
|
||||||
if (AudioService.source && AudioService.source.audio) {
|
|
||||||
AudioService.source.audio.muted = false;
|
|
||||||
AudioService.source.audio.volume = newMicLevel / 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onClicked: (mouse) => {
|
|
||||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
|
||||||
let newMicLevel = Math.round(ratio * 100);
|
|
||||||
if (AudioService.source && AudioService.source.audio) {
|
|
||||||
AudioService.source.audio.muted = false;
|
|
||||||
AudioService.source.audio.volume = newMicLevel / 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
Behavior on scale {
|
||||||
id: micGlobalMouseArea
|
NumberAnimation {
|
||||||
|
duration: Anims.durShort
|
||||||
x: 0
|
easing.type: Easing.BezierSpline
|
||||||
y: 0
|
easing.bezierCurve: Anims.standard
|
||||||
width: root.parent ? root.parent.width : 0
|
|
||||||
height: root.parent ? root.parent.height : 0
|
|
||||||
enabled: micMouseArea.isDragging
|
|
||||||
visible: false
|
|
||||||
preventStealing: true
|
|
||||||
onPositionChanged: (mouse) => {
|
|
||||||
if (micMouseArea.isDragging) {
|
|
||||||
let globalPos = mapToItem(micSliderTrack, mouse.x, mouse.y);
|
|
||||||
let ratio = Math.max(0, Math.min(1, globalPos.x / micSliderTrack.width));
|
|
||||||
let newMicLevel = Math.max(0, Math.min(100, Math.round(ratio * 100)));
|
|
||||||
if (AudioService.source && AudioService.source.audio) {
|
|
||||||
AudioService.source.audio.muted = false;
|
|
||||||
AudioService.source.audio.volume = newMicLevel / 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onReleased: {
|
|
||||||
micMouseArea.isDragging = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
MouseArea {
|
||||||
name: "mic"
|
id: micMouseArea
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.surfaceText
|
property bool isDragging: false
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
preventStealing: true
|
||||||
|
onPressed: mouse => {
|
||||||
|
isDragging = true
|
||||||
|
let ratio = Math.max(0, Math.min(
|
||||||
|
1, mouse.x / micSliderTrack.width))
|
||||||
|
let newMicLevel = Math.round(ratio * 100)
|
||||||
|
if (AudioService.source && AudioService.source.audio) {
|
||||||
|
AudioService.source.audio.muted = false
|
||||||
|
AudioService.source.audio.volume = newMicLevel / 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onReleased: {
|
||||||
|
isDragging = false
|
||||||
}
|
}
|
||||||
|
onPositionChanged: mouse => {
|
||||||
|
if (pressed && isDragging) {
|
||||||
|
let ratio = Math.max(
|
||||||
|
0, Math.min(1, mouse.x / micSliderTrack.width))
|
||||||
|
let newMicLevel = Math.max(
|
||||||
|
0, Math.min(100, Math.round(ratio * 100)))
|
||||||
|
if (AudioService.source
|
||||||
|
&& AudioService.source.audio) {
|
||||||
|
AudioService.source.audio.muted = false
|
||||||
|
AudioService.source.audio.volume = newMicLevel / 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClicked: mouse => {
|
||||||
|
let ratio = Math.max(0, Math.min(
|
||||||
|
1, mouse.x / micSliderTrack.width))
|
||||||
|
let newMicLevel = Math.round(ratio * 100)
|
||||||
|
if (AudioService.source && AudioService.source.audio) {
|
||||||
|
AudioService.source.audio.muted = false
|
||||||
|
AudioService.source.audio.volume = newMicLevel / 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: micGlobalMouseArea
|
||||||
|
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: root.parent ? root.parent.width : 0
|
||||||
|
height: root.parent ? root.parent.height : 0
|
||||||
|
enabled: micMouseArea.isDragging
|
||||||
|
visible: false
|
||||||
|
preventStealing: true
|
||||||
|
onPositionChanged: mouse => {
|
||||||
|
if (micMouseArea.isDragging) {
|
||||||
|
let globalPos = mapToItem(micSliderTrack,
|
||||||
|
mouse.x, mouse.y)
|
||||||
|
let ratio = Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(1,
|
||||||
|
globalPos.x / micSliderTrack.width))
|
||||||
|
let newMicLevel = Math.max(
|
||||||
|
0, Math.min(100, Math.round(ratio * 100)))
|
||||||
|
if (AudioService.source
|
||||||
|
&& AudioService.source.audio) {
|
||||||
|
AudioService.source.audio.muted = false
|
||||||
|
AudioService.source.audio.volume = newMicLevel / 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onReleased: {
|
||||||
|
micMouseArea.isDragging = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "mic"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,60 +4,63 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property real volumeLevel: Math.min(100, (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0)
|
property real volumeLevel: Math.min(
|
||||||
property bool volumeMuted: (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.muted) || false
|
100,
|
||||||
|
(AudioService.sink && AudioService.sink.audio
|
||||||
|
&& AudioService.sink.audio.volume * 100) || 0)
|
||||||
|
property bool volumeMuted: (AudioService.sink && AudioService.sink.audio
|
||||||
|
&& AudioService.sink.audio.muted) || false
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Volume"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSlider {
|
||||||
|
id: volumeSlider
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
leftIcon: root.volumeMuted ? "volume_off" : "volume_down"
|
||||||
|
rightIcon: "volume_up"
|
||||||
|
enabled: !root.volumeMuted
|
||||||
|
showValue: true
|
||||||
|
unit: "%"
|
||||||
|
|
||||||
StyledText {
|
Connections {
|
||||||
text: "Volume"
|
target: AudioService.sink
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
&& AudioService.sink.audio ? AudioService.sink.audio : null
|
||||||
color: Theme.surfaceText
|
function onVolumeChanged() {
|
||||||
font.weight: Font.Medium
|
volumeSlider.value = Math.round(AudioService.sink.audio.volume * 100)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankSlider {
|
Component.onCompleted: {
|
||||||
id: volumeSlider
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
value = Math.round(AudioService.sink.audio.volume * 100)
|
||||||
|
}
|
||||||
|
|
||||||
width: parent.width
|
let leftIconItem = volumeSlider.children[0].children[0]
|
||||||
minimum: 0
|
if (leftIconItem) {
|
||||||
maximum: 100
|
let mouseArea = Qt.createQmlObject(
|
||||||
leftIcon: root.volumeMuted ? "volume_off" : "volume_down"
|
'import QtQuick; import qs.Services; MouseArea { anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onClicked: { if (AudioService.sink && AudioService.sink.audio) AudioService.sink.audio.muted = !AudioService.sink.audio.muted; } }',
|
||||||
rightIcon: "volume_up"
|
leftIconItem, "dynamicMouseArea")
|
||||||
enabled: !root.volumeMuted
|
}
|
||||||
showValue: true
|
|
||||||
unit: "%"
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
|
|
||||||
function onVolumeChanged() {
|
|
||||||
volumeSlider.value = Math.round(AudioService.sink.audio.volume * 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (AudioService.sink && AudioService.sink.audio) {
|
|
||||||
value = Math.round(AudioService.sink.audio.volume * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
let leftIconItem = volumeSlider.children[0].children[0];
|
|
||||||
if (leftIconItem) {
|
|
||||||
let mouseArea = Qt.createQmlObject(
|
|
||||||
'import QtQuick; import qs.Services; MouseArea { anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onClicked: { if (AudioService.sink && AudioService.sink.audio) AudioService.sink.audio.muted = !AudioService.sink.audio.muted; } }',
|
|
||||||
leftIconItem,
|
|
||||||
"dynamicMouseArea"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onSliderValueChanged: (newValue) => {
|
|
||||||
if (AudioService.sink && AudioService.sink.audio) {
|
|
||||||
AudioService.sink.audio.muted = false;
|
|
||||||
AudioService.sink.audio.volume = newValue / 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSliderValueChanged: newValue => {
|
||||||
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
AudioService.sink.audio.muted = false
|
||||||
|
AudioService.sink.audio.volume = newValue / 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -10,111 +10,111 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: audioTab
|
id: audioTab
|
||||||
|
|
||||||
property int audioSubTab: 0
|
property int audioSubTab: 0
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
DankTabBar {
|
DankTabBar {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
tabHeight: 40
|
tabHeight: 40
|
||||||
currentIndex: audioTab.audioSubTab
|
currentIndex: audioTab.audioSubTab
|
||||||
showIcons: false
|
showIcons: false
|
||||||
model: [{
|
model: [{
|
||||||
"text": "Output"
|
"text": "Output"
|
||||||
}, {
|
}, {
|
||||||
"text": "Input"
|
"text": "Input"
|
||||||
}]
|
}]
|
||||||
onTabClicked: function(index) {
|
onTabClicked: function (index) {
|
||||||
audioTab.audioSubTab = index;
|
audioTab.audioSubTab = index
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Output Tab - DankFlickable
|
|
||||||
DankFlickable {
|
|
||||||
width: parent.width
|
|
||||||
height: parent.height - 48
|
|
||||||
visible: audioTab.audioSubTab === 0
|
|
||||||
clip: true
|
|
||||||
contentHeight: outputColumn.height
|
|
||||||
contentWidth: width
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: outputColumn
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingL
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
width: parent.width
|
|
||||||
sourceComponent: volumeComponent
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
width: parent.width
|
|
||||||
sourceComponent: outputDevicesComponent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input Tab - DankFlickable
|
|
||||||
DankFlickable {
|
|
||||||
width: parent.width
|
|
||||||
height: parent.height - 48
|
|
||||||
visible: audioTab.audioSubTab === 1
|
|
||||||
clip: true
|
|
||||||
contentHeight: inputColumn.height
|
|
||||||
contentWidth: width
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: inputColumn
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingL
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
width: parent.width
|
|
||||||
sourceComponent: microphoneComponent
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
width: parent.width
|
|
||||||
sourceComponent: inputDevicesComponent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume Control Component
|
// Output Tab - DankFlickable
|
||||||
Component {
|
DankFlickable {
|
||||||
id: volumeComponent
|
width: parent.width
|
||||||
VolumeControl {
|
height: parent.height - 48
|
||||||
width: parent.width
|
visible: audioTab.audioSubTab === 0
|
||||||
|
clip: true
|
||||||
|
contentHeight: outputColumn.height
|
||||||
|
contentWidth: width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: outputColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
width: parent.width
|
||||||
|
sourceComponent: volumeComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
width: parent.width
|
||||||
|
sourceComponent: outputDevicesComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Microphone Control Component
|
// Input Tab - DankFlickable
|
||||||
Component {
|
DankFlickable {
|
||||||
id: microphoneComponent
|
width: parent.width
|
||||||
MicrophoneControl {
|
height: parent.height - 48
|
||||||
width: parent.width
|
visible: audioTab.audioSubTab === 1
|
||||||
}
|
clip: true
|
||||||
}
|
contentHeight: inputColumn.height
|
||||||
|
contentWidth: width
|
||||||
|
|
||||||
// Output Devices Component
|
Column {
|
||||||
Component {
|
id: inputColumn
|
||||||
id: outputDevicesComponent
|
width: parent.width
|
||||||
AudioDevicesList {
|
spacing: Theme.spacingL
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input Devices Component
|
Loader {
|
||||||
Component {
|
width: parent.width
|
||||||
id: inputDevicesComponent
|
sourceComponent: microphoneComponent
|
||||||
AudioInputDevicesList {
|
|
||||||
width: parent.width
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
width: parent.width
|
||||||
|
sourceComponent: inputDevicesComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Volume Control Component
|
||||||
|
Component {
|
||||||
|
id: volumeComponent
|
||||||
|
VolumeControl {
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Microphone Control Component
|
||||||
|
Component {
|
||||||
|
id: microphoneComponent
|
||||||
|
MicrophoneControl {
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output Devices Component
|
||||||
|
Component {
|
||||||
|
id: outputDevicesComponent
|
||||||
|
AudioDevicesList {
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input Devices Component
|
||||||
|
Component {
|
||||||
|
id: inputDevicesComponent
|
||||||
|
AudioInputDevicesList {
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,398 +9,427 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||||
|
|
||||||
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
|
||||||
|
|
||||||
Row {
|
StyledText {
|
||||||
width: parent.width
|
text: "Available Devices"
|
||||||
spacing: Theme.spacingM
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
StyledText {
|
font.weight: Font.Medium
|
||||||
text: "Available Devices"
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
}
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width - scanButton.width - parent.spacing - 150 // Spacer to push button right
|
|
||||||
height: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: scanButton
|
|
||||||
|
|
||||||
width: Math.max(100, scanText.contentWidth + Theme.spacingL * 2)
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: scanArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
|
||||||
border.color: Theme.primary
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "bluetooth_searching"
|
|
||||||
size: Theme.iconSize - 6
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: scanText
|
|
||||||
|
|
||||||
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Stop Scanning" : "Scan"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.primary
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: scanArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (BluetoothService.adapter)
|
|
||||||
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width - scanButton.width - parent.spacing - 150 // Spacer to push button right
|
||||||
|
height: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
id: scanButton
|
||||||
height: noteColumn.implicitHeight + Theme.spacingM * 2
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08)
|
|
||||||
border.color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.2)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Column {
|
width: Math.max(100, scanText.contentWidth + Theme.spacingL * 2)
|
||||||
id: noteColumn
|
height: 32
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: scanArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.08)
|
||||||
|
border.color: Theme.primary
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
anchors.fill: parent
|
Row {
|
||||||
anchors.margins: Theme.spacingM
|
anchors.centerIn: parent
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "info"
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: Theme.warning
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Pairing Limitation"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.warning
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Quickshell does not support pairing devices that require pin or confirmation."
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: {
|
|
||||||
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
var filtered = Bluetooth.devices.values.filter((dev) => {
|
|
||||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
|
||||||
});
|
|
||||||
return BluetoothService.sortDevices(filtered);
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
property bool canConnect: BluetoothService.canConnect(modelData)
|
|
||||||
property bool isBusy: BluetoothService.isDeviceBusy(modelData)
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: 70
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: {
|
|
||||||
if (availableDeviceArea.containsMouse && !isBusy)
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
|
|
||||||
|
|
||||||
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
|
|
||||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
|
||||||
|
|
||||||
if (modelData.blocked)
|
|
||||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08);
|
|
||||||
|
|
||||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
|
|
||||||
}
|
|
||||||
border.color: {
|
|
||||||
if (modelData.pairing)
|
|
||||||
return Theme.warning;
|
|
||||||
|
|
||||||
if (modelData.blocked)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2);
|
|
||||||
}
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: BluetoothService.getDeviceIcon(modelData)
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: {
|
|
||||||
if (modelData.pairing)
|
|
||||||
return Theme.warning;
|
|
||||||
|
|
||||||
if (modelData.blocked)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
spacing: 2
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.name || modelData.deviceName
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: {
|
|
||||||
if (modelData.pairing)
|
|
||||||
return Theme.warning;
|
|
||||||
|
|
||||||
if (modelData.blocked)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
font.weight: modelData.pairing ? Font.Medium : Font.Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (modelData.pairing)
|
|
||||||
return "Pairing...";
|
|
||||||
|
|
||||||
if (modelData.blocked)
|
|
||||||
return "Blocked";
|
|
||||||
|
|
||||||
return BluetoothService.getSignalStrength(modelData);
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: {
|
|
||||||
if (modelData.pairing)
|
|
||||||
return Theme.warning;
|
|
||||||
|
|
||||||
if (modelData.blocked)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: BluetoothService.getSignalIcon(modelData)
|
|
||||||
size: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
visible: modelData.signalStrength !== undefined && modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: (modelData.signalStrength !== undefined && modelData.signalStrength > 0) ? modelData.signalStrength + "%" : ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
|
||||||
visible: modelData.signalStrength !== undefined && modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 80
|
|
||||||
height: 28
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: modelData.state !== BluetoothDeviceState.Connecting
|
|
||||||
color: {
|
|
||||||
if (!canConnect && !isBusy)
|
|
||||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3);
|
|
||||||
|
|
||||||
if (actionButtonArea.containsMouse && !isBusy)
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
|
||||||
|
|
||||||
return "transparent";
|
|
||||||
}
|
|
||||||
border.color: canConnect || isBusy ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
border.width: 1
|
|
||||||
opacity: canConnect || isBusy ? 1 : 0.5
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: {
|
|
||||||
if (modelData.pairing)
|
|
||||||
return "Pairing...";
|
|
||||||
|
|
||||||
if (modelData.blocked)
|
|
||||||
return "Blocked";
|
|
||||||
|
|
||||||
return "Connect";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: canConnect || isBusy ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: actionButtonArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
|
|
||||||
enabled: canConnect && !isBusy
|
|
||||||
onClicked: {
|
|
||||||
if (modelData)
|
|
||||||
BluetoothService.connectDeviceWithTrust(modelData);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: availableDeviceArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.rightMargin: 90
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
|
|
||||||
enabled: canConnect && !isBusy
|
|
||||||
onClicked: {
|
|
||||||
if (modelData)
|
|
||||||
BluetoothService.connectDeviceWithTrust(modelData);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
visible: {
|
|
||||||
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var availableCount = Bluetooth.devices.values.filter((dev) => {
|
|
||||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
|
||||||
}).length;
|
|
||||||
return availableCount === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "sync"
|
|
||||||
size: Theme.iconSizeLarge
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
RotationAnimation on rotation {
|
|
||||||
running: true
|
|
||||||
loops: Animation.Infinite
|
|
||||||
from: 0
|
|
||||||
to: 360
|
|
||||||
duration: 2000
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Scanning for devices..."
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: BluetoothService.adapter
|
||||||
|
&& BluetoothService.adapter.discovering ? "stop" : "bluetooth_searching"
|
||||||
|
size: Theme.iconSize - 6
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "Make sure your device is in pairing mode"
|
id: scanText
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
text: BluetoothService.adapter
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
&& BluetoothService.adapter.discovering ? "Stop Scanning" : "Scan"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.primary
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: scanArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (BluetoothService.adapter)
|
||||||
|
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: noteColumn.implicitHeight + Theme.spacingM * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08)
|
||||||
|
border.color: Qt.rgba(Theme.warning.r, Theme.warning.g,
|
||||||
|
Theme.warning.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: noteColumn
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "info"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: Theme.warning
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Pairing Limitation"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.warning
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Quickshell does not support pairing devices that require pin or confirmation."
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.8)
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: {
|
||||||
|
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering
|
||||||
|
|| !Bluetooth.devices)
|
||||||
|
return []
|
||||||
|
|
||||||
|
var filtered = Bluetooth.devices.values.filter(dev => {
|
||||||
|
return dev && !dev.paired
|
||||||
|
&& !dev.pairing
|
||||||
|
&& !dev.blocked
|
||||||
|
&& (dev.signalStrength === undefined
|
||||||
|
|| dev.signalStrength > 0)
|
||||||
|
})
|
||||||
|
return BluetoothService.sortDevices(filtered)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
property bool canConnect: BluetoothService.canConnect(modelData)
|
||||||
|
property bool isBusy: BluetoothService.isDeviceBusy(modelData)
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: 70
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
if (availableDeviceArea.containsMouse && !isBusy)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||||
|
Theme.primary.b, 0.08)
|
||||||
|
|
||||||
|
if (modelData.pairing
|
||||||
|
|| modelData.state === BluetoothDeviceState.Connecting)
|
||||||
|
return Qt.rgba(Theme.warning.r, Theme.warning.g,
|
||||||
|
Theme.warning.b, 0.12)
|
||||||
|
|
||||||
|
if (modelData.blocked)
|
||||||
|
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08)
|
||||||
|
|
||||||
|
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.08)
|
||||||
|
}
|
||||||
|
border.color: {
|
||||||
|
if (modelData.pairing)
|
||||||
|
return Theme.warning
|
||||||
|
|
||||||
|
if (modelData.blocked)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
}
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: BluetoothService.getDeviceIcon(modelData)
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: {
|
||||||
|
if (modelData.pairing)
|
||||||
|
return Theme.warning
|
||||||
|
|
||||||
|
if (modelData.blocked)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.name || modelData.deviceName
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: {
|
||||||
|
if (modelData.pairing)
|
||||||
|
return Theme.warning
|
||||||
|
|
||||||
|
if (modelData.blocked)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
font.weight: modelData.pairing ? Font.Medium : Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (modelData.pairing)
|
||||||
|
return "Pairing..."
|
||||||
|
|
||||||
|
if (modelData.blocked)
|
||||||
|
return "Blocked"
|
||||||
|
|
||||||
|
return BluetoothService.getSignalStrength(modelData)
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: {
|
||||||
|
if (modelData.pairing)
|
||||||
|
return Theme.warning
|
||||||
|
|
||||||
|
if (modelData.blocked)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: BluetoothService.getSignalIcon(modelData)
|
||||||
|
size: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
visible: modelData.signalStrength !== undefined
|
||||||
|
&& modelData.signalStrength > 0 && !modelData.pairing
|
||||||
|
&& !modelData.blocked
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: (modelData.signalStrength !== undefined
|
||||||
|
&& modelData.signalStrength > 0) ? modelData.signalStrength + "%" : ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.5)
|
||||||
|
visible: modelData.signalStrength !== undefined
|
||||||
|
&& modelData.signalStrength > 0 && !modelData.pairing
|
||||||
|
&& !modelData.blocked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 80
|
||||||
|
height: 28
|
||||||
|
radius: Theme.cornerRadiusSmall
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: modelData.state !== BluetoothDeviceState.Connecting
|
||||||
|
color: {
|
||||||
|
if (!canConnect && !isBusy)
|
||||||
|
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.3)
|
||||||
|
|
||||||
|
if (actionButtonArea.containsMouse && !isBusy)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||||
|
Theme.primary.b, 0.12)
|
||||||
|
|
||||||
|
return "transparent"
|
||||||
|
}
|
||||||
|
border.color: canConnect || isBusy ? Theme.primary : Qt.rgba(
|
||||||
|
Theme.outline.r,
|
||||||
|
Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
opacity: canConnect || isBusy ? 1 : 0.5
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: {
|
||||||
|
if (modelData.pairing)
|
||||||
|
return "Pairing..."
|
||||||
|
|
||||||
|
if (modelData.blocked)
|
||||||
|
return "Blocked"
|
||||||
|
|
||||||
|
return "Connect"
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: canConnect || isBusy ? Theme.primary : Qt.rgba(
|
||||||
|
Theme.surfaceText.r,
|
||||||
|
Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.5)
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: actionButtonArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: canConnect
|
||||||
|
&& !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
|
||||||
|
enabled: canConnect && !isBusy
|
||||||
|
onClicked: {
|
||||||
|
if (modelData)
|
||||||
|
BluetoothService.connectDeviceWithTrust(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: availableDeviceArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: 90
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: canConnect
|
||||||
|
&& !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
|
||||||
|
enabled: canConnect && !isBusy
|
||||||
|
onClicked: {
|
||||||
|
if (modelData)
|
||||||
|
BluetoothService.connectDeviceWithTrust(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: {
|
||||||
|
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering
|
||||||
|
|| !Bluetooth.devices)
|
||||||
|
return false
|
||||||
|
|
||||||
|
var availableCount = Bluetooth.devices.values.filter(dev => {
|
||||||
|
return dev
|
||||||
|
&& !dev.paired
|
||||||
|
&& !dev.pairing
|
||||||
|
&& !dev.blocked
|
||||||
|
&& (dev.signalStrength === undefined
|
||||||
|
|| dev.signalStrength > 0)
|
||||||
|
}).length
|
||||||
|
return availableCount === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "sync"
|
||||||
|
size: Theme.iconSizeLarge
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
RotationAnimation on rotation {
|
||||||
|
running: true
|
||||||
|
loops: Animation.Infinite
|
||||||
|
from: 0
|
||||||
|
to: 360
|
||||||
|
duration: 2000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Scanning for devices..."
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "No devices found. Put your device in pairing mode and click Start Scanning."
|
text: "Make sure your device is in pairing mode"
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
visible: {
|
Theme.surfaceText.b, 0.7)
|
||||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
return true;
|
|
||||||
|
|
||||||
var availableCount = Bluetooth.devices.values.filter((dev) => {
|
|
||||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
|
||||||
}).length;
|
|
||||||
return availableCount === 0 && !BluetoothService.adapter.discovering;
|
|
||||||
}
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
width: parent.width
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "No devices found. Put your device in pairing mode and click Start Scanning."
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
visible: {
|
||||||
|
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||||
|
return true
|
||||||
|
|
||||||
|
var availableCount = Bluetooth.devices.values.filter(dev => {
|
||||||
|
return dev
|
||||||
|
&& !dev.paired
|
||||||
|
&& !dev.pairing
|
||||||
|
&& !dev.blocked
|
||||||
|
&& (dev.signalStrength === undefined
|
||||||
|
|| dev.signalStrength > 0)
|
||||||
|
}).length
|
||||||
|
return availableCount === 0 && !BluetoothService.adapter.discovering
|
||||||
|
}
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,203 +9,199 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var deviceData: null
|
property var deviceData: null
|
||||||
property bool menuVisible: false
|
property bool menuVisible: false
|
||||||
property var parentItem
|
property var parentItem
|
||||||
|
|
||||||
function show(x, y) {
|
function show(x, y) {
|
||||||
const menuWidth = 160;
|
const menuWidth = 160
|
||||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2;
|
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
|
||||||
let finalX = x - menuWidth / 2;
|
let finalX = x - menuWidth / 2
|
||||||
let finalY = y;
|
let finalY = y
|
||||||
finalX = Math.max(0, Math.min(finalX, parentItem.width - menuWidth));
|
finalX = Math.max(0, Math.min(finalX, parentItem.width - menuWidth))
|
||||||
finalY = Math.max(0, Math.min(finalY, parentItem.height - menuHeight));
|
finalY = Math.max(0, Math.min(finalY, parentItem.height - menuHeight))
|
||||||
root.x = finalX;
|
root.x = finalX
|
||||||
root.y = finalY;
|
root.y = finalY
|
||||||
root.visible = true;
|
root.visible = true
|
||||||
root.menuVisible = true;
|
root.menuVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
root.menuVisible = false;
|
root.menuVisible = false
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
root.visible = false;
|
root.visible = false
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
visible: false
|
visible: false
|
||||||
width: 160
|
width: 160
|
||||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||||
radius: Theme.cornerRadiusLarge
|
radius: Theme.cornerRadiusLarge
|
||||||
color: Theme.popupBackground()
|
color: Theme.popupBackground()
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
z: 1000
|
z: 1000
|
||||||
opacity: menuVisible ? 1 : 0
|
opacity: menuVisible ? 1 : 0
|
||||||
scale: menuVisible ? 1 : 0.85
|
scale: menuVisible ? 1 : 0.85
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 4
|
||||||
|
anchors.leftMargin: 2
|
||||||
|
anchors.rightMargin: -2
|
||||||
|
anchors.bottomMargin: -4
|
||||||
|
radius: parent.radius
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.15)
|
||||||
|
z: parent.z - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: menuColumn
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
spacing: 1
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
width: parent.width
|
||||||
anchors.topMargin: 4
|
height: 32
|
||||||
anchors.leftMargin: 2
|
radius: Theme.cornerRadiusSmall
|
||||||
anchors.rightMargin: -2
|
color: connectArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
anchors.bottomMargin: -4
|
Theme.primary.g,
|
||||||
radius: parent.radius
|
Theme.primary.b,
|
||||||
color: Qt.rgba(0, 0, 0, 0.15)
|
0.12) : "transparent"
|
||||||
z: parent.z - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Row {
|
||||||
id: menuColumn
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: root.deviceData
|
||||||
|
&& root.deviceData.connected ? "link_off" : "link"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: 0.7
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.deviceData
|
||||||
|
&& root.deviceData.connected ? "Disconnect" : "Connect"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: connectArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingS
|
hoverEnabled: true
|
||||||
spacing: 1
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
Rectangle {
|
if (root.deviceData) {
|
||||||
width: parent.width
|
if (root.deviceData.connected)
|
||||||
height: 32
|
root.deviceData.disconnect()
|
||||||
radius: Theme.cornerRadiusSmall
|
else
|
||||||
color: connectArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
BluetoothService.connectDeviceWithTrust(root.deviceData)
|
||||||
|
}
|
||||||
Row {
|
root.hide()
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: root.deviceData && root.deviceData.connected ? "link_off" : "link"
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.7
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: root.deviceData && root.deviceData.connected ? "Disconnect" : "Connect"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: connectArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (root.deviceData) {
|
|
||||||
if (root.deviceData.connected)
|
|
||||||
root.deviceData.disconnect();
|
|
||||||
else
|
|
||||||
BluetoothService.connectDeviceWithTrust(root.deviceData);
|
|
||||||
}
|
|
||||||
root.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Behavior on color {
|
||||||
width: parent.width - Theme.spacingS * 2
|
ColorAnimation {
|
||||||
height: 5
|
duration: Theme.shortDuration
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
easing.type: Theme.standardEasing
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: forgetArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "delete"
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: forgetArea.containsMouse ? Theme.error : Theme.surfaceText
|
|
||||||
opacity: 0.7
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Forget Device"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: forgetArea.containsMouse ? Theme.error : Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: forgetArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (root.deviceData)
|
|
||||||
root.deviceData.forget();
|
|
||||||
|
|
||||||
root.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Rectangle {
|
||||||
NumberAnimation {
|
width: parent.width - Theme.spacingS * 2
|
||||||
duration: Theme.mediumDuration
|
height: 5
|
||||||
easing.type: Theme.emphasizedEasing
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
color: "transparent"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scale {
|
Rectangle {
|
||||||
NumberAnimation {
|
width: parent.width
|
||||||
duration: Theme.mediumDuration
|
height: 32
|
||||||
easing.type: Theme.emphasizedEasing
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: forgetArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g,
|
||||||
|
Theme.error.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "delete"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: forgetArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
|
opacity: 0.7
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
StyledText {
|
||||||
|
text: "Forget Device"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: forgetArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: forgetArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (root.deviceData)
|
||||||
|
root.deviceData.forget()
|
||||||
|
|
||||||
|
root.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,60 +9,64 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 60
|
height: 60
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: bluetoothToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (BluetoothService.adapter && BluetoothService.adapter.enabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12))
|
color: bluetoothToggle.containsMouse ? Qt.rgba(
|
||||||
border.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : "transparent"
|
Theme.primary.r, Theme.primary.g,
|
||||||
border.width: 2
|
Theme.primary.b, 0.12) : (BluetoothService.adapter
|
||||||
|
&& BluetoothService.adapter.enabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12))
|
||||||
|
border.color: BluetoothService.adapter
|
||||||
|
&& BluetoothService.adapter.enabled ? Theme.primary : "transparent"
|
||||||
|
border.width: 2
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Theme.spacingL
|
anchors.leftMargin: Theme.spacingL
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "bluetooth"
|
|
||||||
size: Theme.iconSizeLarge
|
|
||||||
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
spacing: 2
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Bluetooth"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: BluetoothService.adapter && BluetoothService.adapter.enabled ? "Enabled" : "Disabled"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "bluetooth"
|
||||||
|
size: Theme.iconSizeLarge
|
||||||
|
color: BluetoothService.adapter
|
||||||
|
&& BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
Column {
|
||||||
id: bluetoothToggle
|
spacing: 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
anchors.fill: parent
|
StyledText {
|
||||||
hoverEnabled: true
|
text: "Bluetooth"
|
||||||
cursorShape: Qt.PointingHandCursor
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
onClicked: {
|
color: BluetoothService.adapter
|
||||||
if (BluetoothService.adapter)
|
&& BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText
|
||||||
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled;
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
}
|
StyledText {
|
||||||
|
text: BluetoothService.adapter
|
||||||
|
&& BluetoothService.adapter.enabled ? "Enabled" : "Disabled"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: bluetoothToggle
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (BluetoothService.adapter)
|
||||||
|
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,158 +9,173 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
function findBluetoothContextMenu() {
|
function findBluetoothContextMenu() {
|
||||||
var p = parent;
|
var p = parent
|
||||||
while (p) {
|
while (p) {
|
||||||
if (p.bluetoothContextMenuWindow)
|
if (p.bluetoothContextMenuWindow)
|
||||||
return p.bluetoothContextMenuWindow;
|
return p.bluetoothContextMenuWindow
|
||||||
p = p.parent;
|
p = p.parent
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "Paired Devices"
|
text: "Paired Devices"
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: BluetoothService.adapter && BluetoothService.adapter.devices ? BluetoothService.adapter.devices.values.filter((dev) => {
|
model: BluetoothService.adapter
|
||||||
return dev && (dev.paired || dev.trusted);
|
&& BluetoothService.adapter.devices ? BluetoothService.adapter.devices.values.filter(
|
||||||
}) : []
|
dev => {
|
||||||
|
return dev
|
||||||
|
&& (dev.paired
|
||||||
|
|| dev.trusted)
|
||||||
|
}) : []
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 60
|
height: 60
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: btDeviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
color: btDeviceArea.containsMouse ? Qt.rgba(
|
||||||
border.color: modelData.connected ? Theme.primary : "transparent"
|
Theme.primary.r, Theme.primary.g,
|
||||||
border.width: 1
|
Theme.primary.b,
|
||||||
|
0.08) : (modelData.connected ? Qt.rgba(
|
||||||
|
Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : Qt.rgba(
|
||||||
|
Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b,
|
||||||
|
0.08))
|
||||||
|
border.color: modelData.connected ? Theme.primary : "transparent"
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Theme.spacingM
|
anchors.leftMargin: Theme.spacingM
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: BluetoothService.getDeviceIcon(modelData)
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
spacing: 2
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.name || modelData.deviceName
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: modelData.connected ? Font.Medium : Font.Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: BluetoothDeviceState.toString(modelData.state)
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (modelData.batteryAvailable && modelData.battery > 0)
|
|
||||||
return "• " + Math.round(modelData.battery * 100) + "%";
|
|
||||||
|
|
||||||
var btBattery = BatteryService.bluetoothDevices.find((dev) => {
|
|
||||||
return dev.name === (modelData.name || modelData.deviceName) || dev.name.toLowerCase().includes((modelData.name || modelData.deviceName).toLowerCase()) || (modelData.name || modelData.deviceName).toLowerCase().includes(dev.name.toLowerCase());
|
|
||||||
});
|
|
||||||
return btBattery ? "• " + btBattery.percentage + "%" : "";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
visible: text.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: btMenuButton
|
|
||||||
|
|
||||||
width: 32
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: btMenuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "more_vert"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.6
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: btMenuButtonArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
var contextMenu = root.findBluetoothContextMenu();
|
|
||||||
if (contextMenu) {
|
|
||||||
contextMenu.deviceData = modelData;
|
|
||||||
let localPos = btMenuButtonArea.mapToItem(contextMenu.parentItem, btMenuButtonArea.width / 2, btMenuButtonArea.height);
|
|
||||||
contextMenu.show(localPos.x, localPos.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: btDeviceArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.rightMargin: 40
|
|
||||||
hoverEnabled: true
|
|
||||||
enabled: !BluetoothService.isDeviceBusy(modelData)
|
|
||||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.BusyCursor
|
|
||||||
onClicked: {
|
|
||||||
if (modelData.connected)
|
|
||||||
modelData.disconnect();
|
|
||||||
else
|
|
||||||
BluetoothService.connectDeviceWithTrust(modelData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: BluetoothService.getDeviceIcon(modelData)
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
Column {
|
||||||
|
spacing: 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.name || modelData.deviceName
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||||
|
font.weight: modelData.connected ? Font.Medium : Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: BluetoothDeviceState.toString(modelData.state)
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (modelData.batteryAvailable && modelData.battery > 0)
|
||||||
|
return "• " + Math.round(modelData.battery * 100) + "%"
|
||||||
|
|
||||||
|
var btBattery = BatteryService.bluetoothDevices.find(dev => {
|
||||||
|
return dev.name === (modelData.name || modelData.deviceName) || dev.name.toLowerCase().includes((modelData.name || modelData.deviceName).toLowerCase()) || (modelData.name || modelData.deviceName).toLowerCase(
|
||||||
|
).includes(
|
||||||
|
dev.name.toLowerCase(
|
||||||
|
))
|
||||||
|
})
|
||||||
|
return btBattery ? "• " + btBattery.percentage + "%" : ""
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
visible: text.length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: btMenuButton
|
||||||
|
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: btMenuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r,
|
||||||
|
Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b,
|
||||||
|
0.08) : "transparent"
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "more_vert"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: 0.6
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: btMenuButtonArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
var contextMenu = root.findBluetoothContextMenu()
|
||||||
|
if (contextMenu) {
|
||||||
|
contextMenu.deviceData = modelData
|
||||||
|
let localPos = btMenuButtonArea.mapToItem(
|
||||||
|
contextMenu.parentItem, btMenuButtonArea.width / 2,
|
||||||
|
btMenuButtonArea.height)
|
||||||
|
contextMenu.show(localPos.x, localPos.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: btDeviceArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: 40
|
||||||
|
hoverEnabled: true
|
||||||
|
enabled: !BluetoothService.isDeviceBusy(modelData)
|
||||||
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.BusyCursor
|
||||||
|
onClicked: {
|
||||||
|
if (modelData.connected)
|
||||||
|
modelData.disconnect()
|
||||||
|
else
|
||||||
|
BluetoothService.connectDeviceWithTrust(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,78 +10,79 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: bluetoothTab
|
id: bluetoothTab
|
||||||
|
|
||||||
property alias bluetoothContextMenuWindow: bluetoothContextMenuWindow
|
property alias bluetoothContextMenuWindow: bluetoothContextMenuWindow
|
||||||
|
|
||||||
DankFlickable {
|
DankFlickable {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: true
|
clip: true
|
||||||
contentHeight: mainColumn.height
|
contentHeight: mainColumn.height
|
||||||
contentWidth: width
|
contentWidth: width
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: mainColumn
|
id: mainColumn
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
sourceComponent: toggleComponent
|
sourceComponent: toggleComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
sourceComponent: pairedComponent
|
sourceComponent: pairedComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
sourceComponent: availableComponent
|
sourceComponent: availableComponent
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BluetoothContextMenu {
|
BluetoothContextMenu {
|
||||||
id: bluetoothContextMenuWindow
|
id: bluetoothContextMenuWindow
|
||||||
parentItem: bluetoothTab
|
parentItem: bluetoothTab
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: bluetoothContextMenuWindow.visible
|
||||||
|
onClicked: {
|
||||||
|
bluetoothContextMenuWindow.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
x: bluetoothContextMenuWindow.x
|
||||||
visible: bluetoothContextMenuWindow.visible
|
y: bluetoothContextMenuWindow.y
|
||||||
onClicked: {
|
width: bluetoothContextMenuWindow.width
|
||||||
bluetoothContextMenuWindow.hide();
|
height: bluetoothContextMenuWindow.height
|
||||||
}
|
onClicked: {
|
||||||
|
|
||||||
MouseArea {
|
}
|
||||||
x: bluetoothContextMenuWindow.x
|
|
||||||
y: bluetoothContextMenuWindow.y
|
|
||||||
width: bluetoothContextMenuWindow.width
|
|
||||||
height: bluetoothContextMenuWindow.height
|
|
||||||
onClicked: {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: toggleComponent
|
id: toggleComponent
|
||||||
BluetoothToggle {
|
BluetoothToggle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: pairedComponent
|
id: pairedComponent
|
||||||
PairedDevicesList {
|
PairedDevicesList {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: availableComponent
|
id: availableComponent
|
||||||
AvailableDevicesList {
|
AvailableDevicesList {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -9,213 +9,221 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: displayTab
|
id: displayTab
|
||||||
|
|
||||||
property var brightnessDebounceTimer
|
property var brightnessDebounceTimer
|
||||||
|
|
||||||
brightnessDebounceTimer: Timer {
|
brightnessDebounceTimer: Timer {
|
||||||
property int pendingValue: 0
|
property int pendingValue: 0
|
||||||
|
|
||||||
interval: BrightnessService.ddcAvailable ? 500 : 50
|
interval: BrightnessService.ddcAvailable ? 500 : 50
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
BrightnessService.setBrightnessInternal(pendingValue);
|
BrightnessService.setBrightnessInternal(pendingValue)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DankFlickable {
|
DankFlickable {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: true
|
clip: true
|
||||||
contentHeight: mainColumn.height
|
contentHeight: mainColumn.height
|
||||||
contentWidth: width
|
contentWidth: width
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: mainColumn
|
id: mainColumn
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
sourceComponent: brightnessComponent
|
sourceComponent: brightnessComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
sourceComponent: settingsComponent
|
sourceComponent: settingsComponent
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: nightModeEnableProcess
|
id: nightModeEnableProcess
|
||||||
command: ["bash", "-c", "if command -v wlsunset > /dev/null; then pkill wlsunset; wlsunset -t 3000 & elif command -v redshift > /dev/null; then pkill redshift; redshift -P -O 3000 & else echo 'No night mode tool available'; fi"]
|
command: ["bash", "-c", "if command -v wlsunset > /dev/null; then pkill wlsunset; wlsunset -t 3000 & elif command -v redshift > /dev/null; then pkill redshift; redshift -P -O 3000 & else echo 'No night mode tool available'; fi"]
|
||||||
running: false
|
running: false
|
||||||
onExited: (exitCode) => {
|
onExited: exitCode => {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
SettingsData.setNightModeEnabled(false);
|
SettingsData.setNightModeEnabled(false)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: nightModeDisableProcess
|
|
||||||
command: ["bash", "-c", "pkill wlsunset; pkill redshift; if command -v wlsunset > /dev/null; then wlsunset -t 6500 -T 6500 & sleep 1; pkill wlsunset; elif command -v redshift > /dev/null; then redshift -P -O 6500; redshift -x; fi"]
|
|
||||||
running: false
|
|
||||||
onExited: (exitCode) => {
|
|
||||||
if (exitCode !== 0) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: brightnessComponent
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
visible: BrightnessService.brightnessAvailable
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Brightness"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankSlider {
|
|
||||||
width: parent.width
|
|
||||||
value: BrightnessService.brightnessLevel
|
|
||||||
leftIcon: "brightness_low"
|
|
||||||
rightIcon: "brightness_high"
|
|
||||||
enabled: BrightnessService.brightnessAvailable
|
|
||||||
onSliderValueChanged: function(newValue) {
|
|
||||||
brightnessDebounceTimer.pendingValue = newValue;
|
|
||||||
brightnessDebounceTimer.restart();
|
|
||||||
}
|
}
|
||||||
onSliderDragFinished: function(finalValue) {
|
}
|
||||||
brightnessDebounceTimer.stop();
|
}
|
||||||
BrightnessService.setBrightnessInternal(finalValue);
|
|
||||||
|
Process {
|
||||||
|
id: nightModeDisableProcess
|
||||||
|
command: ["bash", "-c", "pkill wlsunset; pkill redshift; if command -v wlsunset > /dev/null; then wlsunset -t 6500 -T 6500 & sleep 1; pkill wlsunset; elif command -v redshift > /dev/null; then redshift -P -O 6500; redshift -x; fi"]
|
||||||
|
running: false
|
||||||
|
onExited: exitCode => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: brightnessComponent
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: BrightnessService.brightnessAvailable
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Brightness"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSlider {
|
||||||
|
width: parent.width
|
||||||
|
value: BrightnessService.brightnessLevel
|
||||||
|
leftIcon: "brightness_low"
|
||||||
|
rightIcon: "brightness_high"
|
||||||
|
enabled: BrightnessService.brightnessAvailable
|
||||||
|
onSliderValueChanged: function (newValue) {
|
||||||
|
brightnessDebounceTimer.pendingValue = newValue
|
||||||
|
brightnessDebounceTimer.restart()
|
||||||
|
}
|
||||||
|
onSliderDragFinished: function (finalValue) {
|
||||||
|
brightnessDebounceTimer.stop()
|
||||||
|
BrightnessService.setBrightnessInternal(finalValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "using ddc - changes may take a moment to apply"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
visible: BrightnessService.ddcAvailable
|
||||||
|
&& !BrightnessService.laptopBacklightAvailable
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: settingsComponent
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Display Settings"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
height: 80
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: SettingsData.nightModeEnabled ? Qt.rgba(
|
||||||
|
Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : (nightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||||
|
border.color: SettingsData.nightModeEnabled ? Theme.primary : "transparent"
|
||||||
|
border.width: SettingsData.nightModeEnabled ? 1 : 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: SettingsData.nightModeEnabled ? "nightlight" : "dark_mode"
|
||||||
|
size: Theme.iconSizeLarge
|
||||||
|
color: SettingsData.nightModeEnabled ? Theme.primary : Theme.surfaceText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "using ddc - changes may take a moment to apply"
|
text: "Night Mode"
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceVariantText
|
color: SettingsData.nightModeEnabled ? Theme.primary : Theme.surfaceText
|
||||||
visible: BrightnessService.ddcAvailable && !BrightnessService.laptopBacklightAvailable
|
font.weight: Font.Medium
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
MouseArea {
|
||||||
id: settingsComponent
|
id: nightModeToggle
|
||||||
Column {
|
anchors.fill: parent
|
||||||
width: parent.width
|
hoverEnabled: true
|
||||||
spacing: Theme.spacingM
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (SettingsData.nightModeEnabled) {
|
||||||
|
nightModeDisableProcess.running = true
|
||||||
|
SettingsData.setNightModeEnabled(false)
|
||||||
|
} else {
|
||||||
|
nightModeEnableProcess.running = true
|
||||||
|
SettingsData.setNightModeEnabled(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
height: 80
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.isLightMode ? Qt.rgba(
|
||||||
|
Theme.primary.r, Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : (lightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||||
|
border.color: Theme.isLightMode ? Theme.primary : "transparent"
|
||||||
|
border.width: Theme.isLightMode ? 1 : 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: Theme.isLightMode ? "light_mode" : "palette"
|
||||||
|
size: Theme.iconSizeLarge
|
||||||
|
color: Theme.isLightMode ? Theme.primary : Theme.surfaceText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "Display Settings"
|
text: Theme.isLightMode ? "Light Mode" : "Dark Mode"
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceText
|
color: Theme.isLightMode ? Theme.primary : Theme.surfaceText
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
MouseArea {
|
||||||
width: parent.width
|
id: lightModeToggle
|
||||||
spacing: Theme.spacingM
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
Rectangle {
|
cursorShape: Qt.PointingHandCursor
|
||||||
width: (parent.width - Theme.spacingM) / 2
|
onClicked: {
|
||||||
height: 80
|
Theme.toggleLightMode()
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: SettingsData.nightModeEnabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (nightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
|
||||||
border.color: SettingsData.nightModeEnabled ? Theme.primary : "transparent"
|
|
||||||
border.width: SettingsData.nightModeEnabled ? 1 : 0
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: SettingsData.nightModeEnabled ? "nightlight" : "dark_mode"
|
|
||||||
size: Theme.iconSizeLarge
|
|
||||||
color: SettingsData.nightModeEnabled ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Night Mode"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: SettingsData.nightModeEnabled ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: nightModeToggle
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (SettingsData.nightModeEnabled) {
|
|
||||||
nightModeDisableProcess.running = true;
|
|
||||||
SettingsData.setNightModeEnabled(false);
|
|
||||||
} else {
|
|
||||||
nightModeEnableProcess.running = true;
|
|
||||||
SettingsData.setNightModeEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: (parent.width - Theme.spacingM) / 2
|
|
||||||
height: 80
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.isLightMode ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (lightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
|
||||||
border.color: Theme.isLightMode ? Theme.primary : "transparent"
|
|
||||||
border.width: Theme.isLightMode ? 1 : 0
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: Theme.isLightMode ? "light_mode" : "palette"
|
|
||||||
size: Theme.iconSizeLarge
|
|
||||||
color: Theme.isLightMode ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: Theme.isLightMode ? "Light Mode" : "Dark Mode"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.isLightMode ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: lightModeToggle
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
Theme.toggleLightMode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,121 +8,131 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: ethernetCard
|
id: ethernetCard
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 80
|
height: 80
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: {
|
||||||
if (ethernetPreferenceArea.containsMouse && NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "ethernet")
|
if (ethernetPreferenceArea.containsMouse && NetworkService.ethernetConnected
|
||||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8);
|
&& NetworkService.wifiEnabled
|
||||||
|
&& NetworkService.networkStatus !== "ethernet")
|
||||||
|
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||||
|
Theme.surfaceContainer.b, 0.8)
|
||||||
|
|
||||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5);
|
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||||
}
|
Theme.surfaceContainer.b, 0.5)
|
||||||
border.color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
}
|
||||||
border.width: NetworkService.networkStatus === "ethernet" ? 2 : 1
|
border.color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Qt.rgba(
|
||||||
|
Theme.outline.r,
|
||||||
|
Theme.outline.g,
|
||||||
|
Theme.outline.b,
|
||||||
|
0.12)
|
||||||
|
border.width: NetworkService.networkStatus === "ethernet" ? 2 : 1
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Theme.spacingM
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: ethernetToggle.left
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "lan"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: ethernetToggle.left
|
}
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Row {
|
StyledText {
|
||||||
spacing: Theme.spacingM
|
text: "Ethernet"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
DankIcon {
|
color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Theme.surfaceText
|
||||||
name: "lan"
|
font.weight: Font.Medium
|
||||||
size: Theme.iconSize
|
|
||||||
color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Ethernet"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: NetworkService.ethernetConnected ? (NetworkService.ethernetIP || "Connected") : "Disconnected"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
leftPadding: Theme.iconSize + Theme.spacingM
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
id: ethernetLoadingSpinner
|
|
||||||
|
|
||||||
name: "refresh"
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.right: ethernetToggle.left
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: NetworkService.changingPreference && NetworkService.targetPreference === "ethernet"
|
elide: Text.ElideRight
|
||||||
z: 10
|
}
|
||||||
|
|
||||||
RotationAnimation {
|
|
||||||
target: ethernetLoadingSpinner
|
|
||||||
property: "rotation"
|
|
||||||
running: ethernetLoadingSpinner.visible
|
|
||||||
from: 0
|
|
||||||
to: 360
|
|
||||||
duration: 1000
|
|
||||||
loops: Animation.Infinite
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankToggle {
|
StyledText {
|
||||||
id: ethernetToggle
|
text: NetworkService.ethernetConnected ? (NetworkService.ethernetIP
|
||||||
|
|| "Connected") : "Disconnected"
|
||||||
checked: NetworkService.ethernetConnected
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
enabled: true
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
anchors.right: parent.right
|
Theme.surfaceText.b, 0.7)
|
||||||
anchors.rightMargin: Theme.spacingM
|
leftPadding: Theme.iconSize + Theme.spacingM
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
elide: Text.ElideRight
|
||||||
onClicked: {
|
|
||||||
NetworkService.toggleNetworkConnection("ethernet");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
DankIcon {
|
||||||
id: ethernetPreferenceArea
|
id: ethernetLoadingSpinner
|
||||||
|
|
||||||
anchors.fill: parent
|
name: "refresh"
|
||||||
anchors.rightMargin: 60 // Exclude toggle area
|
size: Theme.iconSize - 4
|
||||||
hoverEnabled: true
|
color: Theme.primary
|
||||||
cursorShape: (NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "ethernet") ? Qt.PointingHandCursor : Qt.ArrowCursor
|
anchors.right: ethernetToggle.left
|
||||||
enabled: NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "ethernet" && !NetworkService.changingNetworkPreference
|
anchors.rightMargin: Theme.spacingS
|
||||||
onClicked: {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
if (NetworkService.ethernetConnected && NetworkService.wifiEnabled) {
|
visible: NetworkService.changingPreference
|
||||||
|
&& NetworkService.targetPreference === "ethernet"
|
||||||
|
z: 10
|
||||||
|
|
||||||
if (NetworkService.networkStatus !== "ethernet")
|
RotationAnimation {
|
||||||
NetworkService.setNetworkPreference("ethernet");
|
target: ethernetLoadingSpinner
|
||||||
else
|
property: "rotation"
|
||||||
NetworkService.setNetworkPreference("auto");
|
running: ethernetLoadingSpinner.visible
|
||||||
}
|
from: 0
|
||||||
}
|
to: 360
|
||||||
|
duration: 1000
|
||||||
|
loops: Animation.Infinite
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on color {
|
DankToggle {
|
||||||
ColorAnimation {
|
id: ethernetToggle
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
|
checked: NetworkService.ethernetConnected
|
||||||
|
enabled: true
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onClicked: {
|
||||||
|
NetworkService.toggleNetworkConnection("ethernet")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: ethernetPreferenceArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: 60 // Exclude toggle area
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: (NetworkService.ethernetConnected && NetworkService.wifiEnabled
|
||||||
|
&& NetworkService.networkStatus
|
||||||
|
!== "ethernet") ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
enabled: NetworkService.ethernetConnected && NetworkService.wifiEnabled
|
||||||
|
&& NetworkService.networkStatus !== "ethernet"
|
||||||
|
&& !NetworkService.changingNetworkPreference
|
||||||
|
onClicked: {
|
||||||
|
if (NetworkService.ethernetConnected && NetworkService.wifiEnabled) {
|
||||||
|
|
||||||
|
if (NetworkService.networkStatus !== "ethernet")
|
||||||
|
NetworkService.setNetworkPreference("ethernet")
|
||||||
|
else
|
||||||
|
NetworkService.setNetworkPreference("auto")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,174 +8,182 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: wifiCard
|
id: wifiCard
|
||||||
|
|
||||||
property var refreshTimer
|
property var refreshTimer
|
||||||
|
|
||||||
function getWiFiSignalIcon(signalStrength) {
|
function getWiFiSignalIcon(signalStrength) {
|
||||||
switch (signalStrength) {
|
switch (signalStrength) {
|
||||||
case "excellent":
|
case "excellent":
|
||||||
return "wifi";
|
return "wifi"
|
||||||
case "good":
|
case "good":
|
||||||
return "wifi_2_bar";
|
return "wifi_2_bar"
|
||||||
case "fair":
|
case "fair":
|
||||||
return "wifi_1_bar";
|
return "wifi_1_bar"
|
||||||
case "poor":
|
case "poor":
|
||||||
return "signal_wifi_0_bar";
|
return "signal_wifi_0_bar"
|
||||||
default:
|
default:
|
||||||
return "wifi";
|
return "wifi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: 80
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
if (wifiPreferenceArea.containsMouse && NetworkService.ethernetConnected
|
||||||
|
&& NetworkService.wifiEnabled
|
||||||
|
&& NetworkService.networkStatus !== "wifi")
|
||||||
|
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||||
|
Theme.surfaceContainer.b, 0.8)
|
||||||
|
|
||||||
|
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||||
|
Theme.surfaceContainer.b, 0.5)
|
||||||
|
}
|
||||||
|
border.color: NetworkService.networkStatus === "wifi" ? Theme.primary : Qt.rgba(
|
||||||
|
Theme.outline.r,
|
||||||
|
Theme.outline.g,
|
||||||
|
Theme.outline.b,
|
||||||
|
0.12)
|
||||||
|
border.width: NetworkService.networkStatus === "wifi" ? 2 : 1
|
||||||
|
visible: NetworkService.wifiAvailable
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: wifiToggle.left
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: {
|
||||||
|
if (!NetworkService.wifiEnabled)
|
||||||
|
return "wifi_off"
|
||||||
|
else if (NetworkService.currentWifiSSID !== "")
|
||||||
|
return getWiFiSignalIcon(NetworkService.wifiSignalStrength)
|
||||||
|
else
|
||||||
|
return "wifi"
|
||||||
}
|
}
|
||||||
}
|
size: Theme.iconSize
|
||||||
|
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
|
||||||
width: parent.width
|
|
||||||
height: 80
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: {
|
|
||||||
if (wifiPreferenceArea.containsMouse && NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "wifi")
|
|
||||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8);
|
|
||||||
|
|
||||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5);
|
|
||||||
}
|
|
||||||
border.color: NetworkService.networkStatus === "wifi" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
|
||||||
border.width: NetworkService.networkStatus === "wifi" ? 2 : 1
|
|
||||||
visible: NetworkService.wifiAvailable
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: wifiToggle.left
|
}
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: {
|
|
||||||
if (!NetworkService.wifiEnabled)
|
|
||||||
return "wifi_off";
|
|
||||||
else if (NetworkService.currentWifiSSID !== "")
|
|
||||||
return getWiFiSignalIcon(NetworkService.wifiSignalStrength);
|
|
||||||
else
|
|
||||||
return "wifi";
|
|
||||||
}
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (!NetworkService.wifiEnabled)
|
|
||||||
return "WiFi is off";
|
|
||||||
else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID)
|
|
||||||
return NetworkService.currentWifiSSID || "Connected";
|
|
||||||
else
|
|
||||||
return "Not Connected";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (!NetworkService.wifiEnabled)
|
||||||
|
return "WiFi is off"
|
||||||
|
else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID)
|
||||||
|
return NetworkService.currentWifiSSID || "Connected"
|
||||||
|
else
|
||||||
|
return "Not Connected"
|
||||||
}
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
StyledText {
|
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
|
||||||
text: {
|
font.weight: Font.Medium
|
||||||
if (!NetworkService.wifiEnabled)
|
|
||||||
return "Turn on WiFi to see networks";
|
|
||||||
else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID)
|
|
||||||
return NetworkService.wifiIP || "Connected";
|
|
||||||
else
|
|
||||||
return "Select a network below";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
leftPadding: Theme.iconSize + Theme.spacingM
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
id: wifiLoadingSpinner
|
|
||||||
|
|
||||||
name: "refresh"
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.right: wifiToggle.left
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: NetworkService.changingPreference && NetworkService.targetPreference === "wifi"
|
elide: Text.ElideRight
|
||||||
z: 10
|
}
|
||||||
|
|
||||||
RotationAnimation {
|
|
||||||
target: wifiLoadingSpinner
|
|
||||||
property: "rotation"
|
|
||||||
running: wifiLoadingSpinner.visible
|
|
||||||
from: 0
|
|
||||||
to: 360
|
|
||||||
duration: 1000
|
|
||||||
loops: Animation.Infinite
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankToggle {
|
StyledText {
|
||||||
id: wifiToggle
|
text: {
|
||||||
|
if (!NetworkService.wifiEnabled)
|
||||||
checked: NetworkService.wifiEnabled
|
return "Turn on WiFi to see networks"
|
||||||
enabled: true
|
else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID)
|
||||||
toggling: NetworkService.wifiToggling
|
return NetworkService.wifiIP || "Connected"
|
||||||
anchors.right: parent.right
|
else
|
||||||
anchors.rightMargin: Theme.spacingM
|
return "Select a network below"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
}
|
||||||
onClicked: {
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
if (NetworkService.wifiEnabled) {
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
NetworkService.currentWifiSSID = "";
|
Theme.surfaceText.b, 0.7)
|
||||||
NetworkService.wifiSignalStrength = "excellent";
|
leftPadding: Theme.iconSize + Theme.spacingM
|
||||||
NetworkService.wifiNetworks = [];
|
elide: Text.ElideRight
|
||||||
NetworkService.savedWifiNetworks = [];
|
|
||||||
NetworkService.connectionStatus = "";
|
|
||||||
NetworkService.connectingSSID = "";
|
|
||||||
NetworkService.isScanning = false;
|
|
||||||
NetworkService.refreshNetworkStatus();
|
|
||||||
}
|
|
||||||
NetworkService.toggleWifiRadio();
|
|
||||||
if (refreshTimer)
|
|
||||||
refreshTimer.triggered = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
DankIcon {
|
||||||
id: wifiPreferenceArea
|
id: wifiLoadingSpinner
|
||||||
|
|
||||||
anchors.fill: parent
|
name: "refresh"
|
||||||
anchors.rightMargin: 60 // Exclude toggle area
|
size: Theme.iconSize - 4
|
||||||
hoverEnabled: true
|
color: Theme.primary
|
||||||
cursorShape: (NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "wifi") ? Qt.PointingHandCursor : Qt.ArrowCursor
|
anchors.right: wifiToggle.left
|
||||||
enabled: NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "wifi" && !NetworkService.changingNetworkPreference
|
anchors.rightMargin: Theme.spacingS
|
||||||
onClicked: {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
if (NetworkService.ethernetConnected && NetworkService.wifiEnabled) {
|
visible: NetworkService.changingPreference
|
||||||
|
&& NetworkService.targetPreference === "wifi"
|
||||||
|
z: 10
|
||||||
|
|
||||||
if (NetworkService.networkStatus !== "wifi")
|
RotationAnimation {
|
||||||
NetworkService.setNetworkPreference("wifi");
|
target: wifiLoadingSpinner
|
||||||
else
|
property: "rotation"
|
||||||
NetworkService.setNetworkPreference("auto");
|
running: wifiLoadingSpinner.visible
|
||||||
}
|
from: 0
|
||||||
}
|
to: 360
|
||||||
|
duration: 1000
|
||||||
|
loops: Animation.Infinite
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on color {
|
DankToggle {
|
||||||
ColorAnimation {
|
id: wifiToggle
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
|
checked: NetworkService.wifiEnabled
|
||||||
|
enabled: true
|
||||||
|
toggling: NetworkService.wifiToggling
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onClicked: {
|
||||||
|
if (NetworkService.wifiEnabled) {
|
||||||
|
NetworkService.currentWifiSSID = ""
|
||||||
|
NetworkService.wifiSignalStrength = "excellent"
|
||||||
|
NetworkService.wifiNetworks = []
|
||||||
|
NetworkService.savedWifiNetworks = []
|
||||||
|
NetworkService.connectionStatus = ""
|
||||||
|
NetworkService.connectingSSID = ""
|
||||||
|
NetworkService.isScanning = false
|
||||||
|
NetworkService.refreshNetworkStatus()
|
||||||
|
}
|
||||||
|
NetworkService.toggleWifiRadio()
|
||||||
|
if (refreshTimer)
|
||||||
|
refreshTimer.triggered = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: wifiPreferenceArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: 60 // Exclude toggle area
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: (NetworkService.ethernetConnected && NetworkService.wifiEnabled
|
||||||
|
&& NetworkService.networkStatus
|
||||||
|
!== "wifi") ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
enabled: NetworkService.ethernetConnected && NetworkService.wifiEnabled
|
||||||
|
&& NetworkService.networkStatus !== "wifi"
|
||||||
|
&& !NetworkService.changingNetworkPreference
|
||||||
|
onClicked: {
|
||||||
|
if (NetworkService.ethernetConnected && NetworkService.wifiEnabled) {
|
||||||
|
|
||||||
|
if (NetworkService.networkStatus !== "wifi")
|
||||||
|
NetworkService.setNetworkPreference("wifi")
|
||||||
|
else
|
||||||
|
NetworkService.setNetworkPreference("auto")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,281 +8,291 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: wifiContextMenuWindow
|
id: wifiContextMenuWindow
|
||||||
|
|
||||||
property var networkData: null
|
property var networkData: null
|
||||||
property bool menuVisible: false
|
property bool menuVisible: false
|
||||||
property var parentItem
|
property var parentItem
|
||||||
property var wifiPasswordModalRef
|
property var wifiPasswordModalRef
|
||||||
property var networkInfoModalRef
|
property var networkInfoModalRef
|
||||||
|
|
||||||
function show(x, y) {
|
function show(x, y) {
|
||||||
const menuWidth = 160;
|
const menuWidth = 160
|
||||||
wifiContextMenuWindow.visible = true;
|
wifiContextMenuWindow.visible = true
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
const menuHeight = wifiMenuColumn.implicitHeight + Theme.spacingS * 2;
|
const menuHeight = wifiMenuColumn.implicitHeight + Theme.spacingS * 2
|
||||||
let finalX = x - menuWidth / 2;
|
let finalX = x - menuWidth / 2
|
||||||
let finalY = y + 4;
|
let finalY = y + 4
|
||||||
finalX = Math.max(Theme.spacingS, Math.min(finalX, parentItem.width - menuWidth - Theme.spacingS));
|
finalX = Math.max(
|
||||||
finalY = Math.max(Theme.spacingS, Math.min(finalY, parentItem.height - menuHeight - Theme.spacingS));
|
Theme.spacingS,
|
||||||
if (finalY + menuHeight > parentItem.height - Theme.spacingS) {
|
Math.min(finalX,
|
||||||
finalY = y - menuHeight - 4;
|
parentItem.width - menuWidth - Theme.spacingS))
|
||||||
finalY = Math.max(Theme.spacingS, finalY);
|
finalY = Math.max(
|
||||||
|
Theme.spacingS,
|
||||||
|
Math.min(finalY,
|
||||||
|
parentItem.height - menuHeight - Theme.spacingS))
|
||||||
|
if (finalY + menuHeight > parentItem.height - Theme.spacingS) {
|
||||||
|
finalY = y - menuHeight - 4
|
||||||
|
finalY = Math.max(Theme.spacingS, finalY)
|
||||||
|
}
|
||||||
|
wifiContextMenuWindow.x = finalX
|
||||||
|
wifiContextMenuWindow.y = finalY
|
||||||
|
wifiContextMenuWindow.menuVisible = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
wifiContextMenuWindow.menuVisible = false
|
||||||
|
Qt.callLater(() => {
|
||||||
|
wifiContextMenuWindow.visible = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
width: 160
|
||||||
|
height: wifiMenuColumn.implicitHeight + Theme.spacingS * 2
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Theme.popupBackground()
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
z: 1000
|
||||||
|
opacity: menuVisible ? 1 : 0
|
||||||
|
scale: menuVisible ? 1 : 0.85
|
||||||
|
Component.onCompleted: {
|
||||||
|
menuVisible = false
|
||||||
|
visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 4
|
||||||
|
anchors.leftMargin: 2
|
||||||
|
anchors.rightMargin: -2
|
||||||
|
anchors.bottomMargin: -4
|
||||||
|
radius: parent.radius
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.15)
|
||||||
|
z: parent.z - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: wifiMenuColumn
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
spacing: 1
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 32
|
||||||
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: connectWifiArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: wifiContextMenuWindow.networkData
|
||||||
|
&& wifiContextMenuWindow.networkData.connected ? "wifi_off" : "wifi"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: 0.7
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: wifiContextMenuWindow.networkData
|
||||||
|
&& wifiContextMenuWindow.networkData.connected ? "Disconnect" : "Connect"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: connectWifiArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (wifiContextMenuWindow.networkData) {
|
||||||
|
if (wifiContextMenuWindow.networkData.connected) {
|
||||||
|
NetworkService.disconnectWifi()
|
||||||
|
} else {
|
||||||
|
if (wifiContextMenuWindow.networkData.saved) {
|
||||||
|
NetworkService.connectToWifi(
|
||||||
|
wifiContextMenuWindow.networkData.ssid)
|
||||||
|
} else if (wifiContextMenuWindow.networkData.secured) {
|
||||||
|
if (wifiPasswordModalRef) {
|
||||||
|
wifiPasswordModalRef.wifiPasswordSSID = wifiContextMenuWindow.networkData.ssid
|
||||||
|
wifiPasswordModalRef.wifiPasswordInput = ""
|
||||||
|
wifiPasswordModalRef.wifiPasswordModalVisible = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NetworkService.connectToWifi(
|
||||||
|
wifiContextMenuWindow.networkData.ssid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
wifiContextMenuWindow.x = finalX;
|
}
|
||||||
wifiContextMenuWindow.y = finalY;
|
wifiContextMenuWindow.hide()
|
||||||
wifiContextMenuWindow.menuVisible = true;
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function hide() {
|
Behavior on color {
|
||||||
wifiContextMenuWindow.menuVisible = false;
|
ColorAnimation {
|
||||||
Qt.callLater(() => {
|
duration: Theme.shortDuration
|
||||||
wifiContextMenuWindow.visible = false;
|
easing.type: Theme.standardEasing
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visible: false
|
|
||||||
width: 160
|
|
||||||
height: wifiMenuColumn.implicitHeight + Theme.spacingS * 2
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Theme.popupBackground()
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
z: 1000
|
|
||||||
opacity: menuVisible ? 1 : 0
|
|
||||||
scale: menuVisible ? 1 : 0.85
|
|
||||||
Component.onCompleted: {
|
|
||||||
menuVisible = false;
|
|
||||||
visible = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
width: parent.width - Theme.spacingS * 2
|
||||||
anchors.topMargin: 4
|
height: 5
|
||||||
anchors.leftMargin: 2
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.rightMargin: -2
|
color: "transparent"
|
||||||
anchors.bottomMargin: -4
|
|
||||||
radius: parent.radius
|
Rectangle {
|
||||||
color: Qt.rgba(0, 0, 0, 0.15)
|
anchors.centerIn: parent
|
||||||
z: parent.z - 1
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Rectangle {
|
||||||
id: wifiMenuColumn
|
width: parent.width
|
||||||
|
height: 32
|
||||||
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: forgetWifiArea.containsMouse ? Qt.rgba(Theme.error.r,
|
||||||
|
Theme.error.g,
|
||||||
|
Theme.error.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
visible: wifiContextMenuWindow.networkData
|
||||||
|
&& (wifiContextMenuWindow.networkData.saved
|
||||||
|
|| wifiContextMenuWindow.networkData.connected)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "delete"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: forgetWifiArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
|
opacity: 0.7
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Forget Network"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: forgetWifiArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: forgetWifiArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingS
|
hoverEnabled: true
|
||||||
spacing: 1
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
Rectangle {
|
if (wifiContextMenuWindow.networkData)
|
||||||
width: parent.width
|
NetworkService.forgetWifiNetwork(
|
||||||
height: 32
|
wifiContextMenuWindow.networkData.ssid)
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: connectWifiArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: wifiContextMenuWindow.networkData && wifiContextMenuWindow.networkData.connected ? "wifi_off" : "wifi"
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.7
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: wifiContextMenuWindow.networkData && wifiContextMenuWindow.networkData.connected ? "Disconnect" : "Connect"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: connectWifiArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (wifiContextMenuWindow.networkData) {
|
|
||||||
if (wifiContextMenuWindow.networkData.connected) {
|
|
||||||
NetworkService.disconnectWifi();
|
|
||||||
} else {
|
|
||||||
if (wifiContextMenuWindow.networkData.saved) {
|
|
||||||
NetworkService.connectToWifi(wifiContextMenuWindow.networkData.ssid);
|
|
||||||
} else if (wifiContextMenuWindow.networkData.secured) {
|
|
||||||
if (wifiPasswordModalRef) {
|
|
||||||
wifiPasswordModalRef.wifiPasswordSSID = wifiContextMenuWindow.networkData.ssid;
|
|
||||||
wifiPasswordModalRef.wifiPasswordInput = "";
|
|
||||||
wifiPasswordModalRef.wifiPasswordModalVisible = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
NetworkService.connectToWifi(wifiContextMenuWindow.networkData.ssid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wifiContextMenuWindow.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
wifiContextMenuWindow.hide()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Behavior on color {
|
||||||
width: parent.width - Theme.spacingS * 2
|
ColorAnimation {
|
||||||
height: 5
|
duration: Theme.shortDuration
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
easing.type: Theme.standardEasing
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: forgetWifiArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
|
||||||
visible: wifiContextMenuWindow.networkData && (wifiContextMenuWindow.networkData.saved || wifiContextMenuWindow.networkData.connected)
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "delete"
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: forgetWifiArea.containsMouse ? Theme.error : Theme.surfaceText
|
|
||||||
opacity: 0.7
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Forget Network"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: forgetWifiArea.containsMouse ? Theme.error : Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: forgetWifiArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (wifiContextMenuWindow.networkData)
|
|
||||||
NetworkService.forgetWifiNetwork(wifiContextMenuWindow.networkData.ssid);
|
|
||||||
|
|
||||||
wifiContextMenuWindow.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: infoWifiArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "info"
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.7
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Network Info"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: infoWifiArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (wifiContextMenuWindow.networkData && networkInfoModalRef)
|
|
||||||
networkInfoModalRef.showNetworkInfo(wifiContextMenuWindow.networkData.ssid, wifiContextMenuWindow.networkData);
|
|
||||||
|
|
||||||
wifiContextMenuWindow.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Rectangle {
|
||||||
NumberAnimation {
|
width: parent.width
|
||||||
duration: Theme.mediumDuration
|
height: 32
|
||||||
easing.type: Theme.emphasizedEasing
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: infoWifiArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "info"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: 0.7
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
StyledText {
|
||||||
|
text: "Network Info"
|
||||||
Behavior on scale {
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
NumberAnimation {
|
color: Theme.surfaceText
|
||||||
duration: Theme.mediumDuration
|
font.weight: Font.Normal
|
||||||
easing.type: Theme.emphasizedEasing
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: infoWifiArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (wifiContextMenuWindow.networkData && networkInfoModalRef)
|
||||||
|
networkInfoModalRef.showNetworkInfo(
|
||||||
|
wifiContextMenuWindow.networkData.ssid,
|
||||||
|
wifiContextMenuWindow.networkData)
|
||||||
|
|
||||||
|
wifiContextMenuWindow.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,305 +8,316 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var wifiContextMenuWindow
|
property var wifiContextMenuWindow
|
||||||
property var sortedWifiNetworks
|
property var sortedWifiNetworks
|
||||||
property var wifiPasswordModalRef
|
property var wifiPasswordModalRef
|
||||||
|
|
||||||
function getWiFiSignalIcon(signalStrength) {
|
function getWiFiSignalIcon(signalStrength) {
|
||||||
switch (signalStrength) {
|
switch (signalStrength) {
|
||||||
case "excellent":
|
case "excellent":
|
||||||
return "wifi";
|
return "wifi"
|
||||||
case "good":
|
case "good":
|
||||||
return "wifi_2_bar";
|
return "wifi_2_bar"
|
||||||
case "fair":
|
case "fair":
|
||||||
return "wifi_1_bar";
|
return "wifi_1_bar"
|
||||||
case "poor":
|
case "poor":
|
||||||
return "signal_wifi_0_bar";
|
return "signal_wifi_0_bar"
|
||||||
default:
|
default:
|
||||||
return "wifi";
|
return "wifi"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: 100
|
anchors.topMargin: 100
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
visible: NetworkService.wifiEnabled
|
visible: NetworkService.wifiEnabled
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Row {
|
StyledText {
|
||||||
width: parent.width
|
text: "Available Networks"
|
||||||
spacing: Theme.spacingS
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
StyledText {
|
Item {
|
||||||
text: "Available Networks"
|
width: parent.width - 170
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
height: 1
|
||||||
color: Theme.surfaceText
|
}
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
Rectangle {
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
radius: 14
|
||||||
|
color: refreshAreaSpan.containsMouse ? Qt.rgba(
|
||||||
|
Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : NetworkService.isScanning ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
id: refreshIconSpan
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "refresh"
|
||||||
|
size: Theme.iconSize - 6
|
||||||
|
color: refreshAreaSpan.containsMouse ? Theme.primary : Theme.surfaceText
|
||||||
|
rotation: NetworkService.isScanning ? refreshIconSpan.rotation : 0
|
||||||
|
|
||||||
|
RotationAnimation {
|
||||||
|
target: refreshIconSpan
|
||||||
|
property: "rotation"
|
||||||
|
running: NetworkService.isScanning
|
||||||
|
from: 0
|
||||||
|
to: 360
|
||||||
|
duration: 1000
|
||||||
|
loops: Animation.Infinite
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Behavior on rotation {
|
||||||
width: parent.width - 170
|
RotationAnimation {
|
||||||
height: 1
|
duration: 200
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: refreshAreaSpan
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (!NetworkService.isScanning) {
|
||||||
|
refreshIconSpan.rotation += 30
|
||||||
|
NetworkService.scanWifi()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - 40
|
||||||
|
clip: true
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: spanningNetworksColumn.height
|
||||||
|
boundsBehavior: Flickable.DragAndOvershootBounds
|
||||||
|
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
||||||
|
flickDeceleration: 1500
|
||||||
|
maximumFlickVelocity: 2000
|
||||||
|
|
||||||
|
// Custom wheel handler for Qt 6.9+ responsive mouse wheel scrolling
|
||||||
|
WheelHandler {
|
||||||
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||||
|
onWheel: event => {
|
||||||
|
let delta = event.pixelDelta.y
|
||||||
|
!== 0 ? event.pixelDelta.y * 1.8 : event.angleDelta.y / 120 * 60
|
||||||
|
let newY = parent.contentY - delta
|
||||||
|
newY = Math.max(0,
|
||||||
|
Math.min(parent.contentHeight - parent.height,
|
||||||
|
newY))
|
||||||
|
parent.contentY = newY
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: spanningNetworksColumn
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: NetworkService.wifiAvailable
|
||||||
|
&& NetworkService.wifiEnabled ? sortedWifiNetworks : []
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 28
|
width: spanningNetworksColumn.width
|
||||||
height: 28
|
height: 38
|
||||||
radius: 14
|
radius: Theme.cornerRadiusSmall
|
||||||
color: refreshAreaSpan.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : NetworkService.isScanning ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"
|
color: networkArea2.containsMouse ? Qt.rgba(
|
||||||
|
Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.08) : modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||||
|
border.color: modelData.connected ? Theme.primary : "transparent"
|
||||||
|
border.width: modelData.connected ? 1 : 0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingXS
|
||||||
|
anchors.rightMargin: Theme.spacingM // Extra right margin for scrollbar
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
id: refreshIconSpan
|
id: signalIcon2
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "refresh"
|
|
||||||
size: Theme.iconSize - 6
|
|
||||||
color: refreshAreaSpan.containsMouse ? Theme.primary : Theme.surfaceText
|
|
||||||
rotation: NetworkService.isScanning ? refreshIconSpan.rotation : 0
|
|
||||||
|
|
||||||
RotationAnimation {
|
|
||||||
target: refreshIconSpan
|
|
||||||
property: "rotation"
|
|
||||||
running: NetworkService.isScanning
|
|
||||||
from: 0
|
|
||||||
to: 360
|
|
||||||
duration: 1000
|
|
||||||
loops: Animation.Infinite
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on rotation {
|
|
||||||
RotationAnimation {
|
|
||||||
duration: 200
|
|
||||||
easing.type: Easing.OutQuad
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
name: getWiFiSignalIcon(modelData.signalStrength)
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
Column {
|
||||||
id: refreshAreaSpan
|
anchors.left: signalIcon2.right
|
||||||
|
anchors.leftMargin: Theme.spacingXS
|
||||||
|
anchors.right: rightIcons2.left
|
||||||
|
anchors.rightMargin: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
anchors.fill: parent
|
StyledText {
|
||||||
hoverEnabled: true
|
width: parent.width
|
||||||
cursorShape: Qt.PointingHandCursor
|
text: modelData.ssid
|
||||||
onClicked: {
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
if (!NetworkService.isScanning) {
|
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||||
refreshIconSpan.rotation += 30;
|
font.weight: modelData.connected ? Font.Medium : Font.Normal
|
||||||
NetworkService.scanWifi();
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: {
|
||||||
|
if (modelData.connected)
|
||||||
|
return "Connected"
|
||||||
|
|
||||||
|
if (NetworkService.connectionStatus === "connecting"
|
||||||
|
&& NetworkService.connectingSSID === modelData.ssid)
|
||||||
|
return "Connecting..."
|
||||||
|
|
||||||
|
if (NetworkService.connectionStatus === "invalid_password"
|
||||||
|
&& NetworkService.connectingSSID === modelData.ssid)
|
||||||
|
return "Invalid password"
|
||||||
|
|
||||||
|
if (modelData.saved)
|
||||||
|
return "Saved" + (modelData.secured ? " • Secured" : " • Open")
|
||||||
|
|
||||||
|
return modelData.secured ? "Secured" : "Open"
|
||||||
}
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall - 1
|
||||||
|
color: {
|
||||||
|
if (NetworkService.connectionStatus === "connecting"
|
||||||
|
&& NetworkService.connectingSSID === modelData.ssid)
|
||||||
|
return Theme.primary
|
||||||
|
|
||||||
|
if (NetworkService.connectionStatus === "invalid_password"
|
||||||
|
&& NetworkService.connectingSSID === modelData.ssid)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
}
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: rightIcons2
|
||||||
|
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "lock"
|
||||||
|
size: Theme.iconSize - 8
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.6)
|
||||||
|
visible: modelData.secured
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: wifiMenuButton
|
||||||
|
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
radius: 12
|
||||||
|
color: wifiMenuButtonArea.containsMouse ? Qt.rgba(
|
||||||
|
Theme.surfaceText.r,
|
||||||
|
Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b,
|
||||||
|
0.08) : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "more_vert"
|
||||||
|
size: Theme.iconSize - 8
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: 0.6
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: wifiMenuButtonArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
wifiContextMenuWindow.networkData = modelData
|
||||||
|
let buttonCenter = wifiMenuButtonArea.width / 2
|
||||||
|
let buttonBottom = wifiMenuButtonArea.height
|
||||||
|
let globalPos = wifiMenuButtonArea.mapToItem(
|
||||||
|
wifiContextMenuWindow.parentItem, buttonCenter,
|
||||||
|
buttonBottom)
|
||||||
|
Qt.callLater(() => {
|
||||||
|
wifiContextMenuWindow.show(globalPos.x,
|
||||||
|
globalPos.y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: networkArea2
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: 32 // Exclude menu button area
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (modelData.connected)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (modelData.saved) {
|
||||||
|
NetworkService.connectToWifi(modelData.ssid)
|
||||||
|
} else if (modelData.secured) {
|
||||||
|
if (wifiPasswordModalRef) {
|
||||||
|
wifiPasswordModalRef.wifiPasswordSSID = modelData.ssid
|
||||||
|
wifiPasswordModalRef.wifiPasswordInput = ""
|
||||||
|
wifiPasswordModalRef.wifiPasswordModalVisible = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NetworkService.connectToWifi(modelData.ssid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Flickable {
|
ScrollBar.vertical: ScrollBar {
|
||||||
width: parent.width
|
policy: ScrollBar.AsNeeded
|
||||||
height: parent.height - 40
|
|
||||||
clip: true
|
|
||||||
contentWidth: width
|
|
||||||
contentHeight: spanningNetworksColumn.height
|
|
||||||
boundsBehavior: Flickable.DragAndOvershootBounds
|
|
||||||
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
|
||||||
flickDeceleration: 1500
|
|
||||||
maximumFlickVelocity: 2000
|
|
||||||
|
|
||||||
// Custom wheel handler for Qt 6.9+ responsive mouse wheel scrolling
|
|
||||||
WheelHandler {
|
|
||||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
|
||||||
onWheel: (event) => {
|
|
||||||
let delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y * 1.8 : event.angleDelta.y / 120 * 60
|
|
||||||
let newY = parent.contentY - delta
|
|
||||||
newY = Math.max(0, Math.min(parent.contentHeight - parent.height, newY))
|
|
||||||
parent.contentY = newY
|
|
||||||
event.accepted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: spanningNetworksColumn
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: NetworkService.wifiAvailable && NetworkService.wifiEnabled ? sortedWifiNetworks : []
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: spanningNetworksColumn.width
|
|
||||||
height: 38
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: networkArea2.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
border.color: modelData.connected ? Theme.primary : "transparent"
|
|
||||||
border.width: modelData.connected ? 1 : 0
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingXS
|
|
||||||
anchors.rightMargin: Theme.spacingM // Extra right margin for scrollbar
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
id: signalIcon2
|
|
||||||
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
name: getWiFiSignalIcon(modelData.signalStrength)
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.left: signalIcon2.right
|
|
||||||
anchors.leftMargin: Theme.spacingXS
|
|
||||||
anchors.right: rightIcons2.left
|
|
||||||
anchors.rightMargin: Theme.spacingXS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
width: parent.width
|
|
||||||
text: modelData.ssid
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: modelData.connected ? Font.Medium : Font.Normal
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
width: parent.width
|
|
||||||
text: {
|
|
||||||
if (modelData.connected)
|
|
||||||
return "Connected";
|
|
||||||
|
|
||||||
if (NetworkService.connectionStatus === "connecting" && NetworkService.connectingSSID === modelData.ssid)
|
|
||||||
return "Connecting...";
|
|
||||||
|
|
||||||
if (NetworkService.connectionStatus === "invalid_password" && NetworkService.connectingSSID === modelData.ssid)
|
|
||||||
return "Invalid password";
|
|
||||||
|
|
||||||
if (modelData.saved)
|
|
||||||
return "Saved" + (modelData.secured ? " • Secured" : " • Open");
|
|
||||||
|
|
||||||
return modelData.secured ? "Secured" : "Open";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall - 1
|
|
||||||
color: {
|
|
||||||
if (NetworkService.connectionStatus === "connecting" && NetworkService.connectingSSID === modelData.ssid)
|
|
||||||
return Theme.primary;
|
|
||||||
|
|
||||||
if (NetworkService.connectionStatus === "invalid_password" && NetworkService.connectingSSID === modelData.ssid)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
|
|
||||||
}
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: rightIcons2
|
|
||||||
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "lock"
|
|
||||||
size: Theme.iconSize - 8
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
|
||||||
visible: modelData.secured
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: wifiMenuButton
|
|
||||||
|
|
||||||
width: 24
|
|
||||||
height: 24
|
|
||||||
radius: 12
|
|
||||||
color: wifiMenuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "more_vert"
|
|
||||||
size: Theme.iconSize - 8
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.6
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: wifiMenuButtonArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
wifiContextMenuWindow.networkData = modelData;
|
|
||||||
let buttonCenter = wifiMenuButtonArea.width / 2;
|
|
||||||
let buttonBottom = wifiMenuButtonArea.height;
|
|
||||||
let globalPos = wifiMenuButtonArea.mapToItem(wifiContextMenuWindow.parentItem, buttonCenter, buttonBottom);
|
|
||||||
Qt.callLater(() => {
|
|
||||||
wifiContextMenuWindow.show(globalPos.x, globalPos.y);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: networkArea2
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.rightMargin: 32 // Exclude menu button area
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (modelData.connected)
|
|
||||||
return ;
|
|
||||||
|
|
||||||
if (modelData.saved) {
|
|
||||||
NetworkService.connectToWifi(modelData.ssid);
|
|
||||||
} else if (modelData.secured) {
|
|
||||||
if (wifiPasswordModalRef) {
|
|
||||||
wifiPasswordModalRef.wifiPasswordSSID = modelData.ssid;
|
|
||||||
wifiPasswordModalRef.wifiPasswordInput = "";
|
|
||||||
wifiPasswordModalRef.wifiPasswordModalVisible = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
NetworkService.connectToWifi(modelData.ssid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
|
||||||
policy: ScrollBar.AsNeeded
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,322 +9,332 @@ import qs.Widgets
|
|||||||
import qs.Modules.ControlCenter.Network
|
import qs.Modules.ControlCenter.Network
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: networkTab
|
id: networkTab
|
||||||
|
|
||||||
property var wifiPasswordModalRef: wifiPasswordModal
|
property var wifiPasswordModalRef: wifiPasswordModal
|
||||||
property var networkInfoModalRef: networkInfoModal
|
property var networkInfoModalRef: networkInfoModal
|
||||||
|
|
||||||
property var sortedWifiNetworks: {
|
property var sortedWifiNetworks: {
|
||||||
if (!NetworkService.wifiAvailable || !NetworkService.wifiEnabled) {
|
if (!NetworkService.wifiAvailable || !NetworkService.wifiEnabled) {
|
||||||
return [];
|
return []
|
||||||
}
|
|
||||||
|
|
||||||
var allNetworks = NetworkService.wifiNetworks;
|
|
||||||
var savedNetworks = NetworkService.savedWifiNetworks;
|
|
||||||
var currentSSID = NetworkService.currentWifiSSID;
|
|
||||||
var signalStrength = NetworkService.wifiSignalStrength;
|
|
||||||
var refreshTrigger = forceRefresh; // Force recalculation
|
|
||||||
|
|
||||||
var networks = [...allNetworks];
|
|
||||||
|
|
||||||
networks.forEach(function(network) {
|
|
||||||
network.connected = (network.ssid === currentSSID);
|
|
||||||
network.saved = savedNetworks.some(function(saved) {
|
|
||||||
return saved.ssid === network.ssid;
|
|
||||||
});
|
|
||||||
if (network.connected && signalStrength) {
|
|
||||||
network.signalStrength = signalStrength;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
networks.sort(function(a, b) {
|
|
||||||
if (a.connected && !b.connected) return -1;
|
|
||||||
if (!a.connected && b.connected) return 1;
|
|
||||||
return b.signal - a.signal;
|
|
||||||
});
|
|
||||||
|
|
||||||
return networks;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property int forceRefresh: 0
|
var allNetworks = NetworkService.wifiNetworks
|
||||||
|
var savedNetworks = NetworkService.savedWifiNetworks
|
||||||
|
var currentSSID = NetworkService.currentWifiSSID
|
||||||
|
var signalStrength = NetworkService.wifiSignalStrength
|
||||||
|
var refreshTrigger = forceRefresh
|
||||||
|
|
||||||
Connections {
|
// Force recalculation
|
||||||
target: NetworkService
|
var networks = [...allNetworks]
|
||||||
function onNetworksUpdated() {
|
|
||||||
forceRefresh++;
|
networks.forEach(function (network) {
|
||||||
}
|
network.connected = (network.ssid === currentSSID)
|
||||||
|
network.saved = savedNetworks.some(function (saved) {
|
||||||
|
return saved.ssid === network.ssid
|
||||||
|
})
|
||||||
|
if (network.connected && signalStrength) {
|
||||||
|
network.signalStrength = signalStrength
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
networks.sort(function (a, b) {
|
||||||
|
if (a.connected && !b.connected)
|
||||||
|
return -1
|
||||||
|
if (!a.connected && b.connected)
|
||||||
|
return 1
|
||||||
|
return b.signal - a.signal
|
||||||
|
})
|
||||||
|
|
||||||
|
return networks
|
||||||
|
}
|
||||||
|
|
||||||
|
property int forceRefresh: 0
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: NetworkService
|
||||||
|
function onNetworksUpdated() {
|
||||||
|
forceRefresh++
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
NetworkService.addRef();
|
NetworkService.addRef()
|
||||||
NetworkService.autoRefreshEnabled = true;
|
NetworkService.autoRefreshEnabled = true
|
||||||
if (NetworkService.wifiEnabled)
|
if (NetworkService.wifiEnabled)
|
||||||
NetworkService.scanWifi();
|
NetworkService.scanWifi()
|
||||||
wifiMonitorTimer.start();
|
wifiMonitorTimer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
NetworkService.removeRef();
|
NetworkService.removeRef()
|
||||||
NetworkService.autoRefreshEnabled = false;
|
NetworkService.autoRefreshEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: (parent.width - Theme.spacingM) / 2
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
height: parent.height
|
height: parent.height
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Flickable {
|
Flickable {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height - 30
|
height: parent.height - 30
|
||||||
clip: true
|
clip: true
|
||||||
contentWidth: width
|
contentWidth: width
|
||||||
contentHeight: wifiContent.height
|
contentHeight: wifiContent.height
|
||||||
boundsBehavior: Flickable.DragAndOvershootBounds
|
boundsBehavior: Flickable.DragAndOvershootBounds
|
||||||
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
||||||
flickDeceleration: 1500
|
flickDeceleration: 1500
|
||||||
maximumFlickVelocity: 2000
|
maximumFlickVelocity: 2000
|
||||||
|
|
||||||
// Custom wheel handler for Qt 6.9+ responsive mouse wheel scrolling
|
// Custom wheel handler for Qt 6.9+ responsive mouse wheel scrolling
|
||||||
WheelHandler {
|
WheelHandler {
|
||||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||||
onWheel: (event) => {
|
onWheel: event => {
|
||||||
let delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y * 1.8 : event.angleDelta.y / 120 * 60
|
let delta = event.pixelDelta.y
|
||||||
let newY = parent.contentY - delta
|
!== 0 ? event.pixelDelta.y * 1.8 : event.angleDelta.y / 120 * 60
|
||||||
newY = Math.max(0, Math.min(parent.contentHeight - parent.height, newY))
|
let newY = parent.contentY - delta
|
||||||
parent.contentY = newY
|
newY = Math.max(
|
||||||
event.accepted = true
|
0, Math.min(parent.contentHeight - parent.height, newY))
|
||||||
}
|
parent.contentY = newY
|
||||||
}
|
event.accepted = true
|
||||||
|
}
|
||||||
Column {
|
|
||||||
id: wifiContent
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
WiFiCard {
|
|
||||||
refreshTimer: refreshTimer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
|
||||||
policy: ScrollBar.AsNeeded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: (parent.width - Theme.spacingM) / 2
|
id: wifiContent
|
||||||
height: parent.height
|
width: parent.width
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Flickable {
|
WiFiCard {
|
||||||
width: parent.width
|
refreshTimer: refreshTimer
|
||||||
height: parent.height - 30
|
}
|
||||||
clip: true
|
|
||||||
contentWidth: width
|
|
||||||
contentHeight: ethernetContent.height
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
|
||||||
flickDeceleration: 1500
|
|
||||||
maximumFlickVelocity: 2000
|
|
||||||
|
|
||||||
// Custom wheel handler for Qt 6.9+ responsive mouse wheel scrolling
|
|
||||||
WheelHandler {
|
|
||||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
|
||||||
onWheel: (event) => {
|
|
||||||
let delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y * 1.8 : event.angleDelta.y / 120 * 60
|
|
||||||
let newY = parent.contentY - delta
|
|
||||||
newY = Math.max(0, Math.min(parent.contentHeight - parent.height, newY))
|
|
||||||
parent.contentY = newY
|
|
||||||
event.accepted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: ethernetContent
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
EthernetCard {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
|
||||||
policy: ScrollBar.AsNeeded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
policy: ScrollBar.AsNeeded
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Column {
|
||||||
anchors.top: parent.top
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
anchors.topMargin: 100
|
height: parent.height
|
||||||
anchors.left: parent.left
|
spacing: Theme.spacingS
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
Flickable {
|
||||||
color: "transparent"
|
width: parent.width
|
||||||
visible: !NetworkService.wifiEnabled
|
height: parent.height - 30
|
||||||
|
clip: true
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: ethernetContent.height
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
||||||
|
flickDeceleration: 1500
|
||||||
|
maximumFlickVelocity: 2000
|
||||||
|
|
||||||
|
// Custom wheel handler for Qt 6.9+ responsive mouse wheel scrolling
|
||||||
|
WheelHandler {
|
||||||
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||||
|
onWheel: event => {
|
||||||
|
let delta = event.pixelDelta.y
|
||||||
|
!== 0 ? event.pixelDelta.y * 1.8 : event.angleDelta.y / 120 * 60
|
||||||
|
let newY = parent.contentY - delta
|
||||||
|
newY = Math.max(
|
||||||
|
0, Math.min(parent.contentHeight - parent.height, newY))
|
||||||
|
parent.contentY = newY
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.centerIn: parent
|
id: ethernetContent
|
||||||
spacing: Theme.spacingM
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
DankIcon {
|
EthernetCard {}
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
name: "wifi_off"
|
|
||||||
size: 48
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
text: "WiFi is turned off"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
text: "Turn on WiFi to see networks"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
WiFiNetworksList {
|
ScrollBar.vertical: ScrollBar {
|
||||||
wifiContextMenuWindow: wifiContextMenuWindow
|
policy: ScrollBar.AsNeeded
|
||||||
sortedWifiNetworks: networkTab.sortedWifiNetworks
|
|
||||||
wifiPasswordModalRef: networkTab.wifiPasswordModalRef
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: refreshTimer
|
|
||||||
interval: 2000
|
|
||||||
running: visible && refreshTimer.triggered
|
|
||||||
property bool triggered: false
|
|
||||||
onTriggered: {
|
|
||||||
NetworkService.refreshNetworkStatus();
|
|
||||||
if (NetworkService.wifiEnabled && !NetworkService.isScanning) {
|
|
||||||
NetworkService.scanWifi();
|
|
||||||
}
|
|
||||||
triggered = false;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Rectangle {
|
||||||
target: NetworkService
|
anchors.top: parent.top
|
||||||
function onWifiEnabledChanged() {
|
anchors.topMargin: 100
|
||||||
if (NetworkService.wifiEnabled && visible) {
|
anchors.left: parent.left
|
||||||
wifiScanDelayTimer.start();
|
anchors.right: parent.right
|
||||||
wifiMonitorTimer.start();
|
anchors.bottom: parent.bottom
|
||||||
} else {
|
color: "transparent"
|
||||||
NetworkService.currentWifiSSID = "";
|
visible: !NetworkService.wifiEnabled
|
||||||
NetworkService.wifiSignalStrength = "excellent";
|
|
||||||
NetworkService.wifiNetworks = [];
|
Column {
|
||||||
NetworkService.savedWifiNetworks = [];
|
anchors.centerIn: parent
|
||||||
NetworkService.connectionStatus = "";
|
spacing: Theme.spacingM
|
||||||
NetworkService.connectingSSID = "";
|
|
||||||
NetworkService.isScanning = false;
|
DankIcon {
|
||||||
NetworkService.refreshNetworkStatus();
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
wifiMonitorTimer.stop();
|
name: "wifi_off"
|
||||||
}
|
size: 48
|
||||||
}
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: "WiFi is turned off"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.6)
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: "Turn on WiFi to see networks"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.4)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
WiFiNetworksList {
|
||||||
id: wifiScanDelayTimer
|
wifiContextMenuWindow: wifiContextMenuWindow
|
||||||
interval: 1500
|
sortedWifiNetworks: networkTab.sortedWifiNetworks
|
||||||
running: false
|
wifiPasswordModalRef: networkTab.wifiPasswordModalRef
|
||||||
repeat: false
|
}
|
||||||
onTriggered: {
|
|
||||||
if (NetworkService.wifiEnabled && visible) {
|
Timer {
|
||||||
if (!NetworkService.isScanning) {
|
id: refreshTimer
|
||||||
NetworkService.scanWifi();
|
interval: 2000
|
||||||
} else {
|
running: visible && refreshTimer.triggered
|
||||||
wifiRetryTimer.start();
|
property bool triggered: false
|
||||||
}
|
onTriggered: {
|
||||||
}
|
NetworkService.refreshNetworkStatus()
|
||||||
}
|
if (NetworkService.wifiEnabled && !NetworkService.isScanning) {
|
||||||
|
NetworkService.scanWifi()
|
||||||
|
}
|
||||||
|
triggered = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Connections {
|
||||||
id: wifiRetryTimer
|
target: NetworkService
|
||||||
interval: 2000
|
function onWifiEnabledChanged() {
|
||||||
running: false
|
if (NetworkService.wifiEnabled && visible) {
|
||||||
repeat: false
|
wifiScanDelayTimer.start()
|
||||||
onTriggered: {
|
wifiMonitorTimer.start()
|
||||||
if (NetworkService.wifiEnabled && visible && NetworkService.wifiNetworks.length === 0) {
|
} else {
|
||||||
if (!NetworkService.isScanning) {
|
NetworkService.currentWifiSSID = ""
|
||||||
NetworkService.scanWifi();
|
NetworkService.wifiSignalStrength = "excellent"
|
||||||
}
|
NetworkService.wifiNetworks = []
|
||||||
}
|
NetworkService.savedWifiNetworks = []
|
||||||
}
|
NetworkService.connectionStatus = ""
|
||||||
|
NetworkService.connectingSSID = ""
|
||||||
|
NetworkService.isScanning = false
|
||||||
|
NetworkService.refreshNetworkStatus()
|
||||||
|
wifiMonitorTimer.stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: wifiMonitorTimer
|
id: wifiScanDelayTimer
|
||||||
interval: 8000 // Check every 8 seconds
|
interval: 1500
|
||||||
running: false
|
running: false
|
||||||
repeat: true
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!visible || !NetworkService.wifiEnabled) {
|
if (NetworkService.wifiEnabled && visible) {
|
||||||
running = false;
|
if (!NetworkService.isScanning) {
|
||||||
return;
|
NetworkService.scanWifi()
|
||||||
}
|
|
||||||
|
|
||||||
var shouldScan = false;
|
|
||||||
var reason = "";
|
|
||||||
|
|
||||||
if (NetworkService.networkStatus !== "wifi") {
|
|
||||||
shouldScan = true;
|
|
||||||
reason = "not connected to WiFi";
|
|
||||||
}
|
|
||||||
else if (NetworkService.wifiNetworks.length === 0) {
|
|
||||||
shouldScan = true;
|
|
||||||
reason = "no networks cached";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldScan && !NetworkService.isScanning) {
|
|
||||||
NetworkService.scanWifi();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible && NetworkService.wifiEnabled) {
|
|
||||||
wifiMonitorTimer.start();
|
|
||||||
} else {
|
} else {
|
||||||
wifiMonitorTimer.stop();
|
wifiRetryTimer.start()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WiFiContextMenu {
|
Timer {
|
||||||
id: wifiContextMenuWindow
|
id: wifiRetryTimer
|
||||||
parentItem: networkTab
|
interval: 2000
|
||||||
wifiPasswordModalRef: networkTab.wifiPasswordModalRef
|
running: false
|
||||||
networkInfoModalRef: networkTab.networkInfoModalRef
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (NetworkService.wifiEnabled && visible
|
||||||
|
&& NetworkService.wifiNetworks.length === 0) {
|
||||||
|
if (!NetworkService.isScanning) {
|
||||||
|
NetworkService.scanWifi()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: wifiMonitorTimer
|
||||||
|
interval: 8000 // Check every 8 seconds
|
||||||
|
running: false
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
if (!visible || !NetworkService.wifiEnabled) {
|
||||||
|
running = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var shouldScan = false
|
||||||
|
var reason = ""
|
||||||
|
|
||||||
|
if (NetworkService.networkStatus !== "wifi") {
|
||||||
|
shouldScan = true
|
||||||
|
reason = "not connected to WiFi"
|
||||||
|
} else if (NetworkService.wifiNetworks.length === 0) {
|
||||||
|
shouldScan = true
|
||||||
|
reason = "no networks cached"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldScan && !NetworkService.isScanning) {
|
||||||
|
NetworkService.scanWifi()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible && NetworkService.wifiEnabled) {
|
||||||
|
wifiMonitorTimer.start()
|
||||||
|
} else {
|
||||||
|
wifiMonitorTimer.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WiFiContextMenu {
|
||||||
|
id: wifiContextMenuWindow
|
||||||
|
parentItem: networkTab
|
||||||
|
wifiPasswordModalRef: networkTab.wifiPasswordModalRef
|
||||||
|
networkInfoModalRef: networkTab.networkInfoModalRef
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: wifiContextMenuWindow.visible
|
||||||
|
onClicked: {
|
||||||
|
wifiContextMenuWindow.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
x: wifiContextMenuWindow.x
|
||||||
visible: wifiContextMenuWindow.visible
|
y: wifiContextMenuWindow.y
|
||||||
onClicked: {
|
width: wifiContextMenuWindow.width
|
||||||
wifiContextMenuWindow.hide();
|
height: wifiContextMenuWindow.height
|
||||||
}
|
onClicked: {
|
||||||
|
|
||||||
MouseArea {
|
}
|
||||||
x: wifiContextMenuWindow.x
|
|
||||||
y: wifiContextMenuWindow.y
|
|
||||||
width: wifiContextMenuWindow.width
|
|
||||||
height: wifiContextMenuWindow.height
|
|
||||||
onClicked: {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,296 +8,307 @@ import qs.Common
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool powerMenuVisible: false
|
property bool powerMenuVisible: false
|
||||||
property bool powerConfirmVisible: false
|
property bool powerConfirmVisible: false
|
||||||
property string powerConfirmAction: ""
|
property string powerConfirmAction: ""
|
||||||
property string powerConfirmTitle: ""
|
property string powerConfirmTitle: ""
|
||||||
property string powerConfirmMessage: ""
|
property string powerConfirmMessage: ""
|
||||||
|
|
||||||
visible: powerMenuVisible
|
visible: powerMenuVisible
|
||||||
implicitWidth: 400
|
implicitWidth: 400
|
||||||
implicitHeight: 320
|
implicitHeight: 320
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: true
|
top: true
|
||||||
left: true
|
left: true
|
||||||
right: true
|
right: true
|
||||||
bottom: true
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
powerMenuVisible = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Math.min(320, parent.width - Theme.spacingL * 2)
|
||||||
|
height: 320 // Fixed height to prevent cropping
|
||||||
|
x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL)
|
||||||
|
y: Theme.barHeight + Theme.spacingXS
|
||||||
|
color: Theme.popupBackground()
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
opacity: powerMenuVisible ? 1 : 0
|
||||||
|
scale: powerMenuVisible ? 1 : 0.85
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
anchors.fill: parent
|
||||||
powerMenuVisible = false;
|
onClicked: {
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Column {
|
||||||
width: Math.min(320, parent.width - Theme.spacingL * 2)
|
anchors.fill: parent
|
||||||
height: 320 // Fixed height to prevent cropping
|
anchors.margins: Theme.spacingL
|
||||||
x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL)
|
spacing: Theme.spacingM
|
||||||
y: Theme.barHeight + Theme.spacingXS
|
|
||||||
color: Theme.popupBackground()
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
opacity: powerMenuVisible ? 1 : 0
|
|
||||||
scale: powerMenuVisible ? 1 : 0.85
|
|
||||||
|
|
||||||
MouseArea {
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
anchors.fill: parent
|
StyledText {
|
||||||
onClicked: {
|
text: "Power Options"
|
||||||
}
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Item {
|
||||||
anchors.fill: parent
|
width: parent.width - 150
|
||||||
anchors.margins: Theme.spacingL
|
height: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||||
|
onClicked: {
|
||||||
|
powerMenuVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 50
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: logoutArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.08) : Qt.rgba(
|
||||||
|
Theme.surfaceVariant.r,
|
||||||
|
Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.08)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Row {
|
DankIcon {
|
||||||
width: parent.width
|
name: "logout"
|
||||||
|
size: Theme.iconSize
|
||||||
StyledText {
|
color: Theme.surfaceText
|
||||||
text: "Power Options"
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width - 150
|
|
||||||
height: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
iconName: "close"
|
|
||||||
iconSize: Theme.iconSize - 4
|
|
||||||
iconColor: Theme.surfaceText
|
|
||||||
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
|
||||||
onClicked: {
|
|
||||||
powerMenuVisible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
StyledText {
|
||||||
width: parent.width
|
text: "Log Out"
|
||||||
spacing: Theme.spacingS
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
Rectangle {
|
font.weight: Font.Medium
|
||||||
width: parent.width
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
height: 50
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: logoutArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "logout"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Log Out"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: logoutArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
powerMenuVisible = false;
|
|
||||||
root.powerConfirmAction = "logout";
|
|
||||||
root.powerConfirmTitle = "Log Out";
|
|
||||||
root.powerConfirmMessage = "Are you sure you want to log out?";
|
|
||||||
root.powerConfirmVisible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 50
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: suspendArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "bedtime"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Suspend"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: suspendArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
powerMenuVisible = false;
|
|
||||||
root.powerConfirmAction = "suspend";
|
|
||||||
root.powerConfirmTitle = "Suspend";
|
|
||||||
root.powerConfirmMessage = "Are you sure you want to suspend the system?";
|
|
||||||
root.powerConfirmVisible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 50
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: rebootArea.containsMouse ? Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "restart_alt"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Reboot"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: rebootArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
powerMenuVisible = false;
|
|
||||||
root.powerConfirmAction = "reboot";
|
|
||||||
root.powerConfirmTitle = "Reboot";
|
|
||||||
root.powerConfirmMessage = "Are you sure you want to reboot the system?";
|
|
||||||
root.powerConfirmVisible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 50
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: powerOffArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "power_settings_new"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Power Off"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: powerOffArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
powerMenuVisible = false;
|
|
||||||
root.powerConfirmAction = "poweroff";
|
|
||||||
root.powerConfirmTitle = "Power Off";
|
|
||||||
root.powerConfirmMessage = "Are you sure you want to power off the system?";
|
|
||||||
root.powerConfirmVisible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: logoutArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
powerMenuVisible = false
|
||||||
|
root.powerConfirmAction = "logout"
|
||||||
|
root.powerConfirmTitle = "Log Out"
|
||||||
|
root.powerConfirmMessage = "Are you sure you want to log out?"
|
||||||
|
root.powerConfirmVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Rectangle {
|
||||||
NumberAnimation {
|
width: parent.width
|
||||||
duration: Theme.mediumDuration
|
height: 50
|
||||||
easing.type: Theme.emphasizedEasing
|
radius: Theme.cornerRadius
|
||||||
|
color: suspendArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.08) : Qt.rgba(
|
||||||
|
Theme.surfaceVariant.r,
|
||||||
|
Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.08)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "bedtime"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Suspend"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: suspendArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
powerMenuVisible = false
|
||||||
|
root.powerConfirmAction = "suspend"
|
||||||
|
root.powerConfirmTitle = "Suspend"
|
||||||
|
root.powerConfirmMessage = "Are you sure you want to suspend the system?"
|
||||||
|
root.powerConfirmVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scale {
|
Rectangle {
|
||||||
NumberAnimation {
|
width: parent.width
|
||||||
duration: Theme.mediumDuration
|
height: 50
|
||||||
easing.type: Theme.emphasizedEasing
|
radius: Theme.cornerRadius
|
||||||
|
color: rebootArea.containsMouse ? Qt.rgba(Theme.warning.r,
|
||||||
|
Theme.warning.g,
|
||||||
|
Theme.warning.b,
|
||||||
|
0.08) : Qt.rgba(
|
||||||
|
Theme.surfaceVariant.r,
|
||||||
|
Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.08)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "restart_alt"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Reboot"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: rebootArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
powerMenuVisible = false
|
||||||
|
root.powerConfirmAction = "reboot"
|
||||||
|
root.powerConfirmTitle = "Reboot"
|
||||||
|
root.powerConfirmMessage = "Are you sure you want to reboot the system?"
|
||||||
|
root.powerConfirmVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 50
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: powerOffArea.containsMouse ? Qt.rgba(Theme.error.r,
|
||||||
|
Theme.error.g,
|
||||||
|
Theme.error.b,
|
||||||
|
0.08) : Qt.rgba(
|
||||||
|
Theme.surfaceVariant.r,
|
||||||
|
Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.08)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "power_settings_new"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Power Off"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: powerOffArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
powerMenuVisible = false
|
||||||
|
root.powerConfirmAction = "poweroff"
|
||||||
|
root.powerConfirmTitle = "Power Off"
|
||||||
|
root.powerConfirmMessage = "Are you sure you want to power off the system?"
|
||||||
|
root.powerConfirmVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,181 +8,194 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: dock
|
id: dock
|
||||||
|
|
||||||
WlrLayershell.layer: WlrLayershell.Top
|
WlrLayershell.layer: WlrLayershell.Top
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
|
||||||
property var modelData
|
property var modelData
|
||||||
property var contextMenu
|
property var contextMenu
|
||||||
property var windowsMenu
|
property var windowsMenu
|
||||||
property bool autoHide: SettingsData.dockAutoHide
|
property bool autoHide: SettingsData.dockAutoHide
|
||||||
property real backgroundTransparency: SettingsData.dockTransparency
|
property real backgroundTransparency: SettingsData.dockTransparency
|
||||||
|
|
||||||
|
property bool contextMenuOpen: (contextMenu && contextMenu.visible
|
||||||
|
&& contextMenu.screen === modelData)
|
||||||
|
|| (windowsMenu && windowsMenu.visible
|
||||||
|
&& windowsMenu.screen === modelData)
|
||||||
|
property bool windowIsFullscreen: {
|
||||||
|
if (!NiriService.focusedWindowId || !NiriService.niriAvailable)
|
||||||
|
return false
|
||||||
|
var focusedWindow = NiriService.windows.find(
|
||||||
|
w => w.id === NiriService.focusedWindowId)
|
||||||
|
if (!focusedWindow)
|
||||||
|
return false
|
||||||
|
var fullscreenApps = ["vlc", "mpv", "kodi", "steam", "lutris", "wine", "dosbox"]
|
||||||
|
return fullscreenApps.some(app => focusedWindow.app_id
|
||||||
|
&& focusedWindow.app_id.toLowerCase(
|
||||||
|
).includes(app))
|
||||||
|
}
|
||||||
|
property bool reveal: (!autoHide || dockMouseArea.containsMouse
|
||||||
|
|| dockApps.requestDockShow || contextMenuOpen)
|
||||||
|
&& !windowIsFullscreen
|
||||||
|
|
||||||
property bool contextMenuOpen: (contextMenu && contextMenu.visible && contextMenu.screen === modelData) || (windowsMenu && windowsMenu.visible && windowsMenu.screen === modelData)
|
Connections {
|
||||||
property bool windowIsFullscreen: {
|
target: SettingsData
|
||||||
if (!NiriService.focusedWindowId || !NiriService.niriAvailable) return false
|
function onDockTransparencyChanged() {
|
||||||
var focusedWindow = NiriService.windows.find(w => w.id === NiriService.focusedWindowId)
|
dock.backgroundTransparency = SettingsData.dockTransparency
|
||||||
if (!focusedWindow) return false
|
|
||||||
var fullscreenApps = ["vlc", "mpv", "kodi", "steam", "lutris", "wine", "dosbox"]
|
|
||||||
return fullscreenApps.some(app => focusedWindow.app_id && focusedWindow.app_id.toLowerCase().includes(app))
|
|
||||||
}
|
}
|
||||||
property bool reveal: (!autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen) && !windowIsFullscreen
|
}
|
||||||
|
|
||||||
Connections {
|
screen: modelData
|
||||||
target: SettingsData
|
visible: SettingsData.showDock
|
||||||
function onDockTransparencyChanged() {
|
color: "transparent"
|
||||||
dock.backgroundTransparency = SettingsData.dockTransparency;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
screen: modelData
|
anchors {
|
||||||
visible: SettingsData.showDock
|
bottom: true
|
||||||
color: "transparent"
|
left: true
|
||||||
|
right: true
|
||||||
|
}
|
||||||
|
|
||||||
|
margins {
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: 100
|
||||||
|
exclusiveZone: autoHide ? -1 : 65 - 16
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: dockMouseArea
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: dockMouseArea
|
||||||
|
height: dock.reveal ? 65 : 12
|
||||||
anchors {
|
anchors {
|
||||||
bottom: true
|
bottom: parent.bottom
|
||||||
left: true
|
horizontalCenter: parent.horizontalCenter
|
||||||
right: true
|
}
|
||||||
|
implicitWidth: dock.reveal ? dockBackground.width + 32 : (dockBackground.width + 32)
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 200
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
margins {
|
Item {
|
||||||
left: 0
|
id: dockContainer
|
||||||
right: 0
|
anchors.fill: parent
|
||||||
}
|
|
||||||
|
|
||||||
implicitHeight: 100
|
transform: Translate {
|
||||||
exclusiveZone: autoHide ? -1 : 65 - 16
|
id: dockSlide
|
||||||
|
y: dock.reveal ? 0 : 60
|
||||||
|
|
||||||
mask: Region {
|
Behavior on y {
|
||||||
item: dockMouseArea
|
NumberAnimation {
|
||||||
}
|
duration: 200
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
Rectangle {
|
||||||
id: dockMouseArea
|
id: dockBackground
|
||||||
height: dock.reveal ? 65 : 12
|
objectName: "dockBackground"
|
||||||
anchors {
|
anchors {
|
||||||
bottom: parent.bottom
|
top: parent.top
|
||||||
horizontalCenter: parent.horizontalCenter
|
bottom: parent.bottom
|
||||||
}
|
horizontalCenter: parent.horizontalCenter
|
||||||
implicitWidth: dock.reveal ? dockBackground.width + 32 : (dockBackground.width + 32)
|
|
||||||
hoverEnabled: true
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 200
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
width: dockApps.implicitWidth + 12
|
||||||
|
height: parent.height - 8
|
||||||
|
|
||||||
Item {
|
anchors.topMargin: 4
|
||||||
id: dockContainer
|
anchors.bottomMargin: 1
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
transform: Translate {
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||||
id: dockSlide
|
Theme.surfaceContainer.b, backgroundTransparency)
|
||||||
y: dock.reveal ? 0 : 60
|
radius: Theme.cornerRadiusXLarge
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.outlineMedium
|
||||||
|
layer.enabled: true
|
||||||
|
|
||||||
Behavior on y {
|
Rectangle {
|
||||||
NumberAnimation {
|
anchors.fill: parent
|
||||||
duration: 200
|
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g,
|
||||||
easing.type: Easing.OutCubic
|
Theme.surfaceTint.b, 0.04)
|
||||||
}
|
radius: parent.radius
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: dockBackground
|
|
||||||
objectName: "dockBackground"
|
|
||||||
anchors {
|
|
||||||
top: parent.top
|
|
||||||
bottom: parent.bottom
|
|
||||||
horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
width: dockApps.implicitWidth + 12
|
|
||||||
height: parent.height - 8
|
|
||||||
|
|
||||||
anchors.topMargin: 4
|
|
||||||
anchors.bottomMargin: 1
|
|
||||||
|
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, backgroundTransparency)
|
|
||||||
radius: Theme.cornerRadiusXLarge
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.outlineMedium
|
|
||||||
layer.enabled: true
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
|
|
||||||
radius: parent.radius
|
|
||||||
}
|
|
||||||
|
|
||||||
DockApps {
|
|
||||||
id: dockApps
|
|
||||||
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.topMargin: 4
|
|
||||||
anchors.bottomMargin: 4
|
|
||||||
|
|
||||||
contextMenu: dock.contextMenu
|
|
||||||
windowsMenu: dock.windowsMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: appTooltip
|
|
||||||
|
|
||||||
property var hoveredButton: {
|
|
||||||
if (!dockApps.children[0]) return null
|
|
||||||
var row = dockApps.children[0]
|
|
||||||
var repeater = null
|
|
||||||
for (var i = 0; i < row.children.length; i++) {
|
|
||||||
var child = row.children[i]
|
|
||||||
if (child && typeof child.count !== "undefined" && typeof child.itemAt === "function") {
|
|
||||||
repeater = child
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!repeater || !repeater.itemAt) return null
|
|
||||||
for (var i = 0; i < repeater.count; i++) {
|
|
||||||
var item = repeater.itemAt(i)
|
|
||||||
if (item && item.dockButton && item.dockButton.showTooltip) {
|
|
||||||
return item.dockButton
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
property string tooltipText: hoveredButton ? hoveredButton.tooltipText : ""
|
|
||||||
|
|
||||||
visible: hoveredButton !== null && tooltipText !== ""
|
|
||||||
width: tooltipLabel.implicitWidth + 24
|
|
||||||
height: tooltipLabel.implicitHeight + 12
|
|
||||||
|
|
||||||
color: Theme.surfaceContainer
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.outlineMedium
|
|
||||||
|
|
||||||
y: -height - 8
|
|
||||||
x: hoveredButton ? hoveredButton.mapToItem(dockContainer, hoveredButton.width/2, 0).x - width/2 : 0
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: tooltipLabel
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: appTooltip.tooltipText
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DockApps {
|
||||||
|
id: dockApps
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.topMargin: 4
|
||||||
|
anchors.bottomMargin: 4
|
||||||
|
|
||||||
|
contextMenu: dock.contextMenu
|
||||||
|
windowsMenu: dock.windowsMenu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: appTooltip
|
||||||
|
|
||||||
|
property var hoveredButton: {
|
||||||
|
if (!dockApps.children[0])
|
||||||
|
return null
|
||||||
|
var row = dockApps.children[0]
|
||||||
|
var repeater = null
|
||||||
|
for (var i = 0; i < row.children.length; i++) {
|
||||||
|
var child = row.children[i]
|
||||||
|
if (child && typeof child.count !== "undefined"
|
||||||
|
&& typeof child.itemAt === "function") {
|
||||||
|
repeater = child
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!repeater || !repeater.itemAt)
|
||||||
|
return null
|
||||||
|
for (var i = 0; i < repeater.count; i++) {
|
||||||
|
var item = repeater.itemAt(i)
|
||||||
|
if (item && item.dockButton && item.dockButton.showTooltip) {
|
||||||
|
return item.dockButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
property string tooltipText: hoveredButton ? hoveredButton.tooltipText : ""
|
||||||
|
|
||||||
|
visible: hoveredButton !== null && tooltipText !== ""
|
||||||
|
width: tooltipLabel.implicitWidth + 24
|
||||||
|
height: tooltipLabel.implicitHeight + 12
|
||||||
|
|
||||||
|
color: Theme.surfaceContainer
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.outlineMedium
|
||||||
|
|
||||||
|
y: -height - 8
|
||||||
|
x: hoveredButton ? hoveredButton.mapToItem(dockContainer,
|
||||||
|
hoveredButton.width / 2,
|
||||||
|
0).x - width / 2 : 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: tooltipLabel
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: appTooltip.tooltipText
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,283 +7,307 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var appData
|
property var appData
|
||||||
property var contextMenu: null
|
property var contextMenu: null
|
||||||
property var windowsMenu: null
|
property var windowsMenu: null
|
||||||
property var dockApps: null
|
property var dockApps: null
|
||||||
property int index: -1
|
property int index: -1
|
||||||
property bool longPressing: false
|
property bool longPressing: false
|
||||||
property bool dragging: false
|
property bool dragging: false
|
||||||
property point dragStartPos: Qt.point(0, 0)
|
property point dragStartPos: Qt.point(0, 0)
|
||||||
property point dragOffset: Qt.point(0, 0)
|
property point dragOffset: Qt.point(0, 0)
|
||||||
property int targetIndex: -1
|
property int targetIndex: -1
|
||||||
property int originalIndex: -1
|
property int originalIndex: -1
|
||||||
|
|
||||||
width: 40
|
width: 40
|
||||||
height: 40
|
height: 40
|
||||||
|
|
||||||
property bool isHovered: mouseArea.containsMouse && !dragging
|
property bool isHovered: mouseArea.containsMouse && !dragging
|
||||||
|
|
||||||
transform: Translate {
|
transform: Translate {
|
||||||
id: translateY
|
id: translateY
|
||||||
y: 0
|
y: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
SequentialAnimation {
|
SequentialAnimation {
|
||||||
id: bounceAnimation
|
id: bounceAnimation
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: translateY
|
target: translateY
|
||||||
property: "y"
|
property: "y"
|
||||||
to: -10
|
to: -10
|
||||||
duration: Anims.durShort
|
duration: Anims.durShort
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Anims.emphasizedAccel
|
easing.bezierCurve: Anims.emphasizedAccel
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
target: translateY
|
|
||||||
property: "y"
|
|
||||||
to: -8
|
|
||||||
duration: Anims.durShort
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.emphasizedDecel
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
id: exitAnimation
|
target: translateY
|
||||||
running: false
|
property: "y"
|
||||||
target: translateY
|
to: -8
|
||||||
property: "y"
|
duration: Anims.durShort
|
||||||
to: 0
|
easing.type: Easing.BezierSpline
|
||||||
duration: Anims.durShort
|
easing.bezierCurve: Anims.emphasizedDecel
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.emphasizedDecel
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onIsHoveredChanged: {
|
NumberAnimation {
|
||||||
if (isHovered) {
|
id: exitAnimation
|
||||||
exitAnimation.stop()
|
running: false
|
||||||
if (!bounceAnimation.running) {
|
target: translateY
|
||||||
bounceAnimation.restart()
|
property: "y"
|
||||||
}
|
to: 0
|
||||||
} else {
|
duration: Anims.durShort
|
||||||
bounceAnimation.stop()
|
easing.type: Easing.BezierSpline
|
||||||
exitAnimation.restart()
|
easing.bezierCurve: Anims.emphasizedDecel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onIsHoveredChanged: {
|
||||||
|
if (isHovered) {
|
||||||
|
exitAnimation.stop()
|
||||||
|
if (!bounceAnimation.running) {
|
||||||
|
bounceAnimation.restart()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bounceAnimation.stop()
|
||||||
|
exitAnimation.restart()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||||
border.width: 2
|
border.width: 2
|
||||||
border.color: Theme.primary
|
border.color: Theme.primary
|
||||||
visible: dragging
|
visible: dragging
|
||||||
z: -1
|
z: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: longPressTimer
|
||||||
|
interval: 500
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (appData && appData.isPinned) {
|
||||||
|
longPressing = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
MouseArea {
|
||||||
id: longPressTimer
|
id: mouseArea
|
||||||
interval: 500
|
anchors.fill: parent
|
||||||
repeat: false
|
anchors.bottomMargin: -20
|
||||||
onTriggered: {
|
hoverEnabled: true
|
||||||
if (appData && appData.isPinned) {
|
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
|
||||||
longPressing = true
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
onPressed: mouse => {
|
||||||
id: mouseArea
|
if (mouse.button === Qt.LeftButton && appData
|
||||||
anchors.fill: parent
|
&& appData.isPinned) {
|
||||||
anchors.bottomMargin: -20
|
dragStartPos = Qt.point(mouse.x, mouse.y)
|
||||||
hoverEnabled: true
|
longPressTimer.start()
|
||||||
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
|
}
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
}
|
||||||
|
|
||||||
|
onReleased: mouse => {
|
||||||
onPressed: (mouse) => {
|
longPressTimer.stop()
|
||||||
if (mouse.button === Qt.LeftButton && appData && appData.isPinned) {
|
if (longPressing) {
|
||||||
dragStartPos = Qt.point(mouse.x, mouse.y)
|
if (dragging && targetIndex >= 0
|
||||||
longPressTimer.start()
|
&& targetIndex !== originalIndex && dockApps) {
|
||||||
}
|
dockApps.movePinnedApp(originalIndex, targetIndex)
|
||||||
}
|
|
||||||
|
|
||||||
onReleased: (mouse) => {
|
|
||||||
longPressTimer.stop()
|
|
||||||
if (longPressing) {
|
|
||||||
if (dragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps) {
|
|
||||||
dockApps.movePinnedApp(originalIndex, targetIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
longPressing = false
|
|
||||||
dragging = false
|
|
||||||
dragOffset = Qt.point(0, 0)
|
|
||||||
targetIndex = -1
|
|
||||||
originalIndex = -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onPositionChanged: (mouse) => {
|
|
||||||
if (longPressing && !dragging) {
|
|
||||||
var distance = Math.sqrt(Math.pow(mouse.x - dragStartPos.x, 2) + Math.pow(mouse.y - dragStartPos.y, 2))
|
|
||||||
if (distance > 5) {
|
|
||||||
dragging = true
|
|
||||||
targetIndex = index
|
|
||||||
originalIndex = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dragging) {
|
|
||||||
dragOffset = Qt.point(mouse.x - dragStartPos.x, mouse.y - dragStartPos.y)
|
|
||||||
|
|
||||||
if (dockApps) {
|
|
||||||
var threshold = 40
|
|
||||||
var newTargetIndex = targetIndex
|
|
||||||
|
|
||||||
if (dragOffset.x > threshold && targetIndex < dockApps.pinnedAppCount - 1) {
|
|
||||||
newTargetIndex = targetIndex + 1
|
|
||||||
} else if (dragOffset.x < -threshold && targetIndex > 0) {
|
|
||||||
newTargetIndex = targetIndex - 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newTargetIndex !== targetIndex) {
|
longPressing = false
|
||||||
targetIndex = newTargetIndex
|
dragging = false
|
||||||
dragStartPos = Qt.point(mouse.x, mouse.y)
|
dragOffset = Qt.point(0, 0)
|
||||||
}
|
targetIndex = -1
|
||||||
|
originalIndex = -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: (mouse) => {
|
onPositionChanged: mouse => {
|
||||||
if (!appData || longPressing) return
|
if (longPressing && !dragging) {
|
||||||
|
var distance = Math.sqrt(
|
||||||
|
Math.pow(mouse.x - dragStartPos.x,
|
||||||
|
2) + Math.pow(mouse.y - dragStartPos.y,
|
||||||
|
2))
|
||||||
|
if (distance > 5) {
|
||||||
|
dragging = true
|
||||||
|
targetIndex = index
|
||||||
|
originalIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
if (dragging) {
|
||||||
var windowCount = appData.windows ? appData.windows.count : 0
|
dragOffset = Qt.point(mouse.x - dragStartPos.x,
|
||||||
|
mouse.y - dragStartPos.y)
|
||||||
|
|
||||||
if (windowCount === 0) {
|
if (dockApps) {
|
||||||
if (appData && appData.appId) {
|
var threshold = 40
|
||||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
var newTargetIndex = targetIndex
|
||||||
if (desktopEntry) {
|
|
||||||
AppUsageHistoryData.addAppUsage({
|
if (dragOffset.x > threshold
|
||||||
id: appData.appId,
|
&& targetIndex < dockApps.pinnedAppCount - 1) {
|
||||||
name: desktopEntry.name || appData.appId,
|
newTargetIndex = targetIndex + 1
|
||||||
icon: desktopEntry.icon || "",
|
} else if (dragOffset.x < -threshold
|
||||||
exec: desktopEntry.exec || "",
|
&& targetIndex > 0) {
|
||||||
comment: desktopEntry.comment || ""
|
newTargetIndex = targetIndex - 1
|
||||||
})
|
}
|
||||||
}
|
|
||||||
Quickshell.execDetached(["gtk-launch", appData.appId])
|
if (newTargetIndex !== targetIndex) {
|
||||||
}
|
targetIndex = newTargetIndex
|
||||||
} else if (windowCount === 1) {
|
dragStartPos = Qt.point(mouse.x, mouse.y)
|
||||||
var window = appData.windows.get(0)
|
}
|
||||||
NiriService.focusWindow(window.id)
|
}
|
||||||
} else {
|
}
|
||||||
windowsMenu.showForButton(root, appData, 40)
|
}
|
||||||
}
|
|
||||||
} else if (mouse.button === Qt.MiddleButton) {
|
onClicked: mouse => {
|
||||||
if (appData && appData.appId) {
|
if (!appData || longPressing)
|
||||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
return
|
||||||
if (desktopEntry) {
|
|
||||||
AppUsageHistoryData.addAppUsage({
|
if (mouse.button === Qt.LeftButton) {
|
||||||
id: appData.appId,
|
var windowCount = appData.windows ? appData.windows.count : 0
|
||||||
name: desktopEntry.name || appData.appId,
|
|
||||||
icon: desktopEntry.icon || "",
|
if (windowCount === 0) {
|
||||||
exec: desktopEntry.exec || "",
|
if (appData && appData.appId) {
|
||||||
comment: desktopEntry.comment || ""
|
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||||
})
|
if (desktopEntry) {
|
||||||
}
|
AppUsageHistoryData.addAppUsage({
|
||||||
Quickshell.execDetached(["gtk-launch", appData.appId])
|
"id": appData.appId,
|
||||||
}
|
"name": desktopEntry.name
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
|| appData.appId,
|
||||||
if (contextMenu) {
|
"icon": desktopEntry.icon
|
||||||
contextMenu.showForButton(root, appData, 40)
|
|| "",
|
||||||
}
|
"exec": desktopEntry.exec
|
||||||
}
|
|| "",
|
||||||
}
|
"comment": desktopEntry.comment
|
||||||
|
|| ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Quickshell.execDetached(["gtk-launch", appData.appId])
|
||||||
|
}
|
||||||
|
} else if (windowCount === 1) {
|
||||||
|
var window = appData.windows.get(0)
|
||||||
|
NiriService.focusWindow(window.id)
|
||||||
|
} else {
|
||||||
|
windowsMenu.showForButton(root, appData, 40)
|
||||||
|
}
|
||||||
|
} else if (mouse.button === Qt.MiddleButton) {
|
||||||
|
if (appData && appData.appId) {
|
||||||
|
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||||
|
if (desktopEntry) {
|
||||||
|
AppUsageHistoryData.addAppUsage({
|
||||||
|
"id": appData.appId,
|
||||||
|
"name": desktopEntry.name
|
||||||
|
|| appData.appId,
|
||||||
|
"icon": desktopEntry.icon
|
||||||
|
|| "",
|
||||||
|
"exec": desktopEntry.exec
|
||||||
|
|| "",
|
||||||
|
"comment": desktopEntry.comment
|
||||||
|
|| ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Quickshell.execDetached(["gtk-launch", appData.appId])
|
||||||
|
}
|
||||||
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
|
if (contextMenu) {
|
||||||
|
contextMenu.showForButton(root, appData, 40)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool showTooltip: mouseArea.containsMouse && !dragging
|
||||||
|
property string tooltipText: {
|
||||||
|
if (!appData || !appData.appId)
|
||||||
|
return ""
|
||||||
|
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||||
|
return desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId
|
||||||
|
}
|
||||||
|
|
||||||
|
IconImage {
|
||||||
|
id: iconImg
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
anchors.centerIn: parent
|
||||||
|
source: {
|
||||||
|
if (!appData || !appData.appId)
|
||||||
|
return ""
|
||||||
|
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||||
|
if (desktopEntry && desktopEntry.icon) {
|
||||||
|
var iconPath = Quickshell.iconPath(
|
||||||
|
desktopEntry.icon,
|
||||||
|
SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme)
|
||||||
|
return iconPath
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
smooth: true
|
||||||
|
mipmap: true
|
||||||
|
asynchronous: true
|
||||||
|
visible: status === Image.Ready
|
||||||
|
implicitSize: 40
|
||||||
|
}
|
||||||
|
|
||||||
property bool showTooltip: mouseArea.containsMouse && !dragging
|
Rectangle {
|
||||||
property string tooltipText: {
|
width: 40
|
||||||
if (!appData || !appData.appId) return ""
|
height: 40
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: !iconImg.visible
|
||||||
|
color: Theme.surfaceLight
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.primarySelected
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: {
|
||||||
|
if (!appData || !appData.appId)
|
||||||
|
return "?"
|
||||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||||
return desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId
|
if (desktopEntry && desktopEntry.name) {
|
||||||
}
|
return desktopEntry.name.charAt(0).toUpperCase()
|
||||||
|
|
||||||
|
|
||||||
IconImage {
|
|
||||||
id: iconImg
|
|
||||||
width: 40
|
|
||||||
height: 40
|
|
||||||
anchors.centerIn: parent
|
|
||||||
source: {
|
|
||||||
if (!appData || !appData.appId) return ""
|
|
||||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
|
||||||
if (desktopEntry && desktopEntry.icon) {
|
|
||||||
var iconPath = Quickshell.iconPath(desktopEntry.icon, SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme)
|
|
||||||
return iconPath
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
smooth: true
|
return appData.appId.charAt(0).toUpperCase()
|
||||||
mipmap: true
|
}
|
||||||
asynchronous: true
|
font.pixelSize: 14
|
||||||
visible: status === Image.Ready
|
color: Theme.primary
|
||||||
implicitSize: 40
|
font.weight: Font.Bold
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Row {
|
||||||
width: 40
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
height: 40
|
anchors.bottom: parent.bottom
|
||||||
anchors.centerIn: parent
|
anchors.bottomMargin: -2
|
||||||
visible: !iconImg.visible
|
spacing: 2
|
||||||
color: Theme.surfaceLight
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.primarySelected
|
|
||||||
|
|
||||||
Text {
|
Repeater {
|
||||||
anchors.centerIn: parent
|
model: appData && appData.windows ? Math.min(appData.windows.count, 4) : 0
|
||||||
text: {
|
|
||||||
if (!appData || !appData.appId) return "?"
|
Rectangle {
|
||||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
width: appData && appData.windows && appData.windows.count <= 3 ? 5 : 3
|
||||||
if (desktopEntry && desktopEntry.name) {
|
height: 2
|
||||||
return desktopEntry.name.charAt(0).toUpperCase()
|
radius: 1
|
||||||
}
|
color: {
|
||||||
return appData.appId.charAt(0).toUpperCase()
|
if (!appData || !appData.windows || appData.windows.count === 0)
|
||||||
}
|
return "transparent"
|
||||||
font.pixelSize: 14
|
var window = appData.windows.get(index)
|
||||||
color: Theme.primary
|
return window
|
||||||
font.weight: Font.Bold
|
&& window.id == NiriService.focusedWindowId ? Theme.primary : Qt.rgba(
|
||||||
}
|
Theme.surfaceText.r,
|
||||||
}
|
Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b,
|
||||||
|
0.6)
|
||||||
Row {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: -2
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: appData && appData.windows ? Math.min(appData.windows.count, 4) : 0
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: appData && appData.windows && appData.windows.count <= 3 ? 5 : 3
|
|
||||||
height: 2
|
|
||||||
radius: 1
|
|
||||||
color: {
|
|
||||||
if (!appData || !appData.windows || appData.windows.count === 0) return "transparent"
|
|
||||||
var window = appData.windows.get(index)
|
|
||||||
return window && window.id == NiriService.focusedWindowId ? Theme.primary :
|
|
||||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,164 +6,176 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var contextMenu: null
|
property var contextMenu: null
|
||||||
property var windowsMenu: null
|
property var windowsMenu: null
|
||||||
property bool requestDockShow: false
|
property bool requestDockShow: false
|
||||||
property int pinnedAppCount: 0
|
property int pinnedAppCount: 0
|
||||||
|
|
||||||
implicitWidth: row.width
|
implicitWidth: row.width
|
||||||
implicitHeight: row.height
|
implicitHeight: row.height
|
||||||
|
|
||||||
function movePinnedApp(fromIndex, toIndex) {
|
function movePinnedApp(fromIndex, toIndex) {
|
||||||
if (fromIndex === toIndex) return
|
if (fromIndex === toIndex)
|
||||||
|
return
|
||||||
|
|
||||||
var currentPinned = [...(SessionData.pinnedApps || [])]
|
var currentPinned = [...(SessionData.pinnedApps || [])]
|
||||||
if (fromIndex < 0 || fromIndex >= currentPinned.length || toIndex < 0 || toIndex >= currentPinned.length) return
|
if (fromIndex < 0 || fromIndex >= currentPinned.length || toIndex < 0
|
||||||
|
|| toIndex >= currentPinned.length)
|
||||||
|
return
|
||||||
|
|
||||||
var movedApp = currentPinned.splice(fromIndex, 1)[0]
|
var movedApp = currentPinned.splice(fromIndex, 1)[0]
|
||||||
currentPinned.splice(toIndex, 0, movedApp)
|
currentPinned.splice(toIndex, 0, movedApp)
|
||||||
|
|
||||||
SessionData.setPinnedApps(currentPinned)
|
SessionData.setPinnedApps(currentPinned)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: row
|
id: row
|
||||||
spacing: 2
|
spacing: 2
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
height: 40
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: repeater
|
||||||
|
model: ListModel {
|
||||||
|
id: dockModel
|
||||||
|
|
||||||
|
Component.onCompleted: updateModel()
|
||||||
|
|
||||||
|
function updateModel() {
|
||||||
|
clear()
|
||||||
|
|
||||||
|
var items = []
|
||||||
|
var runningApps = NiriService.getRunningAppIds()
|
||||||
|
var pinnedApps = [...(SessionData.pinnedApps || [])]
|
||||||
|
var addedApps = new Set()
|
||||||
|
|
||||||
|
pinnedApps.forEach(appId => {
|
||||||
|
var lowerAppId = appId.toLowerCase()
|
||||||
|
if (!addedApps.has(lowerAppId)) {
|
||||||
|
var windows = NiriService.getWindowsByAppId(
|
||||||
|
appId)
|
||||||
|
items.push({
|
||||||
|
"appId": appId,
|
||||||
|
"windows": windows,
|
||||||
|
"isPinned": true,
|
||||||
|
"isRunning": windows.length > 0
|
||||||
|
})
|
||||||
|
addedApps.add(lowerAppId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
root.pinnedAppCount = pinnedApps.length
|
||||||
|
var appUsageRanking = AppUsageHistoryData.appUsageRanking || {}
|
||||||
|
|
||||||
|
var unpinnedApps = []
|
||||||
|
var unpinnedAppsSet = new Set()
|
||||||
|
|
||||||
|
// First: Add ALL currently running apps that aren't pinned
|
||||||
|
runningApps.forEach(appId => {
|
||||||
|
var lowerAppId = appId.toLowerCase()
|
||||||
|
if (!addedApps.has(lowerAppId)) {
|
||||||
|
unpinnedApps.push(appId)
|
||||||
|
unpinnedAppsSet.add(lowerAppId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Then: Fill remaining slots up to 3 with recently used apps
|
||||||
|
var remainingSlots = Math.max(0, 3 - unpinnedApps.length)
|
||||||
|
if (remainingSlots > 0) {
|
||||||
|
// Sort recent apps by usage
|
||||||
|
var recentApps = []
|
||||||
|
for (var appId in appUsageRanking) {
|
||||||
|
var lowerAppId = appId.toLowerCase()
|
||||||
|
if (!addedApps.has(lowerAppId) && !unpinnedAppsSet.has(
|
||||||
|
lowerAppId)) {
|
||||||
|
recentApps.push({
|
||||||
|
"appId": appId,
|
||||||
|
"lastUsed": appUsageRanking[appId].lastUsed
|
||||||
|
|| 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recentApps.sort((a, b) => b.lastUsed - a.lastUsed)
|
||||||
|
|
||||||
|
var recentToAdd = Math.min(remainingSlots, recentApps.length)
|
||||||
|
for (var i = 0; i < recentToAdd; i++) {
|
||||||
|
unpinnedApps.push(recentApps[i].appId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pinnedApps.length > 0 && unpinnedApps.length > 0) {
|
||||||
|
items.push({
|
||||||
|
"appId": "__SEPARATOR__",
|
||||||
|
"windows": [],
|
||||||
|
"isPinned": false,
|
||||||
|
"isRunning": false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
unpinnedApps.forEach(appId => {
|
||||||
|
var windows = NiriService.getWindowsByAppId(
|
||||||
|
appId)
|
||||||
|
items.push({
|
||||||
|
"appId": appId,
|
||||||
|
"windows": windows,
|
||||||
|
"isPinned": false,
|
||||||
|
"isRunning": windows.length > 0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
items.forEach(item => {
|
||||||
|
append(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
id: delegateItem
|
||||||
|
property alias dockButton: button
|
||||||
|
|
||||||
|
width: model.appId === "__SEPARATOR__" ? 16 : 40
|
||||||
height: 40
|
height: 40
|
||||||
|
|
||||||
Repeater {
|
Rectangle {
|
||||||
id: repeater
|
visible: model.appId === "__SEPARATOR__"
|
||||||
model: ListModel {
|
width: 2
|
||||||
id: dockModel
|
height: 20
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||||
Component.onCompleted: updateModel()
|
radius: 1
|
||||||
|
anchors.centerIn: parent
|
||||||
function updateModel() {
|
|
||||||
clear()
|
|
||||||
|
|
||||||
var items = []
|
|
||||||
var runningApps = NiriService.getRunningAppIds()
|
|
||||||
var pinnedApps = [...(SessionData.pinnedApps || [])]
|
|
||||||
var addedApps = new Set()
|
|
||||||
|
|
||||||
|
|
||||||
pinnedApps.forEach(appId => {
|
|
||||||
var lowerAppId = appId.toLowerCase()
|
|
||||||
if (!addedApps.has(lowerAppId)) {
|
|
||||||
var windows = NiriService.getWindowsByAppId(appId)
|
|
||||||
items.push({
|
|
||||||
appId: appId,
|
|
||||||
windows: windows,
|
|
||||||
isPinned: true,
|
|
||||||
isRunning: windows.length > 0
|
|
||||||
})
|
|
||||||
addedApps.add(lowerAppId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
root.pinnedAppCount = pinnedApps.length
|
|
||||||
var appUsageRanking = AppUsageHistoryData.appUsageRanking || {}
|
|
||||||
|
|
||||||
var unpinnedApps = []
|
|
||||||
var unpinnedAppsSet = new Set()
|
|
||||||
|
|
||||||
// First: Add ALL currently running apps that aren't pinned
|
|
||||||
runningApps.forEach(appId => {
|
|
||||||
var lowerAppId = appId.toLowerCase()
|
|
||||||
if (!addedApps.has(lowerAppId)) {
|
|
||||||
unpinnedApps.push(appId)
|
|
||||||
unpinnedAppsSet.add(lowerAppId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Then: Fill remaining slots up to 3 with recently used apps
|
|
||||||
var remainingSlots = Math.max(0, 3 - unpinnedApps.length)
|
|
||||||
if (remainingSlots > 0) {
|
|
||||||
// Sort recent apps by usage
|
|
||||||
var recentApps = []
|
|
||||||
for (var appId in appUsageRanking) {
|
|
||||||
var lowerAppId = appId.toLowerCase()
|
|
||||||
if (!addedApps.has(lowerAppId) && !unpinnedAppsSet.has(lowerAppId)) {
|
|
||||||
recentApps.push({
|
|
||||||
appId: appId,
|
|
||||||
lastUsed: appUsageRanking[appId].lastUsed || 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
recentApps.sort((a, b) => b.lastUsed - a.lastUsed)
|
|
||||||
|
|
||||||
var recentToAdd = Math.min(remainingSlots, recentApps.length)
|
|
||||||
for (var i = 0; i < recentToAdd; i++) {
|
|
||||||
unpinnedApps.push(recentApps[i].appId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pinnedApps.length > 0 && unpinnedApps.length > 0) {
|
|
||||||
items.push({
|
|
||||||
appId: "__SEPARATOR__",
|
|
||||||
windows: [],
|
|
||||||
isPinned: false,
|
|
||||||
isRunning: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
unpinnedApps.forEach(appId => {
|
|
||||||
var windows = NiriService.getWindowsByAppId(appId)
|
|
||||||
items.push({
|
|
||||||
appId: appId,
|
|
||||||
windows: windows,
|
|
||||||
isPinned: false,
|
|
||||||
isRunning: windows.length > 0
|
|
||||||
})
|
|
||||||
})
|
|
||||||
items.forEach(item => {
|
|
||||||
append(item)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Item {
|
|
||||||
id: delegateItem
|
|
||||||
property alias dockButton: button
|
|
||||||
|
|
||||||
width: model.appId === "__SEPARATOR__" ? 16 : 40
|
|
||||||
height: 40
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: model.appId === "__SEPARATOR__"
|
|
||||||
width: 2
|
|
||||||
height: 20
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
|
||||||
radius: 1
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
DockAppButton {
|
|
||||||
id: button
|
|
||||||
visible: model.appId !== "__SEPARATOR__"
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
width: 40
|
|
||||||
height: 40
|
|
||||||
|
|
||||||
appData: model
|
|
||||||
contextMenu: root.contextMenu
|
|
||||||
windowsMenu: root.windowsMenu
|
|
||||||
dockApps: root
|
|
||||||
index: model.index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
DockAppButton {
|
||||||
target: NiriService
|
id: button
|
||||||
function onWindowsChanged() { dockModel.updateModel() }
|
visible: model.appId !== "__SEPARATOR__"
|
||||||
function onWindowOpenedOrChanged() { dockModel.updateModel() }
|
anchors.centerIn: parent
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
width: 40
|
||||||
target: SessionData
|
height: 40
|
||||||
function onPinnedAppsChanged() { dockModel.updateModel() }
|
|
||||||
|
appData: model
|
||||||
|
contextMenu: root.contextMenu
|
||||||
|
windowsMenu: root.windowsMenu
|
||||||
|
dockApps: root
|
||||||
|
index: model.index
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: NiriService
|
||||||
|
function onWindowsChanged() {
|
||||||
|
dockModel.updateModel()
|
||||||
|
}
|
||||||
|
function onWindowOpenedOrChanged() {
|
||||||
|
dockModel.updateModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SessionData
|
||||||
|
function onPinnedAppsChanged() {
|
||||||
|
dockModel.updateModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,276 +8,304 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool showContextMenu: false
|
property bool showContextMenu: false
|
||||||
property var appData: null
|
property var appData: null
|
||||||
property var anchorItem: null
|
property var anchorItem: null
|
||||||
property real dockVisibleHeight: 40
|
property real dockVisibleHeight: 40
|
||||||
property int margin: 10
|
property int margin: 10
|
||||||
|
|
||||||
function showForButton(button, data, dockHeight) {
|
function showForButton(button, data, dockHeight) {
|
||||||
anchorItem = button
|
anchorItem = button
|
||||||
appData = data
|
appData = data
|
||||||
dockVisibleHeight = dockHeight || 40
|
dockVisibleHeight = dockHeight || 40
|
||||||
|
|
||||||
var dockWindow = button.Window.window
|
var dockWindow = button.Window.window
|
||||||
if (dockWindow) {
|
if (dockWindow) {
|
||||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||||
var s = Quickshell.screens[i]
|
var s = Quickshell.screens[i]
|
||||||
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
|
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
|
||||||
root.screen = s
|
root.screen = s
|
||||||
break
|
break
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
showContextMenu = true
|
|
||||||
}
|
}
|
||||||
function close() { showContextMenu = false }
|
|
||||||
|
|
||||||
screen: Quickshell.screens[0]
|
showContextMenu = true
|
||||||
|
}
|
||||||
|
function close() {
|
||||||
|
showContextMenu = false
|
||||||
|
}
|
||||||
|
|
||||||
visible: showContextMenu
|
screen: Quickshell.screens[0]
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
color: "transparent"
|
|
||||||
anchors { top: true; left: true; right: true; bottom: true }
|
|
||||||
|
|
||||||
property point anchorPos: Qt.point(screen.width/2, screen.height - 100)
|
visible: showContextMenu
|
||||||
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
color: "transparent"
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
onAnchorItemChanged: updatePosition()
|
property point anchorPos: Qt.point(screen.width / 2, screen.height - 100)
|
||||||
onVisibleChanged: if (visible) updatePosition()
|
|
||||||
|
|
||||||
function updatePosition() {
|
onAnchorItemChanged: updatePosition()
|
||||||
if (!anchorItem) {
|
onVisibleChanged: if (visible)
|
||||||
anchorPos = Qt.point(screen.width/2, screen.height - 100)
|
updatePosition()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var dockWindow = anchorItem.Window.window
|
function updatePosition() {
|
||||||
if (!dockWindow) {
|
if (!anchorItem) {
|
||||||
anchorPos = Qt.point(screen.width/2, screen.height - 100)
|
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
var buttonPosInDock = anchorItem.mapToItem(dockWindow.contentItem, 0, 0)
|
|
||||||
|
|
||||||
var actualDockHeight = root.dockVisibleHeight // fallback
|
|
||||||
|
|
||||||
function findDockBackground(item) {
|
|
||||||
if (item.objectName === "dockBackground") {
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
for (var i = 0; i < item.children.length; i++) {
|
|
||||||
var found = findDockBackground(item.children[i])
|
|
||||||
if (found) return found
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
var dockBackground = findDockBackground(dockWindow.contentItem)
|
|
||||||
if (dockBackground) {
|
|
||||||
actualDockHeight = dockBackground.height
|
|
||||||
}
|
|
||||||
|
|
||||||
var dockBottomMargin = 16 // The dock has bottom margin
|
|
||||||
var buttonScreenY = root.screen.height - actualDockHeight - dockBottomMargin - 20
|
|
||||||
|
|
||||||
var dockContentWidth = dockWindow.width
|
|
||||||
var screenWidth = root.screen.width
|
|
||||||
var dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2)
|
|
||||||
var buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width/2
|
|
||||||
|
|
||||||
anchorPos = Qt.point(buttonScreenX, buttonScreenY)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dockWindow = anchorItem.Window.window
|
||||||
|
if (!dockWindow) {
|
||||||
|
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonPosInDock = anchorItem.mapToItem(dockWindow.contentItem, 0, 0)
|
||||||
|
|
||||||
|
var actualDockHeight = root.dockVisibleHeight // fallback
|
||||||
|
|
||||||
|
function findDockBackground(item) {
|
||||||
|
if (item.objectName === "dockBackground") {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
for (var i = 0; i < item.children.length; i++) {
|
||||||
|
var found = findDockBackground(item.children[i])
|
||||||
|
if (found)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
var dockBackground = findDockBackground(dockWindow.contentItem)
|
||||||
|
if (dockBackground) {
|
||||||
|
actualDockHeight = dockBackground.height
|
||||||
|
}
|
||||||
|
|
||||||
|
var dockBottomMargin = 16 // The dock has bottom margin
|
||||||
|
var buttonScreenY = root.screen.height - actualDockHeight - dockBottomMargin - 20
|
||||||
|
|
||||||
|
var dockContentWidth = dockWindow.width
|
||||||
|
var screenWidth = root.screen.width
|
||||||
|
var dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2)
|
||||||
|
var buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2
|
||||||
|
|
||||||
|
anchorPos = Qt.point(buttonScreenX, buttonScreenY)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: menuContainer
|
||||||
|
|
||||||
|
width: Math.min(400,
|
||||||
|
Math.max(200,
|
||||||
|
menuColumn.implicitWidth + Theme.spacingS * 2))
|
||||||
|
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
|
||||||
|
|
||||||
|
x: {
|
||||||
|
var left = 10
|
||||||
|
var right = root.width - width - 10
|
||||||
|
var want = root.anchorPos.x - width / 2
|
||||||
|
return Math.max(left, Math.min(right, want))
|
||||||
|
}
|
||||||
|
y: Math.max(10, root.anchorPos.y - height + 30)
|
||||||
|
color: Theme.popupBackground()
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
opacity: showContextMenu ? 1 : 0
|
||||||
|
scale: showContextMenu ? 1 : 0.85
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: menuContainer
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 4
|
||||||
|
anchors.leftMargin: 2
|
||||||
|
anchors.rightMargin: -2
|
||||||
|
anchors.bottomMargin: -4
|
||||||
|
radius: parent.radius
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.15)
|
||||||
|
z: parent.z - 1
|
||||||
|
}
|
||||||
|
|
||||||
width: Math.min(400, Math.max(200, menuColumn.implicitWidth + Theme.spacingS * 2))
|
Column {
|
||||||
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
|
id: menuColumn
|
||||||
|
width: parent.width - Theme.spacingS * 2
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: Theme.spacingS
|
||||||
|
spacing: 1
|
||||||
|
|
||||||
x: {
|
Rectangle {
|
||||||
var left = 10
|
width: parent.width
|
||||||
var right = root.width - width - 10
|
height: 28
|
||||||
var want = root.anchorPos.x - width/2
|
radius: Theme.cornerRadiusSmall
|
||||||
return Math.max(left, Math.min(right, want))
|
color: pinArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.appData
|
||||||
|
&& root.appData.isPinned ? "Unpin from Dock" : "Pin to Dock"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
}
|
}
|
||||||
y: Math.max(10, root.anchorPos.y - height + 30)
|
|
||||||
color: Theme.popupBackground()
|
MouseArea {
|
||||||
radius: Theme.cornerRadiusLarge
|
id: pinArea
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
anchors.fill: parent
|
||||||
border.width: 1
|
hoverEnabled: true
|
||||||
opacity: showContextMenu ? 1 : 0
|
cursorShape: Qt.PointingHandCursor
|
||||||
scale: showContextMenu ? 1 : 0.85
|
onClicked: {
|
||||||
|
if (!root.appData)
|
||||||
|
return
|
||||||
|
if (root.appData.isPinned) {
|
||||||
|
SessionData.removePinnedApp(root.appData.appId)
|
||||||
|
} else {
|
||||||
|
SessionData.addPinnedApp(root.appData.appId)
|
||||||
|
}
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: root.appData && root.appData.windows
|
||||||
|
&& root.appData.windows.count > 0
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.appData
|
||||||
|
&& root.appData.windows ? root.appData.windows : null
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
required property var model
|
||||||
|
width: menuColumn.width
|
||||||
|
height: 28
|
||||||
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: windowArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: model.title || "Untitled Window"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: windowArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: 4
|
hoverEnabled: true
|
||||||
anchors.leftMargin: 2
|
cursorShape: Qt.PointingHandCursor
|
||||||
anchors.rightMargin: -2
|
onClicked: {
|
||||||
anchors.bottomMargin: -4
|
NiriService.focusWindow(model.id)
|
||||||
radius: parent.radius
|
root.close()
|
||||||
color: Qt.rgba(0, 0, 0, 0.15)
|
}
|
||||||
z: parent.z - 1
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: root.appData && root.appData.windows
|
||||||
|
&& root.appData.windows.count > 1
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: root.appData && root.appData.windows
|
||||||
|
&& root.appData.windows.count > 1
|
||||||
|
width: parent.width
|
||||||
|
height: 28
|
||||||
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: closeAllArea.containsMouse ? Qt.rgba(Theme.error.r,
|
||||||
|
Theme.error.g,
|
||||||
|
Theme.error.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: "Close All Windows"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: closeAllArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
MouseArea {
|
||||||
id: menuColumn
|
id: closeAllArea
|
||||||
width: parent.width - Theme.spacingS * 2
|
anchors.fill: parent
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
hoverEnabled: true
|
||||||
anchors.top: parent.top
|
cursorShape: Qt.PointingHandCursor
|
||||||
anchors.topMargin: Theme.spacingS
|
onClicked: {
|
||||||
spacing: 1
|
if (!root.appData || !root.appData.windows)
|
||||||
|
return
|
||||||
Rectangle {
|
for (var i = 0; i < root.appData.windows.count; i++) {
|
||||||
width: parent.width
|
var window = root.appData.windows.get(i)
|
||||||
height: 28
|
NiriService.closeWindow(window.id)
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: pinArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: root.appData && root.appData.isPinned ? "Unpin from Dock" : "Pin to Dock"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: pinArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (!root.appData) return
|
|
||||||
if (root.appData.isPinned) {
|
|
||||||
SessionData.removePinnedApp(root.appData.appId)
|
|
||||||
} else {
|
|
||||||
SessionData.addPinnedApp(root.appData.appId)
|
|
||||||
}
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: root.appData && root.appData.windows && root.appData.windows.count > 0
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: root.appData && root.appData.windows ? root.appData.windows : null
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
required property var model
|
|
||||||
width: menuColumn.width
|
|
||||||
height: 28
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: windowArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: model.title || "Untitled Window"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: windowArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
NiriService.focusWindow(model.id)
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: root.appData && root.appData.windows && root.appData.windows.count > 1
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: root.appData && root.appData.windows && root.appData.windows.count > 1
|
|
||||||
width: parent.width
|
|
||||||
height: 28
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: closeAllArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "Close All Windows"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: closeAllArea.containsMouse ? Theme.error : Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: closeAllArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (!root.appData || !root.appData.windows) return
|
|
||||||
for (var i = 0; i < root.appData.windows.count; i++) {
|
|
||||||
var window = root.appData.windows.get(i)
|
|
||||||
NiriService.closeWindow(window.id)
|
|
||||||
}
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
z: -1
|
|
||||||
onClicked: {
|
|
||||||
root.close()
|
root.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
z: -1
|
||||||
|
onClicked: {
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,193 +8,207 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool showWindowsMenu: false
|
property bool showWindowsMenu: false
|
||||||
property var appData: null
|
property var appData: null
|
||||||
property var anchorItem: null
|
property var anchorItem: null
|
||||||
property real dockVisibleHeight: 40
|
property real dockVisibleHeight: 40
|
||||||
property int margin: 10
|
property int margin: 10
|
||||||
|
|
||||||
function showForButton(button, data, dockHeight) {
|
function showForButton(button, data, dockHeight) {
|
||||||
anchorItem = button
|
anchorItem = button
|
||||||
appData = data
|
appData = data
|
||||||
dockVisibleHeight = dockHeight || 40
|
dockVisibleHeight = dockHeight || 40
|
||||||
|
|
||||||
var dockWindow = button.Window.window
|
var dockWindow = button.Window.window
|
||||||
if (dockWindow) {
|
if (dockWindow) {
|
||||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||||
var s = Quickshell.screens[i]
|
var s = Quickshell.screens[i]
|
||||||
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
|
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
|
||||||
root.screen = s
|
root.screen = s
|
||||||
break
|
break
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
showWindowsMenu = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() { showWindowsMenu = false }
|
showWindowsMenu = true
|
||||||
|
}
|
||||||
|
|
||||||
screen: Quickshell.screens[0]
|
function close() {
|
||||||
|
showWindowsMenu = false
|
||||||
|
}
|
||||||
|
|
||||||
visible: showWindowsMenu
|
screen: Quickshell.screens[0]
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
color: "transparent"
|
|
||||||
anchors { top: true; left: true; right: true; bottom: true }
|
|
||||||
|
|
||||||
property point anchorPos: Qt.point(screen.width/2, screen.height - 100)
|
visible: showWindowsMenu
|
||||||
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
color: "transparent"
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
onAnchorItemChanged: updatePosition()
|
property point anchorPos: Qt.point(screen.width / 2, screen.height - 100)
|
||||||
onVisibleChanged: if (visible) updatePosition()
|
|
||||||
|
|
||||||
function updatePosition() {
|
onAnchorItemChanged: updatePosition()
|
||||||
if (!anchorItem) {
|
onVisibleChanged: if (visible)
|
||||||
anchorPos = Qt.point(screen.width/2, screen.height - 100)
|
updatePosition()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var dockWindow = anchorItem.Window.window
|
function updatePosition() {
|
||||||
if (!dockWindow) {
|
if (!anchorItem) {
|
||||||
anchorPos = Qt.point(screen.width/2, screen.height - 100)
|
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
var buttonPosInDock = anchorItem.mapToItem(dockWindow.contentItem, 0, 0)
|
|
||||||
|
|
||||||
var actualDockHeight = root.dockVisibleHeight // fallback
|
|
||||||
|
|
||||||
function findDockBackground(item) {
|
|
||||||
if (item.objectName === "dockBackground") {
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
for (var i = 0; i < item.children.length; i++) {
|
|
||||||
var found = findDockBackground(item.children[i])
|
|
||||||
if (found) return found
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
var dockBackground = findDockBackground(dockWindow.contentItem)
|
|
||||||
if (dockBackground) {
|
|
||||||
actualDockHeight = dockBackground.height
|
|
||||||
}
|
|
||||||
|
|
||||||
var dockBottomMargin = 16 // The dock has bottom margin
|
|
||||||
var buttonScreenY = root.screen.height - actualDockHeight - dockBottomMargin - 20
|
|
||||||
|
|
||||||
var dockContentWidth = dockWindow.width
|
|
||||||
var screenWidth = root.screen.width
|
|
||||||
var dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2)
|
|
||||||
var buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width/2
|
|
||||||
|
|
||||||
anchorPos = Qt.point(buttonScreenX, buttonScreenY)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dockWindow = anchorItem.Window.window
|
||||||
|
if (!dockWindow) {
|
||||||
|
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonPosInDock = anchorItem.mapToItem(dockWindow.contentItem, 0, 0)
|
||||||
|
|
||||||
|
var actualDockHeight = root.dockVisibleHeight // fallback
|
||||||
|
|
||||||
|
function findDockBackground(item) {
|
||||||
|
if (item.objectName === "dockBackground") {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
for (var i = 0; i < item.children.length; i++) {
|
||||||
|
var found = findDockBackground(item.children[i])
|
||||||
|
if (found)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
var dockBackground = findDockBackground(dockWindow.contentItem)
|
||||||
|
if (dockBackground) {
|
||||||
|
actualDockHeight = dockBackground.height
|
||||||
|
}
|
||||||
|
|
||||||
|
var dockBottomMargin = 16 // The dock has bottom margin
|
||||||
|
var buttonScreenY = root.screen.height - actualDockHeight - dockBottomMargin - 20
|
||||||
|
|
||||||
|
var dockContentWidth = dockWindow.width
|
||||||
|
var screenWidth = root.screen.width
|
||||||
|
var dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2)
|
||||||
|
var buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2
|
||||||
|
|
||||||
|
anchorPos = Qt.point(buttonScreenX, buttonScreenY)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: menuContainer
|
||||||
|
|
||||||
|
width: Math.min(600,
|
||||||
|
Math.max(250,
|
||||||
|
windowColumn.implicitWidth + Theme.spacingS * 2))
|
||||||
|
height: Math.max(60, windowColumn.implicitHeight + Theme.spacingS * 2)
|
||||||
|
|
||||||
|
x: {
|
||||||
|
var left = 10
|
||||||
|
var right = root.width - width - 10
|
||||||
|
var want = root.anchorPos.x - width / 2
|
||||||
|
return Math.max(left, Math.min(right, want))
|
||||||
|
}
|
||||||
|
y: Math.max(10, root.anchorPos.y - height + 30)
|
||||||
|
color: Theme.popupBackground()
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
opacity: showWindowsMenu ? 1 : 0
|
||||||
|
scale: showWindowsMenu ? 1 : 0.85
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: menuContainer
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 4
|
||||||
|
anchors.leftMargin: 2
|
||||||
|
anchors.rightMargin: -2
|
||||||
|
anchors.bottomMargin: -4
|
||||||
|
radius: parent.radius
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.15)
|
||||||
|
z: parent.z - 1
|
||||||
|
}
|
||||||
|
|
||||||
width: Math.min(600, Math.max(250, windowColumn.implicitWidth + Theme.spacingS * 2))
|
Column {
|
||||||
height: Math.max(60, windowColumn.implicitHeight + Theme.spacingS * 2)
|
id: windowColumn
|
||||||
|
width: parent.width - Theme.spacingS * 2
|
||||||
x: {
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
var left = 10
|
anchors.top: parent.top
|
||||||
var right = root.width - width - 10
|
anchors.topMargin: Theme.spacingS
|
||||||
var want = root.anchorPos.x - width/2
|
spacing: 1
|
||||||
return Math.max(left, Math.min(right, want))
|
|
||||||
}
|
|
||||||
y: Math.max(10, root.anchorPos.y - height + 30)
|
|
||||||
color: Theme.popupBackground()
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
opacity: showWindowsMenu ? 1 : 0
|
|
||||||
scale: showWindowsMenu ? 1 : 0.85
|
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.appData
|
||||||
|
&& root.appData.windows ? root.appData.windows : null
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
required property var model
|
||||||
|
width: windowColumn.width
|
||||||
|
height: 32
|
||||||
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: windowArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: model.title || "Untitled Window"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: model.is_focused ? Theme.primary : Theme.surfaceText
|
||||||
|
font.weight: model.is_focused ? Font.Medium : Font.Normal
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: windowArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: 4
|
hoverEnabled: true
|
||||||
anchors.leftMargin: 2
|
cursorShape: Qt.PointingHandCursor
|
||||||
anchors.rightMargin: -2
|
onClicked: {
|
||||||
anchors.bottomMargin: -4
|
NiriService.focusWindow(model.id)
|
||||||
radius: parent.radius
|
root.close()
|
||||||
color: Qt.rgba(0, 0, 0, 0.15)
|
|
||||||
z: parent.z - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: windowColumn
|
|
||||||
width: parent.width - Theme.spacingS * 2
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: Theme.spacingS
|
|
||||||
spacing: 1
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: root.appData && root.appData.windows ? root.appData.windows : null
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
required property var model
|
|
||||||
width: windowColumn.width
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: windowArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: model.title || "Untitled Window"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: model.is_focused ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: model.is_focused ? Font.Medium : Font.Normal
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: windowArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
NiriService.focusWindow(model.id)
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
Behavior on opacity {
|
||||||
anchors.fill: parent
|
NumberAnimation {
|
||||||
z: -1
|
duration: Theme.shortDuration
|
||||||
hoverEnabled: false
|
easing.type: Theme.emphasizedEasing
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
}
|
||||||
onClicked: {
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
z: -1
|
||||||
|
hoverEnabled: false
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
onClicked: {
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -7,57 +7,57 @@ import Quickshell.Wayland
|
|||||||
import qs.Common
|
import qs.Common
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
function activate() {
|
function activate() {
|
||||||
loader.activeAsync = true
|
loader.activeAsync = true
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyLoader {
|
||||||
|
id: loader
|
||||||
|
|
||||||
|
WlSessionLock {
|
||||||
|
id: lock
|
||||||
|
|
||||||
|
property bool unlocked: false
|
||||||
|
property string sharedPasswordBuffer: ""
|
||||||
|
|
||||||
|
locked: true
|
||||||
|
|
||||||
|
onLockedChanged: {
|
||||||
|
if (!locked)
|
||||||
|
loader.active = false
|
||||||
|
}
|
||||||
|
|
||||||
|
LockSurface {
|
||||||
|
lock: lock
|
||||||
|
sharedPasswordBuffer: lock.sharedPasswordBuffer
|
||||||
|
onPasswordChanged: newPassword => {
|
||||||
|
lock.sharedPasswordBuffer = newPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LockScreenDemo {
|
||||||
|
id: demoWindow
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
target: "lock"
|
||||||
|
|
||||||
|
function lock(): void {
|
||||||
|
console.log("Lock screen requested via IPC")
|
||||||
|
loader.activeAsync = true
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyLoader {
|
function demo(): void {
|
||||||
id: loader
|
console.log("Lock screen DEMO mode requested via IPC")
|
||||||
|
demoWindow.showDemo()
|
||||||
WlSessionLock {
|
|
||||||
id: lock
|
|
||||||
|
|
||||||
property bool unlocked: false
|
|
||||||
property string sharedPasswordBuffer: ""
|
|
||||||
|
|
||||||
locked: true
|
|
||||||
|
|
||||||
onLockedChanged: {
|
|
||||||
if (!locked)
|
|
||||||
loader.active = false
|
|
||||||
}
|
|
||||||
|
|
||||||
LockSurface {
|
|
||||||
lock: lock
|
|
||||||
sharedPasswordBuffer: lock.sharedPasswordBuffer
|
|
||||||
onPasswordChanged: (newPassword) => {
|
|
||||||
lock.sharedPasswordBuffer = newPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LockScreenDemo {
|
function isLocked(): bool {
|
||||||
id: demoWindow
|
return loader.active
|
||||||
}
|
|
||||||
|
|
||||||
IpcHandler {
|
|
||||||
target: "lock"
|
|
||||||
|
|
||||||
function lock(): void {
|
|
||||||
console.log("Lock screen requested via IPC")
|
|
||||||
loader.activeAsync = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function demo(): void {
|
|
||||||
console.log("Lock screen DEMO mode requested via IPC")
|
|
||||||
demoWindow.showDemo()
|
|
||||||
}
|
|
||||||
|
|
||||||
function isLocked(): bool {
|
|
||||||
return loader.active
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -7,46 +7,46 @@ import qs.Common
|
|||||||
import qs.Modals
|
import qs.Modals
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool demoActive: false
|
property bool demoActive: false
|
||||||
|
|
||||||
visible: demoActive
|
visible: demoActive
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: true
|
top: true
|
||||||
bottom: true
|
bottom: true
|
||||||
left: true
|
left: true
|
||||||
right: true
|
right: true
|
||||||
}
|
}
|
||||||
|
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
function showDemo(): void {
|
function showDemo(): void {
|
||||||
console.log("Showing lock screen demo")
|
console.log("Showing lock screen demo")
|
||||||
demoActive = true
|
demoActive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideDemo(): void {
|
function hideDemo(): void {
|
||||||
console.log("Hiding lock screen demo")
|
console.log("Hiding lock screen demo")
|
||||||
demoActive = false
|
demoActive = false
|
||||||
}
|
}
|
||||||
|
|
||||||
PowerConfirmModal {
|
PowerConfirmModal {
|
||||||
id: powerModal
|
id: powerModal
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: demoActive
|
active: demoActive
|
||||||
sourceComponent: LockScreenContent {
|
sourceComponent: LockScreenContent {
|
||||||
demoMode: true
|
demoMode: true
|
||||||
powerModal: powerModal
|
powerModal: powerModal
|
||||||
onUnlockRequested: root.hideDemo()
|
onUnlockRequested: root.hideDemo()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -7,50 +7,50 @@ import qs.Common
|
|||||||
import qs.Modals
|
import qs.Modals
|
||||||
|
|
||||||
WlSessionLockSurface {
|
WlSessionLockSurface {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property WlSessionLock lock
|
required property WlSessionLock lock
|
||||||
required property string sharedPasswordBuffer
|
required property string sharedPasswordBuffer
|
||||||
|
|
||||||
signal passwordChanged(string newPassword)
|
signal passwordChanged(string newPassword)
|
||||||
|
|
||||||
property bool thisLocked: false
|
property bool thisLocked: false
|
||||||
readonly property bool locked: thisLocked && !lock.unlocked
|
readonly property bool locked: thisLocked && !lock.unlocked
|
||||||
|
|
||||||
function unlock(): void {
|
function unlock(): void {
|
||||||
console.log("LockSurface.unlock() called")
|
console.log("LockSurface.unlock() called")
|
||||||
lock.unlocked = true
|
lock.unlocked = true
|
||||||
animDelay.start()
|
animDelay.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
thisLocked = true
|
thisLocked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: animDelay
|
id: animDelay
|
||||||
interval: 1500 // Longer delay for success feedback
|
interval: 1500 // Longer delay for success feedback
|
||||||
onTriggered: root.lock.locked = false
|
onTriggered: root.lock.locked = false
|
||||||
}
|
}
|
||||||
|
|
||||||
PowerConfirmModal {
|
PowerConfirmModal {
|
||||||
id: powerModal
|
id: powerModal
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
sourceComponent: LockScreenContent {
|
sourceComponent: LockScreenContent {
|
||||||
demoMode: false
|
demoMode: false
|
||||||
powerModal: powerModal
|
powerModal: powerModal
|
||||||
passwordBuffer: root.sharedPasswordBuffer
|
passwordBuffer: root.sharedPasswordBuffer
|
||||||
onUnlockRequested: root.unlock()
|
onUnlockRequested: root.unlock()
|
||||||
onPasswordBufferChanged: {
|
onPasswordBufferChanged: {
|
||||||
if (root.sharedPasswordBuffer !== passwordBuffer) {
|
if (root.sharedPasswordBuffer !== passwordBuffer) {
|
||||||
root.passwordChanged(passwordBuffer)
|
root.passwordChanged(passwordBuffer)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,109 +8,112 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var modelData
|
property var modelData
|
||||||
property bool micPopupVisible: false
|
property bool micPopupVisible: false
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
root.micPopupVisible = true;
|
root.micPopupVisible = true
|
||||||
hideTimer.restart();
|
hideTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
screen: modelData
|
||||||
|
visible: micPopupVisible
|
||||||
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: hideTimer
|
||||||
|
|
||||||
|
interval: 2000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
root.micPopupVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onMicMuteChanged() {
|
||||||
|
root.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
screen: modelData
|
target: AudioService
|
||||||
visible: micPopupVisible
|
}
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
anchors {
|
Rectangle {
|
||||||
top: true
|
id: micPopup
|
||||||
left: true
|
|
||||||
right: true
|
width: Theme.iconSize + Theme.spacingS * 2
|
||||||
bottom: true
|
height: Theme.iconSize + Theme.spacingS * 2
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: Theme.spacingM
|
||||||
|
color: Theme.popupBackground()
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
opacity: root.micPopupVisible ? 1 : 0
|
||||||
|
scale: root.micPopupVisible ? 1 : 0.9
|
||||||
|
layer.enabled: true
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
id: micContent
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: AudioService.source && AudioService.source.audio
|
||||||
|
&& AudioService.source.audio.muted ? "mic_off" : "mic"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: AudioService.source && AudioService.source.audio
|
||||||
|
&& AudioService.source.audio.muted ? Theme.error : Theme.primary
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
layer.effect: MultiEffect {
|
||||||
id: hideTimer
|
shadowEnabled: true
|
||||||
|
shadowHorizontalOffset: 0
|
||||||
interval: 2000
|
shadowVerticalOffset: 4
|
||||||
repeat: false
|
shadowBlur: 0.8
|
||||||
onTriggered: {
|
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
||||||
root.micPopupVisible = false;
|
shadowOpacity: 0.3
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
transform: Translate {
|
||||||
function onMicMuteChanged() {
|
y: root.micPopupVisible ? 0 : 20
|
||||||
root.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
target: AudioService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Behavior on opacity {
|
||||||
id: micPopup
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
width: Theme.iconSize + Theme.spacingS * 2
|
easing.type: Theme.emphasizedEasing
|
||||||
height: Theme.iconSize + Theme.spacingS * 2
|
}
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: Theme.spacingM
|
|
||||||
color: Theme.popupBackground()
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
opacity: root.micPopupVisible ? 1 : 0
|
|
||||||
scale: root.micPopupVisible ? 1 : 0.9
|
|
||||||
layer.enabled: true
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
id: micContent
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: AudioService.source && AudioService.source.audio && AudioService.source.audio.muted ? "mic_off" : "mic"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: AudioService.source && AudioService.source.audio && AudioService.source.audio.muted ? Theme.error : Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowHorizontalOffset: 0
|
|
||||||
shadowVerticalOffset: 4
|
|
||||||
shadowBlur: 0.8
|
|
||||||
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
|
||||||
shadowOpacity: 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
transform: Translate {
|
|
||||||
y: root.micPopupVisible ? 0 : 20
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on transform {
|
|
||||||
PropertyAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mask: Region {
|
Behavior on scale {
|
||||||
item: micPopup
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on transform {
|
||||||
|
PropertyAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: micPopup
|
||||||
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -9,186 +9,183 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool notificationHistoryVisible: false
|
property bool notificationHistoryVisible: false
|
||||||
property real triggerX: Screen.width - 400 - Theme.spacingL
|
property real triggerX: Screen.width - 400 - Theme.spacingL
|
||||||
property real triggerY: Theme.barHeight + Theme.spacingXS
|
property real triggerY: Theme.barHeight + Theme.spacingXS
|
||||||
property real triggerWidth: 40
|
property real triggerWidth: 40
|
||||||
property string triggerSection: "right"
|
property string triggerSection: "right"
|
||||||
|
|
||||||
function setTriggerPosition(x, y, width, section) {
|
function setTriggerPosition(x, y, width, section) {
|
||||||
triggerX = x;
|
triggerX = x
|
||||||
triggerY = y;
|
triggerY = y
|
||||||
triggerWidth = width;
|
triggerWidth = width
|
||||||
triggerSection = section;
|
triggerSection = section
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: notificationHistoryVisible
|
||||||
|
onNotificationHistoryVisibleChanged: {
|
||||||
|
NotificationService.disablePopups(notificationHistoryVisible)
|
||||||
|
}
|
||||||
|
implicitWidth: 400
|
||||||
|
implicitHeight: Math.min(Screen.height * 0.8, 400)
|
||||||
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: notificationHistoryVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
notificationHistoryVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: mainRect
|
||||||
|
|
||||||
|
function calculateHeight() {
|
||||||
|
let baseHeight = Theme.spacingL * 2
|
||||||
|
baseHeight += notificationHeader.height
|
||||||
|
baseHeight += Theme.spacingM
|
||||||
|
let listHeight = notificationList.listContentHeight
|
||||||
|
if (NotificationService.groupedNotifications.length === 0)
|
||||||
|
listHeight = 200
|
||||||
|
|
||||||
|
baseHeight += Math.min(listHeight, 600)
|
||||||
|
return Math.max(300, baseHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
visible: notificationHistoryVisible
|
readonly property real popupWidth: 400
|
||||||
onNotificationHistoryVisibleChanged: {
|
readonly property real calculatedX: {
|
||||||
NotificationService.disablePopups(notificationHistoryVisible);
|
var centerX = root.triggerX + (root.triggerWidth / 2) - (popupWidth / 2)
|
||||||
}
|
|
||||||
implicitWidth: 400
|
|
||||||
implicitHeight: Math.min(Screen.height * 0.8, 400)
|
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: notificationHistoryVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
anchors {
|
if (centerX >= Theme.spacingM
|
||||||
top: true
|
&& centerX + popupWidth <= Screen.width - Theme.spacingM) {
|
||||||
left: true
|
return centerX
|
||||||
right: true
|
}
|
||||||
bottom: true
|
|
||||||
|
if (centerX < Theme.spacingM) {
|
||||||
|
return Theme.spacingM
|
||||||
|
}
|
||||||
|
|
||||||
|
if (centerX + popupWidth > Screen.width - Theme.spacingM) {
|
||||||
|
return Screen.width - popupWidth - Theme.spacingM
|
||||||
|
}
|
||||||
|
|
||||||
|
return centerX
|
||||||
}
|
}
|
||||||
|
|
||||||
|
width: popupWidth
|
||||||
|
height: calculateHeight()
|
||||||
|
x: calculatedX
|
||||||
|
y: root.triggerY
|
||||||
|
color: Theme.popupBackground()
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
opacity: notificationHistoryVisible ? 1 : 0
|
||||||
|
scale: notificationHistoryVisible ? 1 : 0.9
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: {
|
onClicked: {
|
||||||
notificationHistoryVisible = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Column {
|
||||||
id: mainRect
|
id: contentColumn
|
||||||
|
|
||||||
function calculateHeight() {
|
anchors.fill: parent
|
||||||
let baseHeight = Theme.spacingL * 2;
|
anchors.margins: Theme.spacingL
|
||||||
baseHeight += notificationHeader.height;
|
spacing: Theme.spacingM
|
||||||
baseHeight += Theme.spacingM;
|
focus: true
|
||||||
let listHeight = notificationList.listContentHeight;
|
Component.onCompleted: {
|
||||||
if (NotificationService.groupedNotifications.length === 0)
|
if (notificationHistoryVisible)
|
||||||
listHeight = 200;
|
forceActiveFocus()
|
||||||
|
}
|
||||||
baseHeight += Math.min(listHeight, 600);
|
Keys.onPressed: function (event) {
|
||||||
return Math.max(300, baseHeight);
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
notificationHistoryVisible = false
|
||||||
|
event.accepted = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
readonly property real popupWidth: 400
|
Connections {
|
||||||
readonly property real calculatedX: {
|
function onNotificationHistoryVisibleChanged() {
|
||||||
var centerX = root.triggerX + (root.triggerWidth / 2) - (popupWidth / 2);
|
if (notificationHistoryVisible)
|
||||||
|
Qt.callLater(function () {
|
||||||
if (centerX >= Theme.spacingM && centerX + popupWidth <= Screen.width - Theme.spacingM) {
|
contentColumn.forceActiveFocus()
|
||||||
return centerX;
|
})
|
||||||
}
|
else
|
||||||
|
contentColumn.focus = false
|
||||||
if (centerX < Theme.spacingM) {
|
|
||||||
return Theme.spacingM;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (centerX + popupWidth > Screen.width - Theme.spacingM) {
|
|
||||||
return Screen.width - popupWidth - Theme.spacingM;
|
|
||||||
}
|
|
||||||
|
|
||||||
return centerX;
|
|
||||||
}
|
}
|
||||||
|
target: root
|
||||||
|
}
|
||||||
|
|
||||||
width: popupWidth
|
NotificationHeader {
|
||||||
height: calculateHeight()
|
id: notificationHeader
|
||||||
x: calculatedX
|
}
|
||||||
y: root.triggerY
|
|
||||||
color: Theme.popupBackground()
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
opacity: notificationHistoryVisible ? 1 : 0
|
|
||||||
scale: notificationHistoryVisible ? 1 : 0.9
|
|
||||||
|
|
||||||
MouseArea {
|
NotificationList {
|
||||||
anchors.fill: parent
|
id: notificationList
|
||||||
onClicked: {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: contentColumn
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingL
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
focus: true
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (notificationHistoryVisible)
|
|
||||||
forceActiveFocus();
|
|
||||||
}
|
|
||||||
Keys.onPressed: function(event) {
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
|
||||||
notificationHistoryVisible = false;
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onNotificationHistoryVisibleChanged() {
|
|
||||||
if (notificationHistoryVisible)
|
|
||||||
Qt.callLater(function() {
|
|
||||||
contentColumn.forceActiveFocus();
|
|
||||||
});
|
|
||||||
else
|
|
||||||
contentColumn.focus = false;
|
|
||||||
}
|
|
||||||
target: root
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationHeader {
|
|
||||||
id: notificationHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationList {
|
|
||||||
id: notificationList
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: parent.height - notificationHeader.height - contentColumn.spacing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onNotificationsChanged() {
|
|
||||||
mainRect.height = mainRect.calculateHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onGroupedNotificationsChanged() {
|
|
||||||
mainRect.height = mainRect.calculateHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onExpandedGroupsChanged() {
|
|
||||||
mainRect.height = mainRect.calculateHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onExpandedMessagesChanged() {
|
|
||||||
mainRect.height = mainRect.calculateHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
target: NotificationService
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Anims.durMed
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.emphasized
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Anims.durMed
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.emphasized
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - notificationHeader.height - contentColumn.spacing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onNotificationsChanged() {
|
||||||
|
mainRect.height = mainRect.calculateHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onGroupedNotificationsChanged() {
|
||||||
|
mainRect.height = mainRect.calculateHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onExpandedGroupsChanged() {
|
||||||
|
mainRect.height = mainRect.calculateHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onExpandedMessagesChanged() {
|
||||||
|
mainRect.height = mainRect.calculateHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
target: NotificationService
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Anims.durMed
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.emphasized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Anims.durMed
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.emphasized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,33 +4,33 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 200
|
height: 200
|
||||||
visible: NotificationService.notifications.length === 0
|
visible: NotificationService.notifications.length === 0
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
width: parent.width * 0.8
|
width: parent.width * 0.8
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
name: "notifications_none"
|
|
||||||
size: Theme.iconSizeLarge + 16
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
text: "Nothing to see here"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
|
||||||
font.weight: Font.Medium
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
name: "notifications_none"
|
||||||
|
size: Theme.iconSizeLarge + 16
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: "Nothing to see here"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.3)
|
||||||
|
font.weight: Font.Medium
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,132 +5,129 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 32
|
height: 32
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Notifications"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
id: doNotDisturbButton
|
||||||
|
|
||||||
|
iconName: SessionData.doNotDisturb ? "notifications_off" : "notifications"
|
||||||
|
iconColor: SessionData.doNotDisturb ? Theme.error : Theme.surfaceText
|
||||||
|
buttonSize: 28
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onClicked: SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: doNotDisturbTooltip
|
||||||
|
|
||||||
|
width: tooltipText.contentWidth + Theme.spacingS * 2
|
||||||
|
height: tooltipText.contentHeight + Theme.spacingXS * 2
|
||||||
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: Theme.surfaceContainer
|
||||||
|
border.color: Theme.outline
|
||||||
|
border.width: 1
|
||||||
|
anchors.bottom: parent.top
|
||||||
|
anchors.bottomMargin: Theme.spacingS
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
visible: doNotDisturbButton.children[1].containsMouse // Access StateLayer's containsMouse
|
||||||
|
opacity: visible ? 1 : 0
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "Notifications"
|
id: tooltipText
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Theme.surfaceText
|
text: "Do Not Disturb"
|
||||||
font.weight: Font.Medium
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
font.hintingPreference: Font.PreferFullHinting
|
||||||
}
|
}
|
||||||
|
|
||||||
DankActionButton {
|
Behavior on opacity {
|
||||||
id: doNotDisturbButton
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
iconName: SessionData.doNotDisturb ? "notifications_off" : "notifications"
|
easing.type: Theme.standardEasing
|
||||||
iconColor: SessionData.doNotDisturb ? Theme.error : Theme.surfaceText
|
}
|
||||||
buttonSize: 28
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
onClicked: SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: doNotDisturbTooltip
|
|
||||||
|
|
||||||
width: tooltipText.contentWidth + Theme.spacingS * 2
|
|
||||||
height: tooltipText.contentHeight + Theme.spacingXS * 2
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: Theme.surfaceContainer
|
|
||||||
border.color: Theme.outline
|
|
||||||
border.width: 1
|
|
||||||
anchors.bottom: parent.top
|
|
||||||
anchors.bottomMargin: Theme.spacingS
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
visible: doNotDisturbButton.children[1].containsMouse // Access StateLayer's containsMouse
|
|
||||||
opacity: visible ? 1 : 0
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: tooltipText
|
|
||||||
|
|
||||||
text: "Do Not Disturb"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.centerIn: parent
|
|
||||||
font.hintingPreference: Font.PreferFullHinting
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: clearAllButton
|
id: clearAllButton
|
||||||
|
|
||||||
width: 120
|
width: 120
|
||||||
height: 28
|
height: 28
|
||||||
radius: Theme.cornerRadiusLarge
|
radius: Theme.cornerRadiusLarge
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: NotificationService.notifications.length > 0
|
||||||
|
color: clearArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||||
|
Theme.primary.b, 0.12) : Qt.rgba(
|
||||||
|
Theme.surfaceVariant.r,
|
||||||
|
Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.3)
|
||||||
|
border.color: clearArea.containsMouse ? Theme.primary : Qt.rgba(
|
||||||
|
Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "delete_sweep"
|
||||||
|
size: Theme.iconSizeSmall
|
||||||
|
color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: NotificationService.notifications.length > 0
|
}
|
||||||
color: clearArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
|
||||||
border.color: clearArea.containsMouse ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "delete_sweep"
|
|
||||||
size: Theme.iconSizeSmall
|
|
||||||
color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Clear All"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: clearArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: NotificationService.clearAllNotifications()
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on border.color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Clear All"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: clearArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: NotificationService.clearAllNotifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on border.color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,23 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
DankListView {
|
DankListView {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias count: root.count
|
property alias count: root.count
|
||||||
property alias listContentHeight: root.contentHeight
|
property alias listContentHeight: root.contentHeight
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
clip: true
|
clip: true
|
||||||
model: NotificationService.groupedNotifications
|
model: NotificationService.groupedNotifications
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
NotificationEmptyState {
|
NotificationEmptyState {
|
||||||
visible: root.count === 0
|
visible: root.count === 0
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delegate: NotificationCard {
|
||||||
delegate: NotificationCard {
|
notificationGroup: modelData
|
||||||
notificationGroup: modelData
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,242 +4,246 @@ import qs.Common
|
|||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: manager
|
id: manager
|
||||||
|
|
||||||
property var modelData
|
property var modelData
|
||||||
property int topMargin: 0
|
property int topMargin: 0
|
||||||
property int baseNotificationHeight: 120
|
property int baseNotificationHeight: 120
|
||||||
property int maxTargetNotifications: 3
|
property int maxTargetNotifications: 3
|
||||||
property var popupWindows: [] // strong refs to windows (live until exitFinished)
|
property var popupWindows: [] // strong refs to windows (live until exitFinished)
|
||||||
property var destroyingWindows: new Set()
|
property var destroyingWindows: new Set()
|
||||||
property Component popupComponent
|
property Component popupComponent
|
||||||
|
|
||||||
popupComponent: Component {
|
popupComponent: Component {
|
||||||
NotificationPopup {
|
NotificationPopup {
|
||||||
onEntered: manager._onPopupEntered(this)
|
onEntered: manager._onPopupEntered(this)
|
||||||
onExitFinished: manager._onPopupExitFinished(this)
|
onExitFinished: manager._onPopupExitFinished(this)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Connections notificationConnections
|
||||||
|
|
||||||
|
notificationConnections: Connections {
|
||||||
|
function onVisibleNotificationsChanged() {
|
||||||
|
manager._sync(NotificationService.visibleNotifications)
|
||||||
}
|
}
|
||||||
|
|
||||||
property Connections notificationConnections
|
target: NotificationService
|
||||||
|
}
|
||||||
|
|
||||||
notificationConnections: Connections {
|
property Timer sweeper
|
||||||
function onVisibleNotificationsChanged() {
|
|
||||||
manager._sync(NotificationService.visibleNotifications);
|
sweeper: Timer {
|
||||||
|
interval: 2000
|
||||||
|
running: false // Not running by default
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
let toRemove = []
|
||||||
|
for (let p of popupWindows) {
|
||||||
|
if (!p) {
|
||||||
|
toRemove.push(p)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
const isZombie = p.status === Component.Null || (!p.visible
|
||||||
|
&& !p.exiting)
|
||||||
|
|| (!p.notificationData && !p._isDestroying)
|
||||||
|
|| (!p.hasValidData && !p._isDestroying)
|
||||||
|
if (isZombie) {
|
||||||
|
|
||||||
target: NotificationService
|
toRemove.push(p)
|
||||||
}
|
if (p.forceExit) {
|
||||||
|
p.forceExit()
|
||||||
|
} else if (p.destroy) {
|
||||||
|
try {
|
||||||
|
p.destroy()
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
property Timer sweeper
|
|
||||||
|
|
||||||
sweeper: Timer {
|
|
||||||
interval: 2000
|
|
||||||
running: false // Not running by default
|
|
||||||
repeat: true
|
|
||||||
onTriggered: {
|
|
||||||
let toRemove = [];
|
|
||||||
for (let p of popupWindows) {
|
|
||||||
if (!p) {
|
|
||||||
toRemove.push(p);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const isZombie = p.status === Component.Null || (!p.visible && !p.exiting) || (!p.notificationData && !p._isDestroying) || (!p.hasValidData && !p._isDestroying);
|
|
||||||
if (isZombie) {
|
|
||||||
|
|
||||||
toRemove.push(p);
|
|
||||||
if (p.forceExit) {
|
|
||||||
p.forceExit();
|
|
||||||
} else if (p.destroy) {
|
|
||||||
try {
|
|
||||||
p.destroy();
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (toRemove.length > 0) {
|
}
|
||||||
for (let zombie of toRemove) {
|
|
||||||
const i = popupWindows.indexOf(zombie);
|
|
||||||
if (i !== -1)
|
|
||||||
popupWindows.splice(i, 1);
|
|
||||||
|
|
||||||
}
|
|
||||||
popupWindows = popupWindows.slice();
|
|
||||||
const survivors = _active().sort((a, b) => {
|
|
||||||
return a.screenY - b.screenY;
|
|
||||||
});
|
|
||||||
for (let k = 0; k < survivors.length; ++k) {
|
|
||||||
survivors[k].screenY = topMargin + k * baseNotificationHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (popupWindows.length === 0)
|
|
||||||
sweeper.stop();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (toRemove.length > 0) {
|
||||||
function _hasWindowFor(w) {
|
for (let zombie of toRemove) {
|
||||||
return popupWindows.some((p) => {
|
const i = popupWindows.indexOf(zombie)
|
||||||
return p && p.notificationData === w && !p._isDestroying && p.status !== Component.Null;
|
if (i !== -1)
|
||||||
});
|
popupWindows.splice(i, 1)
|
||||||
}
|
|
||||||
|
|
||||||
function _isValidWindow(p) {
|
|
||||||
return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _sync(newWrappers) {
|
|
||||||
for (let w of newWrappers) {
|
|
||||||
if (w && !_hasWindowFor(w))
|
|
||||||
insertNewestAtTop(w);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
for (let p of popupWindows.slice()) {
|
popupWindows = popupWindows.slice()
|
||||||
if (!_isValidWindow(p))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (p.notificationData && newWrappers.indexOf(p.notificationData) === -1 && !p.exiting) {
|
|
||||||
p.notificationData.removedByLimit = true;
|
|
||||||
p.notificationData.popup = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertNewestAtTop(wrapper) {
|
|
||||||
if (!wrapper) {
|
|
||||||
|
|
||||||
return ;
|
|
||||||
}
|
|
||||||
for (let p of popupWindows) {
|
|
||||||
if (!_isValidWindow(p))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (p.exiting)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
p.screenY = p.screenY + baseNotificationHeight;
|
|
||||||
}
|
|
||||||
const notificationId = wrapper && wrapper.notification ? wrapper.notification.id : "";
|
|
||||||
const win = popupComponent.createObject(null, {
|
|
||||||
"notificationData": wrapper,
|
|
||||||
"notificationId": notificationId,
|
|
||||||
"screenY": topMargin,
|
|
||||||
"screen": manager.modelData
|
|
||||||
});
|
|
||||||
if (!win) {
|
|
||||||
|
|
||||||
return ;
|
|
||||||
}
|
|
||||||
if (!win.hasValidData) {
|
|
||||||
|
|
||||||
win.destroy();
|
|
||||||
return ;
|
|
||||||
}
|
|
||||||
popupWindows.push(win);
|
|
||||||
if (!sweeper.running)
|
|
||||||
sweeper.start();
|
|
||||||
|
|
||||||
_maybeStartOverflow();
|
|
||||||
}
|
|
||||||
|
|
||||||
function _active() {
|
|
||||||
return popupWindows.filter((p) => {
|
|
||||||
return _isValidWindow(p) && p.notificationData && p.notificationData.popup && !p.exiting;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _bottom() {
|
|
||||||
let b = null, maxY = -1;
|
|
||||||
for (let p of _active()) {
|
|
||||||
if (p.screenY > maxY) {
|
|
||||||
maxY = p.screenY;
|
|
||||||
b = p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _maybeStartOverflow() {
|
|
||||||
const activeWindows = _active();
|
|
||||||
if (activeWindows.length <= maxTargetNotifications + 1)
|
|
||||||
return ;
|
|
||||||
|
|
||||||
const b = _bottom();
|
|
||||||
if (b && !b.exiting) {
|
|
||||||
b.notificationData.removedByLimit = true;
|
|
||||||
b.notificationData.popup = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _onPopupEntered(p) {
|
|
||||||
if (_isValidWindow(p))
|
|
||||||
_maybeStartOverflow();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function _onPopupExitFinished(p) {
|
|
||||||
if (!p)
|
|
||||||
return ;
|
|
||||||
|
|
||||||
const windowId = p.toString();
|
|
||||||
if (destroyingWindows.has(windowId))
|
|
||||||
return ;
|
|
||||||
|
|
||||||
destroyingWindows.add(windowId);
|
|
||||||
const i = popupWindows.indexOf(p);
|
|
||||||
if (i !== -1) {
|
|
||||||
popupWindows.splice(i, 1);
|
|
||||||
popupWindows = popupWindows.slice();
|
|
||||||
}
|
|
||||||
if (NotificationService.releaseWrapper && p.notificationData)
|
|
||||||
NotificationService.releaseWrapper(p.notificationData);
|
|
||||||
|
|
||||||
Qt.callLater(() => {
|
|
||||||
if (p && p.destroy) {
|
|
||||||
try {
|
|
||||||
p.destroy();
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Qt.callLater(() => {
|
|
||||||
destroyingWindows.delete(windowId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const survivors = _active().sort((a, b) => {
|
const survivors = _active().sort((a, b) => {
|
||||||
return a.screenY - b.screenY;
|
return a.screenY - b.screenY
|
||||||
});
|
})
|
||||||
for (let k = 0; k < survivors.length; ++k) {
|
for (var k = 0; k < survivors.length; ++k) {
|
||||||
survivors[k].screenY = topMargin + k * baseNotificationHeight;
|
survivors[k].screenY = topMargin + k * baseNotificationHeight
|
||||||
}
|
}
|
||||||
_maybeStartOverflow();
|
}
|
||||||
|
if (popupWindows.length === 0)
|
||||||
|
sweeper.stop()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function cleanupAllWindows() {
|
function _hasWindowFor(w) {
|
||||||
sweeper.stop();
|
return popupWindows.some(p => {
|
||||||
for (let p of popupWindows.slice()) {
|
return p && p.notificationData === w
|
||||||
if (p) {
|
&& !p._isDestroying
|
||||||
try {
|
&& p.status !== Component.Null
|
||||||
if (p.forceExit)
|
})
|
||||||
p.forceExit();
|
}
|
||||||
else if (p.destroy)
|
|
||||||
p.destroy();
|
function _isValidWindow(p) {
|
||||||
} catch (e) {
|
return p && p.status !== Component.Null && !p._isDestroying
|
||||||
|
&& p.hasValidData
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sync(newWrappers) {
|
||||||
|
for (let w of newWrappers) {
|
||||||
|
if (w && !_hasWindowFor(w))
|
||||||
|
insertNewestAtTop(w)
|
||||||
|
}
|
||||||
|
for (let p of popupWindows.slice()) {
|
||||||
|
if (!_isValidWindow(p))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (p.notificationData && newWrappers.indexOf(p.notificationData) === -1
|
||||||
|
&& !p.exiting) {
|
||||||
|
p.notificationData.removedByLimit = true
|
||||||
|
p.notificationData.popup = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertNewestAtTop(wrapper) {
|
||||||
|
if (!wrapper) {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (let p of popupWindows) {
|
||||||
|
if (!_isValidWindow(p))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (p.exiting)
|
||||||
|
continue
|
||||||
|
|
||||||
|
p.screenY = p.screenY + baseNotificationHeight
|
||||||
|
}
|
||||||
|
const notificationId = wrapper
|
||||||
|
&& wrapper.notification ? wrapper.notification.id : ""
|
||||||
|
const win = popupComponent.createObject(null, {
|
||||||
|
"notificationData": wrapper,
|
||||||
|
"notificationId": notificationId,
|
||||||
|
"screenY": topMargin,
|
||||||
|
"screen": manager.modelData
|
||||||
|
})
|
||||||
|
if (!win) {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!win.hasValidData) {
|
||||||
|
|
||||||
|
win.destroy()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
popupWindows.push(win)
|
||||||
|
if (!sweeper.running)
|
||||||
|
sweeper.start()
|
||||||
|
|
||||||
|
_maybeStartOverflow()
|
||||||
|
}
|
||||||
|
|
||||||
|
function _active() {
|
||||||
|
return popupWindows.filter(p => {
|
||||||
|
return _isValidWindow(p) && p.notificationData
|
||||||
|
&& p.notificationData.popup && !p.exiting
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bottom() {
|
||||||
|
let b = null, maxY = -1
|
||||||
|
for (let p of _active()) {
|
||||||
|
if (p.screenY > maxY) {
|
||||||
|
maxY = p.screenY
|
||||||
|
b = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
function _maybeStartOverflow() {
|
||||||
|
const activeWindows = _active()
|
||||||
|
if (activeWindows.length <= maxTargetNotifications + 1)
|
||||||
|
return
|
||||||
|
|
||||||
|
const b = _bottom()
|
||||||
|
if (b && !b.exiting) {
|
||||||
|
b.notificationData.removedByLimit = true
|
||||||
|
b.notificationData.popup = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _onPopupEntered(p) {
|
||||||
|
if (_isValidWindow(p))
|
||||||
|
_maybeStartOverflow()
|
||||||
|
}
|
||||||
|
|
||||||
|
function _onPopupExitFinished(p) {
|
||||||
|
if (!p)
|
||||||
|
return
|
||||||
|
|
||||||
|
const windowId = p.toString()
|
||||||
|
if (destroyingWindows.has(windowId))
|
||||||
|
return
|
||||||
|
|
||||||
|
destroyingWindows.add(windowId)
|
||||||
|
const i = popupWindows.indexOf(p)
|
||||||
|
if (i !== -1) {
|
||||||
|
popupWindows.splice(i, 1)
|
||||||
|
popupWindows = popupWindows.slice()
|
||||||
|
}
|
||||||
|
if (NotificationService.releaseWrapper && p.notificationData)
|
||||||
|
NotificationService.releaseWrapper(p.notificationData)
|
||||||
|
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (p && p.destroy) {
|
||||||
|
try {
|
||||||
|
p.destroy()
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Qt.callLater(() => {
|
||||||
|
destroyingWindows.delete(windowId)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const survivors = _active().sort((a, b) => {
|
||||||
|
return a.screenY - b.screenY
|
||||||
|
})
|
||||||
|
for (var k = 0; k < survivors.length; ++k) {
|
||||||
|
survivors[k].screenY = topMargin + k * baseNotificationHeight
|
||||||
|
}
|
||||||
|
_maybeStartOverflow()
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupAllWindows() {
|
||||||
|
sweeper.stop()
|
||||||
|
for (let p of popupWindows.slice()) {
|
||||||
|
if (p) {
|
||||||
|
try {
|
||||||
|
if (p.forceExit)
|
||||||
|
p.forceExit()
|
||||||
|
else if (p.destroy)
|
||||||
|
p.destroy()
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
popupWindows = [];
|
}
|
||||||
destroyingWindows.clear();
|
|
||||||
}
|
}
|
||||||
|
popupWindows = []
|
||||||
|
destroyingWindows.clear()
|
||||||
|
}
|
||||||
|
|
||||||
onPopupWindowsChanged: {
|
onPopupWindowsChanged: {
|
||||||
if (popupWindows.length > 0 && !sweeper.running)
|
if (popupWindows.length > 0 && !sweeper.running)
|
||||||
sweeper.start();
|
sweeper.start()
|
||||||
else if (popupWindows.length === 0 && sweeper.running)
|
else if (popupWindows.length === 0 && sweeper.running)
|
||||||
sweeper.stop();
|
sweeper.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,476 +5,474 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
function formatNetworkSpeed(bytesPerSec) {
|
function formatNetworkSpeed(bytesPerSec) {
|
||||||
if (bytesPerSec < 1024)
|
if (bytesPerSec < 1024)
|
||||||
return bytesPerSec.toFixed(0) + " B/s";
|
return bytesPerSec.toFixed(0) + " B/s"
|
||||||
else if (bytesPerSec < 1024 * 1024)
|
else if (bytesPerSec < 1024 * 1024)
|
||||||
return (bytesPerSec / 1024).toFixed(1) + " KB/s";
|
return (bytesPerSec / 1024).toFixed(1) + " KB/s"
|
||||||
else if (bytesPerSec < 1024 * 1024 * 1024)
|
else if (bytesPerSec < 1024 * 1024 * 1024)
|
||||||
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s";
|
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s"
|
||||||
else
|
else
|
||||||
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s";
|
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s"
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDiskSpeed(bytesPerSec) {
|
function formatDiskSpeed(bytesPerSec) {
|
||||||
if (bytesPerSec < 1024 * 1024)
|
if (bytesPerSec < 1024 * 1024)
|
||||||
return (bytesPerSec / 1024).toFixed(1) + " KB/s";
|
return (bytesPerSec / 1024).toFixed(1) + " KB/s"
|
||||||
else if (bytesPerSec < 1024 * 1024 * 1024)
|
else if (bytesPerSec < 1024 * 1024 * 1024)
|
||||||
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s";
|
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s"
|
||||||
else
|
else
|
||||||
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s";
|
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s"
|
||||||
}
|
}
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
SysMonitorService.addRef();
|
SysMonitorService.addRef()
|
||||||
}
|
}
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
SysMonitorService.removeRef();
|
SysMonitorService.removeRef()
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 200
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.04)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.06)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 200
|
height: 32
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.04)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingM
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
height: 32
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "CPU"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 80
|
|
||||||
height: 24
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.totalCpuUsage.toFixed(1) + "%"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width - 280
|
|
||||||
height: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.cpuCount + " cores"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
width: parent.width
|
|
||||||
height: parent.height - 40
|
|
||||||
clip: true
|
|
||||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
|
||||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: SysMonitorService.perCoreCpuUsage
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
height: 20
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "C" + index
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
width: 24
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width - 80
|
|
||||||
height: 6
|
|
||||||
radius: 3
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width * Math.min(1, modelData / 100)
|
|
||||||
height: parent.height
|
|
||||||
radius: parent.radius
|
|
||||||
color: {
|
|
||||||
const usage = modelData;
|
|
||||||
if (usage > 80)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
if (usage > 60)
|
|
||||||
return Theme.warning;
|
|
||||||
|
|
||||||
return Theme.primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData ? modelData.toFixed(0) + "%" : "0%"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: 32
|
|
||||||
horizontalAlignment: Text.AlignRight
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 80
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.04)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
anchors.margins: Theme.spacingM
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Memory"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.formatSystemMemory(SysMonitorService.usedMemoryKB) + " / " + SysMonitorService.formatSystemMemory(SysMonitorService.totalMemoryKB)
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: Theme.spacingL
|
|
||||||
height: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 4
|
|
||||||
width: 200
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 16
|
|
||||||
radius: 8
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: SysMonitorService.totalMemoryKB > 0 ? parent.width * (SysMonitorService.usedMemoryKB / SysMonitorService.totalMemoryKB) : 0
|
|
||||||
height: parent.height
|
|
||||||
radius: parent.radius
|
|
||||||
color: {
|
|
||||||
const usage = SysMonitorService.totalMemoryKB > 0 ? (SysMonitorService.usedMemoryKB / SysMonitorService.totalMemoryKB) : 0;
|
|
||||||
if (usage > 0.9)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
if (usage > 0.7)
|
|
||||||
return Theme.warning;
|
|
||||||
|
|
||||||
return Theme.secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.totalMemoryKB > 0 ? ((SysMonitorService.usedMemoryKB / SysMonitorService.totalMemoryKB) * 100).toFixed(1) + "% used" : "No data"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: Theme.spacingL
|
|
||||||
height: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Swap"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.totalSwapKB > 0 ? SysMonitorService.formatSystemMemory(SysMonitorService.usedSwapKB) + " / " + SysMonitorService.formatSystemMemory(SysMonitorService.totalSwapKB) : "No swap configured"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: Theme.spacingL
|
|
||||||
height: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 4
|
|
||||||
width: 200
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 16
|
|
||||||
radius: 8
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: SysMonitorService.totalSwapKB > 0 ? parent.width * (SysMonitorService.usedSwapKB / SysMonitorService.totalSwapKB) : 0
|
|
||||||
height: parent.height
|
|
||||||
radius: parent.radius
|
|
||||||
color: {
|
|
||||||
if (!SysMonitorService.totalSwapKB)
|
|
||||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3);
|
|
||||||
|
|
||||||
const usage = SysMonitorService.usedSwapKB / SysMonitorService.totalSwapKB;
|
|
||||||
if (usage > 0.9)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
if (usage > 0.7)
|
|
||||||
return Theme.warning;
|
|
||||||
|
|
||||||
return Theme.info;
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.totalSwapKB > 0 ? ((SysMonitorService.usedSwapKB / SysMonitorService.totalSwapKB) * 100).toFixed(1) + "% used" : "Not available"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
height: 80
|
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Rectangle {
|
StyledText {
|
||||||
width: (parent.width - Theme.spacingM) / 2
|
text: "CPU"
|
||||||
height: 80
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
radius: Theme.cornerRadiusLarge
|
font.weight: Font.Bold
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.04)
|
color: Theme.surfaceText
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Network"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "↓"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.info
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.networkRxRate > 0 ? formatNetworkSpeed(SysMonitorService.networkRxRate) : "0 B/s"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "↑"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.error
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.networkTxRate > 0 ? formatNetworkSpeed(SysMonitorService.networkTxRate) : "0 B/s"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: (parent.width - Theme.spacingM) / 2
|
width: 80
|
||||||
height: 80
|
height: 24
|
||||||
radius: Theme.cornerRadiusLarge
|
radius: Theme.cornerRadiusSmall
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.04)
|
color: Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
|
Theme.primary.b, 0.12)
|
||||||
border.width: 1
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Disk"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "R"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: formatDiskSpeed(SysMonitorService.diskReadRate)
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "W"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.warning
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: formatDiskSpeed(SysMonitorService.diskWriteRate)
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.totalCpuUsage.toFixed(1) + "%"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width - 280
|
||||||
|
height: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.cpuCount + " cores"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - 40
|
||||||
|
clip: true
|
||||||
|
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||||
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: SysMonitorService.perCoreCpuUsage
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
height: 20
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "C" + index
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
width: 24
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width - 80
|
||||||
|
height: 6
|
||||||
|
radius: 3
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.2)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width * Math.min(1, modelData / 100)
|
||||||
|
height: parent.height
|
||||||
|
radius: parent.radius
|
||||||
|
color: {
|
||||||
|
const usage = modelData
|
||||||
|
if (usage > 80)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
if (usage > 60)
|
||||||
|
return Theme.warning
|
||||||
|
|
||||||
|
return Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData ? modelData.toFixed(0) + "%" : "0%"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: 32
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 80
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.04)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.06)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Memory"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.formatSystemMemory(
|
||||||
|
SysMonitorService.usedMemoryKB) + " / " + SysMonitorService.formatSystemMemory(
|
||||||
|
SysMonitorService.totalMemoryKB)
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: Theme.spacingL
|
||||||
|
height: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 4
|
||||||
|
width: 200
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 16
|
||||||
|
radius: 8
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: SysMonitorService.totalMemoryKB
|
||||||
|
> 0 ? parent.width * (SysMonitorService.usedMemoryKB
|
||||||
|
/ SysMonitorService.totalMemoryKB) : 0
|
||||||
|
height: parent.height
|
||||||
|
radius: parent.radius
|
||||||
|
color: {
|
||||||
|
const usage = SysMonitorService.totalMemoryKB
|
||||||
|
> 0 ? (SysMonitorService.usedMemoryKB
|
||||||
|
/ SysMonitorService.totalMemoryKB) : 0
|
||||||
|
if (usage > 0.9)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
if (usage > 0.7)
|
||||||
|
return Theme.warning
|
||||||
|
|
||||||
|
return Theme.secondary
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.totalMemoryKB
|
||||||
|
> 0 ? ((SysMonitorService.usedMemoryKB
|
||||||
|
/ SysMonitorService.totalMemoryKB) * 100).toFixed(
|
||||||
|
1) + "% used" : "No data"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: Theme.spacingL
|
||||||
|
height: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Swap"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.totalSwapKB
|
||||||
|
> 0 ? SysMonitorService.formatSystemMemory(
|
||||||
|
SysMonitorService.usedSwapKB) + " / "
|
||||||
|
+ SysMonitorService.formatSystemMemory(
|
||||||
|
SysMonitorService.totalSwapKB) : "No swap configured"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: Theme.spacingL
|
||||||
|
height: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 4
|
||||||
|
width: 200
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 16
|
||||||
|
radius: 8
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: SysMonitorService.totalSwapKB
|
||||||
|
> 0 ? parent.width * (SysMonitorService.usedSwapKB
|
||||||
|
/ SysMonitorService.totalSwapKB) : 0
|
||||||
|
height: parent.height
|
||||||
|
radius: parent.radius
|
||||||
|
color: {
|
||||||
|
if (!SysMonitorService.totalSwapKB)
|
||||||
|
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.3)
|
||||||
|
|
||||||
|
const usage = SysMonitorService.usedSwapKB / SysMonitorService.totalSwapKB
|
||||||
|
if (usage > 0.9)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
if (usage > 0.7)
|
||||||
|
return Theme.warning
|
||||||
|
|
||||||
|
return Theme.info
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.totalSwapKB
|
||||||
|
> 0 ? ((SysMonitorService.usedSwapKB
|
||||||
|
/ SysMonitorService.totalSwapKB) * 100).toFixed(
|
||||||
|
1) + "% used" : "Not available"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
height: 80
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
height: 80
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.04)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.06)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Network"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "↓"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.info
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.networkRxRate
|
||||||
|
> 0 ? formatNetworkSpeed(
|
||||||
|
SysMonitorService.networkRxRate) : "0 B/s"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "↑"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.error
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.networkTxRate
|
||||||
|
> 0 ? formatNetworkSpeed(
|
||||||
|
SysMonitorService.networkTxRate) : "0 B/s"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
height: 80
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.04)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.06)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Disk"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "R"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: formatDiskSpeed(SysMonitorService.diskReadRate)
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "W"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.warning
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: formatDiskSpeed(SysMonitorService.diskWriteRate)
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,223 +7,238 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Popup {
|
Popup {
|
||||||
id: processContextMenu
|
id: processContextMenu
|
||||||
|
|
||||||
property var processData: null
|
property var processData: null
|
||||||
|
|
||||||
function show(x, y) {
|
function show(x, y) {
|
||||||
if (!processContextMenu.parent && typeof Overlay !== "undefined" && Overlay.overlay)
|
if (!processContextMenu.parent && typeof Overlay !== "undefined"
|
||||||
processContextMenu.parent = Overlay.overlay;
|
&& Overlay.overlay)
|
||||||
|
processContextMenu.parent = Overlay.overlay
|
||||||
|
|
||||||
const menuWidth = 180;
|
const menuWidth = 180
|
||||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2;
|
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
|
||||||
const screenWidth = Screen.width;
|
const screenWidth = Screen.width
|
||||||
const screenHeight = Screen.height;
|
const screenHeight = Screen.height
|
||||||
let finalX = x;
|
let finalX = x
|
||||||
let finalY = y;
|
let finalY = y
|
||||||
if (x + menuWidth > screenWidth - 20)
|
if (x + menuWidth > screenWidth - 20)
|
||||||
finalX = x - menuWidth;
|
finalX = x - menuWidth
|
||||||
|
|
||||||
if (y + menuHeight > screenHeight - 20)
|
if (y + menuHeight > screenHeight - 20)
|
||||||
finalY = y - menuHeight;
|
finalY = y - menuHeight
|
||||||
|
|
||||||
processContextMenu.x = Math.max(20, finalX);
|
processContextMenu.x = Math.max(20, finalX)
|
||||||
processContextMenu.y = Math.max(20, finalY);
|
processContextMenu.y = Math.max(20, finalY)
|
||||||
open();
|
open()
|
||||||
|
}
|
||||||
|
|
||||||
|
width: 180
|
||||||
|
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||||
|
padding: 0
|
||||||
|
modal: false
|
||||||
|
closePolicy: Popup.CloseOnEscape
|
||||||
|
onClosed: {
|
||||||
|
closePolicy = Popup.CloseOnEscape
|
||||||
|
}
|
||||||
|
onOpened: {
|
||||||
|
outsideClickTimer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: outsideClickTimer
|
||||||
|
|
||||||
|
interval: 100
|
||||||
|
onTriggered: {
|
||||||
|
processContextMenu.closePolicy = Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
width: 180
|
background: Rectangle {
|
||||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
color: "transparent"
|
||||||
padding: 0
|
}
|
||||||
modal: false
|
|
||||||
closePolicy: Popup.CloseOnEscape
|
|
||||||
onClosed: {
|
|
||||||
closePolicy = Popup.CloseOnEscape;
|
|
||||||
}
|
|
||||||
onOpened: {
|
|
||||||
outsideClickTimer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
contentItem: Rectangle {
|
||||||
id: outsideClickTimer
|
id: menuContent
|
||||||
|
|
||||||
interval: 100
|
color: Theme.popupBackground()
|
||||||
onTriggered: {
|
radius: Theme.cornerRadiusLarge
|
||||||
processContextMenu.closePolicy = Popup.CloseOnEscape | Popup.CloseOnPressOutside;
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: menuColumn
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
spacing: 1
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 28
|
||||||
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: copyPidArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: "Copy PID"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
MouseArea {
|
||||||
|
id: copyPidArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (processContextMenu.processData)
|
||||||
|
Quickshell.execDetached(
|
||||||
|
["wl-copy", processContextMenu.processData.pid.toString()])
|
||||||
|
|
||||||
|
processContextMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 28
|
||||||
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: copyNameArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: "Copy Process Name"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: copyNameArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (processContextMenu.processData) {
|
||||||
|
let processName = processContextMenu.processData.displayName
|
||||||
|
|| processContextMenu.processData.command
|
||||||
|
Quickshell.execDetached(["wl-copy", processName])
|
||||||
|
}
|
||||||
|
processContextMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width - Theme.spacingS * 2
|
||||||
|
height: 5
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: Rectangle {
|
Rectangle {
|
||||||
id: menuContent
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
color: Theme.popupBackground()
|
Rectangle {
|
||||||
radius: Theme.cornerRadiusLarge
|
width: parent.width
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
height: 28
|
||||||
border.width: 1
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: killArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g,
|
||||||
Column {
|
Theme.error.b,
|
||||||
id: menuColumn
|
0.12) : "transparent"
|
||||||
|
enabled: processContextMenu.processData
|
||||||
anchors.fill: parent
|
opacity: enabled ? 1 : 0.5
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
spacing: 1
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 28
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: copyPidArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "Copy PID"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: copyPidArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (processContextMenu.processData)
|
|
||||||
Quickshell.execDetached(["wl-copy", processContextMenu.processData.pid.toString()]);
|
|
||||||
|
|
||||||
processContextMenu.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 28
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: copyNameArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "Copy Process Name"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: copyNameArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (processContextMenu.processData) {
|
|
||||||
let processName = processContextMenu.processData.displayName || processContextMenu.processData.command;
|
|
||||||
Quickshell.execDetached(["wl-copy", processName]);
|
|
||||||
}
|
|
||||||
processContextMenu.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width - Theme.spacingS * 2
|
|
||||||
height: 5
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 28
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: killArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
|
||||||
enabled: processContextMenu.processData
|
|
||||||
opacity: enabled ? 1 : 0.5
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "Kill Process"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: parent.enabled ? (killArea.containsMouse ? Theme.error : Theme.surfaceText) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
|
||||||
font.weight: Font.Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: killArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
enabled: parent.enabled
|
|
||||||
onClicked: {
|
|
||||||
if (processContextMenu.processData)
|
|
||||||
Quickshell.execDetached(["kill", processContextMenu.processData.pid.toString()]);
|
|
||||||
|
|
||||||
processContextMenu.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 28
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: forceKillArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
|
||||||
enabled: processContextMenu.processData && processContextMenu.processData.pid > 1000
|
|
||||||
opacity: enabled ? 1 : 0.5
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "Force Kill Process"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: parent.enabled ? (forceKillArea.containsMouse ? Theme.error : Theme.surfaceText) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
|
||||||
font.weight: Font.Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: forceKillArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
enabled: parent.enabled
|
|
||||||
onClicked: {
|
|
||||||
if (processContextMenu.processData)
|
|
||||||
Quickshell.execDetached(["kill", "-9", processContextMenu.processData.pid.toString()]);
|
|
||||||
|
|
||||||
processContextMenu.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: "Kill Process"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: parent.enabled ? (killArea.containsMouse ? Theme.error : Theme.surfaceText) : Qt.rgba(
|
||||||
|
Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.5)
|
||||||
|
font.weight: Font.Normal
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
MouseArea {
|
||||||
|
id: killArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
enabled: parent.enabled
|
||||||
|
onClicked: {
|
||||||
|
if (processContextMenu.processData)
|
||||||
|
Quickshell.execDetached(
|
||||||
|
["kill", processContextMenu.processData.pid.toString()])
|
||||||
|
|
||||||
|
processContextMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 28
|
||||||
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: forceKillArea.containsMouse ? Qt.rgba(Theme.error.r,
|
||||||
|
Theme.error.g,
|
||||||
|
Theme.error.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
enabled: processContextMenu.processData
|
||||||
|
&& processContextMenu.processData.pid > 1000
|
||||||
|
opacity: enabled ? 1 : 0.5
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: "Force Kill Process"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: parent.enabled ? (forceKillArea.containsMouse ? Theme.error : Theme.surfaceText) : Qt.rgba(
|
||||||
|
Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.5)
|
||||||
|
font.weight: Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: forceKillArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
enabled: parent.enabled
|
||||||
|
onClicked: {
|
||||||
|
if (processContextMenu.processData)
|
||||||
|
Quickshell.execDetached(
|
||||||
|
["kill", "-9", processContextMenu.processData.pid.toString(
|
||||||
|
)])
|
||||||
|
|
||||||
|
processContextMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,212 +4,227 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: processItem
|
id: processItem
|
||||||
|
|
||||||
property var process: null
|
property var process: null
|
||||||
property var contextMenu: null
|
property var contextMenu: null
|
||||||
|
|
||||||
width: parent ? parent.width : 0
|
width: parent ? parent.width : 0
|
||||||
height: 40
|
height: 40
|
||||||
radius: Theme.cornerRadiusLarge
|
radius: Theme.cornerRadiusLarge
|
||||||
color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
border.color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
Theme.primary.g,
|
||||||
border.width: 1
|
Theme.primary.b,
|
||||||
|
0.08) : "transparent"
|
||||||
|
border.color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.12) : "transparent"
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: processMouseArea
|
id: processMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
onClicked: mouse => {
|
||||||
|
if (mouse.button === Qt.RightButton) {
|
||||||
|
if (process && process.pid > 0 && contextMenu) {
|
||||||
|
contextMenu.processData = process
|
||||||
|
let globalPos = processMouseArea.mapToGlobal(mouse.x,
|
||||||
|
mouse.y)
|
||||||
|
let localPos = contextMenu.parent ? contextMenu.parent.mapFromGlobal(
|
||||||
|
globalPos.x,
|
||||||
|
globalPos.y) : globalPos
|
||||||
|
contextMenu.show(localPos.x, localPos.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onPressAndHold: {
|
||||||
|
if (process && process.pid > 0 && contextMenu) {
|
||||||
|
contextMenu.processData = process
|
||||||
|
let globalPos = processMouseArea.mapToGlobal(
|
||||||
|
processMouseArea.width / 2, processMouseArea.height / 2)
|
||||||
|
contextMenu.show(globalPos.x, globalPos.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 8
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
id: processIcon
|
||||||
|
|
||||||
|
name: SysMonitorService.getProcessIcon(process ? process.command : "")
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: {
|
||||||
|
if (process && process.cpu > 80)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
if (process && process.cpu > 50)
|
||||||
|
return Theme.warning
|
||||||
|
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
opacity: 0.8
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: process ? process.displayName : ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: 250
|
||||||
|
elide: Text.ElideRight
|
||||||
|
anchors.left: processIcon.right
|
||||||
|
anchors.leftMargin: 8
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: cpuBadge
|
||||||
|
|
||||||
|
width: 80
|
||||||
|
height: 20
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
if (process && process.cpu > 80)
|
||||||
|
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||||
|
|
||||||
|
if (process && process.cpu > 50)
|
||||||
|
return Qt.rgba(Theme.warning.r, Theme.warning.g,
|
||||||
|
Theme.warning.b, 0.12)
|
||||||
|
|
||||||
|
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.08)
|
||||||
|
}
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 194
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.formatCpuUsage(process ? process.cpu : 0)
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: {
|
||||||
|
if (process && process.cpu > 80)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
if (process && process.cpu > 50)
|
||||||
|
return Theme.warning
|
||||||
|
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: memoryBadge
|
||||||
|
|
||||||
|
width: 80
|
||||||
|
height: 20
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
if (process && process.memoryKB > 1024 * 1024)
|
||||||
|
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||||
|
|
||||||
|
if (process && process.memoryKB > 512 * 1024)
|
||||||
|
return Qt.rgba(Theme.warning.r, Theme.warning.g,
|
||||||
|
Theme.warning.b, 0.12)
|
||||||
|
|
||||||
|
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.08)
|
||||||
|
}
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 102
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.formatMemoryUsage(
|
||||||
|
process ? process.memoryKB : 0)
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: {
|
||||||
|
if (process && process.memoryKB > 1024 * 1024)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
if (process && process.memoryKB > 512 * 1024)
|
||||||
|
return Theme.warning
|
||||||
|
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: process ? process.pid.toString() : ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: 0.7
|
||||||
|
width: 50
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 40
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: menuButton
|
||||||
|
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: menuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r,
|
||||||
|
Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b,
|
||||||
|
0.08) : "transparent"
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "more_vert"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: 0.6
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: menuButtonArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
onClicked: {
|
||||||
onClicked: (mouse) => {
|
if (process && process.pid > 0 && contextMenu) {
|
||||||
if (mouse.button === Qt.RightButton) {
|
contextMenu.processData = process
|
||||||
if (process && process.pid > 0 && contextMenu) {
|
let globalPos = menuButtonArea.mapToGlobal(
|
||||||
contextMenu.processData = process;
|
menuButtonArea.width / 2, menuButtonArea.height)
|
||||||
let globalPos = processMouseArea.mapToGlobal(mouse.x, mouse.y);
|
let localPos = contextMenu.parent ? contextMenu.parent.mapFromGlobal(
|
||||||
let localPos = contextMenu.parent ? contextMenu.parent.mapFromGlobal(globalPos.x, globalPos.y) : globalPos;
|
globalPos.x,
|
||||||
contextMenu.show(localPos.x, localPos.y);
|
globalPos.y) : globalPos
|
||||||
}
|
contextMenu.show(localPos.x, localPos.y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onPressAndHold: {
|
}
|
||||||
if (process && process.pid > 0 && contextMenu) {
|
|
||||||
contextMenu.processData = process;
|
Behavior on color {
|
||||||
let globalPos = processMouseArea.mapToGlobal(processMouseArea.width / 2, processMouseArea.height / 2);
|
ColorAnimation {
|
||||||
contextMenu.show(globalPos.x, globalPos.y);
|
duration: Theme.shortDuration
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 8
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
id: processIcon
|
|
||||||
|
|
||||||
name: SysMonitorService.getProcessIcon(process ? process.command : "")
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: {
|
|
||||||
if (process && process.cpu > 80)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
if (process && process.cpu > 50)
|
|
||||||
return Theme.warning;
|
|
||||||
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
opacity: 0.8
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: process ? process.displayName : ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: 250
|
|
||||||
elide: Text.ElideRight
|
|
||||||
anchors.left: processIcon.right
|
|
||||||
anchors.leftMargin: 8
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: cpuBadge
|
|
||||||
|
|
||||||
width: 80
|
|
||||||
height: 20
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: {
|
|
||||||
if (process && process.cpu > 80)
|
|
||||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
|
|
||||||
|
|
||||||
if (process && process.cpu > 50)
|
|
||||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
|
||||||
|
|
||||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08);
|
|
||||||
}
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 194
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.formatCpuUsage(process ? process.cpu : 0)
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: {
|
|
||||||
if (process && process.cpu > 80)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
if (process && process.cpu > 50)
|
|
||||||
return Theme.warning;
|
|
||||||
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: memoryBadge
|
|
||||||
|
|
||||||
width: 80
|
|
||||||
height: 20
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: {
|
|
||||||
if (process && process.memoryKB > 1024 * 1024)
|
|
||||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
|
|
||||||
|
|
||||||
if (process && process.memoryKB > 512 * 1024)
|
|
||||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
|
||||||
|
|
||||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08);
|
|
||||||
}
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 102
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.formatMemoryUsage(process ? process.memoryKB : 0)
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: {
|
|
||||||
if (process && process.memoryKB > 1024 * 1024)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
if (process && process.memoryKB > 512 * 1024)
|
|
||||||
return Theme.warning;
|
|
||||||
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: process ? process.pid.toString() : ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.7
|
|
||||||
width: 50
|
|
||||||
horizontalAlignment: Text.AlignRight
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 40
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: menuButton
|
|
||||||
|
|
||||||
width: 28
|
|
||||||
height: 28
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: menuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "more_vert"
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.6
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: menuButtonArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (process && process.pid > 0 && contextMenu) {
|
|
||||||
contextMenu.processData = process;
|
|
||||||
let globalPos = menuButtonArea.mapToGlobal(menuButtonArea.width / 2, menuButtonArea.height);
|
|
||||||
let localPos = contextMenu.parent ? contextMenu.parent.mapFromGlobal(globalPos.x, globalPos.y) : globalPos;
|
|
||||||
contextMenu.show(localPos.x, localPos.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,202 +12,203 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: processListPopout
|
id: processListPopout
|
||||||
|
|
||||||
property bool isVisible: false
|
property bool isVisible: false
|
||||||
property var parentWidget: null
|
property var parentWidget: null
|
||||||
property real triggerX: Screen.width - 600 - Theme.spacingL
|
property real triggerX: Screen.width - 600 - Theme.spacingL
|
||||||
property real triggerY: Theme.barHeight + Theme.spacingXS
|
property real triggerY: Theme.barHeight + Theme.spacingXS
|
||||||
property real triggerWidth: 55
|
property real triggerWidth: 55
|
||||||
property string triggerSection: "right"
|
property string triggerSection: "right"
|
||||||
property var triggerScreen: null
|
property var triggerScreen: null
|
||||||
|
|
||||||
function setTriggerPosition(x, y, width, section, screen) {
|
function setTriggerPosition(x, y, width, section, screen) {
|
||||||
triggerX = x;
|
triggerX = x
|
||||||
triggerY = y;
|
triggerY = y
|
||||||
triggerWidth = width;
|
triggerWidth = width
|
||||||
triggerSection = section;
|
triggerSection = section
|
||||||
triggerScreen = screen;
|
triggerScreen = screen
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
isVisible = false
|
||||||
|
if (processContextMenu.visible)
|
||||||
|
processContextMenu.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
if (isVisible)
|
||||||
|
hide()
|
||||||
|
else
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: isVisible
|
||||||
|
screen: triggerScreen
|
||||||
|
implicitWidth: 600
|
||||||
|
implicitHeight: 600
|
||||||
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: isVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Ref {
|
||||||
|
service: SysMonitorService
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: function (mouse) {
|
||||||
|
var localPos = mapToItem(contentLoader, mouse.x, mouse.y)
|
||||||
|
if (localPos.x < 0 || localPos.x > contentLoader.width || localPos.y < 0
|
||||||
|
|| localPos.y > contentLoader.height)
|
||||||
|
processListPopout.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: contentLoader
|
||||||
|
|
||||||
|
readonly property real screenWidth: processListPopout.screen ? processListPopout.screen.width : Screen.width
|
||||||
|
readonly property real screenHeight: processListPopout.screen ? processListPopout.screen.height : Screen.height
|
||||||
|
readonly property real targetWidth: Math.min(
|
||||||
|
600, screenWidth - Theme.spacingL * 2)
|
||||||
|
readonly property real targetHeight: Math.min(
|
||||||
|
600,
|
||||||
|
screenHeight - Theme.barHeight - Theme.spacingS * 2)
|
||||||
|
readonly property real calculatedX: {
|
||||||
|
var centerX = processListPopout.triggerX + (processListPopout.triggerWidth
|
||||||
|
/ 2) - (targetWidth / 2)
|
||||||
|
|
||||||
|
if (centerX >= Theme.spacingM
|
||||||
|
&& centerX + targetWidth <= screenWidth - Theme.spacingM) {
|
||||||
|
return centerX
|
||||||
|
}
|
||||||
|
|
||||||
|
if (centerX < Theme.spacingM) {
|
||||||
|
return Theme.spacingM
|
||||||
|
}
|
||||||
|
|
||||||
|
if (centerX + targetWidth > screenWidth - Theme.spacingM) {
|
||||||
|
return screenWidth - targetWidth - Theme.spacingM
|
||||||
|
}
|
||||||
|
|
||||||
|
return centerX
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
asynchronous: true
|
||||||
isVisible = false;
|
active: processListPopout.isVisible
|
||||||
if (processContextMenu.visible)
|
width: targetWidth
|
||||||
processContextMenu.close();
|
height: targetHeight
|
||||||
|
y: processListPopout.triggerY
|
||||||
|
x: calculatedX
|
||||||
|
opacity: processListPopout.isVisible ? 1 : 0
|
||||||
|
scale: processListPopout.isVisible ? 1 : 0.9
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Anims.durMed
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.emphasized
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function show() {
|
Behavior on scale {
|
||||||
isVisible = true;
|
NumberAnimation {
|
||||||
|
duration: Anims.durMed
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.emphasized
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle() {
|
sourceComponent: Rectangle {
|
||||||
if (isVisible)
|
id: dropdownContent
|
||||||
hide();
|
|
||||||
else
|
|
||||||
show();
|
|
||||||
}
|
|
||||||
|
|
||||||
visible: isVisible
|
radius: Theme.cornerRadiusLarge
|
||||||
screen: triggerScreen
|
color: Theme.popupBackground()
|
||||||
implicitWidth: 600
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
implicitHeight: 600
|
Theme.outline.b, 0.08)
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
border.width: 1
|
||||||
WlrLayershell.exclusiveZone: -1
|
clip: true
|
||||||
WlrLayershell.keyboardFocus: isVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
antialiasing: true
|
||||||
color: "transparent"
|
smooth: true
|
||||||
|
focus: true
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (processListPopout.isVisible)
|
||||||
|
forceActiveFocus()
|
||||||
|
}
|
||||||
|
Keys.onPressed: function (event) {
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
processListPopout.hide()
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ref {
|
Connections {
|
||||||
service: SysMonitorService
|
function onIsVisibleChanged() {
|
||||||
}
|
if (processListPopout.isVisible)
|
||||||
|
Qt.callLater(function () {
|
||||||
|
dropdownContent.forceActiveFocus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
target: processListPopout
|
||||||
|
}
|
||||||
|
|
||||||
anchors {
|
ColumnLayout {
|
||||||
top: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: function(mouse) {
|
anchors.margins: Theme.spacingL
|
||||||
var localPos = mapToItem(contentLoader, mouse.x, mouse.y);
|
spacing: Theme.spacingL
|
||||||
if (localPos.x < 0 || localPos.x > contentLoader.width || localPos.y < 0 || localPos.y > contentLoader.height)
|
|
||||||
processListPopout.hide();
|
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: systemOverview.height + Theme.spacingM * 2
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.2)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
SystemOverview {
|
||||||
|
id: systemOverview
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.1)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.05)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
ProcessListView {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
contextMenu: processContextMenu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
ProcessContextMenu {
|
||||||
id: contentLoader
|
id: processContextMenu
|
||||||
|
}
|
||||||
readonly property real screenWidth: processListPopout.screen ? processListPopout.screen.width : Screen.width
|
|
||||||
readonly property real screenHeight: processListPopout.screen ? processListPopout.screen.height : Screen.height
|
|
||||||
readonly property real targetWidth: Math.min(600, screenWidth - Theme.spacingL * 2)
|
|
||||||
readonly property real targetHeight: Math.min(600, screenHeight - Theme.barHeight - Theme.spacingS * 2)
|
|
||||||
readonly property real calculatedX: {
|
|
||||||
var centerX = processListPopout.triggerX + (processListPopout.triggerWidth / 2) - (targetWidth / 2);
|
|
||||||
|
|
||||||
if (centerX >= Theme.spacingM && centerX + targetWidth <= screenWidth - Theme.spacingM) {
|
|
||||||
return centerX;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (centerX < Theme.spacingM) {
|
|
||||||
return Theme.spacingM;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (centerX + targetWidth > screenWidth - Theme.spacingM) {
|
|
||||||
return screenWidth - targetWidth - Theme.spacingM;
|
|
||||||
}
|
|
||||||
|
|
||||||
return centerX;
|
|
||||||
}
|
|
||||||
|
|
||||||
asynchronous: true
|
|
||||||
active: processListPopout.isVisible
|
|
||||||
width: targetWidth
|
|
||||||
height: targetHeight
|
|
||||||
y: processListPopout.triggerY
|
|
||||||
x: calculatedX
|
|
||||||
opacity: processListPopout.isVisible ? 1 : 0
|
|
||||||
scale: processListPopout.isVisible ? 1 : 0.9
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Anims.durMed
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.emphasized
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Anims.durMed
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.emphasized
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceComponent: Rectangle {
|
|
||||||
id: dropdownContent
|
|
||||||
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Theme.popupBackground()
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
clip: true
|
|
||||||
antialiasing: true
|
|
||||||
smooth: true
|
|
||||||
focus: true
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (processListPopout.isVisible)
|
|
||||||
forceActiveFocus();
|
|
||||||
}
|
|
||||||
Keys.onPressed: function(event) {
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
|
||||||
processListPopout.hide();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onIsVisibleChanged() {
|
|
||||||
if (processListPopout.isVisible)
|
|
||||||
Qt.callLater(function() {
|
|
||||||
dropdownContent.forceActiveFocus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
target: processListPopout
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingL
|
|
||||||
spacing: Theme.spacingL
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
height: systemOverview.height + Theme.spacingM * 2
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
SystemOverview {
|
|
||||||
id: systemOverview
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width - Theme.spacingM * 2
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.1)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
ProcessListView {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
contextMenu: processContextMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessContextMenu {
|
|
||||||
id: processContextMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,234 +5,237 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var contextMenu: null
|
property var contextMenu: null
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
SysMonitorService.addRef();
|
SysMonitorService.addRef()
|
||||||
}
|
}
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
SysMonitorService.removeRef();
|
SysMonitorService.removeRef()
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: columnHeaders
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
anchors.leftMargin: 8
|
||||||
|
height: 24
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 60
|
||||||
|
height: 20
|
||||||
|
color: processHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r,
|
||||||
|
Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b,
|
||||||
|
0.08) : "transparent"
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 0
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Process"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: SysMonitorService.sortBy === "name" ? Font.Bold : Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: SysMonitorService.sortBy === "name" ? 1 : 0.7
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: processHeaderArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
SysMonitorService.setSortBy("name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Rectangle {
|
||||||
id: columnHeaders
|
width: 80
|
||||||
|
height: 20
|
||||||
|
color: cpuHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r,
|
||||||
|
Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b,
|
||||||
|
0.08) : "transparent"
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 200
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
width: parent.width
|
StyledText {
|
||||||
anchors.leftMargin: 8
|
text: "CPU"
|
||||||
height: 24
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: SysMonitorService.sortBy === "cpu" ? Font.Bold : Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: SysMonitorService.sortBy === "cpu" ? 1 : 0.7
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
MouseArea {
|
||||||
width: 60
|
id: cpuHeaderArea
|
||||||
height: 20
|
|
||||||
color: processHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 0
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Process"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: SysMonitorService.sortBy === "name" ? Font.Bold : Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: SysMonitorService.sortBy === "name" ? 1 : 0.7
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: processHeaderArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
SysMonitorService.setSortBy("name");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
SysMonitorService.setSortBy("cpu")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Behavior on color {
|
||||||
width: 80
|
ColorAnimation {
|
||||||
height: 20
|
duration: Theme.shortDuration
|
||||||
color: cpuHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 200
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "CPU"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: SysMonitorService.sortBy === "cpu" ? Font.Bold : Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: SysMonitorService.sortBy === "cpu" ? 1 : 0.7
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: cpuHeaderArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
SysMonitorService.setSortBy("cpu");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Rectangle {
|
|
||||||
width: 80
|
|
||||||
height: 20
|
|
||||||
color: memoryHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 112
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "RAM"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: SysMonitorService.sortBy === "memory" ? Font.Bold : Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: SysMonitorService.sortBy === "memory" ? 1 : 0.7
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: memoryHeaderArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
SysMonitorService.setSortBy("memory");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 50
|
|
||||||
height: 20
|
|
||||||
color: pidHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 53
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "PID"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: SysMonitorService.sortBy === "pid" ? Font.Bold : Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: SysMonitorService.sortBy === "pid" ? 1 : 0.7
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: pidHeaderArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
SysMonitorService.setSortBy("pid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 28
|
|
||||||
height: 28
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: sortOrderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.sortDescending ? "↓" : "↑"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: sortOrderArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
SysMonitorService.toggleSortOrder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankListView {
|
Rectangle {
|
||||||
id: processListView
|
width: 80
|
||||||
|
height: 20
|
||||||
|
color: memoryHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r,
|
||||||
|
Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b,
|
||||||
|
0.08) : "transparent"
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 112
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
property string keyRoleName: "pid"
|
StyledText {
|
||||||
|
text: "RAM"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: SysMonitorService.sortBy === "memory" ? Font.Bold : Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: SysMonitorService.sortBy === "memory" ? 1 : 0.7
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
width: parent.width
|
MouseArea {
|
||||||
height: parent.height - columnHeaders.height
|
id: memoryHeaderArea
|
||||||
clip: true
|
|
||||||
spacing: 4
|
|
||||||
model: SysMonitorService.processes
|
|
||||||
|
|
||||||
delegate: ProcessListItem {
|
anchors.fill: parent
|
||||||
process: modelData
|
hoverEnabled: true
|
||||||
contextMenu: root.contextMenu
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
SysMonitorService.setSortBy("memory")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 50
|
||||||
|
height: 20
|
||||||
|
color: pidHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r,
|
||||||
|
Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b,
|
||||||
|
0.08) : "transparent"
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 53
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "PID"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: SysMonitorService.sortBy === "pid" ? Font.Bold : Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: SysMonitorService.sortBy === "pid" ? 1 : 0.7
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: pidHeaderArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
SysMonitorService.setSortBy("pid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: sortOrderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r,
|
||||||
|
Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b,
|
||||||
|
0.08) : "transparent"
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 8
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.sortDescending ? "↓" : "↑"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: sortOrderArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
SysMonitorService.toggleSortOrder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankListView {
|
||||||
|
id: processListView
|
||||||
|
|
||||||
|
property string keyRoleName: "pid"
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - columnHeaders.height
|
||||||
|
clip: true
|
||||||
|
spacing: 4
|
||||||
|
model: SysMonitorService.processes
|
||||||
|
|
||||||
|
delegate: ProcessListItem {
|
||||||
|
process: modelData
|
||||||
|
contextMenu: root.contextMenu
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,25 +5,24 @@ import qs.Modules.ProcessList
|
|||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: processesTab
|
id: processesTab
|
||||||
|
|
||||||
property var contextMenu: null
|
property var contextMenu: null
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
SystemOverview {
|
SystemOverview {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessListView {
|
ProcessListView {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
contextMenu: processesTab.contextMenu || localContextMenu
|
contextMenu: processesTab.contextMenu || localContextMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessContextMenu {
|
|
||||||
id: localContextMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ProcessContextMenu {
|
||||||
|
id: localContextMenu
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,310 +4,334 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
SysMonitorService.addRef();
|
SysMonitorService.addRef()
|
||||||
|
}
|
||||||
|
Component.onDestruction: {
|
||||||
|
SysMonitorService.removeRef()
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingM * 2) / 3
|
||||||
|
height: 80
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: {
|
||||||
|
if (SysMonitorService.sortBy === "cpu")
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16)
|
||||||
|
else if (cpuCardMouseArea.containsMouse)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||||
|
else
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||||
}
|
}
|
||||||
Component.onDestruction: {
|
border.color: SysMonitorService.sortBy === "cpu" ? Qt.rgba(Theme.primary.r,
|
||||||
SysMonitorService.removeRef();
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.4) : Qt.rgba(
|
||||||
|
Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b, 0.2)
|
||||||
|
border.width: SysMonitorService.sortBy === "cpu" ? 2 : 1
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cpuCardMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: SysMonitorService.setSortBy("cpu")
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Column {
|
||||||
width: (parent.width - Theme.spacingM * 2) / 3
|
anchors.left: parent.left
|
||||||
height: 80
|
anchors.leftMargin: Theme.spacingM
|
||||||
radius: Theme.cornerRadiusLarge
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "CPU"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: SysMonitorService.sortBy === "cpu" ? Theme.primary : Theme.secondary
|
||||||
|
opacity: SysMonitorService.sortBy === "cpu" ? 1 : 0.8
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.totalCpuUsage.toFixed(1) + "%"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 1
|
||||||
|
height: 20
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.3)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (SysMonitorService.cpuTemperature === undefined
|
||||||
|
|| SysMonitorService.cpuTemperature === null
|
||||||
|
|| SysMonitorService.cpuTemperature < 0) {
|
||||||
|
return "--°"
|
||||||
|
}
|
||||||
|
return Math.round(SysMonitorService.cpuTemperature) + "°"
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: {
|
||||||
|
if (SysMonitorService.cpuTemperature > 80)
|
||||||
|
return Theme.error
|
||||||
|
if (SysMonitorService.cpuTemperature > 60)
|
||||||
|
return Theme.warning
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.cpuCount + " cores"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: 0.7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on border.color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingM * 2) / 3
|
||||||
|
height: 80
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: {
|
||||||
|
if (SysMonitorService.sortBy === "memory")
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16)
|
||||||
|
else if (memoryCardMouseArea.containsMouse)
|
||||||
|
return Qt.rgba(Theme.secondary.r, Theme.secondary.g,
|
||||||
|
Theme.secondary.b, 0.12)
|
||||||
|
else
|
||||||
|
return Qt.rgba(Theme.secondary.r, Theme.secondary.g,
|
||||||
|
Theme.secondary.b, 0.08)
|
||||||
|
}
|
||||||
|
border.color: SysMonitorService.sortBy === "memory" ? Qt.rgba(
|
||||||
|
Theme.primary.r,
|
||||||
|
Theme.primary.g,
|
||||||
|
Theme.primary.b,
|
||||||
|
0.4) : Qt.rgba(
|
||||||
|
Theme.secondary.r,
|
||||||
|
Theme.secondary.g,
|
||||||
|
Theme.secondary.b,
|
||||||
|
0.2)
|
||||||
|
border.width: SysMonitorService.sortBy === "memory" ? 2 : 1
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: memoryCardMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: SysMonitorService.setSortBy("memory")
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Memory"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: SysMonitorService.sortBy === "memory" ? Theme.primary : Theme.secondary
|
||||||
|
opacity: SysMonitorService.sortBy === "memory" ? 1 : 0.8
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.formatSystemMemory(
|
||||||
|
SysMonitorService.usedMemoryKB)
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 1
|
||||||
|
height: 20
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.3)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: SysMonitorService.totalSwapKB > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.totalSwapKB > 0 ? SysMonitorService.formatSystemMemory(
|
||||||
|
SysMonitorService.usedSwapKB) : ""
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: SysMonitorService.usedSwapKB > 0 ? Theme.warning : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: SysMonitorService.totalSwapKB > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (SysMonitorService.totalSwapKB > 0) {
|
||||||
|
return "of " + SysMonitorService.formatSystemMemory(
|
||||||
|
SysMonitorService.totalMemoryKB) + " + swap"
|
||||||
|
}
|
||||||
|
return "of " + SysMonitorService.formatSystemMemory(
|
||||||
|
SysMonitorService.totalMemoryKB)
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: 0.7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on border.color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingM * 2) / 3
|
||||||
|
height: 80
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.08)
|
||||||
|
border.color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Graphics"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.secondary
|
||||||
|
opacity: 0.8
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (!SysMonitorService.availableGpus
|
||||||
|
|| SysMonitorService.availableGpus.length === 0) {
|
||||||
|
return "None"
|
||||||
|
}
|
||||||
|
if (SysMonitorService.availableGpus.length === 1) {
|
||||||
|
var gpu = SysMonitorService.availableGpus[0]
|
||||||
|
var temp = gpu.temperature
|
||||||
|
var tempText = (temp === undefined || temp === null
|
||||||
|
|| temp === 0) ? "--°" : Math.round(temp) + "°"
|
||||||
|
return tempText
|
||||||
|
}
|
||||||
|
// Multiple GPUs - show average temp
|
||||||
|
var totalTemp = 0
|
||||||
|
var validTemps = 0
|
||||||
|
for (var i = 0; i < SysMonitorService.availableGpus.length; i++) {
|
||||||
|
var temp = SysMonitorService.availableGpus[i].temperature
|
||||||
|
if (temp !== undefined && temp !== null && temp > 0) {
|
||||||
|
totalTemp += temp
|
||||||
|
validTemps++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (validTemps > 0) {
|
||||||
|
return Math.round(totalTemp / validTemps) + "°"
|
||||||
|
}
|
||||||
|
return "--°"
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
color: {
|
color: {
|
||||||
if (SysMonitorService.sortBy === "cpu")
|
if (!SysMonitorService.availableGpus
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16);
|
|| SysMonitorService.availableGpus.length === 0) {
|
||||||
else if (cpuCardMouseArea.containsMouse)
|
return Theme.surfaceText
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
}
|
||||||
else
|
if (SysMonitorService.availableGpus.length === 1) {
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
|
var temp = SysMonitorService.availableGpus[0].temperature || 0
|
||||||
|
if (temp > 80)
|
||||||
|
return Theme.tempDanger
|
||||||
|
if (temp > 60)
|
||||||
|
return Theme.tempWarning
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
// Multiple GPUs - get max temp for coloring
|
||||||
|
var maxTemp = 0
|
||||||
|
for (var i = 0; i < SysMonitorService.availableGpus.length; i++) {
|
||||||
|
var temp = SysMonitorService.availableGpus[i].temperature || 0
|
||||||
|
if (temp > maxTemp)
|
||||||
|
maxTemp = temp
|
||||||
|
}
|
||||||
|
if (maxTemp > 80)
|
||||||
|
return Theme.tempDanger
|
||||||
|
if (maxTemp > 60)
|
||||||
|
return Theme.tempWarning
|
||||||
|
return Theme.surfaceText
|
||||||
}
|
}
|
||||||
border.color: SysMonitorService.sortBy === "cpu" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
|
}
|
||||||
border.width: SysMonitorService.sortBy === "cpu" ? 2 : 1
|
|
||||||
|
|
||||||
MouseArea {
|
StyledText {
|
||||||
id: cpuCardMouseArea
|
text: {
|
||||||
|
if (!SysMonitorService.availableGpus
|
||||||
anchors.fill: parent
|
|| SysMonitorService.availableGpus.length === 0) {
|
||||||
hoverEnabled: true
|
return "No GPUs detected"
|
||||||
cursorShape: Qt.PointingHandCursor
|
}
|
||||||
onClicked: SysMonitorService.setSortBy("cpu")
|
if (SysMonitorService.availableGpus.length === 1) {
|
||||||
|
return SysMonitorService.availableGpus[0].driver.toUpperCase()
|
||||||
|
}
|
||||||
|
return SysMonitorService.availableGpus.length + " GPUs detected"
|
||||||
}
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
Column {
|
font.family: SettingsData.monoFontFamily
|
||||||
anchors.left: parent.left
|
color: Theme.surfaceText
|
||||||
anchors.leftMargin: Theme.spacingM
|
opacity: 0.7
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
}
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "CPU"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: SysMonitorService.sortBy === "cpu" ? Theme.primary : Theme.secondary
|
|
||||||
opacity: SysMonitorService.sortBy === "cpu" ? 1 : 0.8
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.totalCpuUsage.toFixed(1) + "%"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 1
|
|
||||||
height: 20
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (SysMonitorService.cpuTemperature === undefined || SysMonitorService.cpuTemperature === null || SysMonitorService.cpuTemperature < 0) {
|
|
||||||
return "--°";
|
|
||||||
}
|
|
||||||
return Math.round(SysMonitorService.cpuTemperature) + "°";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: {
|
|
||||||
if (SysMonitorService.cpuTemperature > 80)
|
|
||||||
return Theme.error;
|
|
||||||
if (SysMonitorService.cpuTemperature > 60)
|
|
||||||
return Theme.warning;
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.cpuCount + " cores"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.7
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on border.color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Rectangle {
|
|
||||||
width: (parent.width - Theme.spacingM * 2) / 3
|
|
||||||
height: 80
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: {
|
|
||||||
if (SysMonitorService.sortBy === "memory")
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16);
|
|
||||||
else if (memoryCardMouseArea.containsMouse)
|
|
||||||
return Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.12);
|
|
||||||
else
|
|
||||||
return Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08);
|
|
||||||
}
|
|
||||||
border.color: SysMonitorService.sortBy === "memory" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.2)
|
|
||||||
border.width: SysMonitorService.sortBy === "memory" ? 2 : 1
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: memoryCardMouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: SysMonitorService.setSortBy("memory")
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Memory"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: SysMonitorService.sortBy === "memory" ? Theme.primary : Theme.secondary
|
|
||||||
opacity: SysMonitorService.sortBy === "memory" ? 1 : 0.8
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.formatSystemMemory(SysMonitorService.usedMemoryKB)
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 1
|
|
||||||
height: 20
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: SysMonitorService.totalSwapKB > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.totalSwapKB > 0 ? SysMonitorService.formatSystemMemory(SysMonitorService.usedSwapKB) : ""
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: SysMonitorService.usedSwapKB > 0 ? Theme.warning : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: SysMonitorService.totalSwapKB > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (SysMonitorService.totalSwapKB > 0) {
|
|
||||||
return "of " + SysMonitorService.formatSystemMemory(SysMonitorService.totalMemoryKB) + " + swap";
|
|
||||||
}
|
|
||||||
return "of " + SysMonitorService.formatSystemMemory(SysMonitorService.totalMemoryKB);
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.7
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on border.color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: (parent.width - Theme.spacingM * 2) / 3
|
|
||||||
height: 80
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
|
||||||
border.color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Graphics"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.secondary
|
|
||||||
opacity: 0.8
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (!SysMonitorService.availableGpus || SysMonitorService.availableGpus.length === 0) {
|
|
||||||
return "None";
|
|
||||||
}
|
|
||||||
if (SysMonitorService.availableGpus.length === 1) {
|
|
||||||
var gpu = SysMonitorService.availableGpus[0];
|
|
||||||
var temp = gpu.temperature;
|
|
||||||
var tempText = (temp === undefined || temp === null || temp === 0) ? "--°" : Math.round(temp) + "°";
|
|
||||||
return tempText;
|
|
||||||
}
|
|
||||||
// Multiple GPUs - show average temp
|
|
||||||
var totalTemp = 0;
|
|
||||||
var validTemps = 0;
|
|
||||||
for (var i = 0; i < SysMonitorService.availableGpus.length; i++) {
|
|
||||||
var temp = SysMonitorService.availableGpus[i].temperature;
|
|
||||||
if (temp !== undefined && temp !== null && temp > 0) {
|
|
||||||
totalTemp += temp;
|
|
||||||
validTemps++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (validTemps > 0) {
|
|
||||||
return Math.round(totalTemp / validTemps) + "°";
|
|
||||||
}
|
|
||||||
return "--°";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: {
|
|
||||||
if (!SysMonitorService.availableGpus || SysMonitorService.availableGpus.length === 0) {
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
if (SysMonitorService.availableGpus.length === 1) {
|
|
||||||
var temp = SysMonitorService.availableGpus[0].temperature || 0;
|
|
||||||
if (temp > 80) return Theme.tempDanger;
|
|
||||||
if (temp > 60) return Theme.tempWarning;
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
// Multiple GPUs - get max temp for coloring
|
|
||||||
var maxTemp = 0;
|
|
||||||
for (var i = 0; i < SysMonitorService.availableGpus.length; i++) {
|
|
||||||
var temp = SysMonitorService.availableGpus[i].temperature || 0;
|
|
||||||
if (temp > maxTemp) maxTemp = temp;
|
|
||||||
}
|
|
||||||
if (maxTemp > 80) return Theme.tempDanger;
|
|
||||||
if (maxTemp > 60) return Theme.tempWarning;
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (!SysMonitorService.availableGpus || SysMonitorService.availableGpus.length === 0) {
|
|
||||||
return "No GPUs detected";
|
|
||||||
}
|
|
||||||
if (SysMonitorService.availableGpus.length === 1) {
|
|
||||||
return SysMonitorService.availableGpus[0].driver.toUpperCase();
|
|
||||||
}
|
|
||||||
return SysMonitorService.availableGpus.length + " GPUs detected";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.7
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,475 +5,481 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: true
|
clip: true
|
||||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
SysMonitorService.addRef();
|
SysMonitorService.addRef()
|
||||||
}
|
}
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
SysMonitorService.removeRef();
|
SysMonitorService.removeRef()
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: systemInfoColumn.implicitHeight + 2 * Theme.spacingL
|
height: systemInfoColumn.implicitHeight + 2 * Theme.spacingL
|
||||||
radius: Theme.cornerRadiusLarge
|
radius: Theme.cornerRadiusLarge
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.6)
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||||
border.width: 0
|
Theme.surfaceContainer.b, 0.6)
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: systemInfoColumn
|
id: systemInfoColumn
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.margins: Theme.spacingL
|
anchors.margins: Theme.spacingL
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
SystemLogo {
|
SystemLogo {
|
||||||
width: 80
|
width: 80
|
||||||
height: 80
|
height: 80
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width - 80 - Theme.spacingL
|
width: parent.width - 80 - Theme.spacingL
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.hostname
|
|
||||||
font.pixelSize: Theme.fontSizeXLarge
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Light
|
|
||||||
color: Theme.surfaceText
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.distribution + " • " + SysMonitorService.architecture + " • " + SysMonitorService.kernelVersion
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Up " + UserInfoService.uptime + " • Boot: " + SysMonitorService.bootTime
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Load: " + SysMonitorService.loadAverage + " • " + SysMonitorService.processCount + " processes, " + SysMonitorService.threadCount + " threads"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXL
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: (parent.width - Theme.spacingXL) / 2
|
|
||||||
height: Math.max(hardwareColumn.implicitHeight, memoryColumn.implicitHeight) + Theme.spacingM
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.4)
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: hardwareColumn
|
|
||||||
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.margins: Theme.spacingM
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "memory"
|
|
||||||
size: Theme.iconSizeSmall
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Hardware"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.cpuModel
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
maximumLineCount: 1
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.motherboard
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
maximumLineCount: 1
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "BIOS " + SysMonitorService.biosVersion
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: (parent.width - Theme.spacingXL) / 2
|
|
||||||
height: Math.max(hardwareColumn.implicitHeight, memoryColumn.implicitHeight) + Theme.spacingM
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.4)
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: memoryColumn
|
|
||||||
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.margins: Theme.spacingM
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "developer_board"
|
|
||||||
size: Theme.iconSizeSmall
|
|
||||||
color: Theme.secondary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Memory"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.secondary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.formatSystemMemory(SysMonitorService.totalMemoryKB) + " Total"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SysMonitorService.formatSystemMemory(SysMonitorService.usedMemoryKB) + " Used • " + SysMonitorService.formatSystemMemory(SysMonitorService.totalMemoryKB - SysMonitorService.usedMemoryKB) + " Available"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: Theme.fontSizeSmall + Theme.spacingXS
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.hostname
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Light
|
||||||
|
color: Theme.surfaceText
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.distribution + " • " + SysMonitorService.architecture
|
||||||
|
+ " • " + SysMonitorService.kernelVersion
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Up " + UserInfoService.uptime + " • Boot: " + SysMonitorService.bootTime
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.6)
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Load: " + SysMonitorService.loadAverage + " • "
|
||||||
|
+ SysMonitorService.processCount + " processes, "
|
||||||
|
+ SysMonitorService.threadCount + " threads"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.6)
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: storageColumn.implicitHeight + 2 * Theme.spacingL
|
height: 1
|
||||||
radius: Theme.cornerRadiusLarge
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.6)
|
}
|
||||||
border.width: 0
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingXL) / 2
|
||||||
|
height: Math.max(hardwareColumn.implicitHeight,
|
||||||
|
memoryColumn.implicitHeight) + Theme.spacingM
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.surfaceContainerHigh.r,
|
||||||
|
Theme.surfaceContainerHigh.g,
|
||||||
|
Theme.surfaceContainerHigh.b, 0.4)
|
||||||
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.1)
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: storageColumn
|
id: hardwareColumn
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.margins: Theme.spacingL
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Row {
|
DankIcon {
|
||||||
width: parent.width
|
name: "memory"
|
||||||
spacing: Theme.spacingS
|
size: Theme.iconSizeSmall
|
||||||
|
color: Theme.primary
|
||||||
DankIcon {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
name: "storage"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Storage & Disks"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
StyledText {
|
||||||
width: parent.width
|
text: "Hardware"
|
||||||
spacing: 2
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
Row {
|
font.weight: Font.Bold
|
||||||
width: parent.width
|
color: Theme.primary
|
||||||
height: 24
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Device"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width * 0.25
|
|
||||||
elide: Text.ElideRight
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Mount"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width * 0.2
|
|
||||||
elide: Text.ElideRight
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Size"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width * 0.15
|
|
||||||
elide: Text.ElideRight
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Used"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width * 0.15
|
|
||||||
elide: Text.ElideRight
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Available"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width * 0.15
|
|
||||||
elide: Text.ElideRight
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Use%"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width * 0.1
|
|
||||||
elide: Text.ElideRight
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
id: diskMountRepeater
|
|
||||||
|
|
||||||
model: SysMonitorService.diskMounts
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 24
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: diskMouseArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.04) : "transparent"
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: diskMouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.device
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width * 0.25
|
|
||||||
elide: Text.ElideRight
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.mount
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width * 0.2
|
|
||||||
elide: Text.ElideRight
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.size
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width * 0.15
|
|
||||||
elide: Text.ElideRight
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.used
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width * 0.15
|
|
||||||
elide: Text.ElideRight
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.avail
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width * 0.15
|
|
||||||
elide: Text.ElideRight
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.percent
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.family: SettingsData.monoFontFamily
|
|
||||||
color: {
|
|
||||||
const percent = parseInt(modelData.percent);
|
|
||||||
if (percent > 90)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
if (percent > 75)
|
|
||||||
return Theme.warning;
|
|
||||||
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
width: parent.width * 0.1
|
|
||||||
elide: Text.ElideRight
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.cpuModel
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
maximumLineCount: 1
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.motherboard
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.8)
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
maximumLineCount: 1
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "BIOS " + SysMonitorService.biosVersion
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingXL) / 2
|
||||||
|
height: Math.max(hardwareColumn.implicitHeight,
|
||||||
|
memoryColumn.implicitHeight) + Theme.spacingM
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.surfaceContainerHigh.r,
|
||||||
|
Theme.surfaceContainerHigh.g,
|
||||||
|
Theme.surfaceContainerHigh.b, 0.4)
|
||||||
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.1)
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: memoryColumn
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "developer_board"
|
||||||
|
size: Theme.iconSizeSmall
|
||||||
|
color: Theme.secondary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Memory"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.secondary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.formatSystemMemory(
|
||||||
|
SysMonitorService.totalMemoryKB) + " Total"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SysMonitorService.formatSystemMemory(
|
||||||
|
SysMonitorService.usedMemoryKB) + " Used • "
|
||||||
|
+ SysMonitorService.formatSystemMemory(
|
||||||
|
SysMonitorService.totalMemoryKB
|
||||||
|
- SysMonitorService.usedMemoryKB) + " Available"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b, 0.7)
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: Theme.fontSizeSmall + Theme.spacingXS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: storageColumn.implicitHeight + 2 * Theme.spacingL
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||||
|
Theme.surfaceContainer.b, 0.6)
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: storageColumn
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "storage"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Storage & Disks"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
height: 24
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Device"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width * 0.25
|
||||||
|
elide: Text.ElideRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Mount"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width * 0.2
|
||||||
|
elide: Text.ElideRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Size"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width * 0.15
|
||||||
|
elide: Text.ElideRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Used"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width * 0.15
|
||||||
|
elide: Text.ElideRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Available"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width * 0.15
|
||||||
|
elide: Text.ElideRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Use%"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width * 0.1
|
||||||
|
elide: Text.ElideRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: diskMountRepeater
|
||||||
|
|
||||||
|
model: SysMonitorService.diskMounts
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 24
|
||||||
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: diskMouseArea.containsMouse ? Qt.rgba(Theme.surfaceText.r,
|
||||||
|
Theme.surfaceText.g,
|
||||||
|
Theme.surfaceText.b,
|
||||||
|
0.04) : "transparent"
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: diskMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.device
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width * 0.25
|
||||||
|
elide: Text.ElideRight
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.mount
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width * 0.2
|
||||||
|
elide: Text.ElideRight
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.size
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width * 0.15
|
||||||
|
elide: Text.ElideRight
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.used
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width * 0.15
|
||||||
|
elide: Text.ElideRight
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.avail
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width * 0.15
|
||||||
|
elide: Text.ElideRight
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.percent
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: SettingsData.monoFontFamily
|
||||||
|
color: {
|
||||||
|
const percent = parseInt(modelData.percent)
|
||||||
|
if (percent > 90)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
if (percent > 75)
|
||||||
|
return Theme.warning
|
||||||
|
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
width: parent.width * 0.1
|
||||||
|
elide: Text.ElideRight
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,493 +5,497 @@ import qs.Common
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: launcherTab
|
id: launcherTab
|
||||||
|
|
||||||
|
DankFlickable {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: Theme.spacingL
|
||||||
|
anchors.bottomMargin: Theme.spacingXL
|
||||||
|
clip: true
|
||||||
|
contentHeight: mainColumn.height
|
||||||
|
contentWidth: width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mainColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
width: parent.width
|
||||||
|
sourceComponent: appLauncherComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
width: parent.width
|
||||||
|
sourceComponent: dockComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
width: parent.width
|
||||||
|
sourceComponent: recentlyUsedComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// App Launcher Component
|
||||||
|
Component {
|
||||||
|
id: appLauncherComponent
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: appLauncherSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.3)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: appLauncherSection
|
||||||
|
|
||||||
DankFlickable {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: Theme.spacingL
|
anchors.margins: Theme.spacingL
|
||||||
anchors.bottomMargin: Theme.spacingXL
|
spacing: Theme.spacingM
|
||||||
clip: true
|
|
||||||
contentHeight: mainColumn.height
|
DankToggle {
|
||||||
contentWidth: width
|
width: parent.width
|
||||||
|
text: "Use OS Logo"
|
||||||
|
description: "Display operating system logo instead of apps icon"
|
||||||
|
checked: SettingsData.useOSLogo
|
||||||
|
onToggled: checked => {
|
||||||
|
return SettingsData.setUseOSLogo(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width - Theme.spacingL
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
visible: SettingsData.useOSLogo
|
||||||
|
opacity: visible ? 1 : 0
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingL
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: 120
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Color Override"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: 100
|
||||||
|
height: 28
|
||||||
|
placeholderText: "#ffffff"
|
||||||
|
text: SettingsData.osLogoColorOverride
|
||||||
|
maximumLength: 7
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
topPadding: Theme.spacingXS
|
||||||
|
bottomPadding: Theme.spacingXS
|
||||||
|
onEditingFinished: {
|
||||||
|
var color = text.trim()
|
||||||
|
if (color === "" || /^#[0-9A-Fa-f]{6}$/.test(color))
|
||||||
|
SettingsData.setOSLogoColorOverride(color)
|
||||||
|
else
|
||||||
|
text = SettingsData.osLogoColorOverride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: 120
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Brightness"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSlider {
|
||||||
|
width: 100
|
||||||
|
height: 20
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
value: Math.round(SettingsData.osLogoBrightness * 100)
|
||||||
|
unit: "%"
|
||||||
|
showValue: true
|
||||||
|
onSliderValueChanged: newValue => {
|
||||||
|
SettingsData.setOSLogoBrightness(
|
||||||
|
newValue / 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: 120
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Contrast"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSlider {
|
||||||
|
width: 100
|
||||||
|
height: 20
|
||||||
|
minimum: 0
|
||||||
|
maximum: 200
|
||||||
|
value: Math.round(SettingsData.osLogoContrast * 100)
|
||||||
|
unit: "%"
|
||||||
|
showValue: true
|
||||||
|
onSliderValueChanged: newValue => {
|
||||||
|
SettingsData.setOSLogoContrast(
|
||||||
|
newValue / 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dock Component
|
||||||
|
Component {
|
||||||
|
id: dockComponent
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: dockSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.3)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: dockSection
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "dock_to_bottom"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Dock"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: "Show Dock"
|
||||||
|
description: "Display a dock at the bottom of the screen with pinned and running applications"
|
||||||
|
checked: SettingsData.showDock
|
||||||
|
onToggled: checked => {
|
||||||
|
SettingsData.setShowDock(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: "Auto-hide Dock"
|
||||||
|
description: "Hide the dock when not in use and reveal it when hovering near the bottom of the screen"
|
||||||
|
checked: SettingsData.dockAutoHide
|
||||||
|
visible: SettingsData.showDock
|
||||||
|
opacity: visible ? 1 : 0
|
||||||
|
onToggled: checked => {
|
||||||
|
SettingsData.setDockAutoHide(checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: mainColumn
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: SettingsData.showDock
|
||||||
|
opacity: visible ? 1 : 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Dock Transparency"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSlider {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingXL
|
height: 24
|
||||||
|
value: Math.round(SettingsData.dockTransparency * 100)
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
unit: ""
|
||||||
|
showValue: true
|
||||||
|
onSliderValueChanged: newValue => {
|
||||||
|
SettingsData.setDockTransparency(
|
||||||
|
newValue / 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Behavior on opacity {
|
||||||
width: parent.width
|
NumberAnimation {
|
||||||
sourceComponent: appLauncherComponent
|
duration: Theme.mediumDuration
|
||||||
}
|
easing.type: Theme.emphasizedEasing
|
||||||
|
|
||||||
Loader {
|
|
||||||
width: parent.width
|
|
||||||
sourceComponent: dockComponent
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
width: parent.width
|
|
||||||
sourceComponent: recentlyUsedComponent
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// App Launcher Component
|
// Recently Used Apps Component
|
||||||
Component {
|
Component {
|
||||||
id: appLauncherComponent
|
id: recentlyUsedComponent
|
||||||
|
|
||||||
StyledRect {
|
StyledRect {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: appLauncherSection.implicitHeight + Theme.spacingL * 2
|
height: recentlyUsedSection.implicitHeight + Theme.spacingL * 2
|
||||||
radius: Theme.cornerRadiusLarge
|
radius: Theme.cornerRadiusLarge
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
Theme.surfaceVariant.b, 0.3)
|
||||||
border.width: 1
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: appLauncherSection
|
id: recentlyUsedSection
|
||||||
|
|
||||||
anchors.fill: parent
|
property var rankedAppsModel: {
|
||||||
anchors.margins: Theme.spacingL
|
var apps = []
|
||||||
spacing: Theme.spacingM
|
for (var appId in (AppUsageHistoryData.appUsageRanking || {})) {
|
||||||
|
var appData = (AppUsageHistoryData.appUsageRanking || {})[appId]
|
||||||
|
apps.push({
|
||||||
|
"id": appId,
|
||||||
|
"name": appData.name,
|
||||||
|
"exec": appData.exec,
|
||||||
|
"icon": appData.icon,
|
||||||
|
"comment": appData.comment,
|
||||||
|
"usageCount": appData.usageCount,
|
||||||
|
"lastUsed": appData.lastUsed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
apps.sort(function (a, b) {
|
||||||
|
if (a.usageCount !== b.usageCount)
|
||||||
|
return b.usageCount - a.usageCount
|
||||||
|
|
||||||
DankToggle {
|
return a.name.localeCompare(b.name)
|
||||||
width: parent.width
|
})
|
||||||
text: "Use OS Logo"
|
return apps.slice(0, 20)
|
||||||
description: "Display operating system logo instead of apps icon"
|
|
||||||
checked: SettingsData.useOSLogo
|
|
||||||
onToggled: (checked) => {
|
|
||||||
return SettingsData.setUseOSLogo(checked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width - Theme.spacingL
|
|
||||||
spacing: Theme.spacingL
|
|
||||||
visible: SettingsData.useOSLogo
|
|
||||||
opacity: visible ? 1 : 0
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingL
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: 120
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Color Override"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
width: 100
|
|
||||||
height: 28
|
|
||||||
placeholderText: "#ffffff"
|
|
||||||
text: SettingsData.osLogoColorOverride
|
|
||||||
maximumLength: 7
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
topPadding: Theme.spacingXS
|
|
||||||
bottomPadding: Theme.spacingXS
|
|
||||||
onEditingFinished: {
|
|
||||||
var color = text.trim();
|
|
||||||
if (color === "" || /^#[0-9A-Fa-f]{6}$/.test(color))
|
|
||||||
SettingsData.setOSLogoColorOverride(color);
|
|
||||||
else
|
|
||||||
text = SettingsData.osLogoColorOverride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: 120
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Brightness"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankSlider {
|
|
||||||
width: 100
|
|
||||||
height: 20
|
|
||||||
minimum: 0
|
|
||||||
maximum: 100
|
|
||||||
value: Math.round(SettingsData.osLogoBrightness * 100)
|
|
||||||
unit: "%"
|
|
||||||
showValue: true
|
|
||||||
onSliderValueChanged: (newValue) => {
|
|
||||||
SettingsData.setOSLogoBrightness(newValue / 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: 120
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Contrast"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankSlider {
|
|
||||||
width: 100
|
|
||||||
height: 20
|
|
||||||
minimum: 0
|
|
||||||
maximum: 200
|
|
||||||
value: Math.round(SettingsData.osLogoContrast * 100)
|
|
||||||
unit: "%"
|
|
||||||
showValue: true
|
|
||||||
onSliderValueChanged: (newValue) => {
|
|
||||||
SettingsData.setOSLogoContrast(newValue / 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Dock Component
|
anchors.fill: parent
|
||||||
Component {
|
anchors.margins: Theme.spacingL
|
||||||
id: dockComponent
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
StyledRect {
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: dockSection.implicitHeight + Theme.spacingL * 2
|
spacing: Theme.spacingM
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Column {
|
DankIcon {
|
||||||
id: dockSection
|
name: "history"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
anchors.fill: parent
|
StyledText {
|
||||||
anchors.margins: Theme.spacingL
|
text: "Recently Used Apps"
|
||||||
spacing: Theme.spacingM
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Item {
|
||||||
width: parent.width
|
width: parent.width - parent.children[0].width - parent.children[1].width
|
||||||
spacing: Theme.spacingM
|
- clearAllButton.width - Theme.spacingM * 3
|
||||||
|
height: 1
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankActionButton {
|
||||||
name: "dock_to_bottom"
|
id: clearAllButton
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Dock"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankToggle {
|
|
||||||
width: parent.width
|
|
||||||
text: "Show Dock"
|
|
||||||
description: "Display a dock at the bottom of the screen with pinned and running applications"
|
|
||||||
checked: SettingsData.showDock
|
|
||||||
onToggled: (checked) => {
|
|
||||||
SettingsData.setShowDock(checked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankToggle {
|
|
||||||
width: parent.width
|
|
||||||
text: "Auto-hide Dock"
|
|
||||||
description: "Hide the dock when not in use and reveal it when hovering near the bottom of the screen"
|
|
||||||
checked: SettingsData.dockAutoHide
|
|
||||||
visible: SettingsData.showDock
|
|
||||||
opacity: visible ? 1 : 0
|
|
||||||
onToggled: (checked) => {
|
|
||||||
SettingsData.setDockAutoHide(checked)
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
visible: SettingsData.showDock
|
|
||||||
opacity: visible ? 1 : 0
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Dock Transparency"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankSlider {
|
|
||||||
width: parent.width
|
|
||||||
height: 24
|
|
||||||
value: Math.round(SettingsData.dockTransparency * 100)
|
|
||||||
minimum: 0
|
|
||||||
maximum: 100
|
|
||||||
unit: ""
|
|
||||||
showValue: true
|
|
||||||
onSliderValueChanged: (newValue) => {
|
|
||||||
SettingsData.setDockTransparency(newValue / 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
iconName: "delete_sweep"
|
||||||
|
iconSize: Theme.iconSize - 2
|
||||||
|
iconColor: Theme.error
|
||||||
|
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g,
|
||||||
|
Theme.error.b, 0.12)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onClicked: {
|
||||||
|
AppUsageHistoryData.appUsageRanking = {}
|
||||||
|
SettingsData.saveSettings()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Recently Used Apps Component
|
StyledText {
|
||||||
Component {
|
width: parent.width
|
||||||
id: recentlyUsedComponent
|
text: "Apps are ordered by usage frequency, then last used, then alphabetically."
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
StyledRect {
|
Column {
|
||||||
width: parent.width
|
id: rankedAppsList
|
||||||
height: recentlyUsedSection.implicitHeight + Theme.spacingL * 2
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Column {
|
width: parent.width
|
||||||
id: recentlyUsedSection
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
property var rankedAppsModel: {
|
Repeater {
|
||||||
var apps = [];
|
model: recentlyUsedSection.rankedAppsModel
|
||||||
for (var appId in (AppUsageHistoryData.appUsageRanking || {})) {
|
|
||||||
var appData = (AppUsageHistoryData.appUsageRanking || {})[appId];
|
|
||||||
apps.push({
|
|
||||||
"id": appId,
|
|
||||||
"name": appData.name,
|
|
||||||
"exec": appData.exec,
|
|
||||||
"icon": appData.icon,
|
|
||||||
"comment": appData.comment,
|
|
||||||
"usageCount": appData.usageCount,
|
|
||||||
"lastUsed": appData.lastUsed
|
|
||||||
});
|
|
||||||
}
|
|
||||||
apps.sort(function(a, b) {
|
|
||||||
if (a.usageCount !== b.usageCount)
|
|
||||||
return b.usageCount - a.usageCount;
|
|
||||||
|
|
||||||
return a.name.localeCompare(b.name);
|
delegate: Rectangle {
|
||||||
});
|
width: rankedAppsList.width
|
||||||
return apps.slice(0, 20);
|
height: 48
|
||||||
}
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.surfaceContainer.r,
|
||||||
|
Theme.surfaceContainer.g,
|
||||||
|
Theme.surfaceContainer.b, 0.3)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.1)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
anchors.fill: parent
|
Row {
|
||||||
anchors.margins: Theme.spacingL
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "history"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Recently Used Apps"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width - parent.children[0].width - parent.children[1].width - clearAllButton.width - Theme.spacingM * 3
|
|
||||||
height: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
id: clearAllButton
|
|
||||||
|
|
||||||
iconName: "delete_sweep"
|
|
||||||
iconSize: Theme.iconSize - 2
|
|
||||||
iconColor: Theme.error
|
|
||||||
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
onClicked: {
|
|
||||||
AppUsageHistoryData.appUsageRanking = {};
|
|
||||||
SettingsData.saveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
width: parent.width
|
text: (index + 1).toString()
|
||||||
text: "Apps are ordered by usage frequency, then last used, then alphabetically."
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.primary
|
||||||
wrapMode: Text.WordWrap
|
width: 20
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable"
|
||||||
|
sourceSize.width: 24
|
||||||
|
sourceSize.height: 24
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === Image.Error)
|
||||||
|
source = "image://icon/application-x-executable"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: rankedAppsList
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
width: parent.width
|
StyledText {
|
||||||
spacing: Theme.spacingS
|
text: modelData.name || "Unknown App"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
StyledText {
|
||||||
model: recentlyUsedSection.rankedAppsModel
|
text: {
|
||||||
|
if (!modelData.lastUsed)
|
||||||
|
return "Never used"
|
||||||
|
|
||||||
delegate: Rectangle {
|
var date = new Date(modelData.lastUsed)
|
||||||
width: rankedAppsList.width
|
var now = new Date()
|
||||||
height: 48
|
var diffMs = now - date
|
||||||
radius: Theme.cornerRadius
|
var diffMins = Math.floor(diffMs / (1000 * 60))
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
|
var diffHours = Math.floor(diffMs / (1000 * 60 * 60))
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
|
var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
|
||||||
border.width: 1
|
if (diffMins < 1)
|
||||||
|
return "Last launched just now"
|
||||||
|
|
||||||
Row {
|
if (diffMins < 60)
|
||||||
anchors.left: parent.left
|
return "Last launched " + diffMins + " minute"
|
||||||
anchors.leftMargin: Theme.spacingM
|
+ (diffMins === 1 ? "" : "s") + " ago"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
StyledText {
|
if (diffHours < 24)
|
||||||
text: (index + 1).toString()
|
return "Last launched " + diffHours + " hour"
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
+ (diffHours === 1 ? "" : "s") + " ago"
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.primary
|
|
||||||
width: 20
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
if (diffDays < 7)
|
||||||
width: 24
|
return "Last launched " + diffDays + " day"
|
||||||
height: 24
|
+ (diffDays === 1 ? "" : "s") + " ago"
|
||||||
source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable"
|
|
||||||
sourceSize.width: 24
|
|
||||||
sourceSize.height: 24
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
onStatusChanged: {
|
|
||||||
if (status === Image.Error)
|
|
||||||
source = "image://icon/application-x-executable";
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.name || "Unknown App"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (!modelData.lastUsed)
|
|
||||||
return "Never used";
|
|
||||||
|
|
||||||
var date = new Date(modelData.lastUsed);
|
|
||||||
var now = new Date();
|
|
||||||
var diffMs = now - date;
|
|
||||||
var diffMins = Math.floor(diffMs / (1000 * 60));
|
|
||||||
var diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
||||||
var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
||||||
if (diffMins < 1)
|
|
||||||
return "Last launched just now";
|
|
||||||
|
|
||||||
if (diffMins < 60)
|
|
||||||
return "Last launched " + diffMins + " minute" + (diffMins === 1 ? "" : "s") + " ago";
|
|
||||||
|
|
||||||
if (diffHours < 24)
|
|
||||||
return "Last launched " + diffHours + " hour" + (diffHours === 1 ? "" : "s") + " ago";
|
|
||||||
|
|
||||||
if (diffDays < 7)
|
|
||||||
return "Last launched " + diffDays + " day" + (diffDays === 1 ? "" : "s") + " ago";
|
|
||||||
|
|
||||||
return "Last launched " + date.toLocaleDateString();
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
circular: true
|
|
||||||
iconName: "close"
|
|
||||||
iconSize: 16
|
|
||||||
iconColor: Theme.error
|
|
||||||
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
|
||||||
onClicked: {
|
|
||||||
var currentRanking = Object.assign({
|
|
||||||
}, AppUsageHistoryData.appUsageRanking || {});
|
|
||||||
delete currentRanking[modelData.id];
|
|
||||||
AppUsageHistoryData.appUsageRanking = currentRanking;
|
|
||||||
SettingsData.saveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return "Last launched " + date.toLocaleDateString()
|
||||||
}
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
StyledText {
|
color: Theme.surfaceVariantText
|
||||||
width: parent.width
|
}
|
||||||
text: recentlyUsedSection.rankedAppsModel.length === 0 ? "No apps have been launched yet." : ""
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
visible: recentlyUsedSection.rankedAppsModel.length === 0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
circular: true
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: 16
|
||||||
|
iconColor: Theme.error
|
||||||
|
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g,
|
||||||
|
Theme.error.b, 0.12)
|
||||||
|
onClicked: {
|
||||||
|
var currentRanking = Object.assign(
|
||||||
|
{}, AppUsageHistoryData.appUsageRanking || {})
|
||||||
|
delete currentRanking[modelData.id]
|
||||||
|
AppUsageHistoryData.appUsageRanking = currentRanking
|
||||||
|
SettingsData.saveSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: recentlyUsedSection.rankedAppsModel.length
|
||||||
|
=== 0 ? "No apps have been launched yet." : ""
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
visible: recentlyUsedSection.rankedAppsModel.length === 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,114 +1,107 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string title: ""
|
property string title: ""
|
||||||
property string iconName: ""
|
property string iconName: ""
|
||||||
property alias content: contentLoader.sourceComponent
|
property alias content: contentLoader.sourceComponent
|
||||||
property bool expanded: false
|
property bool expanded: false
|
||||||
property bool collapsible: true
|
property bool collapsible: true
|
||||||
property bool lazyLoad: true
|
property bool lazyLoad: true
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: expanded ? Theme.spacingM : 0
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (!collapsible)
|
||||||
|
expanded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: expanded ? Theme.spacingM : 0
|
height: headerRow.height
|
||||||
Component.onCompleted: {
|
enabled: collapsible
|
||||||
if (!collapsible)
|
hoverEnabled: collapsible
|
||||||
expanded = true;
|
onClicked: {
|
||||||
|
if (collapsible)
|
||||||
}
|
expanded = !expanded
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
width: parent.width
|
|
||||||
height: headerRow.height
|
|
||||||
enabled: collapsible
|
|
||||||
hoverEnabled: collapsible
|
|
||||||
onClicked: {
|
|
||||||
if (collapsible)
|
|
||||||
expanded = !expanded;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: parent.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
|
||||||
radius: Theme.radiusS
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: headerRow
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
topPadding: Theme.spacingS
|
|
||||||
bottomPadding: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: root.collapsible ? (root.expanded ? "expand_less" : "expand_more") : root.iconName
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
Behavior on rotation {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Appearance.anim.durations.fast
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Appearance.anim.curves.standard
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: root.iconName
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: root.collapsible
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: root.title
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
anchors.fill: parent
|
||||||
height: 1
|
color: parent.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
Theme.primary.b,
|
||||||
visible: expanded || !collapsible
|
0.08) : "transparent"
|
||||||
|
radius: Theme.radiusS
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Row {
|
||||||
id: contentLoader
|
id: headerRow
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
active: lazyLoad ? expanded || !collapsible : true
|
spacing: Theme.spacingS
|
||||||
visible: expanded || !collapsible
|
topPadding: Theme.spacingS
|
||||||
asynchronous: true
|
bottomPadding: Theme.spacingS
|
||||||
opacity: visible ? 1 : 0
|
|
||||||
|
|
||||||
Behavior on opacity {
|
DankIcon {
|
||||||
NumberAnimation {
|
name: root.collapsible ? (root.expanded ? "expand_less" : "expand_more") : root.iconName
|
||||||
duration: Appearance.anim.durations.normal
|
size: Theme.iconSize - 2
|
||||||
easing.type: Easing.BezierSpline
|
color: Theme.primary
|
||||||
easing.bezierCurve: Appearance.anim.curves.standard
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
|
||||||
|
|
||||||
|
Behavior on rotation {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Appearance.anim.durations.fast
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standard
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: root.iconName
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: root.collapsible
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.title
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||||
|
visible: expanded || !collapsible
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: contentLoader
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
active: lazyLoad ? expanded || !collapsible : true
|
||||||
|
visible: expanded || !collapsible
|
||||||
|
asynchronous: true
|
||||||
|
opacity: visible ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Appearance.anim.durations.normal
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,183 +4,183 @@ import qs.Common
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: timeWeatherTab
|
id: timeWeatherTab
|
||||||
|
|
||||||
|
DankFlickable {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: Theme.spacingL
|
||||||
|
anchors.bottomMargin: Theme.spacingXL
|
||||||
|
clip: true
|
||||||
|
contentHeight: mainColumn.height
|
||||||
|
contentWidth: width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mainColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
width: parent.width
|
||||||
|
sourceComponent: timeComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
width: parent.width
|
||||||
|
sourceComponent: weatherComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time Format Component
|
||||||
|
Component {
|
||||||
|
id: timeComponent
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: timeSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.3)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: timeSection
|
||||||
|
|
||||||
DankFlickable {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: Theme.spacingL
|
anchors.margins: Theme.spacingL
|
||||||
anchors.bottomMargin: Theme.spacingXL
|
spacing: Theme.spacingM
|
||||||
clip: true
|
|
||||||
contentHeight: mainColumn.height
|
Row {
|
||||||
contentWidth: width
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "schedule"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Time Format"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: "24-Hour Format"
|
||||||
|
description: "Use 24-hour time format instead of 12-hour AM/PM"
|
||||||
|
checked: SettingsData.use24HourClock
|
||||||
|
onToggled: checked => {
|
||||||
|
return SettingsData.setClockFormat(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weather Component
|
||||||
|
Component {
|
||||||
|
id: weatherComponent
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: weatherSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.3)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: weatherSection
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "cloud"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Weather"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: "Enable Weather"
|
||||||
|
description: "Show weather information in top bar and centcom center"
|
||||||
|
checked: SettingsData.weatherEnabled
|
||||||
|
onToggled: checked => {
|
||||||
|
return SettingsData.setWeatherEnabled(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: "Fahrenheit"
|
||||||
|
description: "Use Fahrenheit instead of Celsius for temperature"
|
||||||
|
checked: SettingsData.useFahrenheit
|
||||||
|
enabled: SettingsData.weatherEnabled
|
||||||
|
onToggled: checked => {
|
||||||
|
return SettingsData.setTemperatureUnit(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: "Auto Location"
|
||||||
|
description: "Allow wttr.in to determine location based on IP address"
|
||||||
|
checked: SettingsData.useAutoLocation
|
||||||
|
enabled: SettingsData.weatherEnabled
|
||||||
|
onToggled: checked => {
|
||||||
|
return SettingsData.setAutoLocation(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: mainColumn
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: !SettingsData.useAutoLocation && SettingsData.weatherEnabled
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Location"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankLocationSearch {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingXL
|
currentLocation: SettingsData.weatherLocation
|
||||||
|
placeholderText: "New York, NY"
|
||||||
Loader {
|
onLocationSelected: (displayName, coordinates) => {
|
||||||
width: parent.width
|
SettingsData.setWeatherLocation(displayName,
|
||||||
sourceComponent: timeComponent
|
coordinates)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Loader {
|
|
||||||
width: parent.width
|
|
||||||
sourceComponent: weatherComponent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time Format Component
|
|
||||||
Component {
|
|
||||||
id: timeComponent
|
|
||||||
|
|
||||||
StyledRect {
|
|
||||||
width: parent.width
|
|
||||||
height: timeSection.implicitHeight + Theme.spacingL * 2
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: timeSection
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingL
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "schedule"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Time Format"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankToggle {
|
|
||||||
width: parent.width
|
|
||||||
text: "24-Hour Format"
|
|
||||||
description: "Use 24-hour time format instead of 12-hour AM/PM"
|
|
||||||
checked: SettingsData.use24HourClock
|
|
||||||
onToggled: (checked) => {
|
|
||||||
return SettingsData.setClockFormat(checked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Weather Component
|
|
||||||
Component {
|
|
||||||
id: weatherComponent
|
|
||||||
|
|
||||||
StyledRect {
|
|
||||||
width: parent.width
|
|
||||||
height: weatherSection.implicitHeight + Theme.spacingL * 2
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: weatherSection
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingL
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "cloud"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Weather"
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankToggle {
|
|
||||||
width: parent.width
|
|
||||||
text: "Enable Weather"
|
|
||||||
description: "Show weather information in top bar and centcom center"
|
|
||||||
checked: SettingsData.weatherEnabled
|
|
||||||
onToggled: (checked) => {
|
|
||||||
return SettingsData.setWeatherEnabled(checked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankToggle {
|
|
||||||
width: parent.width
|
|
||||||
text: "Fahrenheit"
|
|
||||||
description: "Use Fahrenheit instead of Celsius for temperature"
|
|
||||||
checked: SettingsData.useFahrenheit
|
|
||||||
enabled: SettingsData.weatherEnabled
|
|
||||||
onToggled: (checked) => {
|
|
||||||
return SettingsData.setTemperatureUnit(checked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankToggle {
|
|
||||||
width: parent.width
|
|
||||||
text: "Auto Location"
|
|
||||||
description: "Allow wttr.in to determine location based on IP address"
|
|
||||||
checked: SettingsData.useAutoLocation
|
|
||||||
enabled: SettingsData.weatherEnabled
|
|
||||||
onToggled: (checked) => {
|
|
||||||
return SettingsData.setAutoLocation(checked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
visible: !SettingsData.useAutoLocation && SettingsData.weatherEnabled
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Location"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankLocationSearch {
|
|
||||||
width: parent.width
|
|
||||||
currentLocation: SettingsData.weatherLocation
|
|
||||||
placeholderText: "New York, NY"
|
|
||||||
onLocationSelected: (displayName, coordinates) => {
|
|
||||||
SettingsData.setWeatherLocation(displayName, coordinates);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,380 +5,401 @@ import qs.Widgets
|
|||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var items: []
|
property var items: []
|
||||||
property var allWidgets: []
|
property var allWidgets: []
|
||||||
property string title: ""
|
property string title: ""
|
||||||
property string titleIcon: "widgets"
|
property string titleIcon: "widgets"
|
||||||
property string sectionId: ""
|
property string sectionId: ""
|
||||||
|
|
||||||
signal itemEnabledChanged(string sectionId, string itemId, bool enabled)
|
signal itemEnabledChanged(string sectionId, string itemId, bool enabled)
|
||||||
signal itemOrderChanged(var newOrder)
|
signal itemOrderChanged(var newOrder)
|
||||||
signal addWidget(string sectionId)
|
signal addWidget(string sectionId)
|
||||||
signal removeWidget(string sectionId, int widgetIndex)
|
signal removeWidget(string sectionId, int widgetIndex)
|
||||||
signal spacerSizeChanged(string sectionId, string itemId, int newSize)
|
signal spacerSizeChanged(string sectionId, string itemId, int newSize)
|
||||||
signal compactModeChanged(string widgetId, bool enabled)
|
signal compactModeChanged(string widgetId, bool enabled)
|
||||||
signal gpuSelectionChanged(string sectionId, int widgetIndex, int selectedIndex)
|
signal gpuSelectionChanged(string sectionId, int widgetIndex, int selectedIndex)
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: implicitHeight
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: implicitHeight
|
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Row {
|
DankIcon {
|
||||||
width: parent.width
|
name: root.titleIcon
|
||||||
spacing: Theme.spacingM
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
DankIcon {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
name: root.titleIcon
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: root.title
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width - 60
|
|
||||||
height: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
StyledText {
|
||||||
id: itemsList
|
text: root.title
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
width: parent.width
|
Item {
|
||||||
spacing: Theme.spacingS
|
width: parent.width - 60
|
||||||
|
height: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
Column {
|
||||||
model: root.items
|
id: itemsList
|
||||||
|
|
||||||
delegate: Item {
|
width: parent.width
|
||||||
id: delegateItem
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
property bool held: dragArea.pressed
|
Repeater {
|
||||||
property real originalY: y
|
model: root.items
|
||||||
|
|
||||||
width: itemsList.width
|
delegate: Item {
|
||||||
height: 70
|
id: delegateItem
|
||||||
z: held ? 2 : 1
|
|
||||||
|
|
||||||
Rectangle {
|
property bool held: dragArea.pressed
|
||||||
id: itemBackground
|
property real originalY: y
|
||||||
|
|
||||||
anchors.fill: parent
|
width: itemsList.width
|
||||||
anchors.margins: 2
|
height: 70
|
||||||
radius: Theme.cornerRadius
|
z: held ? 2 : 1
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
DankIcon {
|
Rectangle {
|
||||||
name: "drag_indicator"
|
id: itemBackground
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: Theme.outline
|
anchors.fill: parent
|
||||||
anchors.left: parent.left
|
anchors.margins: 2
|
||||||
anchors.leftMargin: Theme.spacingM + 8
|
radius: Theme.cornerRadius
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||||
opacity: 0.8
|
Theme.surfaceContainer.b, 0.8)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "drag_indicator"
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: Theme.outline
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM + 8
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
opacity: 0.8
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: modelData.icon
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: modelData.enabled ? Theme.primary : Theme.outline
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM * 2 + 40
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM * 3 + 40 + Theme.iconSize
|
||||||
|
anchors.right: actionButtons.left
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.text
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: modelData.enabled ? Theme.surfaceText : Theme.outline
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.description
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: modelData.enabled ? Theme.outline : Qt.rgba(
|
||||||
|
Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.6)
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: actionButtons
|
||||||
|
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 120
|
||||||
|
height: 32
|
||||||
|
visible: modelData.id === "gpuTemp"
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
id: gpuDropdown
|
||||||
|
anchors.fill: parent
|
||||||
|
currentValue: {
|
||||||
|
var selectedIndex = modelData.selectedGpuIndex
|
||||||
|
!== undefined ? modelData.selectedGpuIndex : 0
|
||||||
|
if (SysMonitorService.availableGpus
|
||||||
|
&& SysMonitorService.availableGpus.length > selectedIndex
|
||||||
|
&& selectedIndex >= 0) {
|
||||||
|
var gpu = SysMonitorService.availableGpus[selectedIndex]
|
||||||
|
return gpu.driver.toUpperCase() + " (" + Math.round(
|
||||||
|
gpu.temperature || 0) + "°C)"
|
||||||
|
}
|
||||||
|
return SysMonitorService.availableGpus
|
||||||
|
&& SysMonitorService.availableGpus.length
|
||||||
|
> 0 ? SysMonitorService.availableGpus[0].driver.toUpperCase(
|
||||||
|
) + " (" + Math.round(
|
||||||
|
SysMonitorService.availableGpus[0].temperature
|
||||||
|
|| 0) + "°C)" : ""
|
||||||
|
}
|
||||||
|
options: {
|
||||||
|
var gpuOptions = []
|
||||||
|
if (SysMonitorService.availableGpus
|
||||||
|
&& SysMonitorService.availableGpus.length > 0) {
|
||||||
|
for (var i = 0; i < SysMonitorService.availableGpus.length; i++) {
|
||||||
|
var gpu = SysMonitorService.availableGpus[i]
|
||||||
|
gpuOptions.push(gpu.driver.toUpperCase(
|
||||||
|
) + " (" + Math.round(gpu.temperature
|
||||||
|
|| 0) + "°C)")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
DankIcon {
|
return gpuOptions
|
||||||
name: modelData.icon
|
}
|
||||||
size: Theme.iconSize
|
onValueChanged: value => {
|
||||||
color: modelData.enabled ? Theme.primary : Theme.outline
|
var gpuIndex = options.indexOf(value)
|
||||||
anchors.left: parent.left
|
if (gpuIndex >= 0) {
|
||||||
anchors.leftMargin: Theme.spacingM * 2 + 40
|
root.gpuSelectionChanged(root.sectionId,
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
index, gpuIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingM * 3 + 40 + Theme.iconSize
|
|
||||||
anchors.right: actionButtons.left
|
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.text
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: modelData.enabled ? Theme.surfaceText : Theme.outline
|
|
||||||
elide: Text.ElideRight
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.description
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: modelData.enabled ? Theme.outline : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
|
||||||
elide: Text.ElideRight
|
|
||||||
width: parent.width
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: actionButtons
|
|
||||||
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 120
|
|
||||||
height: 32
|
|
||||||
visible: modelData.id === "gpuTemp"
|
|
||||||
|
|
||||||
DankDropdown {
|
|
||||||
id: gpuDropdown
|
|
||||||
anchors.fill: parent
|
|
||||||
currentValue: {
|
|
||||||
var selectedIndex = modelData.selectedGpuIndex !== undefined ? modelData.selectedGpuIndex : 0;
|
|
||||||
if (SysMonitorService.availableGpus && SysMonitorService.availableGpus.length > selectedIndex && selectedIndex >= 0) {
|
|
||||||
var gpu = SysMonitorService.availableGpus[selectedIndex];
|
|
||||||
return gpu.driver.toUpperCase() + " (" + Math.round(gpu.temperature || 0) + "°C)";
|
|
||||||
}
|
|
||||||
return SysMonitorService.availableGpus && SysMonitorService.availableGpus.length > 0 ? SysMonitorService.availableGpus[0].driver.toUpperCase() + " (" + Math.round(SysMonitorService.availableGpus[0].temperature || 0) + "°C)" : "";
|
|
||||||
}
|
}
|
||||||
options: {
|
}
|
||||||
var gpuOptions = [];
|
}
|
||||||
if (SysMonitorService.availableGpus && SysMonitorService.availableGpus.length > 0) {
|
|
||||||
for (var i = 0; i < SysMonitorService.availableGpus.length; i++) {
|
|
||||||
var gpu = SysMonitorService.availableGpus[i];
|
|
||||||
gpuOptions.push(gpu.driver.toUpperCase() + " (" + Math.round(gpu.temperature || 0) + "°C)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return gpuOptions;
|
|
||||||
}
|
|
||||||
onValueChanged: (value) => {
|
|
||||||
var gpuIndex = options.indexOf(value);
|
|
||||||
if (gpuIndex >= 0) {
|
|
||||||
root.gpuSelectionChanged(root.sectionId, index, gpuIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: 32
|
width: 32
|
||||||
height: 32
|
height: 32
|
||||||
visible: modelData.id === "clock" || modelData.id === "music"
|
visible: modelData.id === "clock" || modelData.id === "music"
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
id: compactModeButton
|
id: compactModeButton
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
buttonSize: 32
|
buttonSize: 32
|
||||||
iconName: (modelData.id === "clock" && SettingsData.clockCompactMode) || (modelData.id === "music" && SettingsData.mediaCompactMode) ? "zoom_out" : "zoom_in"
|
iconName: (modelData.id === "clock"
|
||||||
iconSize: 18
|
&& SettingsData.clockCompactMode)
|
||||||
iconColor: ((modelData.id === "clock" && SettingsData.clockCompactMode) || (modelData.id === "music" && SettingsData.mediaCompactMode)) ? Theme.primary : Theme.outline
|
|| (modelData.id === "music"
|
||||||
onClicked: {
|
&& SettingsData.mediaCompactMode) ? "zoom_out" : "zoom_in"
|
||||||
if (modelData.id === "clock") {
|
iconSize: 18
|
||||||
root.compactModeChanged("clock", !SettingsData.clockCompactMode);
|
iconColor: ((modelData.id === "clock"
|
||||||
} else if (modelData.id === "music") {
|
&& SettingsData.clockCompactMode)
|
||||||
root.compactModeChanged("music", !SettingsData.mediaCompactMode);
|
|| (modelData.id === "music"
|
||||||
}
|
&& SettingsData.mediaCompactMode)) ? Theme.primary : Theme.outline
|
||||||
}
|
onClicked: {
|
||||||
}
|
if (modelData.id === "clock") {
|
||||||
|
root.compactModeChanged("clock",
|
||||||
|
!SettingsData.clockCompactMode)
|
||||||
|
} else if (modelData.id === "music") {
|
||||||
|
root.compactModeChanged("music",
|
||||||
|
!SettingsData.mediaCompactMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: compactModeTooltip
|
id: compactModeTooltip
|
||||||
width: tooltipText.contentWidth + Theme.spacingM * 2
|
width: tooltipText.contentWidth + Theme.spacingM * 2
|
||||||
height: tooltipText.contentHeight + Theme.spacingS * 2
|
height: tooltipText.contentHeight + Theme.spacingS * 2
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceContainer
|
color: Theme.surfaceContainer
|
||||||
border.color: Theme.outline
|
border.color: Theme.outline
|
||||||
border.width: 1
|
border.width: 1
|
||||||
visible: compactModeButton.children[1] && compactModeButton.children[1].containsMouse
|
visible: compactModeButton.children[1]
|
||||||
opacity: visible ? 1 : 0
|
&& compactModeButton.children[1].containsMouse
|
||||||
x: -width - Theme.spacingS
|
opacity: visible ? 1 : 0
|
||||||
y: (parent.height - height) / 2
|
x: -width - Theme.spacingS
|
||||||
z: 100
|
y: (parent.height - height) / 2
|
||||||
|
z: 100
|
||||||
StyledText {
|
|
||||||
id: tooltipText
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "Compact Mode"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
visible: modelData.id !== "spacer"
|
|
||||||
buttonSize: 32
|
|
||||||
iconName: modelData.enabled ? "visibility" : "visibility_off"
|
|
||||||
iconSize: 18
|
|
||||||
iconColor: modelData.enabled ? Theme.primary : Theme.outline
|
|
||||||
onClicked: {
|
|
||||||
root.itemEnabledChanged(root.sectionId, modelData.id, !modelData.enabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
visible: modelData.id === "spacer"
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
buttonSize: 24
|
|
||||||
iconName: "remove"
|
|
||||||
iconSize: 14
|
|
||||||
iconColor: Theme.outline
|
|
||||||
onClicked: {
|
|
||||||
var currentSize = modelData.size || 20;
|
|
||||||
var newSize = Math.max(5, currentSize - 5);
|
|
||||||
root.spacerSizeChanged(root.sectionId, modelData.id, newSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: (modelData.size || 20).toString()
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
buttonSize: 24
|
|
||||||
iconName: "add"
|
|
||||||
iconSize: 14
|
|
||||||
iconColor: Theme.outline
|
|
||||||
onClicked: {
|
|
||||||
var currentSize = modelData.size || 20;
|
|
||||||
var newSize = Math.min(5000, currentSize + 5);
|
|
||||||
root.spacerSizeChanged(root.sectionId, modelData.id, newSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
buttonSize: 32
|
|
||||||
iconName: "close"
|
|
||||||
iconSize: 18
|
|
||||||
iconColor: Theme.error
|
|
||||||
onClicked: {
|
|
||||||
root.removeWidget(root.sectionId, index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: dragArea
|
|
||||||
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
width: 60
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.SizeVerCursor
|
|
||||||
drag.target: held ? delegateItem : undefined
|
|
||||||
drag.axis: Drag.YAxis
|
|
||||||
drag.minimumY: -delegateItem.height
|
|
||||||
drag.maximumY: itemsList.height
|
|
||||||
preventStealing: true
|
|
||||||
onPressed: {
|
|
||||||
delegateItem.z = 2;
|
|
||||||
delegateItem.originalY = delegateItem.y;
|
|
||||||
}
|
|
||||||
onReleased: {
|
|
||||||
delegateItem.z = 1;
|
|
||||||
if (drag.active) {
|
|
||||||
var newIndex = Math.round(delegateItem.y / (delegateItem.height + itemsList.spacing));
|
|
||||||
newIndex = Math.max(0, Math.min(newIndex, root.items.length - 1));
|
|
||||||
if (newIndex !== index) {
|
|
||||||
var newItems = root.items.slice();
|
|
||||||
var draggedItem = newItems.splice(index, 1)[0];
|
|
||||||
newItems.splice(newIndex, 0, draggedItem);
|
|
||||||
root.itemOrderChanged(newItems.map((item) => {
|
|
||||||
return ({
|
|
||||||
"id": item.id,
|
|
||||||
"enabled": item.enabled,
|
|
||||||
"size": item.size
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delegateItem.x = 0;
|
|
||||||
delegateItem.y = delegateItem.originalY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on y {
|
|
||||||
enabled: !dragArea.held && !dragArea.drag.active
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: tooltipText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "Compact Mode"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
DankActionButton {
|
||||||
|
visible: modelData.id !== "spacer"
|
||||||
|
buttonSize: 32
|
||||||
|
iconName: modelData.enabled ? "visibility" : "visibility_off"
|
||||||
|
iconSize: 18
|
||||||
|
iconColor: modelData.enabled ? Theme.primary : Theme.outline
|
||||||
|
onClicked: {
|
||||||
|
root.itemEnabledChanged(root.sectionId, modelData.id,
|
||||||
|
!modelData.enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
Row {
|
||||||
|
visible: modelData.id === "spacer"
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
Rectangle {
|
DankActionButton {
|
||||||
width: 200
|
buttonSize: 24
|
||||||
height: 40
|
iconName: "remove"
|
||||||
radius: Theme.cornerRadius
|
iconSize: 14
|
||||||
color: addButtonArea.containsMouse ? Theme.primaryContainer : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
iconColor: Theme.outline
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
onClicked: {
|
||||||
border.width: 1
|
var currentSize = modelData.size || 20
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
var newSize = Math.max(5, currentSize - 5)
|
||||||
|
root.spacerSizeChanged(root.sectionId, modelData.id, newSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "Add Widget"
|
text: (modelData.size || 20).toString()
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
color: Theme.surfaceText
|
||||||
color: Theme.primary
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
}
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
DankActionButton {
|
||||||
id: addButtonArea
|
buttonSize: 24
|
||||||
|
iconName: "add"
|
||||||
|
iconSize: 14
|
||||||
|
iconColor: Theme.outline
|
||||||
|
onClicked: {
|
||||||
|
var currentSize = modelData.size || 20
|
||||||
|
var newSize = Math.min(5000, currentSize + 5)
|
||||||
|
root.spacerSizeChanged(root.sectionId, modelData.id, newSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
anchors.fill: parent
|
DankActionButton {
|
||||||
|
buttonSize: 32
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: 18
|
||||||
|
iconColor: Theme.error
|
||||||
|
onClicked: {
|
||||||
|
root.removeWidget(root.sectionId, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: dragArea
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
width: 60
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.SizeVerCursor
|
||||||
onClicked: {
|
drag.target: held ? delegateItem : undefined
|
||||||
root.addWidget(root.sectionId);
|
drag.axis: Drag.YAxis
|
||||||
|
drag.minimumY: -delegateItem.height
|
||||||
|
drag.maximumY: itemsList.height
|
||||||
|
preventStealing: true
|
||||||
|
onPressed: {
|
||||||
|
delegateItem.z = 2
|
||||||
|
delegateItem.originalY = delegateItem.y
|
||||||
}
|
}
|
||||||
}
|
onReleased: {
|
||||||
|
delegateItem.z = 1
|
||||||
Behavior on color {
|
if (drag.active) {
|
||||||
ColorAnimation {
|
var newIndex = Math.round(
|
||||||
duration: Theme.shortDuration
|
delegateItem.y / (delegateItem.height + itemsList.spacing))
|
||||||
easing.type: Theme.standardEasing
|
newIndex = Math.max(0, Math.min(newIndex,
|
||||||
|
root.items.length - 1))
|
||||||
|
if (newIndex !== index) {
|
||||||
|
var newItems = root.items.slice()
|
||||||
|
var draggedItem = newItems.splice(index, 1)[0]
|
||||||
|
newItems.splice(newIndex, 0, draggedItem)
|
||||||
|
root.itemOrderChanged(newItems.map(item => {
|
||||||
|
return ({
|
||||||
|
"id": item.id,
|
||||||
|
"enabled": item.enabled,
|
||||||
|
"size": item.size
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegateItem.x = 0
|
||||||
|
delegateItem.y = delegateItem.originalY
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on y {
|
||||||
|
enabled: !dragArea.held && !dragArea.drag.active
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 200
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: addButtonArea.containsMouse ? Theme.primaryContainer : Qt.rgba(
|
||||||
|
Theme.surfaceVariant.r,
|
||||||
|
Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.3)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Add Widget"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: addButtonArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
root.addWidget(root.sectionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,130 +8,124 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var modelData
|
property var modelData
|
||||||
|
|
||||||
screen: modelData
|
screen: modelData
|
||||||
visible: ToastService.toastVisible
|
visible: ToastService.toastVisible
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: true
|
top: true
|
||||||
left: true
|
left: true
|
||||||
right: true
|
right: true
|
||||||
bottom: true
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: toast
|
||||||
|
|
||||||
|
width: Math.min(400, Screen.width - Theme.spacingL * 2)
|
||||||
|
height: toastContent.height + Theme.spacingL * 2
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
y: Theme.barHeight + Theme.spacingL
|
||||||
|
color: {
|
||||||
|
switch (ToastService.currentLevel) {
|
||||||
|
case ToastService.levelError:
|
||||||
|
return Theme.error
|
||||||
|
case ToastService.levelWarn:
|
||||||
|
return Theme.warning
|
||||||
|
case ToastService.levelInfo:
|
||||||
|
return Theme.primary
|
||||||
|
default:
|
||||||
|
return Theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
layer.enabled: true
|
||||||
|
opacity: ToastService.toastVisible ? 0.9 : 0
|
||||||
|
scale: ToastService.toastVisible ? 1 : 0.9
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: toastContent
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: {
|
||||||
|
switch (ToastService.currentLevel) {
|
||||||
|
case ToastService.levelError:
|
||||||
|
return "error"
|
||||||
|
case ToastService.levelWarn:
|
||||||
|
return "warning"
|
||||||
|
case ToastService.levelInfo:
|
||||||
|
return "info"
|
||||||
|
default:
|
||||||
|
return "info"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.background
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: ToastService.currentMessage
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.background
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 300
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
MouseArea {
|
||||||
id: toast
|
anchors.fill: parent
|
||||||
|
onClicked: ToastService.hideToast()
|
||||||
width: Math.min(400, Screen.width - Theme.spacingL * 2)
|
|
||||||
height: toastContent.height + Theme.spacingL * 2
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
y: Theme.barHeight + Theme.spacingL
|
|
||||||
color: {
|
|
||||||
switch (ToastService.currentLevel) {
|
|
||||||
case ToastService.levelError:
|
|
||||||
return Theme.error;
|
|
||||||
case ToastService.levelWarn:
|
|
||||||
return Theme.warning;
|
|
||||||
case ToastService.levelInfo:
|
|
||||||
return Theme.primary;
|
|
||||||
default:
|
|
||||||
return Theme.primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
layer.enabled: true
|
|
||||||
opacity: ToastService.toastVisible ? 0.9 : 0
|
|
||||||
scale: ToastService.toastVisible ? 1 : 0.9
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: toastContent
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: {
|
|
||||||
switch (ToastService.currentLevel) {
|
|
||||||
case ToastService.levelError:
|
|
||||||
return "error";
|
|
||||||
case ToastService.levelWarn:
|
|
||||||
return "warning";
|
|
||||||
case ToastService.levelInfo:
|
|
||||||
return "info";
|
|
||||||
default:
|
|
||||||
return "info";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.background
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: ToastService.currentMessage
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.background
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: 300
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: ToastService.hideToast()
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowHorizontalOffset: 0
|
|
||||||
shadowVerticalOffset: 4
|
|
||||||
shadowBlur: 0.8
|
|
||||||
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
|
||||||
shadowOpacity: 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
transform: Translate {
|
|
||||||
y: ToastService.toastVisible ? 0 : -20
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mask: Region {
|
layer.effect: MultiEffect {
|
||||||
item: toast
|
shadowEnabled: true
|
||||||
|
shadowHorizontalOffset: 0
|
||||||
|
shadowVerticalOffset: 4
|
||||||
|
shadowBlur: 0.8
|
||||||
|
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
||||||
|
shadowOpacity: 0.3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transform: Translate {
|
||||||
|
y: ToastService.toastVisible ? 0 : -20
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: toast
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,66 +4,66 @@ import qs.Common
|
|||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property MprisPlayer activePlayer: MprisController.activePlayer
|
readonly property MprisPlayer activePlayer: MprisController.activePlayer
|
||||||
readonly property bool hasActiveMedia: activePlayer !== null
|
readonly property bool hasActiveMedia: activePlayer !== null
|
||||||
readonly property bool isPlaying: hasActiveMedia && activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing
|
readonly property bool isPlaying: hasActiveMedia && activePlayer
|
||||||
|
&& activePlayer.playbackState === MprisPlaybackState.Playing
|
||||||
|
|
||||||
width: 20
|
width: 20
|
||||||
height: Theme.iconSize
|
height: Theme.iconSize
|
||||||
|
|
||||||
Ref {
|
Ref {
|
||||||
service: CavaService
|
service: CavaService
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: fallbackTimer
|
||||||
|
|
||||||
|
running: !CavaService.cavaAvailable && isPlaying
|
||||||
|
interval: 256
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
CavaService.values = [Math.random() * 40 + 10, Math.random(
|
||||||
|
) * 60 + 20, Math.random() * 50 + 15, Math.random(
|
||||||
|
) * 35 + 20, Math.random() * 45 + 15, Math.random(
|
||||||
|
) * 55 + 25]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Row {
|
||||||
id: fallbackTimer
|
anchors.centerIn: parent
|
||||||
|
spacing: 1.5
|
||||||
|
|
||||||
running: !CavaService.cavaAvailable && isPlaying
|
Repeater {
|
||||||
interval: 256
|
model: 6
|
||||||
repeat: true
|
|
||||||
onTriggered: {
|
Rectangle {
|
||||||
CavaService.values = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20, Math.random() * 45 + 15, Math.random() * 55 + 25];
|
width: 2
|
||||||
|
height: {
|
||||||
|
if (root.isPlaying && CavaService.values.length > index) {
|
||||||
|
const rawLevel = CavaService.values[index] || 0
|
||||||
|
const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0),
|
||||||
|
100) / 100) * 100
|
||||||
|
const maxHeight = Theme.iconSize - 2
|
||||||
|
const minHeight = 3
|
||||||
|
return minHeight + (scaledLevel / 100) * (maxHeight - minHeight)
|
||||||
|
}
|
||||||
|
return 3
|
||||||
}
|
}
|
||||||
}
|
radius: 1.5
|
||||||
|
color: Theme.primary
|
||||||
Row {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 1.5
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: 6
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 2
|
|
||||||
height: {
|
|
||||||
if (root.isPlaying && CavaService.values.length > index) {
|
|
||||||
const rawLevel = CavaService.values[index] || 0;
|
|
||||||
const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100;
|
|
||||||
const maxHeight = Theme.iconSize - 2;
|
|
||||||
const minHeight = 3;
|
|
||||||
return minHeight + (scaledLevel / 100) * (maxHeight - minHeight);
|
|
||||||
}
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
radius: 1.5
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Anims.durShort
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.standardDecel
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Anims.durShort
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.standardDecel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,174 +5,172 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: battery
|
id: battery
|
||||||
|
|
||||||
property bool batteryPopupVisible: false
|
property bool batteryPopupVisible: false
|
||||||
property string section: "right"
|
property string section: "right"
|
||||||
property var popupTarget: null
|
property var popupTarget: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
signal toggleBatteryPopup()
|
signal toggleBatteryPopup
|
||||||
|
|
||||||
width: BatteryService.batteryAvailable ? 70 : 40
|
width: BatteryService.batteryAvailable ? 70 : 40
|
||||||
height: 30
|
height: 30
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
const baseColor = batteryArea.containsMouse
|
||||||
|
|| batteryPopupVisible ? Theme.primaryPressed : Theme.secondaryHover
|
||||||
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
|
baseColor.a * Theme.widgetTransparency)
|
||||||
|
}
|
||||||
|
visible: true
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: Theme.getBatteryIcon(BatteryService.batteryLevel,
|
||||||
|
BatteryService.isCharging,
|
||||||
|
BatteryService.batteryAvailable)
|
||||||
|
size: Theme.iconSize - 6
|
||||||
|
color: {
|
||||||
|
if (!BatteryService.batteryAvailable)
|
||||||
|
return Theme.surfaceText
|
||||||
|
|
||||||
|
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
if (BatteryService.isCharging)
|
||||||
|
return Theme.primary
|
||||||
|
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
SequentialAnimation on opacity {
|
||||||
|
running: BatteryService.isCharging
|
||||||
|
loops: Animation.Infinite
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
to: 0.6
|
||||||
|
duration: Anims.durLong
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.standard
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
to: 1
|
||||||
|
duration: Anims.durLong
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: BatteryService.batteryLevel + "%"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: {
|
||||||
|
if (!BatteryService.batteryAvailable)
|
||||||
|
return Theme.surfaceText
|
||||||
|
|
||||||
|
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||||
|
return Theme.error
|
||||||
|
|
||||||
|
if (BatteryService.isCharging)
|
||||||
|
return Theme.primary
|
||||||
|
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: BatteryService.batteryAvailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: batteryArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||||
|
var globalPos = mapToGlobal(0, 0)
|
||||||
|
var currentScreen = parentScreen || Screen
|
||||||
|
var screenX = currentScreen.x || 0
|
||||||
|
var relativeX = globalPos.x - screenX
|
||||||
|
popupTarget.setTriggerPosition(relativeX,
|
||||||
|
Theme.barHeight + Theme.spacingXS,
|
||||||
|
width, section, currentScreen)
|
||||||
|
}
|
||||||
|
toggleBatteryPopup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: batteryTooltip
|
||||||
|
|
||||||
|
width: Math.max(120, tooltipText.contentWidth + Theme.spacingM * 2)
|
||||||
|
height: tooltipText.contentHeight + Theme.spacingS * 2
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: Theme.surfaceContainer
|
||||||
const baseColor = batteryArea.containsMouse || batteryPopupVisible ? Theme.primaryPressed : Theme.secondaryHover;
|
border.color: Theme.surfaceVariantAlpha
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
border.width: 1
|
||||||
}
|
visible: batteryArea.containsMouse && !batteryPopupVisible
|
||||||
visible: true
|
anchors.bottom: parent.top
|
||||||
|
anchors.bottomMargin: Theme.spacingS
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
opacity: batteryArea.containsMouse ? 1 : 0
|
||||||
|
|
||||||
Row {
|
Column {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: 4
|
spacing: 2
|
||||||
|
|
||||||
DankIcon {
|
StyledText {
|
||||||
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
id: tooltipText
|
||||||
size: Theme.iconSize - 6
|
|
||||||
color: {
|
|
||||||
if (!BatteryService.batteryAvailable)
|
|
||||||
return Theme.surfaceText;
|
|
||||||
|
|
||||||
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
text: {
|
||||||
return Theme.error;
|
if (!BatteryService.batteryAvailable) {
|
||||||
|
if (typeof PowerProfiles === "undefined")
|
||||||
|
return "Power Management"
|
||||||
|
|
||||||
if (BatteryService.isCharging)
|
switch (PowerProfiles.profile) {
|
||||||
return Theme.primary;
|
case PowerProfile.PowerSaver:
|
||||||
|
return "Power Profile: Power Saver"
|
||||||
return Theme.surfaceText;
|
case PowerProfile.Performance:
|
||||||
|
return "Power Profile: Performance"
|
||||||
|
default:
|
||||||
|
return "Power Profile: Balanced"
|
||||||
}
|
}
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
}
|
||||||
|
let status = BatteryService.batteryStatus
|
||||||
SequentialAnimation on opacity {
|
let level = BatteryService.batteryLevel + "%"
|
||||||
running: BatteryService.isCharging
|
let time = BatteryService.formatTimeRemaining()
|
||||||
loops: Animation.Infinite
|
if (time !== "Unknown")
|
||||||
|
return status + " • " + level + " • " + time
|
||||||
NumberAnimation {
|
else
|
||||||
to: 0.6
|
return status + " • " + level
|
||||||
duration: Anims.durLong
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.standard
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
to: 1
|
|
||||||
duration: Anims.durLong
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.standard
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
StyledText {
|
color: Theme.surfaceText
|
||||||
text: BatteryService.batteryLevel + "%"
|
horizontalAlignment: Text.AlignHCenter
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
}
|
||||||
font.weight: Font.Medium
|
|
||||||
color: {
|
|
||||||
if (!BatteryService.batteryAvailable)
|
|
||||||
return Theme.surfaceText;
|
|
||||||
|
|
||||||
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
|
||||||
return Theme.error;
|
|
||||||
|
|
||||||
if (BatteryService.isCharging)
|
|
||||||
return Theme.primary;
|
|
||||||
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: BatteryService.batteryAvailable
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
Behavior on opacity {
|
||||||
id: batteryArea
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
anchors.fill: parent
|
easing.type: Theme.standardEasing
|
||||||
hoverEnabled: true
|
}
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
|
||||||
var globalPos = mapToGlobal(0, 0);
|
|
||||||
var currentScreen = parentScreen || Screen;
|
|
||||||
var screenX = currentScreen.x || 0;
|
|
||||||
var relativeX = globalPos.x - screenX;
|
|
||||||
popupTarget.setTriggerPosition(relativeX, Theme.barHeight + Theme.spacingXS, width, section, currentScreen);
|
|
||||||
}
|
|
||||||
toggleBatteryPopup();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Behavior on color {
|
||||||
id: batteryTooltip
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
width: Math.max(120, tooltipText.contentWidth + Theme.spacingM * 2)
|
easing.type: Theme.standardEasing
|
||||||
height: tooltipText.contentHeight + Theme.spacingS * 2
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.surfaceContainer
|
|
||||||
border.color: Theme.surfaceVariantAlpha
|
|
||||||
border.width: 1
|
|
||||||
visible: batteryArea.containsMouse && !batteryPopupVisible
|
|
||||||
anchors.bottom: parent.top
|
|
||||||
anchors.bottomMargin: Theme.spacingS
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
opacity: batteryArea.containsMouse ? 1 : 0
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: tooltipText
|
|
||||||
|
|
||||||
text: {
|
|
||||||
if (!BatteryService.batteryAvailable) {
|
|
||||||
if (typeof PowerProfiles === "undefined")
|
|
||||||
return "Power Management";
|
|
||||||
|
|
||||||
switch (PowerProfiles.profile) {
|
|
||||||
case PowerProfile.PowerSaver:
|
|
||||||
return "Power Profile: Power Saver";
|
|
||||||
case PowerProfile.Performance:
|
|
||||||
return "Power Profile: Performance";
|
|
||||||
default:
|
|
||||||
return "Power Profile: Balanced";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let status = BatteryService.batteryStatus;
|
|
||||||
let level = BatteryService.batteryLevel + "%";
|
|
||||||
let time = BatteryService.formatTimeRemaining();
|
|
||||||
if (time !== "Unknown")
|
|
||||||
return status + " • " + level + " • " + time;
|
|
||||||
else
|
|
||||||
return status + " • " + level;
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,89 +4,91 @@ import qs.Common
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property date currentDate: new Date()
|
property date currentDate: new Date()
|
||||||
property bool compactMode: false
|
property bool compactMode: false
|
||||||
property string section: "center"
|
property string section: "center"
|
||||||
property var popupTarget: null
|
property var popupTarget: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
signal clockClicked()
|
signal clockClicked
|
||||||
|
|
||||||
width: clockRow.implicitWidth + Theme.spacingS * 2
|
width: clockRow.implicitWidth + Theme.spacingS * 2
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: {
|
||||||
const baseColor = clockMouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover;
|
const baseColor = clockMouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
}
|
baseColor.a * Theme.widgetTransparency)
|
||||||
Component.onCompleted: {
|
}
|
||||||
root.currentDate = systemClock.date;
|
Component.onCompleted: {
|
||||||
|
root.currentDate = systemClock.date
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: clockRow
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SettingsData.use24HourClock ? Qt.formatTime(root.currentDate,
|
||||||
|
"H:mm") : Qt.formatTime(
|
||||||
|
root.currentDate, "h:mm AP")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium - 1
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
StyledText {
|
||||||
id: clockRow
|
text: "•"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
anchors.centerIn: parent
|
color: Theme.outlineButton
|
||||||
spacing: Theme.spacingS
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: !SettingsData.clockCompactMode
|
||||||
StyledText {
|
|
||||||
text: SettingsData.use24HourClock ? Qt.formatTime(root.currentDate, "H:mm") : Qt.formatTime(root.currentDate, "h:mm AP")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium - 1
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "•"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.outlineButton
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: !SettingsData.clockCompactMode
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: Qt.formatDate(root.currentDate, "ddd d")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium - 1
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: !SettingsData.clockCompactMode
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemClock {
|
StyledText {
|
||||||
id: systemClock
|
text: Qt.formatDate(root.currentDate, "ddd d")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium - 1
|
||||||
precision: SystemClock.Seconds
|
color: Theme.surfaceText
|
||||||
onDateChanged: root.currentDate = systemClock.date
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: !SettingsData.clockCompactMode
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
SystemClock {
|
||||||
id: clockMouseArea
|
id: systemClock
|
||||||
|
|
||||||
anchors.fill: parent
|
precision: SystemClock.Seconds
|
||||||
hoverEnabled: true
|
onDateChanged: root.currentDate = systemClock.date
|
||||||
cursorShape: Qt.PointingHandCursor
|
}
|
||||||
onClicked: {
|
|
||||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
MouseArea {
|
||||||
var globalPos = mapToGlobal(0, 0);
|
id: clockMouseArea
|
||||||
var currentScreen = parentScreen || Screen;
|
|
||||||
var screenX = currentScreen.x || 0;
|
anchors.fill: parent
|
||||||
var relativeX = globalPos.x - screenX;
|
hoverEnabled: true
|
||||||
popupTarget.setTriggerPosition(relativeX, Theme.barHeight + Theme.spacingXS, width, section, currentScreen);
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
onClicked: {
|
||||||
root.clockClicked();
|
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||||
}
|
var globalPos = mapToGlobal(0, 0)
|
||||||
|
var currentScreen = parentScreen || Screen
|
||||||
|
var screenX = currentScreen.x || 0
|
||||||
|
var relativeX = globalPos.x - screenX
|
||||||
|
popupTarget.setTriggerPosition(relativeX,
|
||||||
|
Theme.barHeight + Theme.spacingXS,
|
||||||
|
width, section, currentScreen)
|
||||||
|
}
|
||||||
|
root.clockClicked()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
duration: Theme.shortDuration
|
duration: Theme.shortDuration
|
||||||
easing.type: Theme.standardEasing
|
easing.type: Theme.standardEasing
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,140 +4,145 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool isActive: false
|
property bool isActive: false
|
||||||
property string section: "right"
|
property string section: "right"
|
||||||
property var popupTarget: null
|
property var popupTarget: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
signal clicked()
|
signal clicked
|
||||||
|
|
||||||
function getWiFiSignalIcon(signalStrength) {
|
function getWiFiSignalIcon(signalStrength) {
|
||||||
switch (signalStrength) {
|
switch (signalStrength) {
|
||||||
case "excellent":
|
case "excellent":
|
||||||
return "wifi";
|
return "wifi"
|
||||||
case "good":
|
case "good":
|
||||||
return "wifi_2_bar";
|
return "wifi_2_bar"
|
||||||
case "fair":
|
case "fair":
|
||||||
return "wifi_1_bar";
|
return "wifi_1_bar"
|
||||||
case "poor":
|
case "poor":
|
||||||
return "signal_wifi_0_bar";
|
return "signal_wifi_0_bar"
|
||||||
default:
|
default:
|
||||||
return "wifi";
|
return "wifi"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
width: Math.max(80, controlIndicators.implicitWidth + Theme.spacingS * 2)
|
||||||
|
height: 30
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
const baseColor = controlCenterArea.containsMouse
|
||||||
|
|| root.isActive ? Theme.primaryPressed : Theme.secondaryHover
|
||||||
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
|
baseColor.a * Theme.widgetTransparency)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: controlIndicators
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: {
|
||||||
|
if (NetworkService.networkStatus === "ethernet")
|
||||||
|
return "lan"
|
||||||
|
else if (NetworkService.networkStatus === "wifi")
|
||||||
|
return getWiFiSignalIcon(NetworkService.wifiSignalStrength)
|
||||||
|
else
|
||||||
|
return "wifi_off"
|
||||||
|
}
|
||||||
|
size: Theme.iconSize - 8
|
||||||
|
color: NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: true
|
||||||
}
|
}
|
||||||
|
|
||||||
width: Math.max(80, controlIndicators.implicitWidth + Theme.spacingS * 2)
|
DankIcon {
|
||||||
height: 30
|
name: "bluetooth"
|
||||||
radius: Theme.cornerRadius
|
size: Theme.iconSize - 8
|
||||||
color: {
|
color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
|
||||||
const baseColor = controlCenterArea.containsMouse || root.isActive ? Theme.primaryPressed : Theme.secondaryHover;
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
visible: BluetoothService.available && BluetoothService.enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Rectangle {
|
||||||
id: controlIndicators
|
width: audioIcon.implicitWidth + 4
|
||||||
|
height: audioIcon.implicitHeight + 4
|
||||||
|
color: "transparent"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
id: audioIcon
|
||||||
|
|
||||||
|
name: (AudioService.sink && AudioService.sink.audio
|
||||||
|
&& AudioService.sink.audio.muted) ? "volume_off" : (AudioService.sink
|
||||||
|
&& AudioService.sink.audio
|
||||||
|
&& AudioService.sink.audio.volume * 100) < 33 ? "volume_down" : "volume_up"
|
||||||
|
size: Theme.iconSize - 8
|
||||||
|
color: audioWheelArea.containsMouse || controlCenterArea.containsMouse
|
||||||
|
|| root.isActive ? Theme.primary : Theme.surfaceText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Theme.spacingXS
|
}
|
||||||
|
|
||||||
DankIcon {
|
MouseArea {
|
||||||
name: {
|
id: audioWheelArea
|
||||||
if (NetworkService.networkStatus === "ethernet")
|
|
||||||
return "lan";
|
|
||||||
else if (NetworkService.networkStatus === "wifi")
|
|
||||||
return getWiFiSignalIcon(NetworkService.wifiSignalStrength);
|
|
||||||
else
|
|
||||||
return "wifi_off";
|
|
||||||
}
|
|
||||||
size: Theme.iconSize - 8
|
|
||||||
color: NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: true
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "bluetooth"
|
|
||||||
size: Theme.iconSize - 8
|
|
||||||
color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: BluetoothService.available && BluetoothService.enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: audioIcon.implicitWidth + 4
|
|
||||||
height: audioIcon.implicitHeight + 4
|
|
||||||
color: "transparent"
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
id: audioIcon
|
|
||||||
|
|
||||||
name: (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.muted) ? "volume_off" : (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) < 33 ? "volume_down" : "volume_up"
|
|
||||||
size: Theme.iconSize - 8
|
|
||||||
color: audioWheelArea.containsMouse || controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: audioWheelArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
acceptedButtons: Qt.NoButton
|
|
||||||
onWheel: function(wheelEvent) {
|
|
||||||
let delta = wheelEvent.angleDelta.y;
|
|
||||||
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0;
|
|
||||||
let newVolume;
|
|
||||||
if (delta > 0)
|
|
||||||
newVolume = Math.min(100, currentVolume + 5);
|
|
||||||
else
|
|
||||||
newVolume = Math.max(0, currentVolume - 5);
|
|
||||||
if (AudioService.sink && AudioService.sink.audio) {
|
|
||||||
AudioService.sink.audio.muted = false;
|
|
||||||
AudioService.sink.audio.volume = newVolume / 100;
|
|
||||||
}
|
|
||||||
wheelEvent.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "mic"
|
|
||||||
size: Theme.iconSize - 8
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: false // TODO: Add mic detection
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: controlCenterArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
acceptedButtons: Qt.NoButton
|
||||||
onClicked: {
|
onWheel: function (wheelEvent) {
|
||||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
let delta = wheelEvent.angleDelta.y
|
||||||
var globalPos = mapToGlobal(0, 0);
|
let currentVolume = (AudioService.sink && AudioService.sink.audio
|
||||||
var currentScreen = parentScreen || Screen;
|
&& AudioService.sink.audio.volume * 100) || 0
|
||||||
var screenX = currentScreen.x || 0;
|
let newVolume
|
||||||
var relativeX = globalPos.x - screenX;
|
if (delta > 0)
|
||||||
popupTarget.setTriggerPosition(relativeX, Theme.barHeight + Theme.spacingXS, width, section, currentScreen);
|
newVolume = Math.min(100, currentVolume + 5)
|
||||||
}
|
else
|
||||||
root.clicked();
|
newVolume = Math.max(0, currentVolume - 5)
|
||||||
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
AudioService.sink.audio.muted = false
|
||||||
|
AudioService.sink.audio.volume = newVolume / 100
|
||||||
|
}
|
||||||
|
wheelEvent.accepted = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
DankIcon {
|
||||||
ColorAnimation {
|
name: "mic"
|
||||||
duration: Theme.shortDuration
|
size: Theme.iconSize - 8
|
||||||
easing.type: Theme.standardEasing
|
color: Theme.primary
|
||||||
}
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: false // TODO: Add mic detection
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: controlCenterArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||||
|
var globalPos = mapToGlobal(0, 0)
|
||||||
|
var currentScreen = parentScreen || Screen
|
||||||
|
var screenX = currentScreen.x || 0
|
||||||
|
var relativeX = globalPos.x - screenX
|
||||||
|
popupTarget.setTriggerPosition(relativeX,
|
||||||
|
Theme.barHeight + Theme.spacingXS,
|
||||||
|
width, section, currentScreen)
|
||||||
|
}
|
||||||
|
root.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,81 +5,84 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool showPercentage: true
|
property bool showPercentage: true
|
||||||
property bool showIcon: true
|
property bool showIcon: true
|
||||||
property var toggleProcessList
|
property var toggleProcessList
|
||||||
property string section: "right"
|
property string section: "right"
|
||||||
property var popupTarget: null
|
property var popupTarget: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
width: 55
|
width: 55
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: {
|
||||||
const baseColor = cpuArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover;
|
const baseColor = cpuArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
|
baseColor.a * Theme.widgetTransparency)
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
SysMonitorService.addRef()
|
||||||
|
}
|
||||||
|
Component.onDestruction: {
|
||||||
|
SysMonitorService.removeRef()
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cpuArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||||
|
var globalPos = mapToGlobal(0, 0)
|
||||||
|
var currentScreen = parentScreen || Screen
|
||||||
|
var screenX = currentScreen.x || 0
|
||||||
|
var relativeX = globalPos.x - screenX
|
||||||
|
popupTarget.setTriggerPosition(relativeX,
|
||||||
|
Theme.barHeight + Theme.spacingXS,
|
||||||
|
width, section, currentScreen)
|
||||||
|
}
|
||||||
|
SysMonitorService.setSortBy("cpu")
|
||||||
|
if (root.toggleProcessList)
|
||||||
|
root.toggleProcessList()
|
||||||
}
|
}
|
||||||
Component.onCompleted: {
|
}
|
||||||
SysMonitorService.addRef();
|
|
||||||
}
|
Row {
|
||||||
Component.onDestruction: {
|
anchors.centerIn: parent
|
||||||
SysMonitorService.removeRef();
|
spacing: 3
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "memory"
|
||||||
|
size: Theme.iconSize - 8
|
||||||
|
color: {
|
||||||
|
if (SysMonitorService.cpuUsage > 80)
|
||||||
|
return Theme.tempDanger
|
||||||
|
|
||||||
|
if (SysMonitorService.cpuUsage > 60)
|
||||||
|
return Theme.tempWarning
|
||||||
|
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
StyledText {
|
||||||
id: cpuArea
|
text: {
|
||||||
|
if (SysMonitorService.cpuUsage === undefined
|
||||||
anchors.fill: parent
|
|| SysMonitorService.cpuUsage === null
|
||||||
hoverEnabled: true
|
|| SysMonitorService.cpuUsage === 0) {
|
||||||
cursorShape: Qt.PointingHandCursor
|
return "--%"
|
||||||
onClicked: {
|
|
||||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
|
||||||
var globalPos = mapToGlobal(0, 0);
|
|
||||||
var currentScreen = parentScreen || Screen;
|
|
||||||
var screenX = currentScreen.x || 0;
|
|
||||||
var relativeX = globalPos.x - screenX;
|
|
||||||
popupTarget.setTriggerPosition(relativeX, Theme.barHeight + Theme.spacingXS, width, section, currentScreen);
|
|
||||||
}
|
|
||||||
SysMonitorService.setSortBy("cpu");
|
|
||||||
if (root.toggleProcessList)
|
|
||||||
root.toggleProcessList();
|
|
||||||
}
|
}
|
||||||
|
return SysMonitorService.cpuUsage.toFixed(0) + "%"
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 3
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "memory"
|
|
||||||
size: Theme.iconSize - 8
|
|
||||||
color: {
|
|
||||||
if (SysMonitorService.cpuUsage > 80)
|
|
||||||
return Theme.tempDanger;
|
|
||||||
|
|
||||||
if (SysMonitorService.cpuUsage > 60)
|
|
||||||
return Theme.tempWarning;
|
|
||||||
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (SysMonitorService.cpuUsage === undefined || SysMonitorService.cpuUsage === null || SysMonitorService.cpuUsage === 0) {
|
|
||||||
return "--%";
|
|
||||||
}
|
|
||||||
return SysMonitorService.cpuUsage.toFixed(0) + "%";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,87 +5,91 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool showPercentage: true
|
property bool showPercentage: true
|
||||||
property bool showIcon: true
|
property bool showIcon: true
|
||||||
property var toggleProcessList
|
property var toggleProcessList
|
||||||
property string section: "right"
|
property string section: "right"
|
||||||
property var popupTarget: null
|
property var popupTarget: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
width: 55
|
width: 55
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: {
|
||||||
const baseColor = cpuTempArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover;
|
const baseColor = cpuTempArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
|
baseColor.a * Theme.widgetTransparency)
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
SysMonitorService.addRef()
|
||||||
|
}
|
||||||
|
Component.onDestruction: {
|
||||||
|
SysMonitorService.removeRef()
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cpuTempArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||||
|
var globalPos = mapToGlobal(0, 0)
|
||||||
|
var currentScreen = parentScreen || Screen
|
||||||
|
var screenX = currentScreen.x || 0
|
||||||
|
var relativeX = globalPos.x - screenX
|
||||||
|
popupTarget.setTriggerPosition(relativeX,
|
||||||
|
Theme.barHeight + Theme.spacingXS,
|
||||||
|
width, section, currentScreen)
|
||||||
|
}
|
||||||
|
SysMonitorService.setSortBy("cpu")
|
||||||
|
if (root.toggleProcessList)
|
||||||
|
root.toggleProcessList()
|
||||||
}
|
}
|
||||||
Component.onCompleted: {
|
}
|
||||||
SysMonitorService.addRef();
|
|
||||||
}
|
Row {
|
||||||
Component.onDestruction: {
|
anchors.centerIn: parent
|
||||||
SysMonitorService.removeRef();
|
spacing: 3
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "memory"
|
||||||
|
size: Theme.iconSize - 8
|
||||||
|
color: {
|
||||||
|
if (SysMonitorService.cpuTemperature > 85)
|
||||||
|
return Theme.tempDanger
|
||||||
|
|
||||||
|
if (SysMonitorService.cpuTemperature > 69)
|
||||||
|
return Theme.tempWarning
|
||||||
|
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
StyledText {
|
||||||
id: cpuTempArea
|
text: {
|
||||||
|
if (SysMonitorService.cpuTemperature === undefined
|
||||||
anchors.fill: parent
|
|| SysMonitorService.cpuTemperature === null
|
||||||
hoverEnabled: true
|
|| SysMonitorService.cpuTemperature < 0) {
|
||||||
cursorShape: Qt.PointingHandCursor
|
return "--°"
|
||||||
onClicked: {
|
|
||||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
|
||||||
var globalPos = mapToGlobal(0, 0);
|
|
||||||
var currentScreen = parentScreen || Screen;
|
|
||||||
var screenX = currentScreen.x || 0;
|
|
||||||
var relativeX = globalPos.x - screenX;
|
|
||||||
popupTarget.setTriggerPosition(relativeX, Theme.barHeight + Theme.spacingXS, width, section, currentScreen);
|
|
||||||
}
|
|
||||||
SysMonitorService.setSortBy("cpu");
|
|
||||||
if (root.toggleProcessList)
|
|
||||||
root.toggleProcessList();
|
|
||||||
}
|
}
|
||||||
|
return Math.round(SysMonitorService.cpuTemperature) + "°"
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Behavior on color {
|
||||||
anchors.centerIn: parent
|
ColorAnimation {
|
||||||
spacing: 3
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
DankIcon {
|
|
||||||
name: "memory"
|
|
||||||
size: Theme.iconSize - 8
|
|
||||||
color: {
|
|
||||||
if (SysMonitorService.cpuTemperature > 85)
|
|
||||||
return Theme.tempDanger;
|
|
||||||
|
|
||||||
if (SysMonitorService.cpuTemperature > 69)
|
|
||||||
return Theme.tempWarning;
|
|
||||||
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (SysMonitorService.cpuTemperature === undefined || SysMonitorService.cpuTemperature === null || SysMonitorService.cpuTemperature < 0) {
|
|
||||||
return "--°";
|
|
||||||
}
|
|
||||||
return Math.round(SysMonitorService.cpuTemperature) + "°";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,91 +4,93 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool compactMode: false
|
property bool compactMode: false
|
||||||
property int availableWidth: 400
|
property int availableWidth: 400
|
||||||
readonly property int baseWidth: contentRow.implicitWidth + Theme.spacingS * 2
|
readonly property int baseWidth: contentRow.implicitWidth + Theme.spacingS * 2
|
||||||
readonly property int maxNormalWidth: 456
|
readonly property int maxNormalWidth: 456
|
||||||
readonly property int maxCompactWidth: 288
|
readonly property int maxCompactWidth: 288
|
||||||
|
|
||||||
width: compactMode ? Math.min(baseWidth, maxCompactWidth) : Math.min(baseWidth, maxNormalWidth)
|
width: compactMode ? Math.min(baseWidth,
|
||||||
height: 30
|
maxCompactWidth) : Math.min(baseWidth,
|
||||||
radius: Theme.cornerRadius
|
maxNormalWidth)
|
||||||
color: {
|
height: 30
|
||||||
if (!FocusedWindowService.focusedAppName && !FocusedWindowService.focusedWindowTitle)
|
radius: Theme.cornerRadius
|
||||||
return "transparent";
|
color: {
|
||||||
|
if (!FocusedWindowService.focusedAppName
|
||||||
|
&& !FocusedWindowService.focusedWindowTitle)
|
||||||
|
return "transparent"
|
||||||
|
|
||||||
const baseColor = mouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover;
|
const baseColor = mouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
}
|
baseColor.a * Theme.widgetTransparency)
|
||||||
clip: true
|
}
|
||||||
visible: FocusedWindowService.niriAvailable && (FocusedWindowService.focusedAppName || FocusedWindowService.focusedWindowTitle)
|
clip: true
|
||||||
|
visible: FocusedWindowService.niriAvailable
|
||||||
|
&& (FocusedWindowService.focusedAppName
|
||||||
|
|| FocusedWindowService.focusedWindowTitle)
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: contentRow
|
id: contentRow
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: appText
|
id: appText
|
||||||
|
|
||||||
text: FocusedWindowService.focusedAppName || ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
elide: Text.ElideRight
|
|
||||||
maximumLineCount: 1
|
|
||||||
width: Math.min(implicitWidth, compactMode ? 80 : 180)
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "•"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.outlineButton
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: appText.text && titleText.text
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: titleText
|
|
||||||
|
|
||||||
text: FocusedWindowService.focusedWindowTitle || ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
elide: Text.ElideRight
|
|
||||||
maximumLineCount: 1
|
|
||||||
width: Math.min(implicitWidth, compactMode ? 180 : 250)
|
|
||||||
visible: text.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
text: FocusedWindowService.focusedAppName || ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
width: Math.min(implicitWidth, compactMode ? 80 : 180)
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
StyledText {
|
||||||
id: mouseArea
|
text: "•"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
anchors.fill: parent
|
color: Theme.outlineButton
|
||||||
hoverEnabled: true
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: appText.text && titleText.text
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
StyledText {
|
||||||
ColorAnimation {
|
id: titleText
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
|
text: FocusedWindowService.focusedWindowTitle || ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
width: Math.min(implicitWidth, compactMode ? 180 : 250)
|
||||||
|
visible: text.length > 0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on width {
|
MouseArea {
|
||||||
NumberAnimation {
|
id: mouseArea
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,107 +5,115 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool showPercentage: true
|
property bool showPercentage: true
|
||||||
property bool showIcon: true
|
property bool showIcon: true
|
||||||
property var toggleProcessList
|
property var toggleProcessList
|
||||||
property string section: "right"
|
property string section: "right"
|
||||||
property var popupTarget: null
|
property var popupTarget: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
property var widgetData: null
|
property var widgetData: null
|
||||||
property int selectedGpuIndex: (widgetData && widgetData.selectedGpuIndex !== undefined) ? widgetData.selectedGpuIndex : 0
|
property int selectedGpuIndex: (widgetData && widgetData.selectedGpuIndex
|
||||||
|
!== undefined) ? widgetData.selectedGpuIndex : 0
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SettingsData
|
target: SettingsData
|
||||||
function onWidgetDataChanged() {
|
function onWidgetDataChanged() {
|
||||||
// Force property re-evaluation by triggering change detection
|
// Force property re-evaluation by triggering change detection
|
||||||
root.selectedGpuIndex = Qt.binding(() => {
|
root.selectedGpuIndex = Qt.binding(() => {
|
||||||
return (root.widgetData && root.widgetData.selectedGpuIndex !== undefined) ? root.widgetData.selectedGpuIndex : 0;
|
return (root.widgetData
|
||||||
});
|
&& root.widgetData.selectedGpuIndex !== undefined) ? root.widgetData.selectedGpuIndex : 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
width: 55
|
||||||
|
height: 30
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
const baseColor = gpuArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover
|
||||||
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
|
baseColor.a * Theme.widgetTransparency)
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
SysMonitorService.addRef()
|
||||||
|
}
|
||||||
|
Component.onDestruction: {
|
||||||
|
SysMonitorService.removeRef()
|
||||||
|
}
|
||||||
|
|
||||||
|
property real displayTemp: {
|
||||||
|
if (!SysMonitorService.availableGpus
|
||||||
|
|| SysMonitorService.availableGpus.length === 0)
|
||||||
|
return 0
|
||||||
|
if (selectedGpuIndex >= 0
|
||||||
|
&& selectedGpuIndex < SysMonitorService.availableGpus.length) {
|
||||||
|
return SysMonitorService.availableGpus[selectedGpuIndex].temperature || 0
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: gpuArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||||
|
var globalPos = mapToGlobal(0, 0)
|
||||||
|
var currentScreen = parentScreen || Screen
|
||||||
|
var screenX = currentScreen.x || 0
|
||||||
|
var relativeX = globalPos.x - screenX
|
||||||
|
popupTarget.setTriggerPosition(relativeX,
|
||||||
|
Theme.barHeight + Theme.spacingXS,
|
||||||
|
width, section, currentScreen)
|
||||||
|
}
|
||||||
|
SysMonitorService.setSortBy("cpu")
|
||||||
|
if (root.toggleProcessList)
|
||||||
|
root.toggleProcessList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 3
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "auto_awesome_mosaic"
|
||||||
|
size: Theme.iconSize - 8
|
||||||
|
color: {
|
||||||
|
if (root.displayTemp > 80)
|
||||||
|
return Theme.tempDanger
|
||||||
|
|
||||||
|
if (root.displayTemp > 65)
|
||||||
|
return Theme.tempWarning
|
||||||
|
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (root.displayTemp === undefined || root.displayTemp === null
|
||||||
|
|| root.displayTemp === 0) {
|
||||||
|
return "--°"
|
||||||
}
|
}
|
||||||
|
return Math.round(root.displayTemp) + "°"
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
width: 55
|
Behavior on color {
|
||||||
height: 30
|
ColorAnimation {
|
||||||
radius: Theme.cornerRadius
|
duration: Theme.shortDuration
|
||||||
color: {
|
easing.type: Theme.standardEasing
|
||||||
const baseColor = gpuArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover;
|
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
|
||||||
}
|
|
||||||
Component.onCompleted: {
|
|
||||||
SysMonitorService.addRef();
|
|
||||||
}
|
|
||||||
Component.onDestruction: {
|
|
||||||
SysMonitorService.removeRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
property real displayTemp: {
|
|
||||||
if (!SysMonitorService.availableGpus || SysMonitorService.availableGpus.length === 0) return 0;
|
|
||||||
if (selectedGpuIndex >= 0 && selectedGpuIndex < SysMonitorService.availableGpus.length) {
|
|
||||||
return SysMonitorService.availableGpus[selectedGpuIndex].temperature || 0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: gpuArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
|
||||||
var globalPos = mapToGlobal(0, 0);
|
|
||||||
var currentScreen = parentScreen || Screen;
|
|
||||||
var screenX = currentScreen.x || 0;
|
|
||||||
var relativeX = globalPos.x - screenX;
|
|
||||||
popupTarget.setTriggerPosition(relativeX, Theme.barHeight + Theme.spacingXS, width, section, currentScreen);
|
|
||||||
}
|
|
||||||
SysMonitorService.setSortBy("cpu");
|
|
||||||
if (root.toggleProcessList)
|
|
||||||
root.toggleProcessList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 3
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "auto_awesome_mosaic"
|
|
||||||
size: Theme.iconSize - 8
|
|
||||||
color: {
|
|
||||||
if (root.displayTemp > 80)
|
|
||||||
return Theme.tempDanger;
|
|
||||||
|
|
||||||
if (root.displayTemp > 65)
|
|
||||||
return Theme.tempWarning;
|
|
||||||
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
|
|
||||||
return "--°";
|
|
||||||
}
|
|
||||||
return Math.round(root.displayTemp) + "°";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,65 +4,67 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool isActive: false
|
property bool isActive: false
|
||||||
property string section: "left" // Which section this button is in
|
property string section: "left" // Which section this button is in
|
||||||
property var popupTarget: null // Reference to the popup to position
|
property var popupTarget: null // Reference to the popup to position
|
||||||
property var parentScreen: null // The screen this button is on
|
property var parentScreen: null // The screen this button is on
|
||||||
|
|
||||||
signal clicked()
|
signal clicked
|
||||||
|
|
||||||
width: 40
|
width: 40
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: {
|
||||||
const baseColor = launcherArea.containsMouse || isActive ? Theme.surfaceTextPressed : Theme.surfaceTextHover;
|
const baseColor = launcherArea.containsMouse
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
|| isActive ? Theme.surfaceTextPressed : Theme.surfaceTextHover
|
||||||
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
|
baseColor.a * Theme.widgetTransparency)
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemLogo {
|
||||||
|
visible: SettingsData.useOSLogo
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: Theme.iconSize - 3
|
||||||
|
height: Theme.iconSize - 3
|
||||||
|
colorOverride: SettingsData.osLogoColorOverride
|
||||||
|
brightnessOverride: SettingsData.osLogoBrightness
|
||||||
|
contrastOverride: SettingsData.osLogoContrast
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
visible: !SettingsData.useOSLogo
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "apps"
|
||||||
|
size: Theme.iconSize - 6
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: launcherArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||||
|
var globalPos = mapToGlobal(0, 0)
|
||||||
|
var currentScreen = parentScreen || Screen
|
||||||
|
var screenX = currentScreen.x || 0
|
||||||
|
var relativeX = globalPos.x - screenX
|
||||||
|
popupTarget.setTriggerPosition(relativeX,
|
||||||
|
Theme.barHeight + Theme.spacingXS,
|
||||||
|
width, section, currentScreen)
|
||||||
|
}
|
||||||
|
root.clicked()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SystemLogo {
|
Behavior on color {
|
||||||
visible: SettingsData.useOSLogo
|
ColorAnimation {
|
||||||
anchors.centerIn: parent
|
duration: Theme.shortDuration
|
||||||
width: Theme.iconSize - 3
|
easing.type: Theme.standardEasing
|
||||||
height: Theme.iconSize - 3
|
|
||||||
colorOverride: SettingsData.osLogoColorOverride
|
|
||||||
brightnessOverride: SettingsData.osLogoBrightness
|
|
||||||
contrastOverride: SettingsData.osLogoContrast
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
DankIcon {
|
|
||||||
visible: !SettingsData.useOSLogo
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "apps"
|
|
||||||
size: Theme.iconSize - 6
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: launcherArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
|
||||||
var globalPos = mapToGlobal(0, 0);
|
|
||||||
var currentScreen = parentScreen || Screen;
|
|
||||||
var screenX = currentScreen.x || 0;
|
|
||||||
var relativeX = globalPos.x - screenX;
|
|
||||||
popupTarget.setTriggerPosition(relativeX, Theme.barHeight + Theme.spacingXS, width, section, currentScreen);
|
|
||||||
}
|
|
||||||
root.clicked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,260 +5,252 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property MprisPlayer activePlayer: MprisController.activePlayer
|
readonly property MprisPlayer activePlayer: MprisController.activePlayer
|
||||||
readonly property bool playerAvailable: activePlayer !== null
|
readonly property bool playerAvailable: activePlayer !== null
|
||||||
property bool compactMode: false
|
property bool compactMode: false
|
||||||
readonly property int baseContentWidth: mediaRow.implicitWidth + Theme.spacingS * 2
|
readonly property int baseContentWidth: mediaRow.implicitWidth + Theme.spacingS * 2
|
||||||
readonly property int normalContentWidth: Math.min(280, baseContentWidth)
|
readonly property int normalContentWidth: Math.min(280, baseContentWidth)
|
||||||
readonly property int compactContentWidth: Math.min(120, baseContentWidth)
|
readonly property int compactContentWidth: Math.min(120, baseContentWidth)
|
||||||
property string section: "center"
|
property string section: "center"
|
||||||
property var popupTarget: null
|
property var popupTarget: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
signal clicked()
|
signal clicked
|
||||||
|
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: {
|
||||||
const baseColor = Theme.surfaceTextHover;
|
const baseColor = Theme.surfaceTextHover
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
|
baseColor.a * Theme.widgetTransparency)
|
||||||
|
}
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "shown"
|
||||||
|
when: playerAvailable
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
target: root
|
||||||
|
opacity: 1
|
||||||
|
width: SettingsData.mediaCompactMode ? compactContentWidth : normalContentWidth
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "hidden"
|
||||||
|
when: !playerAvailable
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
target: root
|
||||||
|
opacity: 0
|
||||||
|
width: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
states: [
|
]
|
||||||
State {
|
transitions: [
|
||||||
name: "shown"
|
Transition {
|
||||||
when: playerAvailable
|
from: "shown"
|
||||||
|
to: "hidden"
|
||||||
PropertyChanges {
|
|
||||||
target: root
|
|
||||||
opacity: 1
|
|
||||||
width: SettingsData.mediaCompactMode ? compactContentWidth : normalContentWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "hidden"
|
|
||||||
when: !playerAvailable
|
|
||||||
|
|
||||||
PropertyChanges {
|
|
||||||
target: root
|
|
||||||
opacity: 0
|
|
||||||
width: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
PauseAnimation {
|
||||||
|
duration: 500
|
||||||
}
|
}
|
||||||
]
|
|
||||||
transitions: [
|
|
||||||
Transition {
|
|
||||||
from: "shown"
|
|
||||||
to: "hidden"
|
|
||||||
|
|
||||||
SequentialAnimation {
|
|
||||||
PauseAnimation {
|
|
||||||
duration: 500
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
properties: "opacity,width"
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
Transition {
|
|
||||||
from: "hidden"
|
|
||||||
to: "shown"
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
properties: "opacity,width"
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
properties: "opacity,width"
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
|
},
|
||||||
|
Transition {
|
||||||
|
from: "hidden"
|
||||||
|
to: "shown"
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
properties: "opacity,width"
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: mediaRow
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: mediaRow
|
id: mediaInfo
|
||||||
|
|
||||||
anchors.centerIn: parent
|
spacing: Theme.spacingXS
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
Row {
|
AudioVisualization {
|
||||||
id: mediaInfo
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
spacing: Theme.spacingXS
|
StyledText {
|
||||||
|
id: mediaText
|
||||||
|
|
||||||
AudioVisualization {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
width: SettingsData.mediaCompactMode ? 60 : 140
|
||||||
}
|
visible: !SettingsData.mediaCompactMode
|
||||||
|
text: {
|
||||||
StyledText {
|
if (!activePlayer || !activePlayer.trackTitle)
|
||||||
id: mediaText
|
return ""
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: SettingsData.mediaCompactMode ? 60 : 140
|
|
||||||
visible: !SettingsData.mediaCompactMode
|
|
||||||
text: {
|
|
||||||
if (!activePlayer || !activePlayer.trackTitle)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
let identity = activePlayer.identity || "";
|
|
||||||
let isWebMedia = identity.toLowerCase().includes("firefox") || identity.toLowerCase().includes("chrome") || identity.toLowerCase().includes("chromium") || identity.toLowerCase().includes("edge") || identity.toLowerCase().includes("safari");
|
|
||||||
let title = "";
|
|
||||||
let subtitle = "";
|
|
||||||
if (isWebMedia && activePlayer.trackTitle) {
|
|
||||||
title = activePlayer.trackTitle;
|
|
||||||
subtitle = activePlayer.trackArtist || identity;
|
|
||||||
} else {
|
|
||||||
title = activePlayer.trackTitle || "Unknown Track";
|
|
||||||
subtitle = activePlayer.trackArtist || "";
|
|
||||||
}
|
|
||||||
return subtitle.length > 0 ? title + " • " + subtitle : title;
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
maximumLineCount: 1
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (root.popupTarget && root.popupTarget.setTriggerPosition) {
|
|
||||||
var globalPos = mapToGlobal(0, 0);
|
|
||||||
var currentScreen = root.parentScreen || Screen;
|
|
||||||
var screenX = currentScreen.x || 0;
|
|
||||||
var relativeX = globalPos.x - screenX;
|
|
||||||
root.popupTarget.setTriggerPosition(relativeX, Theme.barHeight + Theme.spacingXS, root.width, root.section, currentScreen);
|
|
||||||
}
|
|
||||||
root.clicked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let identity = activePlayer.identity || ""
|
||||||
|
let isWebMedia = identity.toLowerCase().includes("firefox")
|
||||||
|
|| identity.toLowerCase().includes(
|
||||||
|
"chrome") || identity.toLowerCase().includes("chromium")
|
||||||
|
|| identity.toLowerCase().includes(
|
||||||
|
"edge") || identity.toLowerCase().includes("safari")
|
||||||
|
let title = ""
|
||||||
|
let subtitle = ""
|
||||||
|
if (isWebMedia && activePlayer.trackTitle) {
|
||||||
|
title = activePlayer.trackTitle
|
||||||
|
subtitle = activePlayer.trackArtist || identity
|
||||||
|
} else {
|
||||||
|
title = activePlayer.trackTitle || "Unknown Track"
|
||||||
|
subtitle = activePlayer.trackArtist || ""
|
||||||
|
}
|
||||||
|
return subtitle.length > 0 ? title + " • " + subtitle : title
|
||||||
}
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
maximumLineCount: 1
|
||||||
|
|
||||||
Row {
|
MouseArea {
|
||||||
spacing: Theme.spacingXS
|
anchors.fill: parent
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
Rectangle {
|
onClicked: {
|
||||||
width: 20
|
if (root.popupTarget && root.popupTarget.setTriggerPosition) {
|
||||||
height: 20
|
var globalPos = mapToGlobal(0, 0)
|
||||||
radius: 10
|
var currentScreen = root.parentScreen || Screen
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
var screenX = currentScreen.x || 0
|
||||||
color: prevArea.containsMouse ? Theme.primaryHover : "transparent"
|
var relativeX = globalPos.x - screenX
|
||||||
visible: root.playerAvailable
|
root.popupTarget.setTriggerPosition(
|
||||||
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3
|
relativeX, Theme.barHeight + Theme.spacingXS, root.width,
|
||||||
|
root.section, currentScreen)
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "skip_previous"
|
|
||||||
size: 12
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: prevArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (activePlayer)
|
|
||||||
activePlayer.previous();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
root.clicked()
|
||||||
Rectangle {
|
}
|
||||||
width: 24
|
|
||||||
height: 24
|
|
||||||
radius: 12
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Theme.primaryHover
|
|
||||||
visible: root.playerAvailable
|
|
||||||
opacity: activePlayer ? 1 : 0.3
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow"
|
|
||||||
size: 14
|
|
||||||
color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (activePlayer)
|
|
||||||
activePlayer.togglePlaying();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 20
|
|
||||||
height: 20
|
|
||||||
radius: 10
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
color: nextArea.containsMouse ? Theme.primaryHover : "transparent"
|
|
||||||
visible: playerAvailable
|
|
||||||
opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "skip_next"
|
|
||||||
size: 12
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: nextArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (activePlayer)
|
|
||||||
activePlayer.next();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
Row {
|
||||||
ColorAnimation {
|
spacing: Theme.spacingXS
|
||||||
duration: Theme.shortDuration
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
|
Rectangle {
|
||||||
|
width: 20
|
||||||
|
height: 20
|
||||||
|
radius: 10
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: prevArea.containsMouse ? Theme.primaryHover : "transparent"
|
||||||
|
visible: root.playerAvailable
|
||||||
|
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "skip_previous"
|
||||||
|
size: 12
|
||||||
|
color: Theme.surfaceText
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
MouseArea {
|
||||||
|
id: prevArea
|
||||||
|
|
||||||
Behavior on width {
|
anchors.fill: parent
|
||||||
NumberAnimation {
|
hoverEnabled: true
|
||||||
duration: Theme.shortDuration
|
cursorShape: Qt.PointingHandCursor
|
||||||
easing.type: Theme.standardEasing
|
onClicked: {
|
||||||
|
if (activePlayer)
|
||||||
|
activePlayer.previous()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
radius: 12
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: activePlayer
|
||||||
|
&& activePlayer.playbackState === 1 ? Theme.primary : Theme.primaryHover
|
||||||
|
visible: root.playerAvailable
|
||||||
|
opacity: activePlayer ? 1 : 0.3
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: activePlayer
|
||||||
|
&& activePlayer.playbackState === 1 ? "pause" : "play_arrow"
|
||||||
|
size: 14
|
||||||
|
color: activePlayer
|
||||||
|
&& activePlayer.playbackState === 1 ? Theme.background : Theme.primary
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (activePlayer)
|
||||||
|
activePlayer.togglePlaying()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 20
|
||||||
|
height: 20
|
||||||
|
radius: 10
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: nextArea.containsMouse ? Theme.primaryHover : "transparent"
|
||||||
|
visible: playerAvailable
|
||||||
|
opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "skip_next"
|
||||||
|
size: 12
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: nextArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (activePlayer)
|
||||||
|
activePlayer.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,67 +3,70 @@ import qs.Common
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool hasUnread: false
|
property bool hasUnread: false
|
||||||
property bool isActive: false
|
property bool isActive: false
|
||||||
property string section: "right"
|
property string section: "right"
|
||||||
property var popupTarget: null
|
property var popupTarget: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
signal clicked()
|
signal clicked
|
||||||
|
|
||||||
width: 40
|
width: 40
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: {
|
||||||
const baseColor = notificationArea.containsMouse || root.isActive ? Theme.primaryPressed : Theme.secondaryHover;
|
const baseColor = notificationArea.containsMouse
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
|| root.isActive ? Theme.primaryPressed : Theme.secondaryHover
|
||||||
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
|
baseColor.a * Theme.widgetTransparency)
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: SessionData.doNotDisturb ? "notifications_off" : "notifications"
|
||||||
|
size: Theme.iconSize - 6
|
||||||
|
color: SessionData.doNotDisturb ? Theme.error : (notificationArea.containsMouse
|
||||||
|
|| root.isActive ? Theme.primary : Theme.surfaceText)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 8
|
||||||
|
height: 8
|
||||||
|
radius: 4
|
||||||
|
color: Theme.error
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.rightMargin: 6
|
||||||
|
anchors.topMargin: 6
|
||||||
|
visible: root.hasUnread
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: notificationArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||||
|
var globalPos = mapToGlobal(0, 0)
|
||||||
|
var currentScreen = parentScreen || Screen
|
||||||
|
var screenX = currentScreen.x || 0
|
||||||
|
var relativeX = globalPos.x - screenX
|
||||||
|
popupTarget.setTriggerPosition(relativeX,
|
||||||
|
Theme.barHeight + Theme.spacingXS,
|
||||||
|
width, section, currentScreen)
|
||||||
|
}
|
||||||
|
root.clicked()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DankIcon {
|
Behavior on color {
|
||||||
anchors.centerIn: parent
|
ColorAnimation {
|
||||||
name: SessionData.doNotDisturb ? "notifications_off" : "notifications"
|
duration: Theme.shortDuration
|
||||||
size: Theme.iconSize - 6
|
easing.type: Theme.standardEasing
|
||||||
color: SessionData.doNotDisturb ? Theme.error : (notificationArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Rectangle {
|
|
||||||
width: 8
|
|
||||||
height: 8
|
|
||||||
radius: 4
|
|
||||||
color: Theme.error
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.rightMargin: 6
|
|
||||||
anchors.topMargin: 6
|
|
||||||
visible: root.hasUnread
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: notificationArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
|
||||||
var globalPos = mapToGlobal(0, 0);
|
|
||||||
var currentScreen = parentScreen || Screen;
|
|
||||||
var screenX = currentScreen.x || 0;
|
|
||||||
var relativeX = globalPos.x - screenX;
|
|
||||||
popupTarget.setTriggerPosition(relativeX, Theme.barHeight + Theme.spacingXS, width, section, currentScreen);
|
|
||||||
}
|
|
||||||
root.clicked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,169 +5,166 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string section: "right"
|
property string section: "right"
|
||||||
property var popupTarget: null
|
property var popupTarget: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
readonly property bool hasActivePrivacy: PrivacyService.anyPrivacyActive
|
readonly property bool hasActivePrivacy: PrivacyService.anyPrivacyActive
|
||||||
readonly property int activeCount: (PrivacyService.microphoneActive ? 1 : 0) + (PrivacyService.cameraActive ? 1 : 0) + (PrivacyService.screensharingActive ? 1 : 0)
|
readonly property int activeCount: PrivacyService.microphoneActive + PrivacyService.cameraActive
|
||||||
|
+ PrivacyService.screensharingActive
|
||||||
|
|
||||||
width: hasActivePrivacy ? (activeCount > 1 ? 80 : 60) : 0
|
width: hasActivePrivacy ? (activeCount > 1 ? 80 : 60) : 0
|
||||||
height: hasActivePrivacy ? 30 : 0
|
height: hasActivePrivacy ? 30 : 0
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
|
visible: hasActivePrivacy
|
||||||
|
opacity: hasActivePrivacy ? 1 : 0
|
||||||
|
|
||||||
|
color: Qt.rgba(
|
||||||
|
privacyArea.containsMouse ? Theme.errorPressed.r : Theme.errorHover.r,
|
||||||
|
privacyArea.containsMouse ? Theme.errorPressed.g : Theme.errorHover.g,
|
||||||
|
privacyArea.containsMouse ? Theme.errorPressed.b : Theme.errorHover.b,
|
||||||
|
(privacyArea.containsMouse ? Theme.errorPressed.a : Theme.errorHover.a)
|
||||||
|
* Theme.widgetTransparency)
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: privacyArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
visible: hasActivePrivacy
|
visible: hasActivePrivacy
|
||||||
opacity: hasActivePrivacy ? 1 : 0
|
|
||||||
|
|
||||||
color: {
|
Item {
|
||||||
const baseColor = privacyArea.containsMouse ? Theme.errorPressed : Theme.errorHover;
|
width: 18
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
height: 18
|
||||||
}
|
visible: PrivacyService.microphoneActive
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
MouseArea {
|
DankIcon {
|
||||||
id: privacyArea
|
name: "mic"
|
||||||
|
size: Theme.iconSizeSmall
|
||||||
anchors.fill: parent
|
color: Theme.error
|
||||||
hoverEnabled: true
|
filled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Theme.spacingXS
|
}
|
||||||
visible: hasActivePrivacy
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 18
|
|
||||||
height: 18
|
|
||||||
visible: PrivacyService.microphoneActive
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "mic"
|
|
||||||
size: Theme.iconSizeSmall
|
|
||||||
color: Theme.error
|
|
||||||
filled: true
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 18
|
|
||||||
height: 18
|
|
||||||
visible: PrivacyService.cameraActive
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "camera_video"
|
|
||||||
size: Theme.iconSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
filled: true
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 6
|
|
||||||
height: 6
|
|
||||||
radius: 3
|
|
||||||
color: Theme.error
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.rightMargin: -2
|
|
||||||
anchors.topMargin: -1
|
|
||||||
|
|
||||||
SequentialAnimation on opacity {
|
|
||||||
running: parent.visible
|
|
||||||
loops: Animation.Infinite
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
to: 0.3
|
|
||||||
duration: Theme.longDuration
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
to: 1.0
|
|
||||||
duration: Theme.longDuration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 18
|
|
||||||
height: 18
|
|
||||||
visible: PrivacyService.screensharingActive
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "screen_share"
|
|
||||||
size: Theme.iconSizeSmall
|
|
||||||
color: Theme.warning
|
|
||||||
filled: true
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on width {
|
Item {
|
||||||
NumberAnimation {
|
width: 18
|
||||||
duration: Theme.mediumDuration
|
height: 18
|
||||||
easing.type: Theme.emphasizedEasing
|
visible: PrivacyService.cameraActive
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "camera_video"
|
||||||
|
size: Theme.iconSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
filled: true
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 6
|
||||||
|
height: 6
|
||||||
|
radius: 3
|
||||||
|
color: Theme.error
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.rightMargin: -2
|
||||||
|
anchors.topMargin: -1
|
||||||
|
|
||||||
|
SequentialAnimation on opacity {
|
||||||
|
running: parent.visible && hasActivePrivacy
|
||||||
|
loops: Animation.Infinite
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
to: 0.3
|
||||||
|
duration: Theme.longDuration
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
to: 1.0
|
||||||
|
duration: Theme.longDuration
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 18
|
||||||
|
height: 18
|
||||||
|
visible: PrivacyService.screensharingActive
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "screen_share"
|
||||||
|
size: Theme.iconSizeSmall
|
||||||
|
color: Theme.warning
|
||||||
|
filled: true
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
enabled: hasActivePrivacy
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: tooltip
|
||||||
|
|
||||||
|
width: tooltipText.contentWidth + Theme.spacingM * 2
|
||||||
|
height: tooltipText.contentHeight + Theme.spacingS * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.popupBackground()
|
||||||
|
border.color: Theme.outlineMedium
|
||||||
|
border.width: 1
|
||||||
|
visible: false
|
||||||
|
opacity: privacyArea.containsMouse && hasActivePrivacy ? 1 : 0
|
||||||
|
z: 100
|
||||||
|
|
||||||
|
x: (parent.width - width) / 2
|
||||||
|
y: -height - Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: tooltipText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: PrivacyService.getPrivacySummary()
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
enabled: !hasActivePrivacy
|
enabled: hasActivePrivacy
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.mediumDuration
|
duration: Theme.shortDuration
|
||||||
easing.type: Theme.standardEasing
|
easing.type: Theme.standardEasing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: tooltip
|
width: 8
|
||||||
|
height: 8
|
||||||
width: tooltipText.contentWidth + Theme.spacingM * 2
|
color: parent.color
|
||||||
height: tooltipText.contentHeight + Theme.spacingS * 2
|
border.color: parent.border.color
|
||||||
radius: Theme.cornerRadius
|
border.width: parent.border.width
|
||||||
color: Theme.popupBackground()
|
rotation: 45
|
||||||
border.color: Theme.outlineMedium
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
border.width: 1
|
anchors.top: parent.bottom
|
||||||
visible: privacyArea.containsMouse && hasActivePrivacy
|
anchors.topMargin: -4
|
||||||
opacity: visible ? 1 : 0
|
|
||||||
z: 100
|
|
||||||
|
|
||||||
x: (parent.width - width) / 2
|
|
||||||
y: -height - Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: tooltipText
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: PrivacyService.getPrivacySummary()
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 8
|
|
||||||
height: 8
|
|
||||||
color: parent.color
|
|
||||||
border.color: parent.border.color
|
|
||||||
border.width: parent.border.width
|
|
||||||
rotation: 45
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: parent.bottom
|
|
||||||
anchors.topMargin: -4
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,81 +5,84 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool showPercentage: true
|
property bool showPercentage: true
|
||||||
property bool showIcon: true
|
property bool showIcon: true
|
||||||
property var toggleProcessList
|
property var toggleProcessList
|
||||||
property string section: "right"
|
property string section: "right"
|
||||||
property var popupTarget: null
|
property var popupTarget: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
width: 55
|
width: 55
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: {
|
||||||
const baseColor = ramArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover;
|
const baseColor = ramArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
|
baseColor.a * Theme.widgetTransparency)
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
SysMonitorService.addRef()
|
||||||
|
}
|
||||||
|
Component.onDestruction: {
|
||||||
|
SysMonitorService.removeRef()
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: ramArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||||
|
var globalPos = mapToGlobal(0, 0)
|
||||||
|
var currentScreen = parentScreen || Screen
|
||||||
|
var screenX = currentScreen.x || 0
|
||||||
|
var relativeX = globalPos.x - screenX
|
||||||
|
popupTarget.setTriggerPosition(relativeX,
|
||||||
|
Theme.barHeight + Theme.spacingXS,
|
||||||
|
width, section, currentScreen)
|
||||||
|
}
|
||||||
|
SysMonitorService.setSortBy("memory")
|
||||||
|
if (root.toggleProcessList)
|
||||||
|
root.toggleProcessList()
|
||||||
}
|
}
|
||||||
Component.onCompleted: {
|
}
|
||||||
SysMonitorService.addRef();
|
|
||||||
}
|
Row {
|
||||||
Component.onDestruction: {
|
anchors.centerIn: parent
|
||||||
SysMonitorService.removeRef();
|
spacing: 3
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "developer_board"
|
||||||
|
size: Theme.iconSize - 8
|
||||||
|
color: {
|
||||||
|
if (SysMonitorService.memoryUsage > 90)
|
||||||
|
return Theme.tempDanger
|
||||||
|
|
||||||
|
if (SysMonitorService.memoryUsage > 75)
|
||||||
|
return Theme.tempWarning
|
||||||
|
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
StyledText {
|
||||||
id: ramArea
|
text: {
|
||||||
|
if (SysMonitorService.memoryUsage === undefined
|
||||||
anchors.fill: parent
|
|| SysMonitorService.memoryUsage === null
|
||||||
hoverEnabled: true
|
|| SysMonitorService.memoryUsage === 0) {
|
||||||
cursorShape: Qt.PointingHandCursor
|
return "--%"
|
||||||
onClicked: {
|
|
||||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
|
||||||
var globalPos = mapToGlobal(0, 0);
|
|
||||||
var currentScreen = parentScreen || Screen;
|
|
||||||
var screenX = currentScreen.x || 0;
|
|
||||||
var relativeX = globalPos.x - screenX;
|
|
||||||
popupTarget.setTriggerPosition(relativeX, Theme.barHeight + Theme.spacingXS, width, section, currentScreen);
|
|
||||||
}
|
|
||||||
SysMonitorService.setSortBy("memory");
|
|
||||||
if (root.toggleProcessList)
|
|
||||||
root.toggleProcessList();
|
|
||||||
}
|
}
|
||||||
|
return SysMonitorService.memoryUsage.toFixed(0) + "%"
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 3
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "developer_board"
|
|
||||||
size: Theme.iconSize - 8
|
|
||||||
color: {
|
|
||||||
if (SysMonitorService.memoryUsage > 90)
|
|
||||||
return Theme.tempDanger;
|
|
||||||
|
|
||||||
if (SysMonitorService.memoryUsage > 75)
|
|
||||||
return Theme.tempWarning;
|
|
||||||
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (SysMonitorService.memoryUsage === undefined || SysMonitorService.memoryUsage === null || SysMonitorService.memoryUsage === 0) {
|
|
||||||
return "--%";
|
|
||||||
}
|
|
||||||
return SysMonitorService.memoryUsage.toFixed(0) + "%";
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,114 +4,117 @@ import Quickshell.Services.SystemTray
|
|||||||
import qs.Common
|
import qs.Common
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var parentWindow: null
|
property var parentWindow: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
readonly property int calculatedWidth: SystemTray.items.values.length > 0 ? SystemTray.items.values.length * 24 + (SystemTray.items.values.length - 1) * Theme.spacingXS + Theme.spacingS * 2 : 0
|
readonly property int calculatedWidth: SystemTray.items.values.length
|
||||||
|
> 0 ? SystemTray.items.values.length * 24
|
||||||
|
+ (SystemTray.items.values.length - 1)
|
||||||
|
* Theme.spacingXS + Theme.spacingS * 2 : 0
|
||||||
|
|
||||||
|
width: calculatedWidth
|
||||||
|
height: 30
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
if (SystemTray.items.values.length === 0)
|
||||||
|
return "transparent"
|
||||||
|
|
||||||
width: calculatedWidth
|
const baseColor = Theme.secondaryHover
|
||||||
height: 30
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
radius: Theme.cornerRadius
|
baseColor.a * Theme.widgetTransparency)
|
||||||
color: {
|
}
|
||||||
if (SystemTray.items.values.length === 0)
|
visible: SystemTray.items.values.length > 0
|
||||||
return "transparent";
|
|
||||||
|
|
||||||
const baseColor = Theme.secondaryHover;
|
Row {
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
id: systemTrayRow
|
||||||
}
|
|
||||||
visible: SystemTray.items.values.length > 0
|
|
||||||
|
|
||||||
Row {
|
anchors.centerIn: parent
|
||||||
id: systemTrayRow
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
anchors.centerIn: parent
|
Repeater {
|
||||||
spacing: Theme.spacingXS
|
model: SystemTray.items.values
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: SystemTray.items.values
|
|
||||||
|
|
||||||
delegate: Item {
|
|
||||||
property var trayItem: modelData
|
|
||||||
property string iconSource: {
|
|
||||||
let icon = trayItem && trayItem.icon;
|
|
||||||
if (typeof icon === 'string' || icon instanceof String) {
|
|
||||||
if (icon.includes("?path=")) {
|
|
||||||
const [name, path] = icon.split("?path=");
|
|
||||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
|
||||||
return `file://${path}/${fileName}`;
|
|
||||||
}
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
width: 24
|
|
||||||
height: 24
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Theme.cornerRadiusSmall
|
|
||||||
color: trayItemArea.containsMouse ? Theme.primaryHover : "transparent"
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
enabled: trayItemArea.containsMouse !== undefined
|
|
||||||
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 18
|
|
||||||
height: 18
|
|
||||||
source: parent.iconSource
|
|
||||||
asynchronous: true
|
|
||||||
smooth: true
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: trayItemArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: (mouse) => {
|
|
||||||
if (!trayItem)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (trayItem.hasMenu) {
|
|
||||||
var globalPos = mapToGlobal(0, 0)
|
|
||||||
var currentScreen = parentScreen || Screen
|
|
||||||
var screenX = currentScreen.x || 0
|
|
||||||
var relativeX = globalPos.x - screenX
|
|
||||||
menuAnchor.menu = trayItem.menu
|
|
||||||
menuAnchor.anchor.window = parentWindow
|
|
||||||
menuAnchor.anchor.rect = Qt.rect(relativeX, Theme.barHeight + Theme.spacingS, parent.width, 1)
|
|
||||||
menuAnchor.open()
|
|
||||||
} else if (mouse.button === Qt.LeftButton) {
|
|
||||||
trayItem.activate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
property var trayItem: modelData
|
||||||
|
property string iconSource: {
|
||||||
|
let icon = trayItem && trayItem.icon
|
||||||
|
if (typeof icon === 'string' || icon instanceof String) {
|
||||||
|
if (icon.includes("?path=")) {
|
||||||
|
const split = icon.split("?path=")
|
||||||
|
if (split.length !== 2)
|
||||||
|
return icon
|
||||||
|
const name = split[0]
|
||||||
|
const path = split[1]
|
||||||
|
const fileName = name.substring(name.lastIndexOf("/") + 1)
|
||||||
|
return `file://${path}/${fileName}`
|
||||||
}
|
}
|
||||||
|
return icon
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
width: 24
|
||||||
|
height: 24
|
||||||
|
|
||||||
QsMenuAnchor {
|
Rectangle {
|
||||||
id: menuAnchor
|
anchors.fill: parent
|
||||||
}
|
radius: Theme.cornerRadiusSmall
|
||||||
|
color: trayItemArea.containsMouse ? Theme.primaryHover : "transparent"
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
enabled: trayItemArea.containsMouse !== undefined
|
||||||
|
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 18
|
||||||
|
height: 18
|
||||||
|
source: parent.iconSource
|
||||||
|
asynchronous: true
|
||||||
|
smooth: true
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: trayItemArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: mouse => {
|
||||||
|
if (!trayItem)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (trayItem.hasMenu) {
|
||||||
|
var globalPos = mapToGlobal(0, 0)
|
||||||
|
var currentScreen = parentScreen || Screen
|
||||||
|
var screenX = currentScreen.x || 0
|
||||||
|
var relativeX = globalPos.x - screenX
|
||||||
|
menuAnchor.menu = trayItem.menu
|
||||||
|
menuAnchor.anchor.window = parentWindow
|
||||||
|
menuAnchor.anchor.rect = Qt.rect(
|
||||||
|
relativeX, Theme.barHeight + Theme.spacingS,
|
||||||
|
parent.width, 1)
|
||||||
|
menuAnchor.open()
|
||||||
|
} else if (mouse.button === Qt.LeftButton) {
|
||||||
|
trayItem.activate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QsMenuAnchor {
|
||||||
|
id: menuAnchor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,87 +4,87 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string section: "center"
|
property string section: "center"
|
||||||
property var popupTarget: null
|
property var popupTarget: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
signal clicked()
|
signal clicked
|
||||||
|
|
||||||
visible: SettingsData.weatherEnabled
|
visible: SettingsData.weatherEnabled
|
||||||
width: visible ? Math.min(100, weatherRow.implicitWidth + Theme.spacingS * 2) : 0
|
width: visible ? Math.min(100,
|
||||||
height: 30
|
weatherRow.implicitWidth + Theme.spacingS * 2) : 0
|
||||||
radius: Theme.cornerRadius
|
height: 30
|
||||||
color: {
|
radius: Theme.cornerRadius
|
||||||
const baseColor = weatherArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover;
|
color: {
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
const baseColor = weatherArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover
|
||||||
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
|
baseColor.a * Theme.widgetTransparency)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref {
|
||||||
|
service: WeatherService
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: weatherRow
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref {
|
StyledText {
|
||||||
service: WeatherService
|
text: {
|
||||||
}
|
var temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp
|
||||||
|
if (temp === undefined || temp === null || temp === 0) {
|
||||||
Row {
|
return "--°" + (SettingsData.useFahrenheit ? "F" : "C")
|
||||||
id: weatherRow
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
}
|
||||||
|
return temp + "°" + (SettingsData.useFahrenheit ? "F" : "C")
|
||||||
StyledText {
|
}
|
||||||
text: {
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
var temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp;
|
color: Theme.surfaceText
|
||||||
if (temp === undefined || temp === null || temp === 0) {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
return "--°" + (SettingsData.useFahrenheit ? "F" : "C");
|
|
||||||
}
|
|
||||||
return temp + "°" + (SettingsData.useFahrenheit ? "F" : "C");
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: weatherArea
|
id: weatherArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||||
var globalPos = mapToGlobal(0, 0);
|
var globalPos = mapToGlobal(0, 0)
|
||||||
var currentScreen = parentScreen || Screen;
|
var currentScreen = parentScreen || Screen
|
||||||
var screenX = currentScreen.x || 0;
|
var screenX = currentScreen.x || 0
|
||||||
var relativeX = globalPos.x - screenX;
|
var relativeX = globalPos.x - screenX
|
||||||
popupTarget.setTriggerPosition(relativeX, Theme.barHeight + Theme.spacingXS, width, section, currentScreen);
|
popupTarget.setTriggerPosition(relativeX,
|
||||||
}
|
Theme.barHeight + Theme.spacingXS,
|
||||||
root.clicked();
|
width, section, currentScreen)
|
||||||
}
|
}
|
||||||
|
root.clicked()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
duration: Theme.shortDuration
|
duration: Theme.shortDuration
|
||||||
easing.type: Theme.standardEasing
|
easing.type: Theme.standardEasing
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on width {
|
Behavior on width {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.shortDuration
|
duration: Theme.shortDuration
|
||||||
easing.type: Theme.standardEasing
|
easing.type: Theme.standardEasing
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,155 +6,164 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string screenName: ""
|
property string screenName: ""
|
||||||
property int currentWorkspace: getDisplayActiveWorkspace()
|
property int currentWorkspace: getDisplayActiveWorkspace()
|
||||||
property var workspaceList: {
|
property var workspaceList: {
|
||||||
var baseList = getDisplayWorkspaces();
|
var baseList = getDisplayWorkspaces()
|
||||||
return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList;
|
return SettingsData.showWorkspacePadding ? padWorkspaces(
|
||||||
|
baseList) : baseList
|
||||||
|
}
|
||||||
|
|
||||||
|
function padWorkspaces(list) {
|
||||||
|
var padded = list.slice()
|
||||||
|
while (padded.length < 3)
|
||||||
|
padded.push(-1) // Use -1 as a placeholder
|
||||||
|
return padded
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayWorkspaces() {
|
||||||
|
if (!NiriService.niriAvailable || NiriService.allWorkspaces.length === 0)
|
||||||
|
return [1, 2]
|
||||||
|
|
||||||
|
if (!root.screenName)
|
||||||
|
return NiriService.getCurrentOutputWorkspaceNumbers()
|
||||||
|
|
||||||
|
var displayWorkspaces = []
|
||||||
|
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
||||||
|
var ws = NiriService.allWorkspaces[i]
|
||||||
|
if (ws.output === root.screenName)
|
||||||
|
displayWorkspaces.push(ws.idx + 1)
|
||||||
|
}
|
||||||
|
return displayWorkspaces.length > 0 ? displayWorkspaces : [1, 2]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayActiveWorkspace() {
|
||||||
|
if (!NiriService.niriAvailable || NiriService.allWorkspaces.length === 0)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if (!root.screenName)
|
||||||
|
return NiriService.getCurrentWorkspaceNumber()
|
||||||
|
|
||||||
|
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
||||||
|
var ws = NiriService.allWorkspaces[i]
|
||||||
|
if (ws.output === root.screenName && ws.is_active)
|
||||||
|
return ws.idx + 1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
width: SettingsData.showWorkspacePadding ? Math.max(
|
||||||
|
120,
|
||||||
|
workspaceRow.implicitWidth + Theme.spacingL
|
||||||
|
* 2) : workspaceRow.implicitWidth
|
||||||
|
+ Theme.spacingL * 2
|
||||||
|
height: 30
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
color: {
|
||||||
|
const baseColor = Theme.surfaceTextHover
|
||||||
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
|
baseColor.a * Theme.widgetTransparency)
|
||||||
|
}
|
||||||
|
visible: NiriService.niriAvailable
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onAllWorkspacesChanged() {
|
||||||
|
root.workspaceList
|
||||||
|
= SettingsData.showWorkspacePadding ? root.padWorkspaces(
|
||||||
|
root.getDisplayWorkspaces(
|
||||||
|
)) : root.getDisplayWorkspaces()
|
||||||
|
root.currentWorkspace = root.getDisplayActiveWorkspace()
|
||||||
}
|
}
|
||||||
|
|
||||||
function padWorkspaces(list) {
|
function onFocusedWorkspaceIndexChanged() {
|
||||||
var padded = list.slice();
|
root.currentWorkspace = root.getDisplayActiveWorkspace()
|
||||||
while (padded.length < 3)padded.push(-1) // Use -1 as a placeholder
|
|
||||||
return padded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDisplayWorkspaces() {
|
function onNiriAvailableChanged() {
|
||||||
if (!NiriService.niriAvailable || NiriService.allWorkspaces.length === 0)
|
if (NiriService.niriAvailable) {
|
||||||
return [1, 2];
|
root.workspaceList
|
||||||
|
= SettingsData.showWorkspacePadding ? root.padWorkspaces(
|
||||||
if (!root.screenName)
|
root.getDisplayWorkspaces(
|
||||||
return NiriService.getCurrentOutputWorkspaceNumbers();
|
)) : root.getDisplayWorkspaces()
|
||||||
|
root.currentWorkspace = root.getDisplayActiveWorkspace()
|
||||||
var displayWorkspaces = [];
|
}
|
||||||
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
|
||||||
var ws = NiriService.allWorkspaces[i];
|
|
||||||
if (ws.output === root.screenName)
|
|
||||||
displayWorkspaces.push(ws.idx + 1);
|
|
||||||
|
|
||||||
}
|
|
||||||
return displayWorkspaces.length > 0 ? displayWorkspaces : [1, 2];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDisplayActiveWorkspace() {
|
target: NiriService
|
||||||
if (!NiriService.niriAvailable || NiriService.allWorkspaces.length === 0)
|
}
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (!root.screenName)
|
Connections {
|
||||||
return NiriService.getCurrentWorkspaceNumber();
|
function onShowWorkspacePaddingChanged() {
|
||||||
|
var baseList = root.getDisplayWorkspaces()
|
||||||
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
root.workspaceList = SettingsData.showWorkspacePadding ? root.padWorkspaces(
|
||||||
var ws = NiriService.allWorkspaces[i];
|
baseList) : baseList
|
||||||
if (ws.output === root.screenName && ws.is_active)
|
|
||||||
return ws.idx + 1;
|
|
||||||
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
width: SettingsData.showWorkspacePadding ? Math.max(120, workspaceRow.implicitWidth + Theme.spacingL * 2) : workspaceRow.implicitWidth + Theme.spacingL * 2
|
target: SettingsData
|
||||||
height: 30
|
}
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
color: {
|
|
||||||
const baseColor = Theme.surfaceTextHover;
|
|
||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
|
||||||
}
|
|
||||||
visible: NiriService.niriAvailable
|
|
||||||
|
|
||||||
Connections {
|
Row {
|
||||||
function onAllWorkspacesChanged() {
|
id: workspaceRow
|
||||||
root.workspaceList = SettingsData.showWorkspacePadding ? root.padWorkspaces(root.getDisplayWorkspaces()) : root.getDisplayWorkspaces();
|
|
||||||
root.currentWorkspace = root.getDisplayActiveWorkspace();
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.workspaceList
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
property bool isActive: modelData === root.currentWorkspace
|
||||||
|
property bool isPlaceholder: modelData === -1
|
||||||
|
property bool isHovered: mouseArea.containsMouse
|
||||||
|
property int sequentialNumber: index + 1
|
||||||
|
|
||||||
|
width: isActive ? Theme.spacingXL + Theme.spacingM : Theme.spacingL + Theme.spacingXS
|
||||||
|
height: Theme.spacingL
|
||||||
|
radius: height / 2
|
||||||
|
color: isActive ? Theme.primary : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: !isPlaceholder
|
||||||
|
cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor
|
||||||
|
enabled: !isPlaceholder
|
||||||
|
onClicked: {
|
||||||
|
if (!isPlaceholder)
|
||||||
|
NiriService.switchToWorkspace(modelData - 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFocusedWorkspaceIndexChanged() {
|
StyledText {
|
||||||
root.currentWorkspace = root.getDisplayActiveWorkspace();
|
visible: SettingsData.showWorkspaceIndex
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: isPlaceholder ? sequentialNumber : sequentialNumber
|
||||||
|
color: isActive ? Qt.rgba(
|
||||||
|
Theme.surfaceContainer.r,
|
||||||
|
Theme.surfaceContainer.g,
|
||||||
|
Theme.surfaceContainer.b,
|
||||||
|
0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: isActive && !isPlaceholder ? Font.DemiBold : Font.Normal
|
||||||
}
|
}
|
||||||
|
|
||||||
function onNiriAvailableChanged() {
|
Behavior on width {
|
||||||
if (NiriService.niriAvailable) {
|
NumberAnimation {
|
||||||
root.workspaceList = SettingsData.showWorkspacePadding ? root.padWorkspaces(root.getDisplayWorkspaces()) : root.getDisplayWorkspaces();
|
duration: Theme.mediumDuration
|
||||||
root.currentWorkspace = root.getDisplayActiveWorkspace();
|
easing.type: Theme.emphasizedEasing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target: NiriService
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Connections {
|
|
||||||
function onShowWorkspacePaddingChanged() {
|
|
||||||
var baseList = root.getDisplayWorkspaces();
|
|
||||||
root.workspaceList = SettingsData.showWorkspacePadding ? root.padWorkspaces(baseList) : baseList;
|
|
||||||
}
|
|
||||||
|
|
||||||
target: SettingsData
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: workspaceRow
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: root.workspaceList
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
property bool isActive: modelData === root.currentWorkspace
|
|
||||||
property bool isPlaceholder: modelData === -1
|
|
||||||
property bool isHovered: mouseArea.containsMouse
|
|
||||||
property int sequentialNumber: index + 1
|
|
||||||
|
|
||||||
width: isActive ? Theme.spacingXL + Theme.spacingM : Theme.spacingL + Theme.spacingXS
|
|
||||||
height: Theme.spacingL
|
|
||||||
radius: height / 2
|
|
||||||
color: isActive ? Theme.primary : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: !isPlaceholder
|
|
||||||
cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor
|
|
||||||
enabled: !isPlaceholder
|
|
||||||
onClicked: {
|
|
||||||
if (!isPlaceholder)
|
|
||||||
NiriService.switchToWorkspace(modelData - 1);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
visible: SettingsData.showWorkspaceIndex
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: isPlaceholder ? sequentialNumber : sequentialNumber
|
|
||||||
color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: isActive && !isPlaceholder ? Font.DemiBold : Font.Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,212 +8,204 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var modelData
|
property var modelData
|
||||||
property bool volumePopupVisible: false
|
property bool volumePopupVisible: false
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
root.volumePopupVisible = true;
|
root.volumePopupVisible = true
|
||||||
hideTimer.restart();
|
hideTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetHideTimer() {
|
||||||
|
if (root.volumePopupVisible)
|
||||||
|
hideTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
screen: modelData
|
||||||
|
visible: volumePopupVisible
|
||||||
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: hideTimer
|
||||||
|
|
||||||
|
interval: 3000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (!volumePopup.containsMouse)
|
||||||
|
root.volumePopupVisible = false
|
||||||
|
else
|
||||||
|
hideTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onVolumeChanged() {
|
||||||
|
root.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetHideTimer() {
|
function onSinkChanged() {
|
||||||
if (root.volumePopupVisible)
|
if (root.volumePopupVisible)
|
||||||
hideTimer.restart();
|
root.show()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
screen: modelData
|
target: AudioService
|
||||||
visible: volumePopupVisible
|
}
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
anchors {
|
Rectangle {
|
||||||
top: true
|
id: volumePopup
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
property bool containsMouse: popupMouseArea.containsMouse
|
||||||
id: hideTimer
|
|
||||||
|
|
||||||
interval: 3000
|
width: Math.min(260, Screen.width - Theme.spacingM * 2)
|
||||||
repeat: false
|
height: volumeContent.height + Theme.spacingS * 2
|
||||||
onTriggered: {
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
if (!volumePopup.containsMouse)
|
anchors.bottom: parent.bottom
|
||||||
root.volumePopupVisible = false;
|
anchors.bottomMargin: Theme.spacingM
|
||||||
else
|
color: Theme.popupBackground()
|
||||||
hideTimer.restart();
|
radius: Theme.cornerRadiusLarge
|
||||||
}
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
}
|
Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
opacity: root.volumePopupVisible ? 1 : 0
|
||||||
|
scale: root.volumePopupVisible ? 1 : 0.9
|
||||||
|
layer.enabled: true
|
||||||
|
|
||||||
Connections {
|
Column {
|
||||||
function onVolumeChanged() {
|
id: volumeContent
|
||||||
root.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSinkChanged() {
|
anchors.centerIn: parent
|
||||||
if (root.volumePopupVisible)
|
width: parent.width - Theme.spacingS * 2
|
||||||
root.show();
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
}
|
Item {
|
||||||
|
property int gap: Theme.spacingS
|
||||||
|
|
||||||
target: AudioService
|
width: parent.width
|
||||||
}
|
height: 40
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: volumePopup
|
width: Theme.iconSize
|
||||||
|
height: Theme.iconSize
|
||||||
property bool containsMouse: popupMouseArea.containsMouse
|
radius: Theme.iconSize / 2
|
||||||
|
color: "transparent"
|
||||||
width: Math.min(260, Screen.width - Theme.spacingM * 2)
|
x: parent.gap
|
||||||
height: volumeContent.height + Theme.spacingS * 2
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: Theme.spacingM
|
|
||||||
color: Theme.popupBackground()
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
opacity: root.volumePopupVisible ? 1 : 0
|
|
||||||
scale: root.volumePopupVisible ? 1 : 0.9
|
|
||||||
layer.enabled: true
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: volumeContent
|
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width - Theme.spacingS * 2
|
name: AudioService.sink && AudioService.sink.audio
|
||||||
spacing: Theme.spacingXS
|
&& AudioService.sink.audio.muted ? "volume_off" : "volume_up"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: muteButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
MouseArea {
|
||||||
property int gap: Theme.spacingS
|
id: muteButton
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: 40
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: Theme.iconSize
|
|
||||||
height: Theme.iconSize
|
|
||||||
radius: Theme.iconSize / 2
|
|
||||||
color: "transparent"
|
|
||||||
x: parent.gap
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.muted ? "volume_off" : "volume_up"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: muteButton.containsMouse ? Theme.primary : Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: muteButton
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
AudioService.toggleMute();
|
|
||||||
root.resetHideTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankSlider {
|
|
||||||
id: volumeSlider
|
|
||||||
|
|
||||||
width: parent.width - Theme.iconSize - parent.gap * 3
|
|
||||||
height: 40
|
|
||||||
x: parent.gap * 2 + Theme.iconSize
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
minimum: 0
|
|
||||||
maximum: 100
|
|
||||||
enabled: AudioService.sink && AudioService.sink.audio
|
|
||||||
showValue: true
|
|
||||||
unit: "%"
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (AudioService.sink && AudioService.sink.audio)
|
|
||||||
value = Math.round(AudioService.sink.audio.volume * 100);
|
|
||||||
|
|
||||||
}
|
|
||||||
onSliderValueChanged: function(newValue) {
|
|
||||||
if (AudioService.sink && AudioService.sink.audio) {
|
|
||||||
AudioService.sink.audio.volume = newValue / 100;
|
|
||||||
root.resetHideTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onVolumeChanged() {
|
|
||||||
volumeSlider.value = Math.round(AudioService.sink.audio.volume * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: popupMouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
acceptedButtons: Qt.NoButton
|
cursorShape: Qt.PointingHandCursor
|
||||||
propagateComposedEvents: true
|
onClicked: {
|
||||||
z: -1
|
AudioService.toggleMute()
|
||||||
|
root.resetHideTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
DankSlider {
|
||||||
shadowEnabled: true
|
id: volumeSlider
|
||||||
shadowHorizontalOffset: 0
|
|
||||||
shadowVerticalOffset: 4
|
|
||||||
shadowBlur: 0.8
|
|
||||||
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
|
||||||
shadowOpacity: 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
transform: Translate {
|
width: parent.width - Theme.iconSize - parent.gap * 3
|
||||||
y: root.volumePopupVisible ? 0 : 20
|
height: 40
|
||||||
}
|
x: parent.gap * 2 + Theme.iconSize
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
enabled: AudioService.sink && AudioService.sink.audio
|
||||||
|
showValue: true
|
||||||
|
unit: "%"
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (AudioService.sink && AudioService.sink.audio)
|
||||||
|
value = Math.round(AudioService.sink.audio.volume * 100)
|
||||||
|
}
|
||||||
|
onSliderValueChanged: function (newValue) {
|
||||||
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
AudioService.sink.audio.volume = newValue / 100
|
||||||
|
root.resetHideTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Connections {
|
||||||
NumberAnimation {
|
function onVolumeChanged() {
|
||||||
duration: Theme.mediumDuration
|
volumeSlider.value = Math.round(
|
||||||
easing.type: Theme.emphasizedEasing
|
AudioService.sink.audio.volume * 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target: AudioService.sink
|
||||||
|
&& AudioService.sink.audio ? AudioService.sink.audio : null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on transform {
|
|
||||||
PropertyAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mask: Region {
|
MouseArea {
|
||||||
item: volumePopup
|
id: popupMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
propagateComposedEvents: true
|
||||||
|
z: -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
shadowEnabled: true
|
||||||
|
shadowHorizontalOffset: 0
|
||||||
|
shadowVerticalOffset: 4
|
||||||
|
shadowBlur: 0.8
|
||||||
|
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
||||||
|
shadowOpacity: 0.3
|
||||||
|
}
|
||||||
|
|
||||||
|
transform: Translate {
|
||||||
|
y: root.volumePopupVisible ? 0 : 20
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on transform {
|
||||||
|
PropertyAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: volumePopup
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,132 +6,132 @@ import qs.Common
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
LazyLoader {
|
LazyLoader {
|
||||||
active: SessionData.wallpaperPath !== ""
|
active: SessionData.wallpaperPath !== ""
|
||||||
|
|
||||||
Variants {
|
Variants {
|
||||||
model: Quickshell.screens
|
model: Quickshell.screens
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: wallpaperWindow
|
id: wallpaperWindow
|
||||||
|
|
||||||
required property var modelData
|
required property var modelData
|
||||||
|
|
||||||
screen: modelData
|
screen: modelData
|
||||||
|
|
||||||
WlrLayershell.layer: WlrLayer.Background
|
WlrLayershell.layer: WlrLayer.Background
|
||||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
|
|
||||||
anchors.top: true
|
anchors.top: true
|
||||||
anchors.bottom: true
|
anchors.bottom: true
|
||||||
anchors.left: true
|
anchors.left: true
|
||||||
anchors.right: true
|
anchors.right: true
|
||||||
|
|
||||||
color: "black"
|
color: "black"
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
property string source: SessionData.wallpaperPath || ""
|
property string source: SessionData.wallpaperPath || ""
|
||||||
property Image current: one
|
property Image current: one
|
||||||
|
|
||||||
onSourceChanged: {
|
onSourceChanged: {
|
||||||
if (!source)
|
if (!source)
|
||||||
current = null;
|
current = null
|
||||||
else if (current === one)
|
else if (current === one)
|
||||||
two.update();
|
two.update()
|
||||||
else
|
else
|
||||||
one.update();
|
one.update()
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
anchors.fill: parent
|
|
||||||
active: !root.source
|
|
||||||
asynchronous: true
|
|
||||||
|
|
||||||
sourceComponent: Rectangle {
|
|
||||||
color: Theme.surface
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingL
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "sentiment_stressed"
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
size: Theme.iconSize * 5
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Wallpaper missing?"
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
font.pixelSize: Theme.fontSizeXLarge * 2
|
|
||||||
font.weight: Font.Bold
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Set wallpaper in Settings"
|
|
||||||
color: Theme.primary
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Img {
|
|
||||||
id: one
|
|
||||||
}
|
|
||||||
|
|
||||||
Img {
|
|
||||||
id: two
|
|
||||||
}
|
|
||||||
|
|
||||||
component Img: Image {
|
|
||||||
id: img
|
|
||||||
|
|
||||||
function update(): void {
|
|
||||||
source = "";
|
|
||||||
source = root.source;
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
fillMode: Image.PreserveAspectCrop
|
|
||||||
smooth: true
|
|
||||||
asynchronous: true
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
opacity: 0
|
|
||||||
|
|
||||||
onStatusChanged: {
|
|
||||||
if (status === Image.Ready)
|
|
||||||
root.current = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
states: State {
|
|
||||||
name: "visible"
|
|
||||||
when: root.current === img
|
|
||||||
|
|
||||||
PropertyChanges {
|
|
||||||
img.opacity: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transitions: Transition {
|
|
||||||
NumberAnimation {
|
|
||||||
target: img
|
|
||||||
properties: "opacity"
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: !root.source
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
sourceComponent: Rectangle {
|
||||||
|
color: Theme.surface
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "sentiment_stressed"
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
size: Theme.iconSize * 5
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Wallpaper missing?"
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge * 2
|
||||||
|
font.weight: Font.Bold
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Set wallpaper in Settings"
|
||||||
|
color: Theme.primary
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Img {
|
||||||
|
id: one
|
||||||
|
}
|
||||||
|
|
||||||
|
Img {
|
||||||
|
id: two
|
||||||
|
}
|
||||||
|
|
||||||
|
component Img: Image {
|
||||||
|
id: img
|
||||||
|
|
||||||
|
function update(): void {
|
||||||
|
source = ""
|
||||||
|
source = root.source
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
smooth: true
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === Image.Ready)
|
||||||
|
root.current = this
|
||||||
|
}
|
||||||
|
|
||||||
|
states: State {
|
||||||
|
name: "visible"
|
||||||
|
when: root.current === img
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
img.opacity: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transitions: Transition {
|
||||||
|
NumberAnimation {
|
||||||
|
target: img
|
||||||
|
properties: "opacity"
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user