mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
qmlfmt with 4 space
This commit is contained in:
@@ -6,21 +6,21 @@ import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
readonly property int durShort: 200
|
||||
readonly property int durMed: 450
|
||||
readonly property int durLong: 600
|
||||
readonly property int durShort: 200
|
||||
readonly property int durMed: 450
|
||||
readonly property int durLong: 600
|
||||
|
||||
readonly property int slidePx: 80
|
||||
readonly property int slidePx: 80
|
||||
|
||||
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]
|
||||
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]
|
||||
|
||||
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 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 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 standardAccel: [0.30, 0.00, 1.00, 1.00, 1.00, 1.00]
|
||||
}
|
||||
|
||||
@@ -9,123 +9,124 @@ import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property var appUsageRanking: {
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
property var appUsageRanking: {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
loadSettings()
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
function loadSettings() {
|
||||
parseSettings(settingsFile.text())
|
||||
}
|
||||
|
||||
return apps.sort(function (a, b) {
|
||||
if (a.usageCount !== b.usageCount)
|
||||
return b.usageCount - a.usageCount
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
}
|
||||
function parseSettings(content) {
|
||||
try {
|
||||
if (content && content.trim()) {
|
||||
var settings = JSON.parse(content)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
appUsageRanking = currentRanking
|
||||
saveSettings()
|
||||
function saveSettings() {
|
||||
settingsFile.setText(JSON.stringify({
|
||||
"appUsageRanking": appUsageRanking
|
||||
}, null, 2))
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: settingsFile
|
||||
function addAppUsage(app) {
|
||||
if (!app)
|
||||
return
|
||||
|
||||
path: StandardPaths.writableLocation(
|
||||
StandardPaths.GenericStateLocation) + "/DankMaterialShell/appusage.json"
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
watchChanges: true
|
||||
onLoaded: {
|
||||
parseSettings(settingsFile.text())
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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 => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,62 +6,62 @@ import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
readonly property Rounding rounding: Rounding {}
|
||||
readonly property Spacing spacing: Spacing {}
|
||||
readonly property FontSize fontSize: FontSize {}
|
||||
readonly property Anim anim: Anim {}
|
||||
readonly property Rounding rounding: Rounding {}
|
||||
readonly property Spacing spacing: Spacing {}
|
||||
readonly property FontSize fontSize: FontSize {}
|
||||
readonly property Anim anim: Anim {}
|
||||
|
||||
component Rounding: QtObject {
|
||||
readonly property int small: 8
|
||||
readonly property int normal: 12
|
||||
readonly property int large: 16
|
||||
readonly property int extraLarge: 24
|
||||
readonly property int full: 1000
|
||||
}
|
||||
component Rounding: QtObject {
|
||||
readonly property int small: 8
|
||||
readonly property int normal: 12
|
||||
readonly property int large: 16
|
||||
readonly property int extraLarge: 24
|
||||
readonly property int full: 1000
|
||||
}
|
||||
|
||||
component Spacing: QtObject {
|
||||
readonly property int small: 4
|
||||
readonly property int normal: 8
|
||||
readonly property int large: 12
|
||||
readonly property int extraLarge: 16
|
||||
readonly property int huge: 24
|
||||
}
|
||||
component Spacing: QtObject {
|
||||
readonly property int small: 4
|
||||
readonly property int normal: 8
|
||||
readonly property int large: 12
|
||||
readonly property int extraLarge: 16
|
||||
readonly property int huge: 24
|
||||
}
|
||||
|
||||
component FontSize: QtObject {
|
||||
readonly property int small: 12
|
||||
readonly property int normal: 14
|
||||
readonly property int large: 16
|
||||
readonly property int extraLarge: 20
|
||||
readonly property int huge: 24
|
||||
}
|
||||
component FontSize: QtObject {
|
||||
readonly property int small: 12
|
||||
readonly property int normal: 14
|
||||
readonly property int large: 16
|
||||
readonly property int extraLarge: 20
|
||||
readonly property int huge: 24
|
||||
}
|
||||
|
||||
component AnimCurves: QtObject {
|
||||
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> 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> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
|
||||
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
|
||||
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 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 AnimCurves: QtObject {
|
||||
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> 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> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
|
||||
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
|
||||
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 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 {
|
||||
readonly property int quick: 150
|
||||
readonly property int normal: 300
|
||||
readonly property int slow: 500
|
||||
readonly property int extraSlow: 1000
|
||||
readonly property int expressiveFastSpatial: 350
|
||||
readonly property int expressiveDefaultSpatial: 500
|
||||
readonly property int expressiveEffects: 200
|
||||
}
|
||||
component AnimDurations: QtObject {
|
||||
readonly property int quick: 150
|
||||
readonly property int normal: 300
|
||||
readonly property int slow: 500
|
||||
readonly property int extraSlow: 1000
|
||||
readonly property int expressiveFastSpatial: 350
|
||||
readonly property int expressiveDefaultSpatial: 500
|
||||
readonly property int expressiveEffects: 200
|
||||
}
|
||||
|
||||
component Anim: QtObject {
|
||||
readonly property AnimCurves curves: AnimCurves {}
|
||||
readonly property AnimDurations durations: AnimDurations {}
|
||||
}
|
||||
component Anim: QtObject {
|
||||
readonly property AnimCurves curves: AnimCurves {}
|
||||
readonly property AnimDurations durations: AnimDurations {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,43 +2,44 @@ import Quickshell
|
||||
pragma Singleton
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
// Clear all image cache
|
||||
function clearImageCache() {
|
||||
Quickshell.execDetached(["rm", "-rf", Paths.stringify(Paths.imagecache)])
|
||||
Paths.mkdir(Paths.imagecache)
|
||||
}
|
||||
// Clear all image cache
|
||||
function clearImageCache() {
|
||||
Quickshell.execDetached(["rm", "-rf", Paths.stringify(
|
||||
Paths.imagecache)])
|
||||
Paths.mkdir(Paths.imagecache)
|
||||
}
|
||||
|
||||
// Clear cache older than specified minutes
|
||||
function clearOldCache(ageInMinutes) {
|
||||
Quickshell.execDetached(
|
||||
["find", Paths.stringify(
|
||||
Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"])
|
||||
}
|
||||
// Clear cache older than specified minutes
|
||||
function clearOldCache(ageInMinutes) {
|
||||
Quickshell.execDetached(
|
||||
["find", Paths.stringify(
|
||||
Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"])
|
||||
}
|
||||
|
||||
// Clear cache for specific size
|
||||
function clearCacheForSize(size) {
|
||||
Quickshell.execDetached(
|
||||
["find", Paths.stringify(
|
||||
Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"])
|
||||
}
|
||||
// Clear cache for specific size
|
||||
function clearCacheForSize(size) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,435 +9,448 @@ import Quickshell.Io
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
readonly property string _homeUrl: StandardPaths.writableLocation(
|
||||
StandardPaths.HomeLocation)
|
||||
readonly property string homeDir: _homeUrl.startsWith(
|
||||
"file://") ? _homeUrl.substring(
|
||||
7) : _homeUrl
|
||||
readonly property string _configUrl: StandardPaths.writableLocation(
|
||||
StandardPaths.ConfigLocation)
|
||||
readonly property string configDir: _configUrl.startsWith(
|
||||
"file://") ? _configUrl.substring(
|
||||
7) : _configUrl
|
||||
readonly property string shellDir: Qt.resolvedUrl(".").toString().replace(
|
||||
"file://", "").replace("/Common/", "")
|
||||
readonly property string wallpaperPath: SessionData.wallpaperPath
|
||||
property bool matugenAvailable: false
|
||||
property bool gtkThemingEnabled: false
|
||||
property bool qtThemingEnabled: false
|
||||
property bool systemThemeGenerationInProgress: false
|
||||
property var matugenColors: ({})
|
||||
property bool extractionRequested: false
|
||||
property int colorUpdateTrigger: 0
|
||||
property string lastWallpaperTimestamp: ""
|
||||
property color primary: getMatugenColor("primary", "#42a5f5")
|
||||
property color secondary: getMatugenColor("secondary", "#8ab4f8")
|
||||
property color tertiary: getMatugenColor("tertiary", "#bb86fc")
|
||||
property color tertiaryContainer: getMatugenColor("tertiary_container",
|
||||
"#3700b3")
|
||||
property color error: getMatugenColor("error", "#cf6679")
|
||||
property color inversePrimary: getMatugenColor("inverse_primary", "#6200ea")
|
||||
property color bg: getMatugenColor("background", "#1a1c1e")
|
||||
property color surface: getMatugenColor("surface", "#1a1c1e")
|
||||
property color surfaceContainer: getMatugenColor("surface_container",
|
||||
"#1e2023")
|
||||
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
|
||||
readonly property string _homeUrl: StandardPaths.writableLocation(
|
||||
StandardPaths.HomeLocation)
|
||||
readonly property string homeDir: _homeUrl.startsWith(
|
||||
"file://") ? _homeUrl.substring(
|
||||
7) : _homeUrl
|
||||
readonly property string _configUrl: StandardPaths.writableLocation(
|
||||
StandardPaths.ConfigLocation)
|
||||
readonly property string configDir: _configUrl.startsWith(
|
||||
"file://") ? _configUrl.substring(
|
||||
7) : _configUrl
|
||||
readonly property string shellDir: Qt.resolvedUrl(".").toString().replace(
|
||||
"file://",
|
||||
"").replace("/Common/", "")
|
||||
readonly property string wallpaperPath: SessionData.wallpaperPath
|
||||
property bool matugenAvailable: false
|
||||
property bool gtkThemingEnabled: false
|
||||
property bool qtThemingEnabled: false
|
||||
property bool systemThemeGenerationInProgress: false
|
||||
property var matugenColors: ({})
|
||||
property bool extractionRequested: false
|
||||
property int colorUpdateTrigger: 0
|
||||
property string lastWallpaperTimestamp: ""
|
||||
property color primary: getMatugenColor("primary", "#42a5f5")
|
||||
property color secondary: getMatugenColor("secondary", "#8ab4f8")
|
||||
property color tertiary: getMatugenColor("tertiary", "#bb86fc")
|
||||
property color tertiaryContainer: getMatugenColor("tertiary_container",
|
||||
"#3700b3")
|
||||
property color error: getMatugenColor("error", "#cf6679")
|
||||
property color inversePrimary: getMatugenColor("inverse_primary", "#6200ea")
|
||||
property color bg: getMatugenColor("background", "#1a1c1e")
|
||||
property color surface: getMatugenColor("surface", "#1a1c1e")
|
||||
property color surfaceContainer: getMatugenColor("surface_container",
|
||||
"#1e2023")
|
||||
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() {
|
||||
if (matugenColors && Object.keys(matugenColors).length > 0) {
|
||||
colorUpdateTrigger++
|
||||
colorsUpdated()
|
||||
function onLightModeChanged() {
|
||||
if (matugenColors && Object.keys(matugenColors).length > 0) {
|
||||
colorUpdateTrigger++
|
||||
colorsUpdated()
|
||||
|
||||
if (typeof Theme !== "undefined" && Theme.isDynamicTheme) {
|
||||
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", "image", wallpaperPath, "--json", "hex"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
id: matugenCollector
|
||||
|
||||
onStreamFinished: {
|
||||
if (!matugenCollector.text) {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Wallpaper Processing Failed: Empty JSON extracted from matugen output.")
|
||||
return
|
||||
if (typeof Theme !== "undefined" && Theme.isDynamicTheme) {
|
||||
generateSystemThemes()
|
||||
}
|
||||
}
|
||||
const extractedJson = extractJsonFromText(matugenCollector.text)
|
||||
if (!extractedJson) {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Wallpaper Processing Failed: Invalid JSON extracted from matugen output.")
|
||||
console.log("Raw matugen output:", matugenCollector.text)
|
||||
return
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
try {
|
||||
root.matugenColors = JSON.parse(extractedJson)
|
||||
root.colorsUpdated()
|
||||
generateAppConfigs()
|
||||
ToastService.clearWallpaperError()
|
||||
} catch (e) {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Wallpaper processing failed (JSON parse error after extraction)")
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: code => {
|
||||
if (code !== 0) {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Matugen command failed with exit code " + code)
|
||||
}
|
||||
}
|
||||
}
|
||||
Process {
|
||||
id: fileChecker
|
||||
|
||||
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 {
|
||||
active-color "${primary}"
|
||||
inactive-color "${secondary}"
|
||||
}
|
||||
focus-ring {
|
||||
active-color "${inverse}"
|
||||
}
|
||||
background-color "${bg}"
|
||||
}`
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if (!matugenAvailable) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!wallpaperPath || wallpaperPath === "") {
|
||||
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"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Returns the first complete JSON substring (object or array) or null.
|
||||
function extractJsonFromText(text) {
|
||||
if (!text) return null;
|
||||
|
||||
const start = text.search(/[{\[]/);
|
||||
if (start === -1) return null;
|
||||
|
||||
const open = text[start];
|
||||
const pairs = { '{': '}', '[': ']' };
|
||||
const close = pairs[open];
|
||||
if (!close) return null;
|
||||
|
||||
let inString = false;
|
||||
let escape = false;
|
||||
const stack = [open];
|
||||
|
||||
for (let i = start + 1; i < text.length; i++) {
|
||||
const ch = text[i];
|
||||
|
||||
if (inString) {
|
||||
if (escape) {
|
||||
escape = false;
|
||||
} else if (ch === '\\') {
|
||||
escape = true;
|
||||
} else if (ch === '"') {
|
||||
inString = false;
|
||||
command: ["test", "-r", wallpaperPath]
|
||||
onExited: code => {
|
||||
if (code === 0) {
|
||||
matugenProcess.running = true
|
||||
} else {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Wallpaper processing failed")
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ch === '"') {
|
||||
inString = true;
|
||||
continue;
|
||||
}
|
||||
if (ch === '{' || ch === '[') {
|
||||
stack.push(ch);
|
||||
continue;
|
||||
}
|
||||
if (ch === '}' || ch === ']') {
|
||||
const last = stack.pop();
|
||||
if (!last || pairs[last] !== ch) {
|
||||
return null;
|
||||
Process {
|
||||
id: matugenProcess
|
||||
|
||||
command: ["matugen", "image", wallpaperPath, "--json", "hex"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
id: matugenCollector
|
||||
|
||||
onStreamFinished: {
|
||||
if (!matugenCollector.text) {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError(
|
||||
"Wallpaper Processing Failed: Empty JSON extracted from matugen output.")
|
||||
return
|
||||
}
|
||||
const extractedJson = extractJsonFromText(matugenCollector.text)
|
||||
if (!extractedJson) {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError(
|
||||
"Wallpaper Processing Failed: Invalid JSON extracted from matugen output.")
|
||||
console.log("Raw matugen output:", matugenCollector.text)
|
||||
return
|
||||
}
|
||||
try {
|
||||
root.matugenColors = JSON.parse(extractedJson)
|
||||
root.colorsUpdated()
|
||||
generateAppConfigs()
|
||||
ToastService.clearWallpaperError()
|
||||
} catch (e) {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError(
|
||||
"Wallpaper processing failed (JSON parse error after extraction)")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stack.length === 0) {
|
||||
return text.slice(start, i + 1);
|
||||
|
||||
onExited: code => {
|
||||
if (code !== 0) {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError(
|
||||
"Matugen command failed with exit code " + code)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
function generateAppConfigs() {
|
||||
if (!matugenColors || !matugenColors.colors) {
|
||||
return
|
||||
}
|
||||
|
||||
Process {
|
||||
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)
|
||||
}
|
||||
}
|
||||
generateNiriConfig()
|
||||
generateGhosttyConfig()
|
||||
|
||||
Process {
|
||||
id: qtAvailabilityChecker
|
||||
command: ["bash", "-c", "command -v qt5ct >/dev/null || command -v qt6ct >/dev/null"]
|
||||
running: false
|
||||
onExited: exitCode => {
|
||||
qtThemingEnabled = (exitCode === 0)
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: systemThemeGenerator
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
id: systemThemeStdout
|
||||
if (gtkThemingEnabled && typeof SettingsData !== "undefined"
|
||||
&& SettingsData.gtkThemingEnabled) {
|
||||
generateSystemThemes()
|
||||
} else if (qtThemingEnabled && typeof SettingsData !== "undefined"
|
||||
&& SettingsData.qtThemingEnabled) {
|
||||
generateSystemThemes()
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
id: systemThemeStderr
|
||||
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 {
|
||||
active-color "${primary}"
|
||||
inactive-color "${secondary}"
|
||||
}
|
||||
focus-ring {
|
||||
active-color "${inverse}"
|
||||
}
|
||||
background-color "${bg}"
|
||||
}`
|
||||
|
||||
Quickshell.execDetached(
|
||||
["bash", "-c", `echo '${content}' > niri-colors.generated.kdl`])
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
systemThemeGenerationInProgress = false
|
||||
function generateGhosttyConfig() {
|
||||
var dark = matugenColors.colors.dark
|
||||
var light = matugenColors.colors.light
|
||||
if (!dark || !light)
|
||||
return
|
||||
|
||||
if (exitCode !== 0) {
|
||||
ToastService.showError(
|
||||
"Failed to generate system themes: " + systemThemeStderr.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
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"
|
||||
|
||||
Process {
|
||||
id: systemThemeRestoreProcess
|
||||
running: false
|
||||
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"
|
||||
|
||||
stdout: StdioCollector {
|
||||
id: restoreThemeStdout
|
||||
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}'`])
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
id: restoreThemeStderr
|
||||
function checkGtkThemingAvailability() {
|
||||
gtkAvailabilityChecker.running = true
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
ToastService.showInfo("System themes restored to default")
|
||||
} else {
|
||||
ToastService.showWarning(
|
||||
"Failed to restore system themes: " + restoreThemeStderr.text)
|
||||
}
|
||||
function checkQtThemingAvailability() {
|
||||
qtAvailabilityChecker.running = true
|
||||
}
|
||||
|
||||
function generateSystemThemes() {
|
||||
if (systemThemeGenerationInProgress) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!matugenAvailable) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!wallpaperPath || wallpaperPath === "") {
|
||||
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"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Returns the first complete JSON substring (object or array) or null.
|
||||
function extractJsonFromText(text) {
|
||||
if (!text)
|
||||
return null
|
||||
|
||||
const start = text.search(/[{\[]/)
|
||||
if (start === -1)
|
||||
return null
|
||||
|
||||
const open = text[start]
|
||||
const pairs = {
|
||||
"{": '}',
|
||||
"[": ']'
|
||||
}
|
||||
const close = pairs[open]
|
||||
if (!close)
|
||||
return null
|
||||
|
||||
let inString = false
|
||||
let escape = false
|
||||
const stack = [open]
|
||||
|
||||
for (var i = start + 1; i < text.length; i++) {
|
||||
const ch = text[i]
|
||||
|
||||
if (inString) {
|
||||
if (escape) {
|
||||
escape = false
|
||||
} else if (ch === '\\') {
|
||||
escape = true
|
||||
} else if (ch === '"') {
|
||||
inString = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (ch === '"') {
|
||||
inString = true
|
||||
continue
|
||||
}
|
||||
if (ch === '{' || ch === '[') {
|
||||
stack.push(ch)
|
||||
continue
|
||||
}
|
||||
if (ch === '}' || ch === ']') {
|
||||
const last = stack.pop()
|
||||
if (!last || pairs[last] !== ch) {
|
||||
return null
|
||||
}
|
||||
if (stack.length === 0) {
|
||||
return text.slice(start, i + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
Process {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: qtAvailabilityChecker
|
||||
command: ["bash", "-c", "command -v qt5ct >/dev/null || command -v qt6ct >/dev/null"]
|
||||
running: false
|
||||
onExited: exitCode => {
|
||||
qtThemingEnabled = (exitCode === 0)
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: systemThemeGenerator
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
id: systemThemeStdout
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ pragma Singleton
|
||||
|
||||
QtObject {
|
||||
id: modalManager
|
||||
|
||||
|
||||
signal closeAllModalsExcept(var excludedModal)
|
||||
|
||||
|
||||
function openModal(modal) {
|
||||
if (!modal.allowStacking) {
|
||||
closeAllModalsExcept(modal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,45 +4,45 @@ import Quickshell
|
||||
import QtCore
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
readonly property url home: StandardPaths.standardLocations(
|
||||
StandardPaths.HomeLocation)[0]
|
||||
readonly property url pictures: StandardPaths.standardLocations(
|
||||
StandardPaths.PicturesLocation)[0]
|
||||
readonly property url home: StandardPaths.standardLocations(
|
||||
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 state: `${StandardPaths.standardLocations(
|
||||
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 data: `${StandardPaths.standardLocations(
|
||||
StandardPaths.GenericDataLocation)[0]}/DankMaterialShell`
|
||||
readonly property url state: `${StandardPaths.standardLocations(
|
||||
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 {
|
||||
return path.toString().replace(/%20/g, " ")
|
||||
}
|
||||
function stringify(path: url): string {
|
||||
return path.toString().replace(/%20/g, " ")
|
||||
}
|
||||
|
||||
function expandTilde(path: string): string {
|
||||
return strip(path.replace("~", stringify(root.home)))
|
||||
}
|
||||
function expandTilde(path: string): string {
|
||||
return strip(path.replace("~", stringify(root.home)))
|
||||
}
|
||||
|
||||
function shortenHome(path: string): string {
|
||||
return path.replace(strip(root.home), "~")
|
||||
}
|
||||
function shortenHome(path: string): string {
|
||||
return path.replace(strip(root.home), "~")
|
||||
}
|
||||
|
||||
function strip(path: url): string {
|
||||
return stringify(path).replace("file://", "")
|
||||
}
|
||||
function strip(path: url): string {
|
||||
return stringify(path).replace("file://", "")
|
||||
}
|
||||
|
||||
function mkdir(path: url): void {
|
||||
Quickshell.execDetached(["mkdir", "-p", strip(path)])
|
||||
}
|
||||
function mkdir(path: url): void {
|
||||
Quickshell.execDetached(["mkdir", "-p", strip(path)])
|
||||
}
|
||||
|
||||
function copy(from: url, to: url): void {
|
||||
Quickshell.execDetached(["cp", strip(from), strip(to)])
|
||||
}
|
||||
function copy(from: url, to: url): void {
|
||||
Quickshell.execDetached(["cp", strip(from), strip(to)])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import QtQuick
|
||||
import Quickshell
|
||||
|
||||
QtObject {
|
||||
required property Singleton service
|
||||
required property Singleton service
|
||||
|
||||
Component.onCompleted: service.refCount++
|
||||
Component.onDestruction: service.refCount--
|
||||
Component.onCompleted: service.refCount++
|
||||
Component.onDestruction: service.refCount--
|
||||
}
|
||||
|
||||
@@ -10,290 +10,302 @@ import qs.Services
|
||||
|
||||
Singleton {
|
||||
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property bool isLightMode: false
|
||||
property string wallpaperPath: ""
|
||||
property string wallpaperLastPath: ""
|
||||
property string profileLastPath: ""
|
||||
property bool doNotDisturb: false
|
||||
property bool nightModeEnabled: false
|
||||
property int nightModeTemperature: 4500
|
||||
property var pinnedApps: []
|
||||
property int selectedGpuIndex: 0
|
||||
property bool nvidiaGpuTempEnabled: false
|
||||
property bool nonNvidiaGpuTempEnabled: false
|
||||
property var enabledGpuPciIds: []
|
||||
property bool wallpaperCyclingEnabled: false
|
||||
property string wallpaperCyclingMode: "interval" // "interval" or "time"
|
||||
property int wallpaperCyclingInterval: 300 // seconds (5 minutes)
|
||||
property string wallpaperCyclingTime: "06:00" // HH:mm format
|
||||
property string lastBrightnessDevice: ""
|
||||
property bool isLightMode: false
|
||||
property string wallpaperPath: ""
|
||||
property string wallpaperLastPath: ""
|
||||
property string profileLastPath: ""
|
||||
property bool doNotDisturb: false
|
||||
property bool nightModeEnabled: false
|
||||
property int nightModeTemperature: 4500
|
||||
property var pinnedApps: []
|
||||
property int selectedGpuIndex: 0
|
||||
property bool nvidiaGpuTempEnabled: false
|
||||
property bool nonNvidiaGpuTempEnabled: false
|
||||
property var enabledGpuPciIds: []
|
||||
property bool wallpaperCyclingEnabled: false
|
||||
property string wallpaperCyclingMode: "interval" // "interval" or "time"
|
||||
property int wallpaperCyclingInterval: 300 // seconds (5 minutes)
|
||||
property string wallpaperCyclingTime: "06:00" // HH:mm format
|
||||
property string lastBrightnessDevice: ""
|
||||
|
||||
Component.onCompleted: {
|
||||
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
|
||||
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false
|
||||
nightModeTemperature = settings.nightModeTemperature !== undefined ? settings.nightModeTemperature : 4500
|
||||
pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : []
|
||||
selectedGpuIndex = settings.selectedGpuIndex !== undefined ? settings.selectedGpuIndex : 0
|
||||
nvidiaGpuTempEnabled = settings.nvidiaGpuTempEnabled !== undefined ? settings.nvidiaGpuTempEnabled : false
|
||||
nonNvidiaGpuTempEnabled = settings.nonNvidiaGpuTempEnabled !== undefined ? settings.nonNvidiaGpuTempEnabled : false
|
||||
enabledGpuPciIds = settings.enabledGpuPciIds !== undefined ? settings.enabledGpuPciIds : []
|
||||
wallpaperCyclingEnabled = settings.wallpaperCyclingEnabled !== undefined ? settings.wallpaperCyclingEnabled : false
|
||||
wallpaperCyclingMode = settings.wallpaperCyclingMode !== undefined ? settings.wallpaperCyclingMode : "interval"
|
||||
wallpaperCyclingInterval = settings.wallpaperCyclingInterval !== undefined ? settings.wallpaperCyclingInterval : 300
|
||||
wallpaperCyclingTime = settings.wallpaperCyclingTime !== undefined ? settings.wallpaperCyclingTime : "06:00"
|
||||
lastBrightnessDevice = settings.lastBrightnessDevice !== undefined ? settings.lastBrightnessDevice : ""
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
settingsFile.setText(JSON.stringify({
|
||||
"isLightMode": isLightMode,
|
||||
"wallpaperPath": wallpaperPath,
|
||||
"wallpaperLastPath": wallpaperLastPath,
|
||||
"profileLastPath": profileLastPath,
|
||||
"doNotDisturb": doNotDisturb,
|
||||
"nightModeEnabled": nightModeEnabled,
|
||||
"nightModeTemperature": nightModeTemperature,
|
||||
"pinnedApps": pinnedApps,
|
||||
"selectedGpuIndex": selectedGpuIndex,
|
||||
"nvidiaGpuTempEnabled": nvidiaGpuTempEnabled,
|
||||
"nonNvidiaGpuTempEnabled": nonNvidiaGpuTempEnabled,
|
||||
"enabledGpuPciIds": enabledGpuPciIds,
|
||||
"wallpaperCyclingEnabled": wallpaperCyclingEnabled,
|
||||
"wallpaperCyclingMode": wallpaperCyclingMode,
|
||||
"wallpaperCyclingInterval": wallpaperCyclingInterval,
|
||||
"wallpaperCyclingTime": wallpaperCyclingTime,
|
||||
"lastBrightnessDevice": lastBrightnessDevice
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
function setLightMode(lightMode) {
|
||||
isLightMode = lightMode
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setDoNotDisturb(enabled) {
|
||||
doNotDisturb = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeEnabled(enabled) {
|
||||
nightModeEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeTemperature(temperature) {
|
||||
nightModeTemperature = temperature
|
||||
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
|
||||
}
|
||||
|
||||
function setSelectedGpuIndex(index) {
|
||||
selectedGpuIndex = index
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNvidiaGpuTempEnabled(enabled) {
|
||||
nvidiaGpuTempEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNonNvidiaGpuTempEnabled(enabled) {
|
||||
nonNvidiaGpuTempEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setEnabledGpuPciIds(pciIds) {
|
||||
enabledGpuPciIds = pciIds
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingEnabled(enabled) {
|
||||
wallpaperCyclingEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingMode(mode) {
|
||||
wallpaperCyclingMode = mode
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingInterval(interval) {
|
||||
wallpaperCyclingInterval = interval
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingTime(time) {
|
||||
wallpaperCyclingTime = time
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLastBrightnessDevice(device) {
|
||||
lastBrightnessDevice = device
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
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 || ""
|
||||
Component.onCompleted: {
|
||||
loadSettings()
|
||||
}
|
||||
|
||||
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 loadSettings() {
|
||||
parseSettings(settingsFile.text())
|
||||
}
|
||||
|
||||
function clear(): string {
|
||||
root.setWallpaper("")
|
||||
return "SUCCESS: Wallpaper cleared"
|
||||
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
|
||||
nightModeEnabled = settings.nightModeEnabled
|
||||
!== undefined ? settings.nightModeEnabled : false
|
||||
nightModeTemperature = settings.nightModeTemperature
|
||||
!== undefined ? settings.nightModeTemperature : 4500
|
||||
pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : []
|
||||
selectedGpuIndex = settings.selectedGpuIndex
|
||||
!== undefined ? settings.selectedGpuIndex : 0
|
||||
nvidiaGpuTempEnabled = settings.nvidiaGpuTempEnabled
|
||||
!== undefined ? settings.nvidiaGpuTempEnabled : false
|
||||
nonNvidiaGpuTempEnabled = settings.nonNvidiaGpuTempEnabled
|
||||
!== undefined ? settings.nonNvidiaGpuTempEnabled : false
|
||||
enabledGpuPciIds = settings.enabledGpuPciIds
|
||||
!== undefined ? settings.enabledGpuPciIds : []
|
||||
wallpaperCyclingEnabled = settings.wallpaperCyclingEnabled
|
||||
!== undefined ? settings.wallpaperCyclingEnabled : false
|
||||
wallpaperCyclingMode = settings.wallpaperCyclingMode
|
||||
!== undefined ? settings.wallpaperCyclingMode : "interval"
|
||||
wallpaperCyclingInterval = settings.wallpaperCyclingInterval
|
||||
!== undefined ? settings.wallpaperCyclingInterval : 300
|
||||
wallpaperCyclingTime = settings.wallpaperCyclingTime
|
||||
!== undefined ? settings.wallpaperCyclingTime : "06:00"
|
||||
lastBrightnessDevice = settings.lastBrightnessDevice
|
||||
!== undefined ? settings.lastBrightnessDevice : ""
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function next(): string {
|
||||
if (!root.wallpaperPath) {
|
||||
return "ERROR: No wallpaper set"
|
||||
}
|
||||
|
||||
try {
|
||||
WallpaperCyclingService.cycleNextManually()
|
||||
return "SUCCESS: Cycling to next wallpaper"
|
||||
} catch (e) {
|
||||
return "ERROR: Failed to cycle wallpaper: " + e.toString()
|
||||
}
|
||||
function saveSettings() {
|
||||
settingsFile.setText(JSON.stringify({
|
||||
"isLightMode": isLightMode,
|
||||
"wallpaperPath": wallpaperPath,
|
||||
"wallpaperLastPath": wallpaperLastPath,
|
||||
"profileLastPath": profileLastPath,
|
||||
"doNotDisturb": doNotDisturb,
|
||||
"nightModeEnabled": nightModeEnabled,
|
||||
"nightModeTemperature": nightModeTemperature,
|
||||
"pinnedApps": pinnedApps,
|
||||
"selectedGpuIndex": selectedGpuIndex,
|
||||
"nvidiaGpuTempEnabled": nvidiaGpuTempEnabled,
|
||||
"nonNvidiaGpuTempEnabled": nonNvidiaGpuTempEnabled,
|
||||
"enabledGpuPciIds": enabledGpuPciIds,
|
||||
"wallpaperCyclingEnabled": wallpaperCyclingEnabled,
|
||||
"wallpaperCyclingMode": wallpaperCyclingMode,
|
||||
"wallpaperCyclingInterval": wallpaperCyclingInterval,
|
||||
"wallpaperCyclingTime": wallpaperCyclingTime,
|
||||
"lastBrightnessDevice": lastBrightnessDevice
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
function prev(): string {
|
||||
if (!root.wallpaperPath) {
|
||||
return "ERROR: No wallpaper set"
|
||||
}
|
||||
|
||||
try {
|
||||
WallpaperCyclingService.cyclePrevManually()
|
||||
return "SUCCESS: Cycling to previous wallpaper"
|
||||
} catch (e) {
|
||||
return "ERROR: Failed to cycle wallpaper: " + e.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "theme"
|
||||
|
||||
function toggle(): string {
|
||||
root.setLightMode(!root.isLightMode)
|
||||
return root.isLightMode ? "light" : "dark"
|
||||
function setLightMode(lightMode) {
|
||||
isLightMode = lightMode
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function light(): string {
|
||||
root.setLightMode(true)
|
||||
return "light"
|
||||
function setDoNotDisturb(enabled) {
|
||||
doNotDisturb = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function dark(): string {
|
||||
root.setLightMode(false)
|
||||
return "dark"
|
||||
function setNightModeEnabled(enabled) {
|
||||
nightModeEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function getMode(): string {
|
||||
return root.isLightMode ? "light" : "dark"
|
||||
function setNightModeTemperature(temperature) {
|
||||
nightModeTemperature = temperature
|
||||
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
|
||||
}
|
||||
|
||||
function setSelectedGpuIndex(index) {
|
||||
selectedGpuIndex = index
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNvidiaGpuTempEnabled(enabled) {
|
||||
nvidiaGpuTempEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNonNvidiaGpuTempEnabled(enabled) {
|
||||
nonNvidiaGpuTempEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setEnabledGpuPciIds(pciIds) {
|
||||
enabledGpuPciIds = pciIds
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingEnabled(enabled) {
|
||||
wallpaperCyclingEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingMode(mode) {
|
||||
wallpaperCyclingMode = mode
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingInterval(interval) {
|
||||
wallpaperCyclingInterval = interval
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingTime(time) {
|
||||
wallpaperCyclingTime = time
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLastBrightnessDevice(device) {
|
||||
lastBrightnessDevice = device
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
function next(): string {
|
||||
if (!root.wallpaperPath) {
|
||||
return "ERROR: No wallpaper set"
|
||||
}
|
||||
|
||||
try {
|
||||
WallpaperCyclingService.cycleNextManually()
|
||||
return "SUCCESS: Cycling to next wallpaper"
|
||||
} catch (e) {
|
||||
return "ERROR: Failed to cycle wallpaper: " + e.toString()
|
||||
}
|
||||
}
|
||||
|
||||
function prev(): string {
|
||||
if (!root.wallpaperPath) {
|
||||
return "ERROR: No wallpaper set"
|
||||
}
|
||||
|
||||
try {
|
||||
WallpaperCyclingService.cyclePrevManually()
|
||||
return "SUCCESS: Cycling to previous wallpaper"
|
||||
} catch (e) {
|
||||
return "ERROR: Failed to cycle wallpaper: " + e.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
1233
Common/Theme.qml
1233
Common/Theme.qml
File diff suppressed because it is too large
Load Diff
@@ -21,123 +21,124 @@ DankModal {
|
||||
property Component clipboardContent
|
||||
|
||||
function updateFilteredModel() {
|
||||
filteredClipboardModel.clear();
|
||||
filteredClipboardModel.clear()
|
||||
for (var i = 0; i < clipboardModel.count; i++) {
|
||||
const entry = clipboardModel.get(i).entry;
|
||||
const entry = clipboardModel.get(i).entry
|
||||
if (searchText.trim().length === 0) {
|
||||
filteredClipboardModel.append({
|
||||
"entry": entry
|
||||
});
|
||||
"entry": entry
|
||||
})
|
||||
} else {
|
||||
const content = getEntryPreview(entry).toLowerCase();
|
||||
const content = getEntryPreview(entry).toLowerCase()
|
||||
if (content.includes(searchText.toLowerCase()))
|
||||
filteredClipboardModel.append({
|
||||
"entry": entry
|
||||
});
|
||||
|
||||
"entry": entry
|
||||
})
|
||||
}
|
||||
}
|
||||
clipboardHistoryModal.totalCount = filteredClipboardModel.count;
|
||||
clipboardHistoryModal.totalCount = filteredClipboardModel.count
|
||||
// Clamp selectedIndex to valid range
|
||||
if (filteredClipboardModel.count === 0) {
|
||||
keyboardNavigationActive = false;
|
||||
selectedIndex = 0;
|
||||
keyboardNavigationActive = false
|
||||
selectedIndex = 0
|
||||
} else if (selectedIndex >= filteredClipboardModel.count) {
|
||||
selectedIndex = filteredClipboardModel.count - 1;
|
||||
selectedIndex = filteredClipboardModel.count - 1
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (shouldBeVisible)
|
||||
hide();
|
||||
hide()
|
||||
else
|
||||
show();
|
||||
show()
|
||||
}
|
||||
|
||||
function show() {
|
||||
open();
|
||||
clipboardHistoryModal.searchText = "";
|
||||
|
||||
initializeThumbnailSystem();
|
||||
refreshClipboard();
|
||||
keyboardController.reset();
|
||||
|
||||
Qt.callLater(function() {
|
||||
open()
|
||||
clipboardHistoryModal.searchText = ""
|
||||
|
||||
initializeThumbnailSystem()
|
||||
refreshClipboard()
|
||||
keyboardController.reset()
|
||||
|
||||
Qt.callLater(function () {
|
||||
if (contentLoader.item && contentLoader.item.searchField) {
|
||||
contentLoader.item.searchField.text = "";
|
||||
contentLoader.item.searchField.forceActiveFocus();
|
||||
contentLoader.item.searchField.text = ""
|
||||
contentLoader.item.searchField.forceActiveFocus()
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function hide() {
|
||||
close();
|
||||
clipboardHistoryModal.searchText = "";
|
||||
|
||||
updateFilteredModel();
|
||||
keyboardController.reset();
|
||||
cleanupTempFiles();
|
||||
close()
|
||||
clipboardHistoryModal.searchText = ""
|
||||
|
||||
updateFilteredModel()
|
||||
keyboardController.reset()
|
||||
cleanupTempFiles()
|
||||
}
|
||||
|
||||
function initializeThumbnailSystem() {
|
||||
}
|
||||
function initializeThumbnailSystem() {}
|
||||
|
||||
function cleanupTempFiles() {
|
||||
Quickshell.execDetached(["sh", "-c", "rm -f /tmp/clipboard_*.png"]);
|
||||
Quickshell.execDetached(["sh", "-c", "rm -f /tmp/clipboard_*.png"])
|
||||
}
|
||||
|
||||
function generateThumbnails() {
|
||||
}
|
||||
function generateThumbnails() {}
|
||||
|
||||
function refreshClipboard() {
|
||||
clipboardProcess.running = true;
|
||||
clipboardProcess.running = true
|
||||
}
|
||||
|
||||
function copyEntry(entry) {
|
||||
const entryId = entry.split('\t')[0];
|
||||
Quickshell.execDetached(["sh", "-c", `cliphist decode ${entryId} | wl-copy`]);
|
||||
ToastService.showInfo("Copied to clipboard");
|
||||
hide();
|
||||
const entryId = entry.split('\t')[0]
|
||||
Quickshell.execDetached(
|
||||
["sh", "-c", `cliphist decode ${entryId} | wl-copy`])
|
||||
ToastService.showInfo("Copied to clipboard")
|
||||
hide()
|
||||
}
|
||||
|
||||
function deleteEntry(entry) {
|
||||
deleteProcess.deletedEntry = entry;
|
||||
deleteProcess.deletedEntry = entry
|
||||
deleteProcess.command = ["sh", "-c", `echo '${entry.replace(
|
||||
/'/g, "'\\''")}' | cliphist delete`];
|
||||
deleteProcess.running = true;
|
||||
/'/g, "'\\''")}' | cliphist delete`]
|
||||
deleteProcess.running = true
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
clearProcess.running = true;
|
||||
clearProcess.running = true
|
||||
}
|
||||
|
||||
function getEntryPreview(entry) {
|
||||
let content = entry.replace(/^\s*\d+\s+/, "");
|
||||
if (content.includes("image/") || content.includes("binary data") || /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(content)) {
|
||||
const dimensionMatch = content.match(/(\d+)x(\d+)/);
|
||||
let content = entry.replace(/^\s*\d+\s+/, "")
|
||||
if (content.includes("image/") || content.includes("binary data")
|
||||
|| /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(content)) {
|
||||
const dimensionMatch = content.match(/(\d+)x(\d+)/)
|
||||
if (dimensionMatch)
|
||||
return `Image ${dimensionMatch[1]}×${dimensionMatch[2]}`;
|
||||
return `Image ${dimensionMatch[1]}×${dimensionMatch[2]}`
|
||||
|
||||
const typeMatch = content.match(/\b(png|jpg|jpeg|gif|bmp|webp)\b/i);
|
||||
const typeMatch = content.match(/\b(png|jpg|jpeg|gif|bmp|webp)\b/i)
|
||||
if (typeMatch)
|
||||
return `Image (${typeMatch[1].toUpperCase()})`;
|
||||
return `Image (${typeMatch[1].toUpperCase()})`
|
||||
|
||||
return "Image";
|
||||
return "Image"
|
||||
}
|
||||
if (content.length > 100)
|
||||
return content.substring(0, 100) + "...";
|
||||
return content.substring(0, 100) + "..."
|
||||
|
||||
return content;
|
||||
return content
|
||||
}
|
||||
|
||||
function getEntryType(entry) {
|
||||
if (entry.includes("image/") || entry.includes("binary data") || /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(entry) || /\b(png|jpg|jpeg|gif|bmp|webp)\b/i.test(entry))
|
||||
return "image";
|
||||
if (entry.includes("image/") || entry.includes("binary data")
|
||||
|| /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(entry)
|
||||
|| /\b(png|jpg|jpeg|gif|bmp|webp)\b/i.test(entry))
|
||||
return "image"
|
||||
|
||||
if (entry.length > 200)
|
||||
return "long_text";
|
||||
return "long_text"
|
||||
|
||||
return "text";
|
||||
return "text"
|
||||
}
|
||||
|
||||
visible: false
|
||||
@@ -149,10 +150,10 @@ DankModal {
|
||||
borderWidth: 1
|
||||
enableShadow: true
|
||||
onBackgroundClicked: {
|
||||
hide();
|
||||
hide()
|
||||
}
|
||||
modalFocusScope.Keys.onPressed: function(event) {
|
||||
keyboardController.handleKey(event);
|
||||
modalFocusScope.Keys.onPressed: function (event) {
|
||||
keyboardController.handleKey(event)
|
||||
}
|
||||
content: clipboardContent
|
||||
|
||||
@@ -160,107 +161,116 @@ DankModal {
|
||||
id: keyboardController
|
||||
|
||||
function reset() {
|
||||
selectedIndex = 0;
|
||||
keyboardNavigationActive = false;
|
||||
showKeyboardHints = false;
|
||||
selectedIndex = 0
|
||||
keyboardNavigationActive = false
|
||||
showKeyboardHints = false
|
||||
if (typeof clipboardListView !== 'undefined' && clipboardListView)
|
||||
clipboardListView.keyboardActive = false;
|
||||
|
||||
clipboardListView.keyboardActive = false
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (filteredClipboardModel.count === 0)
|
||||
return ;
|
||||
return
|
||||
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = Math.min(selectedIndex + 1, filteredClipboardModel.count - 1);
|
||||
keyboardNavigationActive = true
|
||||
selectedIndex = Math.min(selectedIndex + 1,
|
||||
filteredClipboardModel.count - 1)
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (filteredClipboardModel.count === 0)
|
||||
return ;
|
||||
return
|
||||
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0);
|
||||
keyboardNavigationActive = true
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0)
|
||||
}
|
||||
|
||||
function copySelected() {
|
||||
if (filteredClipboardModel.count === 0 || selectedIndex < 0 || selectedIndex >= filteredClipboardModel.count)
|
||||
return ;
|
||||
if (filteredClipboardModel.count === 0 || selectedIndex < 0
|
||||
|| selectedIndex >= filteredClipboardModel.count)
|
||||
return
|
||||
|
||||
var selectedEntry = filteredClipboardModel.get(selectedIndex).entry;
|
||||
copyEntry(selectedEntry);
|
||||
var selectedEntry = filteredClipboardModel.get(selectedIndex).entry
|
||||
copyEntry(selectedEntry)
|
||||
}
|
||||
|
||||
function deleteSelected() {
|
||||
if (filteredClipboardModel.count === 0 || selectedIndex < 0 || selectedIndex >= filteredClipboardModel.count)
|
||||
return ;
|
||||
if (filteredClipboardModel.count === 0 || selectedIndex < 0
|
||||
|| selectedIndex >= filteredClipboardModel.count)
|
||||
return
|
||||
|
||||
var selectedEntry = filteredClipboardModel.get(selectedIndex).entry;
|
||||
deleteEntry(selectedEntry);
|
||||
var selectedEntry = filteredClipboardModel.get(selectedIndex).entry
|
||||
deleteEntry(selectedEntry)
|
||||
}
|
||||
|
||||
function handleKey(event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
if (keyboardNavigationActive) {
|
||||
keyboardNavigationActive = false;
|
||||
if (typeof clipboardListView !== 'undefined' && clipboardListView)
|
||||
clipboardListView.keyboardActive = false;
|
||||
keyboardNavigationActive = false
|
||||
if (typeof clipboardListView !== 'undefined'
|
||||
&& clipboardListView)
|
||||
clipboardListView.keyboardActive = false
|
||||
|
||||
event.accepted = true;
|
||||
event.accepted = true
|
||||
} else {
|
||||
hide();
|
||||
event.accepted = true;
|
||||
hide()
|
||||
event.accepted = true
|
||||
}
|
||||
} else if (event.key === Qt.Key_Down) {
|
||||
if (!keyboardNavigationActive) {
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = 0;
|
||||
if (typeof clipboardListView !== 'undefined' && clipboardListView)
|
||||
clipboardListView.keyboardActive = true;
|
||||
keyboardNavigationActive = true
|
||||
selectedIndex = 0
|
||||
if (typeof clipboardListView !== 'undefined'
|
||||
&& clipboardListView)
|
||||
clipboardListView.keyboardActive = true
|
||||
|
||||
event.accepted = true;
|
||||
event.accepted = true
|
||||
} else {
|
||||
selectNext();
|
||||
event.accepted = true;
|
||||
selectNext()
|
||||
event.accepted = true
|
||||
}
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
if (!keyboardNavigationActive) {
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = 0;
|
||||
if (typeof clipboardListView !== 'undefined' && clipboardListView)
|
||||
clipboardListView.keyboardActive = true;
|
||||
keyboardNavigationActive = true
|
||||
selectedIndex = 0
|
||||
if (typeof clipboardListView !== 'undefined'
|
||||
&& clipboardListView)
|
||||
clipboardListView.keyboardActive = true
|
||||
|
||||
event.accepted = true;
|
||||
event.accepted = true
|
||||
} else if (selectedIndex === 0) {
|
||||
keyboardNavigationActive = false;
|
||||
if (typeof clipboardListView !== 'undefined' && clipboardListView)
|
||||
clipboardListView.keyboardActive = false;
|
||||
keyboardNavigationActive = false
|
||||
if (typeof clipboardListView !== 'undefined'
|
||||
&& clipboardListView)
|
||||
clipboardListView.keyboardActive = false
|
||||
|
||||
event.accepted = true;
|
||||
event.accepted = true
|
||||
} else {
|
||||
selectPrevious();
|
||||
event.accepted = true;
|
||||
selectPrevious()
|
||||
event.accepted = true
|
||||
}
|
||||
} else if (event.key === Qt.Key_Delete && (event.modifiers & Qt.ShiftModifier)) {
|
||||
clearAll();
|
||||
hide();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Delete
|
||||
&& (event.modifiers & Qt.ShiftModifier)) {
|
||||
clearAll()
|
||||
hide()
|
||||
event.accepted = true
|
||||
} else if (keyboardNavigationActive) {
|
||||
if ((event.key === Qt.Key_C && (event.modifiers & Qt.ControlModifier)) || event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
copySelected();
|
||||
event.accepted = true;
|
||||
if ((event.key === Qt.Key_C
|
||||
&& (event.modifiers & Qt.ControlModifier))
|
||||
|| event.key === Qt.Key_Return
|
||||
|| event.key === Qt.Key_Enter) {
|
||||
copySelected()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Delete) {
|
||||
deleteSelected();
|
||||
event.accepted = true;
|
||||
deleteSelected()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
if (event.key === Qt.Key_F10) {
|
||||
showKeyboardHints = !showKeyboardHints;
|
||||
event.accepted = true;
|
||||
showKeyboardHints = !showKeyboardHints
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankModal {
|
||||
@@ -269,8 +279,8 @@ DankModal {
|
||||
visible: showClearConfirmation
|
||||
width: 350
|
||||
height: 150
|
||||
onBackgroundClicked: {
|
||||
showClearConfirmation = false;
|
||||
onBackgroundClicked: {
|
||||
showClearConfirmation = false
|
||||
}
|
||||
|
||||
content: Component {
|
||||
@@ -326,7 +336,6 @@ DankModal {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: showClearConfirmation = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -350,22 +359,16 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
clearAll();
|
||||
showClearConfirmation = false;
|
||||
hide();
|
||||
clearAll()
|
||||
showClearConfirmation = false
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ListModel {
|
||||
@@ -384,19 +387,17 @@ DankModal {
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
clipboardModel.clear();
|
||||
const lines = text.trim().split('\n');
|
||||
clipboardModel.clear()
|
||||
const lines = text.trim().split('\n')
|
||||
for (const line of lines) {
|
||||
if (line.trim().length > 0)
|
||||
clipboardModel.append({
|
||||
"entry": line
|
||||
});
|
||||
|
||||
"entry": line
|
||||
})
|
||||
}
|
||||
updateFilteredModel();
|
||||
updateFilteredModel()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Process {
|
||||
@@ -405,33 +406,35 @@ DankModal {
|
||||
property string deletedEntry: ""
|
||||
|
||||
running: false
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
// Just remove the item from models instead of re-fetching everything
|
||||
for (var i = 0; i < clipboardModel.count; i++) {
|
||||
if (clipboardModel.get(i).entry === deleteProcess.deletedEntry) {
|
||||
clipboardModel.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (var j = 0; j < filteredClipboardModel.count; j++) {
|
||||
if (filteredClipboardModel.get(j).entry === deleteProcess.deletedEntry) {
|
||||
filteredClipboardModel.remove(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
clipboardHistoryModal.totalCount = filteredClipboardModel.count;
|
||||
// Clamp selectedIndex to valid range
|
||||
if (filteredClipboardModel.count === 0) {
|
||||
keyboardNavigationActive = false;
|
||||
selectedIndex = 0;
|
||||
} else if (selectedIndex >= filteredClipboardModel.count) {
|
||||
selectedIndex = filteredClipboardModel.count - 1;
|
||||
}
|
||||
} else {
|
||||
console.warn("Failed to delete clipboard entry");
|
||||
}
|
||||
}
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
// Just remove the item from models instead of re-fetching everything
|
||||
for (var i = 0; i < clipboardModel.count; i++) {
|
||||
if (clipboardModel.get(
|
||||
i).entry === deleteProcess.deletedEntry) {
|
||||
clipboardModel.remove(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
for (var j = 0; j < filteredClipboardModel.count; j++) {
|
||||
if (filteredClipboardModel.get(
|
||||
j).entry === deleteProcess.deletedEntry) {
|
||||
filteredClipboardModel.remove(j)
|
||||
break
|
||||
}
|
||||
}
|
||||
clipboardHistoryModal.totalCount = filteredClipboardModel.count
|
||||
// Clamp selectedIndex to valid range
|
||||
if (filteredClipboardModel.count === 0) {
|
||||
keyboardNavigationActive = false
|
||||
selectedIndex = 0
|
||||
} else if (selectedIndex >= filteredClipboardModel.count) {
|
||||
selectedIndex = filteredClipboardModel.count - 1
|
||||
}
|
||||
} else {
|
||||
console.warn("Failed to delete clipboard entry")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
@@ -439,30 +442,31 @@ DankModal {
|
||||
|
||||
command: ["cliphist", "wipe"]
|
||||
running: false
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
clipboardModel.clear();
|
||||
filteredClipboardModel.clear();
|
||||
totalCount = 0;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
clipboardModel.clear()
|
||||
filteredClipboardModel.clear()
|
||||
totalCount = 0
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open() {
|
||||
clipboardHistoryModal.show();
|
||||
return "CLIPBOARD_OPEN_SUCCESS";
|
||||
clipboardHistoryModal.show()
|
||||
return "CLIPBOARD_OPEN_SUCCESS"
|
||||
}
|
||||
|
||||
function close() {
|
||||
hide();
|
||||
return "CLIPBOARD_CLOSE_SUCCESS";
|
||||
hide()
|
||||
return "CLIPBOARD_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
clipboardHistoryModal.toggle();
|
||||
return "CLIPBOARD_TOGGLE_SUCCESS";
|
||||
clipboardHistoryModal.toggle()
|
||||
return "CLIPBOARD_TOGGLE_SUCCESS"
|
||||
}
|
||||
|
||||
target: "clipboard"
|
||||
@@ -472,7 +476,7 @@ DankModal {
|
||||
Item {
|
||||
id: clipboardContent
|
||||
property alias searchField: searchField
|
||||
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Column {
|
||||
@@ -504,7 +508,6 @@ DankModal {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -518,7 +521,7 @@ DankModal {
|
||||
iconColor: showKeyboardHints ? Theme.primary : Theme.surfaceText
|
||||
hoverColor: Theme.primaryHover
|
||||
onClicked: {
|
||||
showKeyboardHints = !showKeyboardHints;
|
||||
showKeyboardHints = !showKeyboardHints
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,7 +531,7 @@ DankModal {
|
||||
iconColor: Theme.error
|
||||
hoverColor: Theme.errorHover
|
||||
onClicked: {
|
||||
showClearConfirmation = true;
|
||||
showClearConfirmation = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,9 +542,7 @@ DankModal {
|
||||
hoverColor: Theme.errorHover
|
||||
onClicked: hide()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
@@ -555,25 +556,25 @@ DankModal {
|
||||
ignoreLeftRightKeys: true
|
||||
keyForwardTargets: [modalFocusScope]
|
||||
onTextChanged: {
|
||||
clipboardHistoryModal.searchText = text;
|
||||
updateFilteredModel();
|
||||
clipboardHistoryModal.searchText = text
|
||||
updateFilteredModel()
|
||||
}
|
||||
Keys.onEscapePressed: function(event) {
|
||||
hide();
|
||||
event.accepted = true;
|
||||
Keys.onEscapePressed: function (event) {
|
||||
hide()
|
||||
event.accepted = true
|
||||
}
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(function() {
|
||||
forceActiveFocus();
|
||||
});
|
||||
Qt.callLater(function () {
|
||||
forceActiveFocus()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: clipboardHistoryModal
|
||||
function onOpened() {
|
||||
Qt.callLater(function() {
|
||||
searchField.forceActiveFocus();
|
||||
});
|
||||
Qt.callLater(function () {
|
||||
searchField.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -592,15 +593,15 @@ DankModal {
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= count)
|
||||
return ;
|
||||
return
|
||||
|
||||
var itemHeight = 72 + spacing;
|
||||
var itemY = index * itemHeight;
|
||||
var itemBottom = itemY + itemHeight;
|
||||
var itemHeight = 72 + spacing
|
||||
var itemY = index * itemHeight
|
||||
var itemBottom = itemY + itemHeight
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
contentY = itemY
|
||||
else if (itemBottom > contentY + height)
|
||||
contentY = itemBottom - height;
|
||||
contentY = itemBottom - height
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
@@ -618,8 +619,7 @@ DankModal {
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
onCurrentIndexChanged: {
|
||||
if (keyboardNavigationActive && currentIndex >= 0)
|
||||
ensureVisible(currentIndex);
|
||||
|
||||
ensureVisible(currentIndex)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -640,7 +640,8 @@ DankModal {
|
||||
|
||||
delegate: Rectangle {
|
||||
property string entryType: getEntryType(model.entry)
|
||||
property string entryPreview: getEntryPreview(model.entry)
|
||||
property string entryPreview: getEntryPreview(
|
||||
model.entry)
|
||||
property int entryIndex: index + 1
|
||||
property string entryData: model.entry
|
||||
property alias thumbnailImageSource: thumbnailImageSource
|
||||
@@ -649,18 +650,25 @@ DankModal {
|
||||
height: 72
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (keyboardNavigationActive && index === selectedIndex)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2);
|
||||
if (keyboardNavigationActive
|
||||
&& index === selectedIndex)
|
||||
return Qt.rgba(Theme.surfaceVariant.r,
|
||||
Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b, 0.2)
|
||||
|
||||
return mouseArea.containsMouse ? Theme.primaryHover : Theme.primaryBackground;
|
||||
return mouseArea.containsMouse ? Theme.primaryHover : Theme.primaryBackground
|
||||
}
|
||||
border.color: {
|
||||
if (keyboardNavigationActive && index === selectedIndex)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5);
|
||||
if (keyboardNavigationActive
|
||||
&& index === selectedIndex)
|
||||
return Qt.rgba(Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b, 0.5)
|
||||
|
||||
return Theme.outlineStrong;
|
||||
return Theme.outlineStrong
|
||||
}
|
||||
border.width: keyboardNavigationActive && index === selectedIndex ? 1.5 : 1
|
||||
border.width: keyboardNavigationActive
|
||||
&& index === selectedIndex ? 1.5 : 1
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
@@ -682,7 +690,6 @@ DankModal {
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -698,10 +705,12 @@ DankModal {
|
||||
CachingImage {
|
||||
id: thumbnailImageSource
|
||||
|
||||
property string entryId: model.entry.split('\t')[0]
|
||||
property string entryId: model.entry.split(
|
||||
'\t')[0]
|
||||
|
||||
anchors.fill: parent
|
||||
source: entryType === "image" && imageLoader.imageData ? `data:image/png;base64,${imageLoader.imageData}` : ""
|
||||
source: entryType === "image"
|
||||
&& imageLoader.imageData ? `data:image/png;base64,${imageLoader.imageData}` : ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
cache: true
|
||||
@@ -718,12 +727,10 @@ DankModal {
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
imageLoader.imageData = text.trim();
|
||||
imageLoader.imageData = text.trim()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
@@ -732,7 +739,8 @@ DankModal {
|
||||
source: thumbnailImageSource
|
||||
maskEnabled: true
|
||||
maskSource: clipboardCircularMask
|
||||
visible: entryType === "image" && thumbnailImageSource.status === Image.Ready
|
||||
visible: entryType === "image"
|
||||
&& thumbnailImageSource.status === Image.Ready
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
@@ -752,25 +760,25 @@ DankModal {
|
||||
color: "black"
|
||||
antialiasing: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
visible: !(entryType === "image" && thumbnailImageSource.status === Image.Ready)
|
||||
visible: !(entryType === "image"
|
||||
&& thumbnailImageSource.status
|
||||
=== Image.Ready)
|
||||
name: {
|
||||
if (entryType === "image")
|
||||
return "image";
|
||||
return "image"
|
||||
|
||||
if (entryType === "long_text")
|
||||
return "subject";
|
||||
return "subject"
|
||||
|
||||
return "content_copy";
|
||||
return "content_copy"
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -782,11 +790,11 @@ DankModal {
|
||||
text: {
|
||||
switch (entryType) {
|
||||
case "image":
|
||||
return "Image • " + entryPreview;
|
||||
return "Image • " + entryPreview
|
||||
case "long_text":
|
||||
return "Long Text";
|
||||
return "Long Text"
|
||||
default:
|
||||
return "Text";
|
||||
return "Text"
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -807,11 +815,8 @@ DankModal {
|
||||
maximumLineCount: entryType === "long_text" ? 3 : 1
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
@@ -823,7 +828,7 @@ DankModal {
|
||||
iconColor: Theme.error
|
||||
hoverColor: Theme.errorHover
|
||||
onClicked: {
|
||||
deleteEntry(model.entry);
|
||||
deleteEntry(model.entry)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -836,11 +841,8 @@ DankModal {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: copyEntry(model.entry)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -852,11 +854,8 @@ DankModal {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -866,7 +865,9 @@ DankModal {
|
||||
anchors.margins: Theme.spacingL
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
|
||||
color: Qt.rgba(Theme.surfaceContainer.r,
|
||||
Theme.surfaceContainer.g,
|
||||
Theme.surfaceContainer.b, 0.95)
|
||||
border.color: Theme.primary
|
||||
border.width: 2
|
||||
opacity: showKeyboardHints ? 1 : 0
|
||||
@@ -889,7 +890,6 @@ DankModal {
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -897,13 +897,8 @@ DankModal {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ PanelWindow {
|
||||
property bool allowFocusOverride: false
|
||||
property bool allowStacking: false
|
||||
|
||||
signal opened()
|
||||
signal dialogClosed()
|
||||
signal backgroundClicked()
|
||||
|
||||
signal opened
|
||||
signal dialogClosed
|
||||
signal backgroundClicked
|
||||
|
||||
Connections {
|
||||
target: ModalManager
|
||||
function onCloseAllModalsExcept(excludedModal) {
|
||||
@@ -49,25 +49,24 @@ PanelWindow {
|
||||
|
||||
function open() {
|
||||
ModalManager.openModal(root)
|
||||
closeTimer.stop();
|
||||
shouldBeVisible = true;
|
||||
visible = true;
|
||||
focusScope.forceActiveFocus();
|
||||
closeTimer.stop()
|
||||
shouldBeVisible = true
|
||||
visible = true
|
||||
focusScope.forceActiveFocus()
|
||||
}
|
||||
|
||||
function close() {
|
||||
shouldBeVisible = false;
|
||||
closeTimer.restart();
|
||||
shouldBeVisible = false
|
||||
closeTimer.restart()
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (shouldBeVisible)
|
||||
close();
|
||||
close()
|
||||
else
|
||||
open();
|
||||
open()
|
||||
}
|
||||
|
||||
|
||||
visible: shouldBeVisible
|
||||
color: "transparent"
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
@@ -75,13 +74,13 @@ PanelWindow {
|
||||
WlrLayershell.keyboardFocus: shouldHaveFocus ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
onVisibleChanged: {
|
||||
if (root.visible) {
|
||||
opened();
|
||||
opened()
|
||||
} else {
|
||||
if (Qt.inputMethod) {
|
||||
Qt.inputMethod.hide();
|
||||
Qt.inputMethod.reset();
|
||||
Qt.inputMethod.hide()
|
||||
Qt.inputMethod.reset()
|
||||
}
|
||||
dialogClosed();
|
||||
dialogClosed()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,12 +89,10 @@ PanelWindow {
|
||||
|
||||
interval: animationDuration + 50
|
||||
onTriggered: {
|
||||
visible = false;
|
||||
visible = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
@@ -114,12 +111,15 @@ PanelWindow {
|
||||
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();
|
||||
|
||||
}
|
||||
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 {
|
||||
@@ -127,9 +127,7 @@ PanelWindow {
|
||||
duration: root.animationDuration
|
||||
easing.type: root.animationEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -140,17 +138,18 @@ PanelWindow {
|
||||
anchors.centerIn: positioning === "center" ? parent : undefined
|
||||
x: {
|
||||
if (positioning === "top-right")
|
||||
return Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL);
|
||||
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"
|
||||
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;
|
||||
return Theme.barHeight + Theme.spacingXS
|
||||
else if (positioning === "custom")
|
||||
return root.customPosition.y;
|
||||
return 0; // Will be overridden by anchors.centerIn when positioning === "center"
|
||||
return root.customPosition.y
|
||||
return 0 // Will be overridden by anchors.centerIn when positioning === "center"
|
||||
}
|
||||
color: root.backgroundColor
|
||||
radius: root.cornerRadius
|
||||
@@ -160,9 +159,9 @@ PanelWindow {
|
||||
opacity: root.shouldBeVisible ? 1 : 0
|
||||
scale: {
|
||||
if (root.animationType === "scale")
|
||||
return root.shouldBeVisible ? 1 : 0.9;
|
||||
return root.shouldBeVisible ? 1 : 0.9
|
||||
|
||||
return 1;
|
||||
return 1
|
||||
}
|
||||
transform: root.animationType === "slide" ? slideTransform : null
|
||||
|
||||
@@ -186,7 +185,6 @@ PanelWindow {
|
||||
duration: root.animationDuration
|
||||
easing.type: root.animationEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
@@ -196,7 +194,6 @@ PanelWindow {
|
||||
duration: root.animationDuration
|
||||
easing.type: root.animationEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
@@ -207,7 +204,6 @@ PanelWindow {
|
||||
shadowColor: Theme.shadowStrong
|
||||
shadowOpacity: 0.3
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
@@ -217,31 +213,35 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
visible: root.visible // Only active when the modal is visible
|
||||
focus: root.visible
|
||||
Keys.onEscapePressed: (event) => {
|
||||
console.log("DankModal escape pressed - shouldHaveFocus:", shouldHaveFocus, "closeOnEscapeKey:", root.closeOnEscapeKey, "objectName:", root.objectName || "unnamed");
|
||||
if (root.closeOnEscapeKey && shouldHaveFocus) {
|
||||
console.log("DankModal handling escape");
|
||||
root.close();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
Keys.onEscapePressed: event => {
|
||||
console.log(
|
||||
"DankModal escape pressed - shouldHaveFocus:",
|
||||
shouldHaveFocus, "closeOnEscapeKey:",
|
||||
root.closeOnEscapeKey, "objectName:",
|
||||
root.objectName || "unnamed")
|
||||
if (root.closeOnEscapeKey
|
||||
&& shouldHaveFocus) {
|
||||
console.log("DankModal handling escape")
|
||||
root.close()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (visible && shouldHaveFocus)
|
||||
Qt.callLater(function() {
|
||||
focusScope.forceActiveFocus();
|
||||
});
|
||||
Qt.callLater(function () {
|
||||
focusScope.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onShouldHaveFocusChanged() {
|
||||
if (shouldHaveFocus && visible) {
|
||||
Qt.callLater(function() {
|
||||
focusScope.forceActiveFocus();
|
||||
});
|
||||
Qt.callLater(function () {
|
||||
focusScope.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,290 +9,291 @@ import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: fileBrowserModal
|
||||
objectName: "fileBrowserModal"
|
||||
allowStacking: true
|
||||
id: fileBrowserModal
|
||||
objectName: "fileBrowserModal"
|
||||
allowStacking: true
|
||||
|
||||
signal fileSelected(string path)
|
||||
signal fileSelected(string path)
|
||||
|
||||
property string homeDir: StandardPaths.writableLocation(
|
||||
StandardPaths.HomeLocation)
|
||||
property string currentPath: ""
|
||||
property var fileExtensions: ["*.*"]
|
||||
property string browserTitle: "Select File"
|
||||
property string browserIcon: "folder_open"
|
||||
property string browserType: "generic" // "wallpaper" or "profile" for last path memory
|
||||
property string homeDir: StandardPaths.writableLocation(
|
||||
StandardPaths.HomeLocation)
|
||||
property string currentPath: ""
|
||||
property var fileExtensions: ["*.*"]
|
||||
property string browserTitle: "Select File"
|
||||
property string browserIcon: "folder_open"
|
||||
property string browserType: "generic" // "wallpaper" or "profile" for last path memory
|
||||
|
||||
FolderListModel {
|
||||
id: folderModel
|
||||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showHidden: false
|
||||
nameFilters: fileExtensions
|
||||
showFiles: true
|
||||
showDirs: true
|
||||
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
|
||||
FolderListModel {
|
||||
id: folderModel
|
||||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showHidden: false
|
||||
nameFilters: fileExtensions
|
||||
showFiles: true
|
||||
showDirs: true
|
||||
folder: currentPath ? "file://" + currentPath : "file://" + homeDir
|
||||
}
|
||||
|
||||
if (lastPath && lastPath !== "") {
|
||||
return lastPath
|
||||
}
|
||||
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
|
||||
enableShadow: true
|
||||
visible: false
|
||||
|
||||
onBackgroundClicked: close()
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
var startPath = getLastPath()
|
||||
currentPath = startPath
|
||||
}
|
||||
}
|
||||
|
||||
onCurrentPathChanged: {
|
||||
|
||||
}
|
||||
|
||||
function navigateUp() {
|
||||
var path = currentPath
|
||||
|
||||
if (path === homeDir) {
|
||||
return
|
||||
function isImageFile(fileName) {
|
||||
if (!fileName)
|
||||
return false
|
||||
var ext = fileName.toLowerCase().split('.').pop()
|
||||
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(ext)
|
||||
}
|
||||
|
||||
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.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
|
||||
}
|
||||
function getLastPath() {
|
||||
var lastPath = ""
|
||||
if (browserType === "wallpaper") {
|
||||
lastPath = SessionData.wallpaperLastPath
|
||||
} else if (browserType === "profile") {
|
||||
lastPath = SessionData.profileLastPath
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
circular: false
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
hoverColor: Theme.errorHover
|
||||
onClicked: fileBrowserModal.close()
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
if (lastPath && lastPath !== "") {
|
||||
return lastPath
|
||||
}
|
||||
}
|
||||
return homeDir
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
function saveLastPath(path) {
|
||||
if (browserType === "wallpaper") {
|
||||
SessionData.setWallpaperLastPath(path)
|
||||
} else if (browserType === "profile") {
|
||||
SessionData.setProfileLastPath(path)
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: 32
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: mouseArea.containsMouse
|
||||
&& currentPath !== homeDir ? Theme.surfaceVariant : "transparent"
|
||||
opacity: currentPath !== homeDir ? 1.0 : 0.0
|
||||
Component.onCompleted: {
|
||||
currentPath = getLastPath()
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "arrow_back"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
width: 800
|
||||
height: 600
|
||||
enableShadow: true
|
||||
visible: false
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
onBackgroundClicked: close()
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
DankGridView {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: 80
|
||||
height: 60
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
CachingImage {
|
||||
anchors.fill: parent
|
||||
imagePath: !delegateRoot.fileIsDir ? delegateRoot.filePath : ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
visible: !delegateRoot.fileIsDir && isImageFile(
|
||||
delegateRoot.fileName)
|
||||
maxCacheSize: 80
|
||||
}
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "description"
|
||||
size: Theme.iconSizeLarge
|
||||
color: Theme.primary
|
||||
visible: !delegateRoot.fileIsDir && !isImageFile(
|
||||
delegateRoot.fileName)
|
||||
}
|
||||
DankIcon {
|
||||
name: browserIcon
|
||||
size: Theme.iconSizeLarge
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "folder"
|
||||
size: Theme.iconSizeLarge
|
||||
color: Theme.primary
|
||||
visible: delegateRoot.fileIsDir
|
||||
}
|
||||
StyledText {
|
||||
text: browserTitle
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
circular: false
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
hoverColor: Theme.errorHover
|
||||
onClicked: fileBrowserModal.close()
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
StyledRect {
|
||||
width: 32
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: mouseArea.containsMouse
|
||||
&& currentPath !== homeDir ? Theme.surfaceVariant : "transparent"
|
||||
opacity: currentPath !== homeDir ? 1.0 : 0.0
|
||||
|
||||
onClicked: {
|
||||
if (delegateRoot.fileIsDir) {
|
||||
navigateTo(delegateRoot.filePath)
|
||||
} else {
|
||||
fileSelected(delegateRoot.filePath)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
DankGridView {
|
||||
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
|
||||
}
|
||||
|
||||
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,195 +8,202 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property bool networkInfoModalVisible: false
|
||||
property string networkSSID: ""
|
||||
property var networkData: null
|
||||
property string networkDetails: ""
|
||||
property bool networkInfoModalVisible: false
|
||||
property string networkSSID: ""
|
||||
property var networkData: null
|
||||
property string networkDetails: ""
|
||||
|
||||
function showNetworkInfo(ssid, data) {
|
||||
networkSSID = ssid
|
||||
networkData = data
|
||||
networkInfoModalVisible = true
|
||||
open()
|
||||
NetworkService.fetchNetworkInfo(ssid)
|
||||
}
|
||||
|
||||
function hideDialog() {
|
||||
networkInfoModalVisible = false
|
||||
close()
|
||||
networkSSID = ""
|
||||
networkData = null
|
||||
networkDetails = ""
|
||||
}
|
||||
|
||||
visible: networkInfoModalVisible
|
||||
width: 600
|
||||
height: 500
|
||||
enableShadow: true
|
||||
onBackgroundClicked: {
|
||||
hideDialog()
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
networkSSID = ""
|
||||
networkData = null
|
||||
networkDetails = ""
|
||||
function showNetworkInfo(ssid, data) {
|
||||
networkSSID = ssid
|
||||
networkData = data
|
||||
networkInfoModalVisible = true
|
||||
open()
|
||||
NetworkService.fetchNetworkInfo(ssid)
|
||||
}
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
function hideDialog() {
|
||||
networkInfoModalVisible = false
|
||||
close()
|
||||
networkSSID = ""
|
||||
networkData = null
|
||||
networkDetails = ""
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
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
|
||||
}
|
||||
visible: networkInfoModalVisible
|
||||
width: 600
|
||||
height: 500
|
||||
enableShadow: true
|
||||
onBackgroundClicked: {
|
||||
hideDialog()
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
networkSSID = ""
|
||||
networkData = null
|
||||
networkDetails = ""
|
||||
}
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 40
|
||||
anchors.fill: parent
|
||||
|
||||
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
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
|
||||
StyledText {
|
||||
id: closeText
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "Close"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.background
|
||||
font.weight: Font.Medium
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: notificationModal
|
||||
|
||||
|
||||
width: 500
|
||||
height: 700
|
||||
visible: false
|
||||
@@ -20,28 +20,28 @@ DankModal {
|
||||
notificationModalOpen = false
|
||||
modalKeyboardController.reset()
|
||||
}
|
||||
|
||||
modalFocusScope.Keys.onPressed: function(event) {
|
||||
|
||||
modalFocusScope.Keys.onPressed: function (event) {
|
||||
modalKeyboardController.handleKey(event)
|
||||
}
|
||||
|
||||
|
||||
NotificationKeyboardController {
|
||||
id: modalKeyboardController
|
||||
listView: null
|
||||
isOpen: notificationModal.notificationModalOpen
|
||||
onClose: function() { notificationModal.hide() }
|
||||
onClose: function () {
|
||||
notificationModal.hide()
|
||||
}
|
||||
}
|
||||
|
||||
property bool notificationModalOpen: false
|
||||
property var notificationListRef: null
|
||||
|
||||
|
||||
|
||||
function show() {
|
||||
notificationModalOpen = true
|
||||
open()
|
||||
modalKeyboardController.reset()
|
||||
|
||||
|
||||
if (modalKeyboardController && notificationListRef) {
|
||||
modalKeyboardController.listView = notificationListRef
|
||||
modalKeyboardController.rebuildFlatNavigation()
|
||||
@@ -61,7 +61,6 @@ DankModal {
|
||||
show()
|
||||
}
|
||||
|
||||
|
||||
IpcHandler {
|
||||
function open() {
|
||||
notificationModal.show()
|
||||
@@ -96,7 +95,7 @@ DankModal {
|
||||
id: notificationHeader
|
||||
keyboardController: modalKeyboardController
|
||||
}
|
||||
|
||||
|
||||
NotificationSettings {
|
||||
id: notificationSettings
|
||||
expanded: notificationHeader.showSettings
|
||||
@@ -104,11 +103,11 @@ DankModal {
|
||||
|
||||
KeyboardNavigatedNotificationList {
|
||||
id: notificationList
|
||||
|
||||
|
||||
width: parent.width
|
||||
height: parent.height - y
|
||||
keyboardController: modalKeyboardController
|
||||
|
||||
|
||||
Component.onCompleted: {
|
||||
notificationModal.notificationListRef = notificationList
|
||||
if (modalKeyboardController) {
|
||||
@@ -117,7 +116,6 @@ DankModal {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
NotificationKeyboardHints {
|
||||
@@ -128,9 +126,8 @@ DankModal {
|
||||
anchors.margins: Theme.spacingL
|
||||
showHints: modalKeyboardController.showKeyboardHints
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
content: notificationContent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,159 +7,160 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property string powerConfirmAction: ""
|
||||
property string powerConfirmTitle: ""
|
||||
property string powerConfirmMessage: ""
|
||||
property string powerConfirmAction: ""
|
||||
property string powerConfirmTitle: ""
|
||||
property string powerConfirmMessage: ""
|
||||
|
||||
function show(action, title, message) {
|
||||
powerConfirmAction = action
|
||||
powerConfirmTitle = title
|
||||
powerConfirmMessage = message
|
||||
open()
|
||||
}
|
||||
|
||||
function executePowerAction(action) {
|
||||
switch (action) {
|
||||
case "logout":
|
||||
NiriService.quit()
|
||||
break
|
||||
case "suspend":
|
||||
Quickshell.execDetached(["systemctl", "suspend"])
|
||||
break
|
||||
case "reboot":
|
||||
Quickshell.execDetached(["systemctl", "reboot"])
|
||||
break
|
||||
case "poweroff":
|
||||
Quickshell.execDetached(["systemctl", "poweroff"])
|
||||
break
|
||||
function show(action, title, message) {
|
||||
powerConfirmAction = action
|
||||
powerConfirmTitle = title
|
||||
powerConfirmMessage = message
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
shouldBeVisible: false
|
||||
width: 350
|
||||
height: 160
|
||||
enableShadow: false
|
||||
onBackgroundClicked: {
|
||||
close()
|
||||
}
|
||||
|
||||
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
|
||||
function executePowerAction(action) {
|
||||
switch (action) {
|
||||
case "logout":
|
||||
NiriService.quit()
|
||||
break
|
||||
case "suspend":
|
||||
Quickshell.execDetached(["systemctl", "suspend"])
|
||||
break
|
||||
case "reboot":
|
||||
Quickshell.execDetached(["systemctl", "reboot"])
|
||||
break
|
||||
case "poweroff":
|
||||
Quickshell.execDetached(["systemctl", "poweroff"])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: powerConfirmMessage
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
shouldBeVisible: false
|
||||
width: 350
|
||||
height: 160
|
||||
enableShadow: false
|
||||
onBackgroundClicked: {
|
||||
close()
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
height: Theme.spacingS
|
||||
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
|
||||
}
|
||||
|
||||
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: {
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: {
|
||||
close()
|
||||
executePowerAction(powerConfirmAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: {
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: {
|
||||
close()
|
||||
executePowerAction(powerConfirmAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,29 +19,28 @@ DankModal {
|
||||
|
||||
function show() {
|
||||
if (!DgopService.dgopAvailable) {
|
||||
console.warn("ProcessListModal: dgop is not available");
|
||||
return ;
|
||||
console.warn("ProcessListModal: dgop is not available")
|
||||
return
|
||||
}
|
||||
open();
|
||||
UserInfoService.getUptime();
|
||||
open()
|
||||
UserInfoService.getUptime()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
close();
|
||||
close()
|
||||
if (processContextMenu.visible)
|
||||
processContextMenu.close();
|
||||
|
||||
processContextMenu.close()
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (!DgopService.dgopAvailable) {
|
||||
console.warn("ProcessListModal: dgop is not available");
|
||||
return ;
|
||||
console.warn("ProcessListModal: dgop is not available")
|
||||
return
|
||||
}
|
||||
if (shouldBeVisible)
|
||||
hide();
|
||||
hide()
|
||||
else
|
||||
show();
|
||||
show()
|
||||
}
|
||||
|
||||
width: 900
|
||||
@@ -58,23 +57,18 @@ DankModal {
|
||||
ProcessesTab {
|
||||
contextMenu: processContextMenu
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: performanceTabComponent
|
||||
|
||||
PerformanceTab {
|
||||
}
|
||||
|
||||
PerformanceTab {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: systemTabComponent
|
||||
|
||||
SystemTab {
|
||||
}
|
||||
|
||||
SystemTab {}
|
||||
}
|
||||
|
||||
ProcessContextMenu {
|
||||
@@ -85,19 +79,19 @@ DankModal {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
Keys.onPressed: function(event) {
|
||||
Keys.onPressed: function (event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
processListModal.hide();
|
||||
event.accepted = true;
|
||||
processListModal.hide()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_1) {
|
||||
currentTab = 0;
|
||||
event.accepted = true;
|
||||
currentTab = 0
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_2) {
|
||||
currentTab = 1;
|
||||
event.accepted = true;
|
||||
currentTab = 1
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_3) {
|
||||
currentTab = 2;
|
||||
event.accepted = true;
|
||||
currentTab = 2
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,9 +133,7 @@ DankModal {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
@@ -175,7 +167,6 @@ DankModal {
|
||||
onClicked: processListModal.hide()
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -210,17 +201,18 @@ DankModal {
|
||||
name: {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return "list_alt";
|
||||
return "list_alt"
|
||||
case 1:
|
||||
return "analytics";
|
||||
return "analytics"
|
||||
case 2:
|
||||
return "settings";
|
||||
return "settings"
|
||||
default:
|
||||
return "tab";
|
||||
return "tab"
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize - 2
|
||||
color: currentTab === index ? Theme.primary : Theme.surfaceText
|
||||
color: currentTab
|
||||
=== index ? Theme.primary : Theme.surfaceText
|
||||
opacity: currentTab === index ? 1 : 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -228,16 +220,15 @@ DankModal {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: currentTab === index ? Theme.primary : Theme.surfaceText
|
||||
color: currentTab
|
||||
=== index ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: -1
|
||||
|
||||
@@ -245,11 +236,8 @@ DankModal {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -259,7 +247,7 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
currentTab = index;
|
||||
currentTab = index
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,22 +255,16 @@ DankModal {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -308,9 +290,7 @@ DankModal {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -328,9 +308,7 @@ DankModal {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -348,17 +326,10 @@ DankModal {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,21 +14,21 @@ DankModal {
|
||||
|
||||
property Component settingsContent
|
||||
|
||||
signal closingModal()
|
||||
signal closingModal
|
||||
|
||||
function show() {
|
||||
open();
|
||||
open()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
close();
|
||||
close()
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (shouldBeVisible)
|
||||
hide();
|
||||
hide()
|
||||
else
|
||||
show();
|
||||
show()
|
||||
}
|
||||
|
||||
objectName: "settingsModal"
|
||||
@@ -40,18 +40,18 @@ DankModal {
|
||||
|
||||
IpcHandler {
|
||||
function open() {
|
||||
settingsModal.show();
|
||||
return "SETTINGS_OPEN_SUCCESS";
|
||||
settingsModal.show()
|
||||
return "SETTINGS_OPEN_SUCCESS"
|
||||
}
|
||||
|
||||
function close() {
|
||||
settingsModal.hide();
|
||||
return "SETTINGS_CLOSE_SUCCESS";
|
||||
settingsModal.hide()
|
||||
return "SETTINGS_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
settingsModal.toggle();
|
||||
return "SETTINGS_TOGGLE_SUCCESS";
|
||||
settingsModal.toggle()
|
||||
return "SETTINGS_TOGGLE_SUCCESS"
|
||||
}
|
||||
|
||||
target: "settings"
|
||||
@@ -103,7 +103,6 @@ DankModal {
|
||||
hoverColor: Theme.errorHover
|
||||
onClicked: settingsModal.hide()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Main content with side navigation
|
||||
@@ -153,7 +152,8 @@ DankModal {
|
||||
height: 80
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
property bool hasImage: profileImageSource.status === Image.Ready
|
||||
property bool hasImage: profileImageSource.status
|
||||
=== Image.Ready
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -168,10 +168,11 @@ DankModal {
|
||||
id: profileImageSource
|
||||
source: {
|
||||
if (PortalService.profileImage === "")
|
||||
return "";
|
||||
if (PortalService.profileImage.startsWith("/"))
|
||||
return "file://" + PortalService.profileImage;
|
||||
return PortalService.profileImage;
|
||||
return ""
|
||||
if (PortalService.profileImage.startsWith(
|
||||
"/"))
|
||||
return "file://" + PortalService.profileImage
|
||||
return PortalService.profileImage
|
||||
}
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
@@ -226,7 +227,8 @@ DankModal {
|
||||
name: "warning"
|
||||
size: Theme.iconSizeLarge
|
||||
color: Theme.error
|
||||
visible: PortalService.profileImage !== "" && profileImageSource.status === Image.Error
|
||||
visible: PortalService.profileImage !== ""
|
||||
&& profileImageSource.status === Image.Error
|
||||
}
|
||||
|
||||
// Hover overlay with edit and clear buttons
|
||||
@@ -235,54 +237,58 @@ DankModal {
|
||||
radius: width / 2
|
||||
color: Qt.rgba(0, 0, 0, 0.7)
|
||||
visible: profileMouseArea.containsMouse
|
||||
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: Qt.rgba(255, 255, 255, 0.9)
|
||||
|
||||
color: Qt.rgba(255, 255,
|
||||
255, 0.9)
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "edit"
|
||||
size: 16
|
||||
color: "black"
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
settingsModal.allowFocusOverride = true;
|
||||
settingsModal.shouldHaveFocus = false;
|
||||
profileBrowser.open();
|
||||
settingsModal.allowFocusOverride = true
|
||||
settingsModal.shouldHaveFocus = false
|
||||
profileBrowser.open(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: Qt.rgba(255, 255, 255, 0.9)
|
||||
color: Qt.rgba(255, 255,
|
||||
255, 0.9)
|
||||
visible: profileImageContainer.hasImage
|
||||
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "close"
|
||||
size: 16
|
||||
color: "black"
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
PortalService.setProfileImage("");
|
||||
PortalService.setProfileImage(
|
||||
"")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,7 +312,8 @@ DankModal {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: UserInfoService.fullName || "User"
|
||||
text: UserInfoService.fullName
|
||||
|| "User"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
@@ -315,7 +322,8 @@ DankModal {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.distribution || "Linux"
|
||||
text: DgopService.distribution
|
||||
|| "Linux"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
@@ -341,33 +349,33 @@ DankModal {
|
||||
id: sidebarRepeater
|
||||
|
||||
model: [{
|
||||
"text": "Personalization",
|
||||
"icon": "person"
|
||||
}, {
|
||||
"text": "Time & Date",
|
||||
"icon": "schedule"
|
||||
}, {
|
||||
"text": "Weather",
|
||||
"icon": "cloud"
|
||||
}, {
|
||||
"text": "Top Bar",
|
||||
"icon": "toolbar"
|
||||
}, {
|
||||
"text": "Widgets",
|
||||
"icon": "widgets"
|
||||
}, {
|
||||
"text": "Dock",
|
||||
"icon": "dock_to_bottom"
|
||||
}, {
|
||||
"text": "Recent Apps",
|
||||
"icon": "history"
|
||||
}, {
|
||||
"text": "Theme & Colors",
|
||||
"icon": "palette"
|
||||
}, {
|
||||
"text": "About",
|
||||
"icon": "info"
|
||||
}]
|
||||
"text": "Personalization",
|
||||
"icon": "person"
|
||||
}, {
|
||||
"text": "Time & Date",
|
||||
"icon": "schedule"
|
||||
}, {
|
||||
"text": "Weather",
|
||||
"icon": "cloud"
|
||||
}, {
|
||||
"text": "Top Bar",
|
||||
"icon": "toolbar"
|
||||
}, {
|
||||
"text": "Widgets",
|
||||
"icon": "widgets"
|
||||
}, {
|
||||
"text": "Dock",
|
||||
"icon": "dock_to_bottom"
|
||||
}, {
|
||||
"text": "Recent Apps",
|
||||
"icon": "history"
|
||||
}, {
|
||||
"text": "Theme & Colors",
|
||||
"icon": "palette"
|
||||
}, {
|
||||
"text": "About",
|
||||
"icon": "info"
|
||||
}]
|
||||
|
||||
Rectangle {
|
||||
property bool isActive: sidebarContainer.currentIndex === index
|
||||
@@ -397,7 +405,6 @@ DankModal {
|
||||
font.weight: parent.parent.isActive ? Font.Medium : Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -407,7 +414,7 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
sidebarContainer.currentIndex = index;
|
||||
sidebarContainer.currentIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,15 +423,10 @@ DankModal {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Main content area
|
||||
@@ -452,9 +454,7 @@ DankModal {
|
||||
PersonalizationTab {
|
||||
parentModal: settingsModal
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -509,11 +509,8 @@ DankModal {
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Component {
|
||||
DockTab {
|
||||
}
|
||||
|
||||
DockTab {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -545,210 +542,224 @@ DankModal {
|
||||
asynchronous: true
|
||||
sourceComponent: AboutTab {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Footer
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
|
||||
// Dank logo
|
||||
Item {
|
||||
width: 68
|
||||
height: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Modals/", "") + "/assets/dank.svg"
|
||||
source: Qt.resolvedUrl(".").toString().replace(
|
||||
"file://",
|
||||
"").replace("/Modals/",
|
||||
"") + "/assets/dank.svg"
|
||||
sourceSize: Qt.size(68, 16)
|
||||
smooth: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
layer.enabled: true
|
||||
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
colorization: 1
|
||||
colorizationColor: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Qt.openUrlExternally("https://github.com/AvengeMedia/DankMaterialShell")
|
||||
onClicked: Qt.openUrlExternally(
|
||||
"https://github.com/AvengeMedia/DankMaterialShell")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: Theme.spacingXS
|
||||
height: 1
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
|
||||
// Niri logo
|
||||
Item {
|
||||
width: 24
|
||||
height: 24
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Modals/", "") + "/assets/niri.svg"
|
||||
source: Qt.resolvedUrl(".").toString().replace(
|
||||
"file://",
|
||||
"").replace("/Modals/",
|
||||
"") + "/assets/niri.svg"
|
||||
sourceSize: Qt.size(24, 24)
|
||||
smooth: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Qt.openUrlExternally("https://github.com/YaLTeR/niri")
|
||||
onClicked: Qt.openUrlExternally(
|
||||
"https://github.com/YaLTeR/niri")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: Theme.spacingXS
|
||||
height: 1
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: Theme.spacingM
|
||||
height: 1
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
|
||||
// Matrix button
|
||||
Item {
|
||||
width: 32
|
||||
height: 20
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Modals/", "") + "/assets/matrix-logo-white.svg"
|
||||
source: Qt.resolvedUrl(".").toString().replace(
|
||||
"file://", "").replace(
|
||||
"/Modals/",
|
||||
"") + "/assets/matrix-logo-white.svg"
|
||||
sourceSize: Qt.size(32, 20)
|
||||
smooth: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
layer.enabled: true
|
||||
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
colorization: 1
|
||||
colorizationColor: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Qt.openUrlExternally("https://matrix.to/#/#niri:matrix.org")
|
||||
onClicked: Qt.openUrlExternally(
|
||||
"https://matrix.to/#/#niri:matrix.org")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: Theme.spacingM
|
||||
height: 1
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: Theme.spacingM
|
||||
height: 1
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
|
||||
// Discord button
|
||||
Item {
|
||||
width: 16
|
||||
height: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Modals/", "") + "/assets/discord.svg"
|
||||
source: Qt.resolvedUrl(".").toString().replace(
|
||||
"file://",
|
||||
"").replace("/Modals/",
|
||||
"") + "/assets/discord.svg"
|
||||
sourceSize: Qt.size(16, 16)
|
||||
smooth: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Qt.openUrlExternally("https://discord.gg/vT8Sfjy7sx")
|
||||
onClicked: Qt.openUrlExternally(
|
||||
"https://discord.gg/vT8Sfjy7sx")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: Theme.spacingM
|
||||
height: 1
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: Theme.spacingM
|
||||
height: 1
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
|
||||
// Reddit button
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Modals/", "") + "/assets/reddit.svg"
|
||||
source: Qt.resolvedUrl(".").toString().replace(
|
||||
"file://",
|
||||
"").replace("/Modals/",
|
||||
"") + "/assets/reddit.svg"
|
||||
sourceSize: Qt.size(18, 18)
|
||||
smooth: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Qt.openUrlExternally("https://reddit.com/r/niri")
|
||||
onClicked: Qt.openUrlExternally(
|
||||
"https://reddit.com/r/niri")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
@@ -758,18 +769,17 @@ DankModal {
|
||||
browserIcon: "person"
|
||||
browserType: "profile"
|
||||
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
|
||||
onFileSelected: (path) => {
|
||||
PortalService.setProfileImage(path);
|
||||
close();
|
||||
}
|
||||
onFileSelected: path => {
|
||||
PortalService.setProfileImage(path)
|
||||
close()
|
||||
}
|
||||
onDialogClosed: {
|
||||
if (settingsModal) {
|
||||
settingsModal.allowFocusOverride = false;
|
||||
settingsModal.allowFocusOverride = false
|
||||
settingsModal.shouldHaveFocus = Qt.binding(() => {
|
||||
return settingsModal.shouldBeVisible;
|
||||
});
|
||||
return settingsModal.shouldBeVisible
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,41 +16,41 @@ DankModal {
|
||||
property Component spotlightContent
|
||||
|
||||
function show() {
|
||||
spotlightOpen = true;
|
||||
open();
|
||||
spotlightOpen = true
|
||||
open()
|
||||
if (contentLoader.item && contentLoader.item.appLauncher)
|
||||
contentLoader.item.appLauncher.searchQuery = "";
|
||||
contentLoader.item.appLauncher.searchQuery = ""
|
||||
|
||||
Qt.callLater(function() {
|
||||
Qt.callLater(function () {
|
||||
if (contentLoader.item && contentLoader.item.searchField)
|
||||
contentLoader.item.searchField.forceActiveFocus();
|
||||
|
||||
});
|
||||
contentLoader.item.searchField.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
|
||||
function hide() {
|
||||
spotlightOpen = false;
|
||||
close();
|
||||
spotlightOpen = false
|
||||
close()
|
||||
if (contentLoader.item && contentLoader.item.appLauncher) {
|
||||
contentLoader.item.appLauncher.searchQuery = "";
|
||||
contentLoader.item.appLauncher.selectedIndex = 0;
|
||||
contentLoader.item.appLauncher.setCategory("All");
|
||||
contentLoader.item.appLauncher.searchQuery = ""
|
||||
contentLoader.item.appLauncher.selectedIndex = 0
|
||||
contentLoader.item.appLauncher.setCategory("All")
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (spotlightOpen)
|
||||
hide();
|
||||
hide()
|
||||
else
|
||||
show();
|
||||
show()
|
||||
}
|
||||
|
||||
shouldBeVisible: spotlightOpen
|
||||
|
||||
|
||||
Connections {
|
||||
target: ModalManager
|
||||
function onCloseAllModalsExcept(excludedModal) {
|
||||
if (excludedModal !== spotlightModal && !allowStacking && spotlightOpen) {
|
||||
if (excludedModal !== spotlightModal && !allowStacking
|
||||
&& spotlightOpen) {
|
||||
spotlightOpen = false
|
||||
}
|
||||
}
|
||||
@@ -64,43 +64,41 @@ DankModal {
|
||||
enableShadow: true
|
||||
onVisibleChanged: {
|
||||
if (visible && !spotlightOpen)
|
||||
show();
|
||||
show()
|
||||
|
||||
if (visible && contentLoader.item)
|
||||
Qt.callLater(function() {
|
||||
Qt.callLater(function () {
|
||||
if (contentLoader.item.searchField)
|
||||
contentLoader.item.searchField.forceActiveFocus();
|
||||
|
||||
});
|
||||
|
||||
contentLoader.item.searchField.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
onBackgroundClicked: {
|
||||
hide();
|
||||
hide()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
|
||||
}
|
||||
content: spotlightContent
|
||||
|
||||
IpcHandler {
|
||||
function open() {
|
||||
spotlightModal.show();
|
||||
return "SPOTLIGHT_OPEN_SUCCESS";
|
||||
spotlightModal.show()
|
||||
return "SPOTLIGHT_OPEN_SUCCESS"
|
||||
}
|
||||
|
||||
function close() {
|
||||
spotlightModal.hide();
|
||||
return "SPOTLIGHT_CLOSE_SUCCESS";
|
||||
spotlightModal.hide()
|
||||
return "SPOTLIGHT_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
spotlightModal.toggle();
|
||||
return "SPOTLIGHT_TOGGLE_SUCCESS";
|
||||
spotlightModal.toggle()
|
||||
return "SPOTLIGHT_TOGGLE_SUCCESS"
|
||||
}
|
||||
|
||||
target: "spotlight"
|
||||
}
|
||||
|
||||
|
||||
spotlightContent: Component {
|
||||
Item {
|
||||
id: spotlightKeyHandler
|
||||
@@ -110,29 +108,34 @@ DankModal {
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
Keys.onPressed: function(event) {
|
||||
Keys.onPressed: function (event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
hide();
|
||||
event.accepted = true;
|
||||
hide()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Down) {
|
||||
appLauncher.selectNext();
|
||||
event.accepted = true;
|
||||
appLauncher.selectNext()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
appLauncher.selectPrevious();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
appLauncher.launchSelected();
|
||||
event.accepted = true;
|
||||
} else if (!searchField.activeFocus && event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\\s]/)) {
|
||||
searchField.forceActiveFocus();
|
||||
searchField.insertText(event.text);
|
||||
event.accepted = true;
|
||||
appLauncher.selectPrevious()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Right
|
||||
&& appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Left
|
||||
&& appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Return
|
||||
|| event.key === Qt.Key_Enter) {
|
||||
appLauncher.launchSelected()
|
||||
event.accepted = true
|
||||
} else if (!searchField.activeFocus && event.text
|
||||
&& event.text.length > 0 && event.text.match(
|
||||
/[a-zA-Z0-9\\s]/)) {
|
||||
searchField.forceActiveFocus()
|
||||
searchField.insertText(event.text)
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,8 +145,8 @@ DankModal {
|
||||
viewMode: SettingsData.spotlightModalViewMode
|
||||
gridColumns: 4
|
||||
onAppLaunched: hide()
|
||||
onViewModeSelected: function(mode) {
|
||||
SettingsData.setSpotlightModalViewMode(mode);
|
||||
onViewModeSelected: function (mode) {
|
||||
SettingsData.setSpotlightModalViewMode(mode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +162,8 @@ DankModal {
|
||||
color: Theme.surfaceVariantAlpha
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
visible: appLauncher.categories.length > 1 || appLauncher.model.count > 0
|
||||
visible: appLauncher.categories.length > 1
|
||||
|| appLauncher.model.count > 0
|
||||
|
||||
CategorySelector {
|
||||
id: categorySelector
|
||||
@@ -169,11 +173,11 @@ DankModal {
|
||||
categories: appLauncher.categories
|
||||
selectedCategory: appLauncher.selectedCategory
|
||||
compact: false
|
||||
onCategorySelected: (category) => {
|
||||
return appLauncher.setCategory(category);
|
||||
}
|
||||
onCategorySelected: category => {
|
||||
return appLauncher.setCategory(
|
||||
category)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -183,10 +187,16 @@ DankModal {
|
||||
DankTextField {
|
||||
id: searchField
|
||||
|
||||
width: parent.width - 80 - Theme.spacingM // Leave space for view toggle buttons
|
||||
width: parent.width - 80
|
||||
- Theme.spacingM // Leave space for view toggle buttons
|
||||
height: 56
|
||||
cornerRadius: Theme.cornerRadius
|
||||
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7)
|
||||
backgroundColor: Qt.rgba(
|
||||
Theme.surfaceVariant.r,
|
||||
Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b,
|
||||
Theme.getContentBackgroundAlpha(
|
||||
) * 0.7)
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
focusedBorderColor: Theme.primary
|
||||
leftIconName: "search"
|
||||
@@ -202,22 +212,26 @@ DankModal {
|
||||
keyForwardTargets: [spotlightKeyHandler]
|
||||
text: appLauncher.searchQuery
|
||||
onTextEdited: {
|
||||
appLauncher.searchQuery = text;
|
||||
}
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
hide();
|
||||
event.accepted = true;
|
||||
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length > 0) {
|
||||
if (appLauncher.keyboardNavigationActive && appLauncher.model.count > 0)
|
||||
appLauncher.launchSelected();
|
||||
else if (appLauncher.model.count > 0)
|
||||
appLauncher.launchApp(appLauncher.model.get(0));
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || event.key === Qt.Key_Left || event.key === Qt.Key_Right || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
|
||||
event.accepted = false;
|
||||
}
|
||||
appLauncher.searchQuery = text
|
||||
}
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
hide()
|
||||
event.accepted = true
|
||||
} else if ((event.key === Qt.Key_Return
|
||||
|| event.key === Qt.Key_Enter)
|
||||
&& text.length > 0) {
|
||||
if (appLauncher.keyboardNavigationActive
|
||||
&& appLauncher.model.count > 0)
|
||||
appLauncher.launchSelected()
|
||||
else if (appLauncher.model.count > 0)
|
||||
appLauncher.launchApp(
|
||||
appLauncher.model.get(0))
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || event.key === Qt.Key_Left || event.key === Qt.Key_Right || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
|
||||
event.accepted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -230,14 +244,16 @@ DankModal {
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: appLauncher.viewMode === "list" ? Theme.primaryHover : listViewArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
border.color: appLauncher.viewMode === "list" ? Theme.primarySelected : "transparent"
|
||||
border.color: appLauncher.viewMode
|
||||
=== "list" ? Theme.primarySelected : "transparent"
|
||||
border.width: 1
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "view_list"
|
||||
size: 18
|
||||
color: appLauncher.viewMode === "list" ? Theme.primary : Theme.surfaceText
|
||||
color: appLauncher.viewMode
|
||||
=== "list" ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -247,10 +263,9 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
appLauncher.setViewMode("list");
|
||||
appLauncher.setViewMode("list")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -258,14 +273,16 @@ DankModal {
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: appLauncher.viewMode === "grid" ? Theme.primaryHover : gridViewArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
border.color: appLauncher.viewMode === "grid" ? Theme.primarySelected : "transparent"
|
||||
border.color: appLauncher.viewMode
|
||||
=== "grid" ? Theme.primarySelected : "transparent"
|
||||
border.width: 1
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "grid_view"
|
||||
size: 18
|
||||
color: appLauncher.viewMode === "grid" ? Theme.primary : Theme.surfaceText
|
||||
color: appLauncher.viewMode
|
||||
=== "grid" ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -275,14 +292,11 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
appLauncher.setViewMode("grid");
|
||||
appLauncher.setViewMode("grid")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -305,20 +319,20 @@ DankModal {
|
||||
property bool hoverUpdatesSelection: false
|
||||
property bool keyboardNavigationActive: appLauncher.keyboardNavigationActive
|
||||
|
||||
signal keyboardNavigationReset()
|
||||
signal keyboardNavigationReset
|
||||
signal itemClicked(int index, var modelData)
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= count)
|
||||
return ;
|
||||
return
|
||||
|
||||
var itemY = index * (itemHeight + itemSpacing);
|
||||
var itemBottom = itemY + itemHeight;
|
||||
var itemY = index * (itemHeight + itemSpacing)
|
||||
var itemBottom = itemY + itemHeight
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
contentY = itemY
|
||||
else if (itemBottom > contentY + height)
|
||||
contentY = itemBottom - height;
|
||||
contentY = itemBottom - height
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
@@ -334,17 +348,16 @@ DankModal {
|
||||
reuseItems: true
|
||||
onCurrentIndexChanged: {
|
||||
if (keyboardNavigationActive)
|
||||
ensureVisible(currentIndex);
|
||||
|
||||
ensureVisible(currentIndex)
|
||||
}
|
||||
onItemClicked: function(index, modelData) {
|
||||
appLauncher.launchApp(modelData);
|
||||
onItemClicked: function (index, modelData) {
|
||||
appLauncher.launchApp(modelData)
|
||||
}
|
||||
onItemRightClicked: function(index, modelData, mouseX, mouseY) {
|
||||
contextMenu.show(mouseX, mouseY, modelData);
|
||||
onItemRightClicked: function (index, modelData, mouseX, mouseY) {
|
||||
contextMenu.show(mouseX, mouseY, modelData)
|
||||
}
|
||||
onKeyboardNavigationReset: {
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
appLauncher.keyboardNavigationActive = false
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
@@ -377,7 +390,9 @@ DankModal {
|
||||
id: listIconImg
|
||||
|
||||
anchors.fill: parent
|
||||
source: (model.icon) ? Quickshell.iconPath(model.icon, SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme) : ""
|
||||
source: (model.icon) ? Quickshell.iconPath(
|
||||
model.icon,
|
||||
SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme) : ""
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
@@ -393,14 +408,16 @@ DankModal {
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
|
||||
text: (model.name
|
||||
&& model.name.length
|
||||
> 0) ? model.name.charAt(
|
||||
0).toUpperCase(
|
||||
) : "A"
|
||||
font.pixelSize: resultsList.iconSize * 0.4
|
||||
color: Theme.primary
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -423,11 +440,11 @@ DankModal {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
visible: resultsList.showDescription && model.comment && model.comment.length > 0
|
||||
visible: resultsList.showDescription
|
||||
&& model.comment
|
||||
&& model.comment.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -439,25 +456,28 @@ DankModal {
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
z: 10
|
||||
onEntered: {
|
||||
if (resultsList.hoverUpdatesSelection && !resultsList.keyboardNavigationActive)
|
||||
resultsList.currentIndex = index;
|
||||
|
||||
if (resultsList.hoverUpdatesSelection
|
||||
&& !resultsList.keyboardNavigationActive)
|
||||
resultsList.currentIndex = index
|
||||
}
|
||||
onPositionChanged: {
|
||||
resultsList.keyboardNavigationReset();
|
||||
}
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
resultsList.itemClicked(index, model);
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
var modalPos = mapToItem(spotlightKeyHandler, mouse.x, mouse.y);
|
||||
resultsList.itemRightClicked(index, model, modalPos.x, modalPos.y);
|
||||
}
|
||||
resultsList.keyboardNavigationReset()
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
resultsList.itemClicked(
|
||||
index, model)
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
var modalPos = mapToItem(
|
||||
spotlightKeyHandler,
|
||||
mouse.x, mouse.y)
|
||||
resultsList.itemRightClicked(
|
||||
index, model,
|
||||
modalPos.x, modalPos.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankGridView {
|
||||
@@ -474,25 +494,30 @@ DankModal {
|
||||
property int minIconSize: 32
|
||||
property bool hoverUpdatesSelection: false
|
||||
property bool keyboardNavigationActive: appLauncher.keyboardNavigationActive
|
||||
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
|
||||
property int baseCellWidth: adaptiveColumns ? Math.max(
|
||||
minCellWidth,
|
||||
Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
|
||||
property int baseCellHeight: baseCellWidth + 20
|
||||
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
|
||||
property int actualColumns: adaptiveColumns ? Math.floor(
|
||||
width
|
||||
/ cellWidth) : columns
|
||||
property int remainingSpace: width - (actualColumns * cellWidth)
|
||||
|
||||
signal keyboardNavigationReset()
|
||||
signal keyboardNavigationReset
|
||||
signal itemClicked(int index, var modelData)
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= count)
|
||||
return ;
|
||||
return
|
||||
|
||||
var itemY = Math.floor(index / actualColumns) * cellHeight;
|
||||
var itemBottom = itemY + cellHeight;
|
||||
var itemY = Math.floor(
|
||||
index / actualColumns) * cellHeight
|
||||
var itemBottom = itemY + cellHeight
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
contentY = itemY
|
||||
else if (itemBottom > contentY + height)
|
||||
contentY = itemBottom - height;
|
||||
contentY = itemBottom - height
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
@@ -510,17 +535,16 @@ DankModal {
|
||||
reuseItems: true
|
||||
onCurrentIndexChanged: {
|
||||
if (keyboardNavigationActive)
|
||||
ensureVisible(currentIndex);
|
||||
|
||||
ensureVisible(currentIndex)
|
||||
}
|
||||
onItemClicked: function(index, modelData) {
|
||||
appLauncher.launchApp(modelData);
|
||||
onItemClicked: function (index, modelData) {
|
||||
appLauncher.launchApp(modelData)
|
||||
}
|
||||
onItemRightClicked: function(index, modelData, mouseX, mouseY) {
|
||||
contextMenu.show(mouseX, mouseY, modelData);
|
||||
onItemRightClicked: function (index, modelData, mouseX, mouseY) {
|
||||
contextMenu.show(mouseX, mouseY, modelData)
|
||||
}
|
||||
onKeyboardNavigationReset: {
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
appLauncher.keyboardNavigationActive = false
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
@@ -536,7 +560,8 @@ DankModal {
|
||||
height: resultsGrid.cellHeight - resultsGrid.cellPadding
|
||||
radius: Theme.cornerRadius
|
||||
color: resultsGrid.currentIndex === index ? Theme.primaryPressed : gridMouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03)
|
||||
border.color: resultsGrid.currentIndex === index ? Theme.primarySelected : Theme.outlineMedium
|
||||
border.color: resultsGrid.currentIndex
|
||||
=== index ? Theme.primarySelected : Theme.outlineMedium
|
||||
border.width: resultsGrid.currentIndex === index ? 2 : 1
|
||||
|
||||
Column {
|
||||
@@ -544,7 +569,12 @@ DankModal {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
property int iconSize: Math.min(resultsGrid.maxIconSize, Math.max(resultsGrid.minIconSize, resultsGrid.cellWidth * resultsGrid.iconSizeRatio))
|
||||
property int iconSize: Math.min(
|
||||
resultsGrid.maxIconSize,
|
||||
Math.max(
|
||||
resultsGrid.minIconSize,
|
||||
resultsGrid.cellWidth
|
||||
* resultsGrid.iconSizeRatio))
|
||||
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
@@ -554,7 +584,9 @@ DankModal {
|
||||
id: gridIconImg
|
||||
|
||||
anchors.fill: parent
|
||||
source: (model.icon) ? Quickshell.iconPath(model.icon, SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme) : ""
|
||||
source: (model.icon) ? Quickshell.iconPath(
|
||||
model.icon,
|
||||
SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme) : ""
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
@@ -570,14 +602,18 @@ DankModal {
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
|
||||
font.pixelSize: Math.min(28, parent.width * 0.5)
|
||||
text: (model.name
|
||||
&& model.name.length
|
||||
> 0) ? model.name.charAt(
|
||||
0).toUpperCase(
|
||||
) : "A"
|
||||
font.pixelSize: Math.min(
|
||||
28,
|
||||
parent.width * 0.5)
|
||||
color: Theme.primary
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -592,7 +628,6 @@ DankModal {
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -604,29 +639,30 @@ DankModal {
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
z: 10
|
||||
onEntered: {
|
||||
if (resultsGrid.hoverUpdatesSelection && !resultsGrid.keyboardNavigationActive)
|
||||
resultsGrid.currentIndex = index;
|
||||
|
||||
if (resultsGrid.hoverUpdatesSelection
|
||||
&& !resultsGrid.keyboardNavigationActive)
|
||||
resultsGrid.currentIndex = index
|
||||
}
|
||||
onPositionChanged: {
|
||||
resultsGrid.keyboardNavigationReset();
|
||||
}
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
resultsGrid.itemClicked(index, model);
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
var modalPos = mapToItem(spotlightKeyHandler, mouse.x, mouse.y);
|
||||
resultsGrid.itemRightClicked(index, model, modalPos.x, modalPos.y);
|
||||
}
|
||||
resultsGrid.keyboardNavigationReset()
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
resultsGrid.itemClicked(
|
||||
index, model)
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
var modalPos = mapToItem(
|
||||
spotlightKeyHandler,
|
||||
mouse.x, mouse.y)
|
||||
resultsGrid.itemRightClicked(
|
||||
index, model,
|
||||
modalPos.x, modalPos.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -637,23 +673,29 @@ DankModal {
|
||||
|
||||
function show(x, y, app) {
|
||||
currentApp = app
|
||||
|
||||
|
||||
const menuWidth = 180
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
let finalX = x + 8
|
||||
let finalY = y + 8
|
||||
|
||||
|
||||
if (finalX + menuWidth > spotlightKeyHandler.width) {
|
||||
finalX = x - menuWidth - 8
|
||||
}
|
||||
|
||||
|
||||
if (finalY + menuHeight > spotlightKeyHandler.height) {
|
||||
finalY = y - menuHeight - 8
|
||||
}
|
||||
|
||||
finalX = Math.max(8, Math.min(finalX, spotlightKeyHandler.width - menuWidth - 8))
|
||||
finalY = Math.max(8, Math.min(finalY, spotlightKeyHandler.height - menuHeight - 8))
|
||||
|
||||
|
||||
finalX = Math.max(
|
||||
8, Math.min(
|
||||
finalX,
|
||||
spotlightKeyHandler.width - menuWidth - 8))
|
||||
finalY = Math.max(
|
||||
8, Math.min(
|
||||
finalY,
|
||||
spotlightKeyHandler.height - menuHeight - 8))
|
||||
|
||||
contextMenu.x = finalX
|
||||
contextMenu.y = finalY
|
||||
contextMenu.visible = true
|
||||
@@ -663,8 +705,8 @@ DankModal {
|
||||
function close() {
|
||||
contextMenu.menuVisible = false
|
||||
Qt.callLater(() => {
|
||||
contextMenu.visible = false
|
||||
})
|
||||
contextMenu.visible = false
|
||||
})
|
||||
}
|
||||
|
||||
visible: false
|
||||
@@ -672,7 +714,8 @@ DankModal {
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
z: 1000
|
||||
opacity: menuVisible ? 1 : 0
|
||||
@@ -700,7 +743,11 @@ DankModal {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
color: pinMouseArea.containsMouse ? Qt.rgba(
|
||||
Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -710,11 +757,15 @@ DankModal {
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
|
||||
if (!contextMenu.currentApp
|
||||
|| !contextMenu.currentApp.desktopEntry)
|
||||
return "push_pin"
|
||||
|
||||
var appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
|
||||
return SessionData.isPinnedApp(appId) ? "keep_off" : "push_pin"
|
||||
var appId = contextMenu.currentApp.desktopEntry.id
|
||||
|| contextMenu.currentApp.desktopEntry.execString
|
||||
|| ""
|
||||
return SessionData.isPinnedApp(
|
||||
appId) ? "keep_off" : "push_pin"
|
||||
}
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
@@ -724,11 +775,15 @@ DankModal {
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
|
||||
if (!contextMenu.currentApp
|
||||
|| !contextMenu.currentApp.desktopEntry)
|
||||
return "Pin to Dock"
|
||||
|
||||
var appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
|
||||
return SessionData.isPinnedApp(appId) ? "Unpin from Dock" : "Pin to Dock"
|
||||
var appId = contextMenu.currentApp.desktopEntry.id
|
||||
|| contextMenu.currentApp.desktopEntry.execString
|
||||
|| ""
|
||||
return SessionData.isPinnedApp(
|
||||
appId) ? "Unpin from Dock" : "Pin to Dock"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
@@ -744,10 +799,13 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
|
||||
if (!contextMenu.currentApp
|
||||
|| !contextMenu.currentApp.desktopEntry)
|
||||
return
|
||||
|
||||
var appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
|
||||
var appId = contextMenu.currentApp.desktopEntry.id
|
||||
|| contextMenu.currentApp.desktopEntry.execString
|
||||
|| ""
|
||||
if (SessionData.isPinnedApp(appId))
|
||||
SessionData.removePinnedApp(appId)
|
||||
else
|
||||
@@ -767,7 +825,8 @@ DankModal {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -775,7 +834,11 @@ DankModal {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
color: launchMouseArea.containsMouse ? Qt.rgba(
|
||||
Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -808,7 +871,8 @@ DankModal {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (contextMenu.currentApp)
|
||||
appLauncher.launchApp(contextMenu.currentApp)
|
||||
appLauncher.launchApp(
|
||||
contextMenu.currentApp)
|
||||
|
||||
contextMenu.close()
|
||||
}
|
||||
@@ -845,13 +909,11 @@ DankModal {
|
||||
width: contextMenu.width
|
||||
height: contextMenu.height
|
||||
onClicked: {
|
||||
|
||||
// Prevent closing when clicking on the menu itself
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,300 +5,306 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property string wifiPasswordSSID: ""
|
||||
property string wifiPasswordInput: ""
|
||||
property string wifiPasswordSSID: ""
|
||||
property string wifiPasswordInput: ""
|
||||
|
||||
function show(ssid) {
|
||||
wifiPasswordSSID = ssid
|
||||
wifiPasswordInput = ""
|
||||
open()
|
||||
Qt.callLater(function() {
|
||||
if (contentLoader.item && contentLoader.item.passwordInput) {
|
||||
contentLoader.item.passwordInput.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
shouldBeVisible: false
|
||||
width: 420
|
||||
height: 230
|
||||
onShouldBeVisibleChanged: {
|
||||
if (!shouldBeVisible)
|
||||
wifiPasswordInput = ""
|
||||
}
|
||||
onOpened: {
|
||||
Qt.callLater(function() {
|
||||
if (contentLoader.item && contentLoader.item.passwordInput) {
|
||||
contentLoader.item.passwordInput.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
onBackgroundClicked: {
|
||||
close()
|
||||
wifiPasswordInput = ""
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onPasswordDialogShouldReopenChanged() {
|
||||
if (NetworkService.passwordDialogShouldReopen
|
||||
&& NetworkService.connectingSSID !== "") {
|
||||
wifiPasswordSSID = NetworkService.connectingSSID
|
||||
function show(ssid) {
|
||||
wifiPasswordSSID = ssid
|
||||
wifiPasswordInput = ""
|
||||
open()
|
||||
NetworkService.passwordDialogShouldReopen = false
|
||||
}
|
||||
Qt.callLater(function () {
|
||||
if (contentLoader.item && contentLoader.item.passwordInput) {
|
||||
contentLoader.item.passwordInput.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
target: NetworkService
|
||||
}
|
||||
|
||||
content: Component {
|
||||
FocusScope {
|
||||
id: wifiContent
|
||||
property alias passwordInput: passwordInput
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
Keys.onEscapePressed: function(event) {
|
||||
shouldBeVisible: false
|
||||
width: 420
|
||||
height: 230
|
||||
onShouldBeVisibleChanged: {
|
||||
if (!shouldBeVisible)
|
||||
wifiPasswordInput = ""
|
||||
}
|
||||
onOpened: {
|
||||
Qt.callLater(function () {
|
||||
if (contentLoader.item && contentLoader.item.passwordInput) {
|
||||
contentLoader.item.passwordInput.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
onBackgroundClicked: {
|
||||
close()
|
||||
wifiPasswordInput = ""
|
||||
event.accepted = 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: {
|
||||
close()
|
||||
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
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
passwordInput.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: passwordInput
|
||||
|
||||
anchors.fill: parent
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
textColor: Theme.surfaceText
|
||||
text: wifiPasswordInput
|
||||
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
|
||||
placeholderText: ""
|
||||
backgroundColor: "transparent"
|
||||
focus: true
|
||||
enabled: root.shouldBeVisible
|
||||
onTextEdited: {
|
||||
wifiPasswordInput = text
|
||||
}
|
||||
onAccepted: {
|
||||
NetworkService.connectToWifiWithPassword(wifiPasswordSSID,
|
||||
passwordInput.text)
|
||||
close()
|
||||
wifiPasswordInput = ""
|
||||
passwordInput.text = ""
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.shouldBeVisible) {
|
||||
focusDelayTimer.start()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: focusDelayTimer
|
||||
interval: 100
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.shouldBeVisible) {
|
||||
passwordInput.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onShouldBeVisibleChanged() {
|
||||
if (root.shouldBeVisible) {
|
||||
focusDelayTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: {
|
||||
close()
|
||||
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)
|
||||
close()
|
||||
wifiPasswordInput = ""
|
||||
passwordInput.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onPasswordDialogShouldReopenChanged() {
|
||||
if (NetworkService.passwordDialogShouldReopen
|
||||
&& NetworkService.connectingSSID !== "") {
|
||||
wifiPasswordSSID = NetworkService.connectingSSID
|
||||
wifiPasswordInput = ""
|
||||
open()
|
||||
NetworkService.passwordDialogShouldReopen = false
|
||||
}
|
||||
}
|
||||
|
||||
target: NetworkService
|
||||
}
|
||||
|
||||
content: Component {
|
||||
FocusScope {
|
||||
id: wifiContent
|
||||
property alias passwordInput: passwordInput
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
Keys.onEscapePressed: function (event) {
|
||||
close()
|
||||
wifiPasswordInput = ""
|
||||
event.accepted = 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: {
|
||||
close()
|
||||
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
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
passwordInput.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: passwordInput
|
||||
|
||||
anchors.fill: parent
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
textColor: Theme.surfaceText
|
||||
text: wifiPasswordInput
|
||||
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
|
||||
placeholderText: ""
|
||||
backgroundColor: "transparent"
|
||||
focus: true
|
||||
enabled: root.shouldBeVisible
|
||||
onTextEdited: {
|
||||
wifiPasswordInput = text
|
||||
}
|
||||
onAccepted: {
|
||||
NetworkService.connectToWifiWithPassword(
|
||||
wifiPasswordSSID, passwordInput.text)
|
||||
close()
|
||||
wifiPasswordInput = ""
|
||||
passwordInput.text = ""
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.shouldBeVisible) {
|
||||
focusDelayTimer.start()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: focusDelayTimer
|
||||
interval: 100
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.shouldBeVisible) {
|
||||
passwordInput.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onShouldBeVisibleChanged() {
|
||||
if (root.shouldBeVisible) {
|
||||
focusDelayTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: {
|
||||
close()
|
||||
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)
|
||||
close()
|
||||
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,185 +6,190 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property string searchQuery: ""
|
||||
property string selectedCategory: "All"
|
||||
property string viewMode: "list" // "list" or "grid"
|
||||
property int selectedIndex: 0
|
||||
property int maxResults: 50
|
||||
property int gridColumns: 4
|
||||
property bool debounceSearch: true
|
||||
property int debounceInterval: 50
|
||||
property bool keyboardNavigationActive: false
|
||||
property var categories: {
|
||||
var allCategories = AppSearchService.getAllCategories().filter(cat => {
|
||||
return cat !== "Education"
|
||||
&& cat !== "Science"
|
||||
})
|
||||
var result = ["All"]
|
||||
return result.concat(allCategories.filter(cat => {
|
||||
return cat !== "All"
|
||||
}))
|
||||
}
|
||||
property var categoryIcons: categories.map(category => {
|
||||
return AppSearchService.getCategoryIcon(
|
||||
category)
|
||||
})
|
||||
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
|
||||
property alias model: filteredModel
|
||||
property var _watchApplications: AppSearchService.applications
|
||||
property string searchQuery: ""
|
||||
property string selectedCategory: "All"
|
||||
property string viewMode: "list" // "list" or "grid"
|
||||
property int selectedIndex: 0
|
||||
property int maxResults: 50
|
||||
property int gridColumns: 4
|
||||
property bool debounceSearch: true
|
||||
property int debounceInterval: 50
|
||||
property bool keyboardNavigationActive: false
|
||||
property var categories: {
|
||||
var allCategories = AppSearchService.getAllCategories().filter(cat => {
|
||||
return cat !== "Education"
|
||||
&& cat !== "Science"
|
||||
})
|
||||
var result = ["All"]
|
||||
return result.concat(allCategories.filter(cat => {
|
||||
return cat !== "All"
|
||||
}))
|
||||
}
|
||||
property var categoryIcons: categories.map(category => {
|
||||
return AppSearchService.getCategoryIcon(
|
||||
category)
|
||||
})
|
||||
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
|
||||
property alias model: filteredModel
|
||||
property var _watchApplications: AppSearchService.applications
|
||||
|
||||
signal appLaunched(var app)
|
||||
signal categorySelected(string category)
|
||||
signal viewModeSelected(string mode)
|
||||
signal appLaunched(var app)
|
||||
signal categorySelected(string category)
|
||||
signal viewModeSelected(string mode)
|
||||
|
||||
function updateFilteredModel() {
|
||||
filteredModel.clear()
|
||||
selectedIndex = 0
|
||||
keyboardNavigationActive = false
|
||||
var apps = []
|
||||
if (searchQuery.length === 0) {
|
||||
if (selectedCategory === "All") {
|
||||
apps = AppSearchService.getAppsInCategory("All") // HACK: Use function call instead of property
|
||||
} else {
|
||||
var categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
|
||||
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)
|
||||
function updateFilteredModel() {
|
||||
filteredModel.clear()
|
||||
selectedIndex = 0
|
||||
keyboardNavigationActive = false
|
||||
var apps = []
|
||||
if (searchQuery.length === 0) {
|
||||
if (selectedCategory === "All") {
|
||||
apps = AppSearchService.getAppsInCategory(
|
||||
"All") // HACK: Use function call instead of property
|
||||
} else {
|
||||
var categoryApps = AppSearchService.getAppsInCategory(
|
||||
selectedCategory)
|
||||
apps = categoryApps.slice(0, maxResults)
|
||||
}
|
||||
} else {
|
||||
apps = []
|
||||
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 {
|
||||
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
|
||||
|
||||
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
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (filteredModel.count > 0) {
|
||||
keyboardNavigationActive = true
|
||||
if (viewMode === "grid") {
|
||||
var newIndex = Math.min(selectedIndex + gridColumns,
|
||||
filteredModel.count - 1)
|
||||
selectedIndex = newIndex
|
||||
} else {
|
||||
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1)
|
||||
}
|
||||
function selectNext() {
|
||||
if (filteredModel.count > 0) {
|
||||
keyboardNavigationActive = true
|
||||
if (viewMode === "grid") {
|
||||
var newIndex = Math.min(selectedIndex + gridColumns,
|
||||
filteredModel.count - 1)
|
||||
selectedIndex = newIndex
|
||||
} else {
|
||||
selectedIndex = Math.min(selectedIndex + 1,
|
||||
filteredModel.count - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (filteredModel.count > 0) {
|
||||
keyboardNavigationActive = true
|
||||
if (viewMode === "grid") {
|
||||
var newIndex = Math.max(selectedIndex - gridColumns, 0)
|
||||
selectedIndex = newIndex
|
||||
} else {
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0)
|
||||
}
|
||||
function selectPrevious() {
|
||||
if (filteredModel.count > 0) {
|
||||
keyboardNavigationActive = true
|
||||
if (viewMode === "grid") {
|
||||
var newIndex = Math.max(selectedIndex - gridColumns, 0)
|
||||
selectedIndex = newIndex
|
||||
} else {
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectNextInRow() {
|
||||
if (filteredModel.count > 0 && viewMode === "grid") {
|
||||
keyboardNavigationActive = true
|
||||
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1)
|
||||
function selectNextInRow() {
|
||||
if (filteredModel.count > 0 && viewMode === "grid") {
|
||||
keyboardNavigationActive = true
|
||||
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectPreviousInRow() {
|
||||
if (filteredModel.count > 0 && viewMode === "grid") {
|
||||
keyboardNavigationActive = true
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0)
|
||||
function selectPreviousInRow() {
|
||||
if (filteredModel.count > 0 && viewMode === "grid") {
|
||||
keyboardNavigationActive = true
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function launchSelected() {
|
||||
if (filteredModel.count > 0 && selectedIndex >= 0
|
||||
&& selectedIndex < filteredModel.count) {
|
||||
var selectedApp = filteredModel.get(selectedIndex)
|
||||
launchApp(selectedApp)
|
||||
function launchSelected() {
|
||||
if (filteredModel.count > 0 && selectedIndex >= 0
|
||||
&& selectedIndex < filteredModel.count) {
|
||||
var selectedApp = filteredModel.get(selectedIndex)
|
||||
launchApp(selectedApp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function launchApp(appData) {
|
||||
if (!appData)
|
||||
return
|
||||
function launchApp(appData) {
|
||||
if (!appData)
|
||||
return
|
||||
|
||||
appData.desktopEntry.execute()
|
||||
appLaunched(appData)
|
||||
AppUsageHistoryData.addAppUsage(appData.desktopEntry)
|
||||
}
|
||||
appData.desktopEntry.execute()
|
||||
appLaunched(appData)
|
||||
AppUsageHistoryData.addAppUsage(appData.desktopEntry)
|
||||
}
|
||||
|
||||
function setCategory(category) {
|
||||
selectedCategory = category
|
||||
categorySelected(category)
|
||||
}
|
||||
function setCategory(category) {
|
||||
selectedCategory = category
|
||||
categorySelected(category)
|
||||
}
|
||||
|
||||
function setViewMode(mode) {
|
||||
viewMode = mode
|
||||
viewModeSelected(mode)
|
||||
}
|
||||
function setViewMode(mode) {
|
||||
viewMode = mode
|
||||
viewModeSelected(mode)
|
||||
}
|
||||
|
||||
onSearchQueryChanged: {
|
||||
if (debounceSearch)
|
||||
searchDebounceTimer.restart()
|
||||
else
|
||||
updateFilteredModel()
|
||||
}
|
||||
onSelectedCategoryChanged: updateFilteredModel()
|
||||
onAppUsageRankingChanged: updateFilteredModel()
|
||||
on_WatchApplicationsChanged: updateFilteredModel()
|
||||
Component.onCompleted: {
|
||||
updateFilteredModel()
|
||||
}
|
||||
onSearchQueryChanged: {
|
||||
if (debounceSearch)
|
||||
searchDebounceTimer.restart()
|
||||
else
|
||||
updateFilteredModel()
|
||||
}
|
||||
onSelectedCategoryChanged: updateFilteredModel()
|
||||
onAppUsageRankingChanged: updateFilteredModel()
|
||||
on_WatchApplicationsChanged: updateFilteredModel()
|
||||
Component.onCompleted: {
|
||||
updateFilteredModel()
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: filteredModel
|
||||
}
|
||||
ListModel {
|
||||
id: filteredModel
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: searchDebounceTimer
|
||||
Timer {
|
||||
id: searchDebounceTimer
|
||||
|
||||
interval: root.debounceInterval
|
||||
repeat: false
|
||||
onTriggered: updateFilteredModel()
|
||||
}
|
||||
interval: root.debounceInterval
|
||||
repeat: false
|
||||
onTriggered: updateFilteredModel()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,145 +4,154 @@ import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property var categories: []
|
||||
property string selectedCategory: "All"
|
||||
property bool compact: false // For different layout styles
|
||||
property var categories: []
|
||||
property string selectedCategory: "All"
|
||||
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
|
||||
|
||||
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.cornerRadius
|
||||
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
|
||||
height: compact ? 36 : (72 + Theme.spacingS) // Single row vs two rows
|
||||
|
||||
Row {
|
||||
property var firstRowCategories: categories.slice(0, Math.min(4, categories.length))
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: compact
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: parent.firstRowCategories
|
||||
Repeater {
|
||||
model: categories.slice(0, Math.min(categories.length,
|
||||
8)) // Limit for space
|
||||
|
||||
Rectangle {
|
||||
height: 36
|
||||
width: (parent.width - (parent.firstRowCategories.length - 1) * Theme.spacingS) / parent.firstRowCategories.length
|
||||
radius: Theme.cornerRadius
|
||||
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 {
|
||||
height: 36
|
||||
width: (parent.width - (Math.min(
|
||||
categories.length,
|
||||
8) - 1) * Theme.spacingS) / Math.min(
|
||||
categories.length, 8)
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
}
|
||||
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 secondRowCategories: categories.slice(4, categories.length)
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: secondRowCategories.length > 0
|
||||
Column {
|
||||
visible: !compact
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: parent.secondRowCategories
|
||||
Row {
|
||||
property var firstRowCategories: categories.slice(
|
||||
0, Math.min(4,
|
||||
categories.length))
|
||||
|
||||
Rectangle {
|
||||
height: 36
|
||||
width: (parent.width - (parent.secondRowCategories.length - 1) * Theme.spacingS) / parent.secondRowCategories.length
|
||||
radius: Theme.cornerRadius
|
||||
color: selectedCategory === modelData ? Theme.primary : "transparent"
|
||||
border.color: selectedCategory === modelData ? "transparent" : Qt.rgba(
|
||||
Theme.outline.r,
|
||||
Theme.outline.g,
|
||||
Theme.outline.b, 0.3)
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
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
|
||||
}
|
||||
Repeater {
|
||||
model: parent.firstRowCategories
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
selectedCategory = modelData
|
||||
categorySelected(modelData)
|
||||
Rectangle {
|
||||
height: 36
|
||||
width: (parent.width - (parent.firstRowCategories.length - 1)
|
||||
* Theme.spacingS) / parent.firstRowCategories.length
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
property var secondRowCategories: categories.slice(
|
||||
4, categories.length)
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: secondRowCategories.length > 0
|
||||
|
||||
Repeater {
|
||||
model: parent.secondRowCategories
|
||||
|
||||
Rectangle {
|
||||
height: 36
|
||||
width: (parent.width - (parent.secondRowCategories.length - 1)
|
||||
* Theme.spacingS) / parent.secondRowCategories.length
|
||||
radius: Theme.cornerRadius
|
||||
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,220 +8,224 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property var modelData
|
||||
property bool brightnessPopupVisible: false
|
||||
property var brightnessDebounceTimer
|
||||
property var modelData
|
||||
property bool brightnessPopupVisible: false
|
||||
property var brightnessDebounceTimer
|
||||
|
||||
brightnessDebounceTimer: Timer {
|
||||
property int pendingValue: 0
|
||||
brightnessDebounceTimer: Timer {
|
||||
property int pendingValue: 0
|
||||
|
||||
interval: BrightnessService.ddcAvailable ? 500 : 50
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
BrightnessService.setBrightnessInternal(pendingValue, BrightnessService.lastIpcDevice)
|
||||
interval: BrightnessService.ddcAvailable ? 500 : 50
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
BrightnessService.setBrightnessInternal(
|
||||
pendingValue, BrightnessService.lastIpcDevice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function show() {
|
||||
root.brightnessPopupVisible = true
|
||||
// Update slider to current device brightness when showing
|
||||
if (BrightnessService.brightnessAvailable) {
|
||||
brightnessSlider.value = BrightnessService.brightnessLevel
|
||||
}
|
||||
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
|
||||
function show() {
|
||||
root.brightnessPopupVisible = true
|
||||
// Update slider to current device brightness when showing
|
||||
if (BrightnessService.brightnessAvailable) {
|
||||
brightnessSlider.value = BrightnessService.brightnessLevel
|
||||
}
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onBrightnessChanged() {
|
||||
root.show()
|
||||
function resetHideTimer() {
|
||||
if (root.brightnessPopupVisible)
|
||||
hideTimer.restart()
|
||||
}
|
||||
|
||||
target: BrightnessService
|
||||
}
|
||||
screen: modelData
|
||||
visible: brightnessPopupVisible
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
id: brightnessPopup
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
property bool containsMouse: popupMouseArea.containsMouse
|
||||
Timer {
|
||||
id: hideTimer
|
||||
|
||||
width: Math.min(260, Screen.width - Theme.spacingM * 2)
|
||||
height: brightnessContent.height + Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Theme.spacingM
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
interval: 3000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (!brightnessPopup.containsMouse)
|
||||
root.brightnessPopupVisible = false
|
||||
else
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: brightnessContent
|
||||
Connections {
|
||||
function onBrightnessChanged() {
|
||||
root.show()
|
||||
}
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
spacing: Theme.spacingXS
|
||||
target: BrightnessService
|
||||
}
|
||||
|
||||
Item {
|
||||
property int gap: Theme.spacingS
|
||||
Rectangle {
|
||||
id: brightnessPopup
|
||||
|
||||
width: parent.width
|
||||
height: 40
|
||||
property bool containsMouse: popupMouseArea.containsMouse
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
radius: Theme.iconSize / 2
|
||||
color: "transparent"
|
||||
x: parent.gap
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Math.min(260, Screen.width - Theme.spacingM * 2)
|
||||
height: brightnessContent.height + Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Theme.spacingM
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
name: {
|
||||
const deviceInfo = BrightnessService.getCurrentDeviceInfo();
|
||||
|
||||
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
|
||||
return "brightness_medium";
|
||||
} else if (deviceInfo.name.includes("kbd")) {
|
||||
return "keyboard";
|
||||
} else {
|
||||
return "lightbulb";
|
||||
}
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Item {
|
||||
property int gap: Theme.spacingS
|
||||
|
||||
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: {
|
||||
const deviceInfo = BrightnessService.getCurrentDeviceInfo()
|
||||
|
||||
if (!deviceInfo || deviceInfo.class === "backlight"
|
||||
|| deviceInfo.class === "ddc") {
|
||||
return "brightness_medium"
|
||||
} else if (deviceInfo.name.includes("kbd")) {
|
||||
return "keyboard"
|
||||
} else {
|
||||
return "lightbulb"
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
BrightnessService.lastIpcDevice)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onBrightnessChanged() {
|
||||
brightnessSlider.value = BrightnessService.brightnessLevel
|
||||
}
|
||||
|
||||
function onDeviceSwitched() {
|
||||
brightnessSlider.value = BrightnessService.brightnessLevel
|
||||
}
|
||||
|
||||
target: BrightnessService
|
||||
}
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
id: brightnessSlider
|
||||
MouseArea {
|
||||
id: popupMouseArea
|
||||
|
||||
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, BrightnessService.lastIpcDevice)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onBrightnessChanged() {
|
||||
brightnessSlider.value = BrightnessService.brightnessLevel
|
||||
}
|
||||
|
||||
function onDeviceSwitched() {
|
||||
brightnessSlider.value = BrightnessService.brightnessLevel
|
||||
}
|
||||
|
||||
target: BrightnessService
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: popupMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
propagateComposedEvents: true
|
||||
z: -1
|
||||
mask: Region {
|
||||
item: brightnessPopup
|
||||
}
|
||||
|
||||
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,267 +6,271 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: calendarGrid
|
||||
id: calendarGrid
|
||||
|
||||
property date displayDate: new Date()
|
||||
property date selectedDate: new Date()
|
||||
property date displayDate: new Date()
|
||||
property date selectedDate: new Date()
|
||||
|
||||
function loadEventsForMonth() {
|
||||
if (!CalendarService || !CalendarService.khalAvailable)
|
||||
return
|
||||
function loadEventsForMonth() {
|
||||
if (!CalendarService || !CalendarService.khalAvailable)
|
||||
return
|
||||
|
||||
let firstDay = new Date(displayDate.getFullYear(),
|
||||
displayDate.getMonth(), 1)
|
||||
let dayOfWeek = firstDay.getDay()
|
||||
let startDate = new Date(firstDay)
|
||||
startDate.setDate(startDate.getDate() - dayOfWeek - 7) // Extra week padding
|
||||
let lastDay = new Date(displayDate.getFullYear(),
|
||||
displayDate.getMonth() + 1, 0)
|
||||
let endDate = new Date(lastDay)
|
||||
endDate.setDate(endDate.getDate() + (6 - lastDay.getDay(
|
||||
)) + 7) // Extra week padding
|
||||
CalendarService.loadEvents(startDate, endDate)
|
||||
}
|
||||
let firstDay = new Date(displayDate.getFullYear(),
|
||||
displayDate.getMonth(), 1)
|
||||
let dayOfWeek = firstDay.getDay()
|
||||
let startDate = new Date(firstDay)
|
||||
startDate.setDate(startDate.getDate(
|
||||
) - dayOfWeek - 7) // Extra week padding
|
||||
let lastDay = new Date(displayDate.getFullYear(),
|
||||
displayDate.getMonth() + 1, 0)
|
||||
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)
|
||||
spacing: Theme.spacingM
|
||||
onDisplayDateChanged: {
|
||||
loadEventsForMonth()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
loadEventsForMonth()
|
||||
}
|
||||
|
||||
target: CalendarService
|
||||
enabled: CalendarService !== null
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
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
|
||||
Connections {
|
||||
function onKhalAvailableChanged() {
|
||||
if (CalendarService && CalendarService.khalAvailable)
|
||||
loadEventsForMonth()
|
||||
}
|
||||
}
|
||||
|
||||
target: CalendarService
|
||||
enabled: CalendarService !== null
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width - 80
|
||||
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 {
|
||||
width: 40
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: nextMonthArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.12) : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "chevron_right"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nextMonthArea
|
||||
|
||||
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 {
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.6)
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
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.cornerRadius
|
||||
clip: true
|
||||
width: 40
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: prevMonthArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.12) : "transparent"
|
||||
|
||||
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
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "chevron_left"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.89
|
||||
color: "transparent"
|
||||
}
|
||||
MouseArea {
|
||||
id: prevMonthArea
|
||||
|
||||
GradientStop {
|
||||
position: 0.9
|
||||
color: {
|
||||
if (isSelected)
|
||||
return Qt.lighter(Theme.primary, 1.3)
|
||||
else if (isToday)
|
||||
return Theme.primary
|
||||
else
|
||||
return Theme.primary
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
let newDate = new Date(displayDate)
|
||||
newDate.setMonth(newDate.getMonth() - 1)
|
||||
displayDate = newDate
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
StyledText {
|
||||
width: parent.width - 80
|
||||
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 {
|
||||
width: 40
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: nextMonthArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.12) : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "chevron_right"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nextMonthArea
|
||||
|
||||
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 {
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.6)
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.cornerRadius
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,315 +10,317 @@ import qs.Modules.CentcomCenter
|
||||
import qs.Services
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
readonly property bool hasActiveMedia: MprisController.activePlayer !== null
|
||||
property bool calendarVisible: false
|
||||
property bool shouldBeVisible: false
|
||||
property real triggerX: (Screen.width - 480) / 2
|
||||
property real triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + 4
|
||||
property real triggerWidth: 80
|
||||
property string triggerSection: "center"
|
||||
property var triggerScreen: null
|
||||
readonly property bool hasActiveMedia: MprisController.activePlayer !== null
|
||||
property bool calendarVisible: false
|
||||
property bool shouldBeVisible: false
|
||||
property real triggerX: (Screen.width - 480) / 2
|
||||
property real triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + 4
|
||||
property real triggerWidth: 80
|
||||
property string triggerSection: "center"
|
||||
property var triggerScreen: null
|
||||
|
||||
function setTriggerPosition(x, y, width, section, screen) {
|
||||
triggerX = x
|
||||
triggerY = y
|
||||
triggerWidth = width
|
||||
triggerSection = section
|
||||
triggerScreen = screen
|
||||
}
|
||||
|
||||
visible: calendarVisible || closeTimer.running
|
||||
screen: triggerScreen
|
||||
onCalendarVisibleChanged: {
|
||||
if (calendarVisible) {
|
||||
closeTimer.stop()
|
||||
shouldBeVisible = true
|
||||
visible = true
|
||||
Qt.callLater(() => {
|
||||
calendarGrid.loadEventsForMonth()
|
||||
})
|
||||
} else {
|
||||
shouldBeVisible = false
|
||||
closeTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closeTimer
|
||||
interval: Theme.mediumDuration + 50
|
||||
onTriggered: {
|
||||
if (!shouldBeVisible) {
|
||||
visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (visible && calendarGrid)
|
||||
calendarGrid.loadEventsForMonth()
|
||||
}
|
||||
implicitWidth: 480
|
||||
implicitHeight: 600
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: shouldBeVisible ? WlrKeyboardFocus.Exclusive : 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)
|
||||
function setTriggerPosition(x, y, width, section, screen) {
|
||||
triggerX = x
|
||||
triggerY = y
|
||||
triggerWidth = width
|
||||
triggerSection = section
|
||||
triggerScreen = screen
|
||||
}
|
||||
|
||||
function calculateHeight() {
|
||||
let contentHeight = Theme.spacingM * 2
|
||||
// margins
|
||||
let widgetHeight = 160
|
||||
widgetHeight += 140 + Theme.spacingM
|
||||
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)
|
||||
visible: calendarVisible || closeTimer.running
|
||||
screen: triggerScreen
|
||||
onCalendarVisibleChanged: {
|
||||
if (calendarVisible) {
|
||||
closeTimer.stop()
|
||||
shouldBeVisible = true
|
||||
visible = true
|
||||
Qt.callLater(() => {
|
||||
calendarGrid.loadEventsForMonth()
|
||||
})
|
||||
} else {
|
||||
shouldBeVisible = false
|
||||
closeTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Timer {
|
||||
id: closeTimer
|
||||
interval: Theme.mediumDuration + 50
|
||||
onTriggered: {
|
||||
if (!shouldBeVisible) {
|
||||
visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
width: targetWidth
|
||||
height: calculateHeight()
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
layer.enabled: true
|
||||
opacity: shouldBeVisible ? 1 : 0
|
||||
scale: shouldBeVisible ? 1 : 0.9
|
||||
x: calculatedX
|
||||
y: root.triggerY
|
||||
onOpacityChanged: {
|
||||
if (opacity === 1)
|
||||
Qt.callLater(() => {
|
||||
height = calculateHeight()
|
||||
})
|
||||
onVisibleChanged: {
|
||||
if (visible && calendarGrid)
|
||||
calendarGrid.loadEventsForMonth()
|
||||
}
|
||||
implicitWidth: 480
|
||||
implicitHeight: 600
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: shouldBeVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
|
||||
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
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g,
|
||||
Theme.surfaceTint.b, 0.04)
|
||||
radius: parent.radius
|
||||
id: mainContainer
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: shouldBeVisible
|
||||
loops: Animation.Infinite
|
||||
readonly property real targetWidth: Math.min(
|
||||
(root.screen ? root.screen.width : Screen.width)
|
||||
* 0.9, 600)
|
||||
|
||||
NumberAnimation {
|
||||
to: 0.08
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
function calculateWidth() {
|
||||
let baseWidth = 320
|
||||
if (leftWidgets.hasAnyWidgets)
|
||||
return Math.min(parent.width * 0.9, 600)
|
||||
|
||||
return Math.min(parent.width * 0.7, 400)
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
to: 0.02
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
function calculateHeight() {
|
||||
let contentHeight = Theme.spacingM * 2
|
||||
// margins
|
||||
let widgetHeight = 160
|
||||
widgetHeight += 140 + Theme.spacingM
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
width: targetWidth
|
||||
height: calculateHeight()
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
layer.enabled: true
|
||||
opacity: shouldBeVisible ? 1 : 0
|
||||
scale: shouldBeVisible ? 1 : 0.9
|
||||
x: calculatedX
|
||||
y: root.triggerY
|
||||
onOpacityChanged: {
|
||||
if (opacity === 1)
|
||||
Qt.callLater(() => {
|
||||
height = calculateHeight()
|
||||
})
|
||||
}
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
id: leftWidgets
|
||||
Connections {
|
||||
function onEventsByDateChanged() {
|
||||
if (mainContainer.opacity === 1)
|
||||
mainContainer.height = mainContainer.calculateHeight()
|
||||
}
|
||||
|
||||
property bool hasAnyWidgets: true
|
||||
function onKhalAvailableChanged() {
|
||||
if (mainContainer.opacity === 1)
|
||||
mainContainer.height = mainContainer.calculateHeight()
|
||||
}
|
||||
|
||||
width: hasAnyWidgets ? parent.width * 0.42 : 0 // Slightly narrower for better proportions
|
||||
height: childrenRect.height
|
||||
spacing: Theme.spacingM
|
||||
visible: hasAnyWidgets
|
||||
anchors.top: parent.top
|
||||
target: CalendarService
|
||||
enabled: CalendarService !== null
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
width: parent.width
|
||||
height: 160
|
||||
}
|
||||
Connections {
|
||||
function onSelectedDateEventsChanged() {
|
||||
if (mainContainer.opacity === 1)
|
||||
mainContainer.height = mainContainer.calculateHeight()
|
||||
}
|
||||
|
||||
Weather {
|
||||
width: parent.width
|
||||
height: 140
|
||||
visible: SettingsData.weatherEnabled
|
||||
}
|
||||
|
||||
SystemInfo {
|
||||
width: parent.width
|
||||
height: 140
|
||||
visible: !SettingsData.weatherEnabled
|
||||
}
|
||||
target: events
|
||||
enabled: events !== null
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: leftWidgets.hasAnyWidgets ? parent.width - leftWidgets.width
|
||||
- Theme.spacingM : parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
}
|
||||
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g,
|
||||
Theme.surfaceTint.b, 0.04)
|
||||
radius: parent.radius
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: shouldBeVisible
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Events {
|
||||
id: events
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
selectedDate: calendarGrid.selectedDate
|
||||
}
|
||||
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.cornerRadius
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Anims.durMed
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasized
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
enabled: shouldBeVisible
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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: shouldBeVisible
|
||||
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,340 +6,348 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: events
|
||||
id: events
|
||||
|
||||
property date selectedDate: new Date()
|
||||
property var selectedDateEvents: []
|
||||
property bool hasEvents: selectedDateEvents && selectedDateEvents.length > 0
|
||||
property bool shouldShow: CalendarService && CalendarService.khalAvailable
|
||||
property date selectedDate: new Date()
|
||||
property var selectedDateEvents: []
|
||||
property bool hasEvents: selectedDateEvents && selectedDateEvents.length > 0
|
||||
property bool shouldShow: CalendarService && CalendarService.khalAvailable
|
||||
|
||||
function updateSelectedDateEvents() {
|
||||
if (CalendarService && CalendarService.khalAvailable) {
|
||||
let events = CalendarService.getEventsForDate(selectedDate)
|
||||
selectedDateEvents = events
|
||||
} else {
|
||||
selectedDateEvents = []
|
||||
}
|
||||
}
|
||||
|
||||
onSelectedDateEventsChanged: {
|
||||
eventsList.model = selectedDateEvents
|
||||
}
|
||||
width: parent.width
|
||||
height: shouldShow ? (hasEvents ? Math.min(
|
||||
300,
|
||||
80 + selectedDateEvents.length * 60) : 120) : 0
|
||||
radius: Theme.cornerRadius
|
||||
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()
|
||||
function updateSelectedDateEvents() {
|
||||
if (CalendarService && CalendarService.khalAvailable) {
|
||||
let events = CalendarService.getEventsForDate(selectedDate)
|
||||
selectedDateEvents = events
|
||||
} else {
|
||||
selectedDateEvents = []
|
||||
}
|
||||
}
|
||||
|
||||
function onKhalAvailableChanged() {
|
||||
updateSelectedDateEvents()
|
||||
onSelectedDateEventsChanged: {
|
||||
eventsList.model = selectedDateEvents
|
||||
}
|
||||
width: parent.width
|
||||
height: shouldShow ? (hasEvents ? Math.min(
|
||||
300,
|
||||
80 + selectedDateEvents.length * 60) : 120) : 0
|
||||
radius: Theme.cornerRadius
|
||||
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()
|
||||
}
|
||||
|
||||
target: CalendarService
|
||||
enabled: CalendarService !== null
|
||||
}
|
||||
Connections {
|
||||
function onEventsByDateChanged() {
|
||||
updateSelectedDateEvents()
|
||||
}
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
function onKhalAvailableChanged() {
|
||||
updateSelectedDateEvents()
|
||||
}
|
||||
|
||||
anchors.top: parent.top
|
||||
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
|
||||
target: CalendarService
|
||||
enabled: CalendarService !== null
|
||||
}
|
||||
|
||||
StyledText {
|
||||
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
|
||||
Row {
|
||||
id: headerRow
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingL + 4
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: 6
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "event"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Math.max(timeRow.height, locationRow.height)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: timeRow
|
||||
DankListView {
|
||||
id: eventsList
|
||||
|
||||
spacing: 4
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
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
|
||||
|
||||
DankIcon {
|
||||
name: "schedule"
|
||||
size: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.7)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
// 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
|
||||
}
|
||||
|
||||
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)
|
||||
Column {
|
||||
id: eventContent
|
||||
|
||||
return startTime
|
||||
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
|
||||
}
|
||||
}
|
||||
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
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Math.max(timeRow.height, locationRow.height)
|
||||
|
||||
spacing: 4
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: modelData.location !== ""
|
||||
Row {
|
||||
id: timeRow
|
||||
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: eventMouseArea
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
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 border.color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 2
|
||||
shadowBlur: 0.25
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
||||
shadowOpacity: 0.1
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 2
|
||||
shadowBlur: 0.25
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
||||
shadowOpacity: 0.1
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,439 +8,464 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: mediaPlayer
|
||||
id: mediaPlayer
|
||||
|
||||
property MprisPlayer activePlayer: MprisController.activePlayer
|
||||
property string lastValidTitle: ""
|
||||
property string lastValidArtist: ""
|
||||
property string lastValidAlbum: ""
|
||||
property string lastValidArtUrl: ""
|
||||
property real currentPosition: activePlayer && activePlayer.positionSupported ? activePlayer.position : 0
|
||||
property MprisPlayer activePlayer: MprisController.activePlayer
|
||||
property string lastValidTitle: ""
|
||||
property string lastValidArtist: ""
|
||||
property string lastValidAlbum: ""
|
||||
property string lastValidArtUrl: ""
|
||||
property real currentPosition: activePlayer
|
||||
&& activePlayer.positionSupported ? activePlayer.position : 0
|
||||
|
||||
function ratio() {
|
||||
if (!activePlayer || activePlayer.length <= 0) {
|
||||
return 0
|
||||
}
|
||||
let calculatedRatio = currentPosition / activePlayer.length
|
||||
return Math.max(0, Math.min(1, calculatedRatio))
|
||||
}
|
||||
|
||||
onActivePlayerChanged: {
|
||||
if (activePlayer && activePlayer.positionSupported) {
|
||||
activePlayer.positionChanged()
|
||||
}
|
||||
}
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
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: positionTimer
|
||||
|
||||
interval: 500
|
||||
running: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing
|
||||
&& !progressMouseArea.isSeeking
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (activePlayer && activePlayer.positionSupported) {
|
||||
activePlayer.positionChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: cleanupTimer
|
||||
|
||||
interval: 2000
|
||||
running: !activePlayer
|
||||
onTriggered: {
|
||||
lastValidTitle = ""
|
||||
lastValidArtist = ""
|
||||
lastValidAlbum = ""
|
||||
lastValidArtUrl = ""
|
||||
currentPosition = 0
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onTrackChanged() {
|
||||
if (activePlayer && activePlayer.positionSupported) {
|
||||
activePlayer.positionChanged()
|
||||
}
|
||||
function ratio() {
|
||||
if (!activePlayer || activePlayer.length <= 0) {
|
||||
return 0
|
||||
}
|
||||
let calculatedRatio = currentPosition / activePlayer.length
|
||||
return Math.max(0, Math.min(1, calculatedRatio))
|
||||
}
|
||||
|
||||
target: activePlayer
|
||||
}
|
||||
onActivePlayerChanged: {
|
||||
if (activePlayer && activePlayer.positionSupported) {
|
||||
activePlayer.positionChanged()
|
||||
}
|
||||
}
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
Timer {
|
||||
id: positionTimer
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
visible: (!activePlayer && !lastValidTitle)
|
||||
|| (activePlayer && activePlayer.trackTitle === ""
|
||||
&& lastValidTitle === "")
|
||||
|
||||
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
|
||||
}
|
||||
interval: 500
|
||||
running: activePlayer
|
||||
&& activePlayer.playbackState === MprisPlaybackState.Playing
|
||||
&& !progressMouseArea.isSeeking
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (activePlayer && activePlayer.positionSupported) {
|
||||
activePlayer.positionChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingS
|
||||
visible: activePlayer && activePlayer.trackTitle !== ""
|
||||
|| lastValidTitle !== ""
|
||||
Timer {
|
||||
id: cleanupTimer
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 60
|
||||
spacing: Theme.spacingM
|
||||
interval: 2000
|
||||
running: !activePlayer
|
||||
onTriggered: {
|
||||
lastValidTitle = ""
|
||||
lastValidArtist = ""
|
||||
lastValidAlbum = ""
|
||||
lastValidArtUrl = ""
|
||||
currentPosition = 0
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 60
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b, 0.3)
|
||||
Connections {
|
||||
function onTrackChanged() {
|
||||
if (activePlayer && activePlayer.positionSupported) {
|
||||
activePlayer.positionChanged()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
target: activePlayer
|
||||
}
|
||||
|
||||
Image {
|
||||
id: albumArt
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
|
||||
anchors.fill: parent
|
||||
source: activePlayer && activePlayer.trackArtUrl
|
||||
|| lastValidArtUrl || ""
|
||||
onSourceChanged: {
|
||||
if (activePlayer && activePlayer.trackArtUrl)
|
||||
lastValidArtUrl = activePlayer.trackArtUrl
|
||||
}
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
cache: true
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
visible: (!activePlayer && !lastValidTitle)
|
||||
|| (activePlayer && activePlayer.trackTitle === ""
|
||||
&& lastValidTitle === "")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: albumArt.status !== Image.Ready
|
||||
color: "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "album"
|
||||
size: 28
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
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 {
|
||||
width: parent.width - 60 - Theme.spacingM
|
||||
height: parent.height
|
||||
spacing: Theme.spacingXS
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingS
|
||||
visible: activePlayer && activePlayer.trackTitle !== ""
|
||||
|| lastValidTitle !== ""
|
||||
|
||||
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
|
||||
}
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 60
|
||||
spacing: Theme.spacingM
|
||||
|
||||
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
|
||||
}
|
||||
Rectangle {
|
||||
width: 60
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r,
|
||||
Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b, 0.3)
|
||||
|
||||
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 {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
Item {
|
||||
id: progressBarContainer
|
||||
Image {
|
||||
id: albumArt
|
||||
|
||||
width: parent.width
|
||||
height: 24
|
||||
anchors.fill: parent
|
||||
source: activePlayer && activePlayer.trackArtUrl
|
||||
|| lastValidArtUrl || ""
|
||||
onSourceChanged: {
|
||||
if (activePlayer && activePlayer.trackArtUrl)
|
||||
lastValidArtUrl = activePlayer.trackArtUrl
|
||||
}
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
cache: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: progressBarBackground
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: albumArt.status !== Image.Ready
|
||||
color: "transparent"
|
||||
|
||||
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: Math.max(0, Math.min(parent.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 && activePlayer.canSeek) {
|
||||
let ratio = Math.max(0, Math.min(
|
||||
1, mouse.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
isSeeking = false
|
||||
}
|
||||
onPositionChanged: function (mouse) {
|
||||
if (pressed && isSeeking && activePlayer
|
||||
&& activePlayer.length > 0 && activePlayer.canSeek) {
|
||||
let ratio = Math.max(0, Math.min(
|
||||
1, mouse.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
}
|
||||
}
|
||||
onClicked: function (mouse) {
|
||||
if (activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
|
||||
let ratio = Math.max(0, Math.min(
|
||||
1, mouse.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = 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 && activePlayer.canSeek) {
|
||||
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
|
||||
}
|
||||
}
|
||||
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 (activePlayer.position > 8 && activePlayer.canSeek) {
|
||||
activePlayer.position = 0
|
||||
} else {
|
||||
activePlayer.previous()
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "album"
|
||||
size: 28
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32
|
||||
height: 32
|
||||
radius: 16
|
||||
color: Theme.primary
|
||||
Column {
|
||||
width: parent.width - 60 - Theme.spacingM
|
||||
height: parent.height
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: activePlayer && activePlayer.playbackState
|
||||
=== MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
||||
size: 20
|
||||
color: Theme.background
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: activePlayer && activePlayer.togglePlaying()
|
||||
}
|
||||
}
|
||||
Item {
|
||||
id: progressBarContainer
|
||||
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: nextBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r,
|
||||
Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b,
|
||||
0.12) : "transparent"
|
||||
width: parent.width
|
||||
height: 24
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_next"
|
||||
size: 16
|
||||
color: Theme.surfaceText
|
||||
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: Math.max(0, Math.min(parent.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
|
||||
&& activePlayer.canSeek) {
|
||||
let ratio = Math.max(
|
||||
0, Math.min(
|
||||
1,
|
||||
mouse.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
isSeeking = false
|
||||
}
|
||||
onPositionChanged: function (mouse) {
|
||||
if (pressed && isSeeking && activePlayer
|
||||
&& activePlayer.length > 0
|
||||
&& activePlayer.canSeek) {
|
||||
let ratio = Math.max(
|
||||
0, Math.min(
|
||||
1,
|
||||
mouse.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
}
|
||||
}
|
||||
onClicked: function (mouse) {
|
||||
if (activePlayer && activePlayer.length > 0
|
||||
&& activePlayer.canSeek) {
|
||||
let ratio = Math.max(
|
||||
0, Math.min(
|
||||
1,
|
||||
mouse.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = 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
|
||||
&& activePlayer.canSeek) {
|
||||
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
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
progressMouseArea.isSeeking = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nextBtnArea
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 32
|
||||
visible: activePlayer !== null
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: activePlayer && activePlayer.next()
|
||||
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 (activePlayer.position > 8
|
||||
&& activePlayer.canSeek) {
|
||||
activePlayer.position = 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,132 +4,134 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
|
||||
Ref {
|
||||
service: DgopService
|
||||
}
|
||||
|
||||
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: DgopService.hostname
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.distribution + " • " + DgopService.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)
|
||||
Ref {
|
||||
service: DgopService
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: "Uptime " + formatUptime(UserInfoService.uptime)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: "Load: " + DgopService.loadAverage
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
SystemLogo {
|
||||
width: 48
|
||||
height: 48
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.processCount + " proc, "
|
||||
+ DgopService.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
|
||||
}
|
||||
}
|
||||
}
|
||||
Column {
|
||||
width: parent.width - 48 - Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
function formatUptime(uptime) {
|
||||
if (!uptime)
|
||||
return "0m"
|
||||
StyledText {
|
||||
text: DgopService.hostname
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Parse the uptime string - handle formats like "1 week, 4 days, 3:45" or "4 days, 3:45" or "3:45"
|
||||
var uptimeStr = uptime.toString().trim()
|
||||
StyledText {
|
||||
text: DgopService.distribution + " • " + DgopService.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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"
|
||||
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: " + DgopService.loadAverage
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.processCount + " proc, " + DgopService.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 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"
|
||||
}
|
||||
}
|
||||
function formatUptime(uptime) {
|
||||
if (!uptime)
|
||||
return "0m"
|
||||
|
||||
// Fallback - return as is but truncated
|
||||
return uptimeStr.length > 8 ? uptimeStr.substring(0, 8) + "…" : uptimeStr
|
||||
}
|
||||
// Parse the uptime string - handle formats like "1 week, 4 days, 3:45" or "4 days, 3:45" or "3:45"
|
||||
var uptimeStr = uptime.toString().trim()
|
||||
|
||||
// Check for weeks and days - need to add them together
|
||||
var weekMatch = uptimeStr.match(/(\d+)\s+weeks?/)
|
||||
var dayMatch = uptimeStr.match(/(\d+)\s+days?/)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,225 +6,231 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: weather
|
||||
id: weather
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
|
||||
Ref {
|
||||
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
|
||||
Ref {
|
||||
service: WeatherService
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
DankIcon {
|
||||
id: refreshButton
|
||||
name: "refresh"
|
||||
size: Theme.iconSize - 6
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: -Theme.spacingS
|
||||
anchors.topMargin: -Theme.spacingS
|
||||
|
||||
property bool isRefreshing: false
|
||||
enabled: !isRefreshing
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ForbiddenCursor
|
||||
onClicked: {
|
||||
refreshButton.isRefreshing = true
|
||||
WeatherService.forceRefresh()
|
||||
refreshTimer.restart()
|
||||
}
|
||||
enabled: parent.enabled
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: refreshTimer
|
||||
interval: 2000
|
||||
onTriggered: refreshButton.isRefreshing = false
|
||||
}
|
||||
|
||||
NumberAnimation on rotation {
|
||||
running: refreshButton.isRefreshing
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingL
|
||||
spacing: Theme.spacingS
|
||||
visible: !WeatherService.weather.available
|
||||
|| WeatherService.weather.temp === 0
|
||||
|
||||
DankIcon {
|
||||
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
||||
size: Theme.iconSize + 8
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
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 {
|
||||
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 || ""
|
||||
StyledText {
|
||||
text: "No Weather Data"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.7)
|
||||
visible: text.length > 0
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
columns: 2
|
||||
spacing: Theme.spacingM
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingS
|
||||
visible: WeatherService.weather.available
|
||||
&& WeatherService.weather.temp !== 0
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 60
|
||||
|
||||
DankIcon {
|
||||
name: "humidity_low"
|
||||
size: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
DankIcon {
|
||||
id: refreshButton
|
||||
name: "refresh"
|
||||
size: Theme.iconSize - 6
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.3)
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: -Theme.spacingS
|
||||
anchors.topMargin: -Theme.spacingS
|
||||
|
||||
property bool isRefreshing: false
|
||||
enabled: !isRefreshing
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ForbiddenCursor
|
||||
onClicked: {
|
||||
refreshButton.isRefreshing = true
|
||||
WeatherService.forceRefresh()
|
||||
refreshTimer.restart()
|
||||
}
|
||||
enabled: parent.enabled
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: refreshTimer
|
||||
interval: 2000
|
||||
onTriggered: refreshButton.isRefreshing = false
|
||||
}
|
||||
|
||||
NumberAnimation on rotation {
|
||||
running: refreshButton.isRefreshing
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: WeatherService.weather.humidity ? WeatherService.weather.humidity + "%" : "--"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,140 +9,142 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
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
|
||||
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
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
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
|
||||
StyledText {
|
||||
text: "Output Device"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
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 {
|
||||
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
|
||||
width: parent.width
|
||||
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
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (modelData.name.includes("bluez"))
|
||||
return "headset"
|
||||
else if (modelData.name.includes("hdmi"))
|
||||
return "tv"
|
||||
else if (modelData.name.includes("usb"))
|
||||
return "headset"
|
||||
else
|
||||
return "speaker"
|
||||
}
|
||||
size: Theme.iconSize
|
||||
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" : ""
|
||||
DankIcon {
|
||||
name: "check_circle"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
}
|
||||
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
|
||||
StyledText {
|
||||
text: "Current: " + (root.currentSinkDisplayName || "None")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
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 {
|
||||
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,139 +9,141 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
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
|
||||
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
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
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
|
||||
StyledText {
|
||||
text: "Input Device"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
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 {
|
||||
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
|
||||
width: parent.width
|
||||
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
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
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" : ""
|
||||
DankIcon {
|
||||
name: "check_circle"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
}
|
||||
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
|
||||
StyledText {
|
||||
text: "Current: " + (root.currentSourceDisplayName || "None")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
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 {
|
||||
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,222 +8,232 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property real micLevel: Math.min(
|
||||
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
|
||||
property real micLevel: Math.min(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
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: root.micMuted ? "mic_off" : "mic"
|
||||
size: Theme.iconSize
|
||||
color: root.micMuted ? Theme.error : Theme.surfaceText
|
||||
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
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
text: "Microphone Level"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Item {
|
||||
id: micSliderContainer
|
||||
|
||||
width: parent.width - 80
|
||||
height: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: micSliderTrack
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b, 0.3)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
id: micSliderFill
|
||||
DankIcon {
|
||||
name: root.micMuted ? "mic_off" : "mic"
|
||||
size: Theme.iconSize
|
||||
color: root.micMuted ? Theme.error : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
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
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (AudioService.source && AudioService.source.audio)
|
||||
AudioService.source.audio.muted = !AudioService.source.audio.muted
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: micHandle
|
||||
Item {
|
||||
id: micSliderContainer
|
||||
|
||||
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
|
||||
width: parent.width - 80
|
||||
height: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: micTooltip
|
||||
Rectangle {
|
||||
id: micSliderTrack
|
||||
|
||||
width: tooltipText.contentWidth + Theme.spacingS * 2
|
||||
height: tooltipText.contentHeight + Theme.spacingXS * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
width: parent.width
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b, 0.3)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
id: tooltipText
|
||||
Rectangle {
|
||||
id: micSliderFill
|
||||
|
||||
text: Math.round(root.micLevel) + "%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
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.cornerRadius
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: micMouseArea
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
property bool isDragging: false
|
||||
|
||||
MouseArea {
|
||||
id: micMouseArea
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
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) {
|
||||
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.max(
|
||||
0, Math.min(100, Math.round(ratio * 100)))
|
||||
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
|
||||
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)
|
||||
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,
|
||||
globalPos.x / micSliderTrack.width))
|
||||
let newMicLevel = Math.max(
|
||||
0, Math.min(100, Math.round(ratio * 100)))
|
||||
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
|
||||
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
|
||||
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,63 +4,66 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property real volumeLevel: Math.min(
|
||||
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
|
||||
property real volumeLevel: Math.min(
|
||||
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
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
leftIcon: root.volumeMuted ? "volume_off" : "volume_down"
|
||||
rightIcon: "volume_up"
|
||||
enabled: !root.volumeMuted
|
||||
showValue: true
|
||||
unit: "%"
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Connections {
|
||||
target: AudioService.sink
|
||||
&& AudioService.sink.audio ? AudioService.sink.audio : null
|
||||
function onVolumeChanged() {
|
||||
volumeSlider.value = Math.round(AudioService.sink.audio.volume * 100)
|
||||
}
|
||||
StyledText {
|
||||
text: "Volume"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
value = Math.round(AudioService.sink.audio.volume * 100)
|
||||
}
|
||||
DankSlider {
|
||||
id: volumeSlider
|
||||
|
||||
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")
|
||||
}
|
||||
width: parent.width
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
leftIcon: root.volumeMuted ? "volume_off" : "volume_down"
|
||||
rightIcon: "volume_up"
|
||||
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,119 +10,119 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: audioTab
|
||||
id: audioTab
|
||||
|
||||
property int audioSubTab: 0
|
||||
property int audioSubTab: 0
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankTabBar {
|
||||
width: parent.width
|
||||
tabHeight: 40
|
||||
currentIndex: audioTab.audioSubTab
|
||||
showIcons: false
|
||||
model: [{
|
||||
"text": "Output"
|
||||
}, {
|
||||
"text": "Input"
|
||||
}]
|
||||
onTabClicked: function (index) {
|
||||
audioTab.audioSubTab = index
|
||||
}
|
||||
}
|
||||
|
||||
// Single Loader that switches between Output and Input
|
||||
Loader {
|
||||
width: parent.width
|
||||
height: parent.height - 48
|
||||
asynchronous: true
|
||||
sourceComponent: audioTab.audioSubTab === 0 ? outputTabComponent : inputTabComponent
|
||||
}
|
||||
}
|
||||
|
||||
// Output Tab Component
|
||||
Component {
|
||||
id: outputTabComponent
|
||||
DankFlickable {
|
||||
clip: true
|
||||
contentHeight: outputColumn.height
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: outputColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: volumeComponent
|
||||
DankTabBar {
|
||||
width: parent.width
|
||||
tabHeight: 40
|
||||
currentIndex: audioTab.audioSubTab
|
||||
showIcons: false
|
||||
model: [{
|
||||
"text": "Output"
|
||||
}, {
|
||||
"text": "Input"
|
||||
}]
|
||||
onTabClicked: function (index) {
|
||||
audioTab.audioSubTab = index
|
||||
}
|
||||
}
|
||||
|
||||
// Single Loader that switches between Output and Input
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: outputDevicesComponent
|
||||
width: parent.width
|
||||
height: parent.height - 48
|
||||
asynchronous: true
|
||||
sourceComponent: audioTab.audioSubTab === 0 ? outputTabComponent : inputTabComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input Tab Component
|
||||
Component {
|
||||
id: inputTabComponent
|
||||
DankFlickable {
|
||||
clip: true
|
||||
contentHeight: inputColumn.height
|
||||
contentWidth: width
|
||||
// Output Tab Component
|
||||
Component {
|
||||
id: outputTabComponent
|
||||
DankFlickable {
|
||||
clip: true
|
||||
contentHeight: outputColumn.height
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: inputColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
Column {
|
||||
id: outputColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: microphoneComponent
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: volumeComponent
|
||||
}
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: outputDevicesComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: inputDevicesComponent
|
||||
// Input Tab Component
|
||||
Component {
|
||||
id: inputTabComponent
|
||||
DankFlickable {
|
||||
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
|
||||
Component {
|
||||
id: volumeComponent
|
||||
VolumeControl {
|
||||
width: parent.width
|
||||
// Volume Control Component
|
||||
Component {
|
||||
id: volumeComponent
|
||||
VolumeControl {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Microphone Control Component
|
||||
Component {
|
||||
id: microphoneComponent
|
||||
MicrophoneControl {
|
||||
width: parent.width
|
||||
// Microphone Control Component
|
||||
Component {
|
||||
id: microphoneComponent
|
||||
MicrophoneControl {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output Devices Component
|
||||
Component {
|
||||
id: outputDevicesComponent
|
||||
AudioDevicesList {
|
||||
width: parent.width
|
||||
// Output Devices Component
|
||||
Component {
|
||||
id: outputDevicesComponent
|
||||
AudioDevicesList {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input Devices Component
|
||||
Component {
|
||||
id: inputDevicesComponent
|
||||
AudioInputDevicesList {
|
||||
width: parent.width
|
||||
// Input Devices Component
|
||||
Component {
|
||||
id: inputDevicesComponent
|
||||
AudioInputDevicesList {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,427 +9,444 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: "Available Devices"
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.cornerRadius
|
||||
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
|
||||
}
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
width: parent.width
|
||||
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: "Available Devices"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Scanning for devices..."
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.cornerRadius
|
||||
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 {
|
||||
text: "Make sure your device is in pairing mode"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.7)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Make sure your device is in pairing mode"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.7)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
text: "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
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,199 +9,203 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property var deviceData: null
|
||||
property bool menuVisible: false
|
||||
property var parentItem
|
||||
property var deviceData: null
|
||||
property bool menuVisible: false
|
||||
property var parentItem
|
||||
|
||||
function show(x, y) {
|
||||
const menuWidth = 160
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
let finalX = x - menuWidth / 2
|
||||
let finalY = y
|
||||
finalX = Math.max(0, Math.min(finalX, parentItem.width - menuWidth))
|
||||
finalY = Math.max(0, Math.min(finalY, parentItem.height - menuHeight))
|
||||
root.x = finalX
|
||||
root.y = finalY
|
||||
root.visible = true
|
||||
root.menuVisible = true
|
||||
}
|
||||
function show(x, y) {
|
||||
const menuWidth = 160
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
let finalX = x - menuWidth / 2
|
||||
let finalY = y
|
||||
finalX = Math.max(0, Math.min(finalX, parentItem.width - menuWidth))
|
||||
finalY = Math.max(0, Math.min(finalY, parentItem.height - menuHeight))
|
||||
root.x = finalX
|
||||
root.y = finalY
|
||||
root.visible = true
|
||||
root.menuVisible = true
|
||||
}
|
||||
|
||||
function hide() {
|
||||
root.menuVisible = false
|
||||
Qt.callLater(() => {
|
||||
root.visible = false
|
||||
})
|
||||
}
|
||||
function hide() {
|
||||
root.menuVisible = false
|
||||
Qt.callLater(() => {
|
||||
root.visible = false
|
||||
})
|
||||
}
|
||||
|
||||
visible: false
|
||||
width: 160
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
|
||||
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
|
||||
visible: false
|
||||
width: 160
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: connectArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.12) : "transparent"
|
||||
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
|
||||
}
|
||||
|
||||
Row {
|
||||
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
|
||||
Column {
|
||||
id: menuColumn
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: connectArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
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 {
|
||||
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: 32
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
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,64 +9,64 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
height: 60
|
||||
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))
|
||||
border.color: BluetoothService.adapter
|
||||
&& BluetoothService.adapter.enabled ? Theme.primary : "transparent"
|
||||
border.width: 2
|
||||
width: parent.width
|
||||
height: 60
|
||||
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))
|
||||
border.color: BluetoothService.adapter
|
||||
&& BluetoothService.adapter.enabled ? Theme.primary : "transparent"
|
||||
border.width: 2
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "bluetooth"
|
||||
size: Theme.iconSizeLarge
|
||||
color: BluetoothService.adapter
|
||||
&& BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
MouseArea {
|
||||
id: bluetoothToggle
|
||||
|
||||
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)
|
||||
}
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (BluetoothService.adapter)
|
||||
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: bluetoothToggle
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (BluetoothService.adapter)
|
||||
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,173 +9,182 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
function findBluetoothContextMenu() {
|
||||
var p = parent
|
||||
while (p) {
|
||||
if (p.bluetoothContextMenuWindow)
|
||||
return p.bluetoothContextMenuWindow
|
||||
p = p.parent
|
||||
function findBluetoothContextMenu() {
|
||||
var p = parent
|
||||
while (p) {
|
||||
if (p.bluetoothContextMenuWindow)
|
||||
return p.bluetoothContextMenuWindow
|
||||
p = p.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
|
||||
StyledText {
|
||||
text: "Paired Devices"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: BluetoothService.adapter
|
||||
&& BluetoothService.adapter.devices ? BluetoothService.adapter.devices.values.filter(
|
||||
dev => {
|
||||
return dev
|
||||
&& (dev.paired
|
||||
|| dev.trusted)
|
||||
}) : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: btDeviceArea.containsMouse ? Qt.rgba(
|
||||
Theme.primary.r, Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.08) : (modelData.connected ? Qt.rgba(
|
||||
Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.12) : Qt.rgba(
|
||||
Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b,
|
||||
0.08))
|
||||
border.color: modelData.connected ? Theme.primary : "transparent"
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
text: "Paired Devices"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: BluetoothService.adapter
|
||||
&& BluetoothService.adapter.devices ? BluetoothService.adapter.devices.values.filter(
|
||||
dev => {
|
||||
return dev
|
||||
&& (dev.paired
|
||||
|| dev.trusted)
|
||||
}) : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: btDeviceArea.containsMouse ? Qt.rgba(
|
||||
Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.08) : (modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: modelData.connected ? Theme.primary : "transparent"
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
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,79 +10,79 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: bluetoothTab
|
||||
id: bluetoothTab
|
||||
|
||||
property alias bluetoothContextMenuWindow: bluetoothContextMenuWindow
|
||||
property alias bluetoothContextMenuWindow: bluetoothContextMenuWindow
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
contentHeight: mainColumn.height
|
||||
contentWidth: width
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
contentHeight: mainColumn.height
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
Column {
|
||||
id: mainColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: toggleComponent
|
||||
}
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: toggleComponent
|
||||
}
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: pairedComponent
|
||||
}
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: pairedComponent
|
||||
}
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: availableComponent
|
||||
}
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: availableComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothContextMenu {
|
||||
id: bluetoothContextMenuWindow
|
||||
parentItem: bluetoothTab
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
visible: bluetoothContextMenuWindow.visible
|
||||
onClicked: {
|
||||
bluetoothContextMenuWindow.hide()
|
||||
BluetoothContextMenu {
|
||||
id: bluetoothContextMenuWindow
|
||||
parentItem: bluetoothTab
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
x: bluetoothContextMenuWindow.x
|
||||
y: bluetoothContextMenuWindow.y
|
||||
width: bluetoothContextMenuWindow.width
|
||||
height: bluetoothContextMenuWindow.height
|
||||
onClicked: {
|
||||
anchors.fill: parent
|
||||
visible: bluetoothContextMenuWindow.visible
|
||||
onClicked: {
|
||||
bluetoothContextMenuWindow.hide()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
x: bluetoothContextMenuWindow.x
|
||||
y: bluetoothContextMenuWindow.y
|
||||
width: bluetoothContextMenuWindow.width
|
||||
height: bluetoothContextMenuWindow.height
|
||||
onClicked: {
|
||||
|
||||
Component {
|
||||
id: toggleComponent
|
||||
BluetoothToggle {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pairedComponent
|
||||
PairedDevicesList {
|
||||
width: parent.width
|
||||
Component {
|
||||
id: toggleComponent
|
||||
BluetoothToggle {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: availableComponent
|
||||
AvailableDevicesList {
|
||||
width: parent.width
|
||||
Component {
|
||||
id: pairedComponent
|
||||
PairedDevicesList {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: availableComponent
|
||||
AvailableDevicesList {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,12 +34,9 @@ Item {
|
||||
width: parent.width
|
||||
sourceComponent: settingsComponent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: brightnessComponent
|
||||
|
||||
@@ -62,57 +59,62 @@ Item {
|
||||
visible: BrightnessService.devices.length > 1
|
||||
text: "Device"
|
||||
description: {
|
||||
const deviceInfo = BrightnessService.getCurrentDeviceInfo();
|
||||
const deviceInfo = BrightnessService.getCurrentDeviceInfo()
|
||||
if (deviceInfo && deviceInfo.class === "ddc") {
|
||||
return "DDC changes can be slow and unreliable";
|
||||
return "DDC changes can be slow and unreliable"
|
||||
}
|
||||
return "";
|
||||
return ""
|
||||
}
|
||||
currentValue: BrightnessService.currentDevice
|
||||
options: BrightnessService.devices.map(function(d) {
|
||||
return d.name;
|
||||
options: BrightnessService.devices.map(function (d) {
|
||||
return d.name
|
||||
})
|
||||
optionIcons: BrightnessService.devices.map(function(d) {
|
||||
optionIcons: BrightnessService.devices.map(function (d) {
|
||||
if (d.class === "backlight")
|
||||
return "desktop_windows";
|
||||
|
||||
return "desktop_windows"
|
||||
|
||||
if (d.class === "ddc")
|
||||
return "tv";
|
||||
return "tv"
|
||||
|
||||
if (d.name.includes("kbd"))
|
||||
return "keyboard";
|
||||
return "keyboard"
|
||||
|
||||
return "lightbulb";
|
||||
return "lightbulb"
|
||||
})
|
||||
onValueChanged: function(value) {
|
||||
BrightnessService.setCurrentDevice(value, true);
|
||||
onValueChanged: function (value) {
|
||||
BrightnessService.setCurrentDevice(value, true)
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: BrightnessService
|
||||
function onDevicesChanged() {
|
||||
if (BrightnessService.currentDevice) {
|
||||
deviceDropdown.currentValue = BrightnessService.currentDevice;
|
||||
deviceDropdown.currentValue = BrightnessService.currentDevice
|
||||
}
|
||||
|
||||
|
||||
// Check if saved device is now available
|
||||
const lastDevice = SessionData.lastBrightnessDevice || "";
|
||||
const lastDevice = SessionData.lastBrightnessDevice
|
||||
|| ""
|
||||
if (lastDevice) {
|
||||
const deviceExists = BrightnessService.devices.some(d => d.name === lastDevice);
|
||||
if (deviceExists && (!BrightnessService.currentDevice || BrightnessService.currentDevice !== lastDevice)) {
|
||||
BrightnessService.setCurrentDevice(lastDevice, false);
|
||||
const deviceExists = BrightnessService.devices.some(
|
||||
d => d.name === lastDevice)
|
||||
if (deviceExists
|
||||
&& (!BrightnessService.currentDevice
|
||||
|| BrightnessService.currentDevice !== lastDevice)) {
|
||||
BrightnessService.setCurrentDevice(lastDevice,
|
||||
false)
|
||||
}
|
||||
}
|
||||
}
|
||||
function onDeviceSwitched() {
|
||||
// Force update the description when device switches
|
||||
deviceDropdown.description = Qt.binding(function() {
|
||||
const deviceInfo = BrightnessService.getCurrentDeviceInfo();
|
||||
deviceDropdown.description = Qt.binding(function () {
|
||||
const deviceInfo = BrightnessService.getCurrentDeviceInfo()
|
||||
if (deviceInfo && deviceInfo.class === "ddc") {
|
||||
return "DDC changes can be slow and unreliable";
|
||||
return "DDC changes can be slow and unreliable"
|
||||
}
|
||||
return "";
|
||||
});
|
||||
return ""
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,31 +125,31 @@ Item {
|
||||
value: BrightnessService.brightnessLevel
|
||||
leftIcon: "brightness_low"
|
||||
rightIcon: "brightness_high"
|
||||
enabled: BrightnessService.brightnessAvailable && BrightnessService.isCurrentDeviceReady()
|
||||
enabled: BrightnessService.brightnessAvailable
|
||||
&& BrightnessService.isCurrentDeviceReady()
|
||||
opacity: BrightnessService.isCurrentDeviceReady() ? 1.0 : 0.5
|
||||
onSliderValueChanged: function(newValue) {
|
||||
brightnessDebounceTimer.pendingValue = newValue;
|
||||
brightnessDebounceTimer.restart();
|
||||
onSliderValueChanged: function (newValue) {
|
||||
brightnessDebounceTimer.pendingValue = newValue
|
||||
brightnessDebounceTimer.restart()
|
||||
}
|
||||
onSliderDragFinished: function(finalValue) {
|
||||
brightnessDebounceTimer.stop();
|
||||
BrightnessService.setBrightnessInternal(finalValue, BrightnessService.currentDevice);
|
||||
onSliderDragFinished: function (finalValue) {
|
||||
brightnessDebounceTimer.stop()
|
||||
BrightnessService.setBrightnessInternal(
|
||||
finalValue, BrightnessService.currentDevice)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: BrightnessService
|
||||
function onBrightnessChanged() {
|
||||
brightnessSlider.value = BrightnessService.brightnessLevel;
|
||||
brightnessSlider.value = BrightnessService.brightnessLevel
|
||||
}
|
||||
|
||||
|
||||
function onDeviceSwitched() {
|
||||
brightnessSlider.value = BrightnessService.brightnessLevel;
|
||||
brightnessSlider.value = BrightnessService.brightnessLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -172,7 +174,11 @@ Item {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: BrightnessService.nightModeActive ? 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))
|
||||
color: BrightnessService.nightModeActive ? 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: BrightnessService.nightModeActive ? Theme.primary : "transparent"
|
||||
border.width: BrightnessService.nightModeActive ? 1 : 0
|
||||
|
||||
@@ -194,7 +200,6 @@ Item {
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -204,17 +209,20 @@ Item {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
BrightnessService.toggleNightMode();
|
||||
BrightnessService.toggleNightMode()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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))
|
||||
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
|
||||
|
||||
@@ -236,7 +244,6 @@ Item {
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -246,7 +253,7 @@ Item {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Theme.toggleLightMode();
|
||||
Theme.toggleLightMode()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,15 +262,10 @@ Item {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
brightnessDebounceTimer: Timer {
|
||||
@@ -271,13 +273,13 @@ Item {
|
||||
|
||||
interval: {
|
||||
// Use longer interval for DDC devices since ddcutil is slow
|
||||
const deviceInfo = BrightnessService.getCurrentDeviceInfo();
|
||||
return (deviceInfo && deviceInfo.class === "ddc") ? 100 : 50;
|
||||
const deviceInfo = BrightnessService.getCurrentDeviceInfo()
|
||||
return (deviceInfo && deviceInfo.class === "ddc") ? 100 : 50
|
||||
}
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
BrightnessService.setBrightnessInternal(pendingValue, BrightnessService.currentDevice);
|
||||
BrightnessService.setBrightnessInternal(
|
||||
pendingValue, BrightnessService.currentDevice)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,117 +8,120 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: ethernetCard
|
||||
id: ethernetCard
|
||||
|
||||
width: parent.width
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (ethernetPreferenceArea.containsMouse && NetworkService.ethernetConnected
|
||||
&& NetworkService.wifiEnabled
|
||||
&& NetworkService.networkStatus !== "ethernet")
|
||||
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||
Theme.surfaceContainer.b, 0.8)
|
||||
width: parent.width
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (ethernetPreferenceArea.containsMouse
|
||||
&& NetworkService.ethernetConnected
|
||||
&& 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)
|
||||
}
|
||||
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
|
||||
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
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "lan"
|
||||
size: Theme.iconSize
|
||||
color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: "Ethernet"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: NetworkService.networkStatus
|
||||
=== "ethernet" ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
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)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "lan"
|
||||
size: Theme.iconSize
|
||||
color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
id: ethernetLoadingSpinner
|
||||
|
||||
name: "refresh"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.changingPreference
|
||||
&& NetworkService.targetPreference === "ethernet"
|
||||
z: 10
|
||||
|
||||
RotationAnimation {
|
||||
target: ethernetLoadingSpinner
|
||||
property: "rotation"
|
||||
running: ethernetLoadingSpinner.visible
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
MouseArea {
|
||||
id: ethernetPreferenceArea
|
||||
|
||||
StyledText {
|
||||
text: "Ethernet"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
anchors.fill: parent
|
||||
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) {
|
||||
|
||||
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)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
if (NetworkService.networkStatus !== "ethernet")
|
||||
NetworkService.setNetworkPreference("ethernet")
|
||||
else
|
||||
NetworkService.setNetworkPreference("auto")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: ethernetLoadingSpinner
|
||||
|
||||
name: "refresh"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.changingPreference
|
||||
&& NetworkService.targetPreference === "ethernet"
|
||||
z: 10
|
||||
|
||||
RotationAnimation {
|
||||
target: ethernetLoadingSpinner
|
||||
property: "rotation"
|
||||
running: ethernetLoadingSpinner.visible
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: ethernetPreferenceArea
|
||||
|
||||
anchors.fill: parent
|
||||
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,159 +8,163 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: wifiCard
|
||||
id: wifiCard
|
||||
|
||||
property var refreshTimer
|
||||
property var refreshTimer
|
||||
|
||||
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)
|
||||
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
|
||||
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
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: wifiToggle.left
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: wifiToggle.left
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: NetworkService.wifiSignalIcon
|
||||
size: Theme.iconSize
|
||||
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
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
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
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)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: NetworkService.wifiSignalIcon
|
||||
size: Theme.iconSize
|
||||
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
id: wifiLoadingSpinner
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
name: "refresh"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.right: wifiToggle.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.changingPreference
|
||||
&& NetworkService.targetPreference === "wifi"
|
||||
z: 10
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!NetworkService.wifiEnabled)
|
||||
return "WiFi is off"
|
||||
else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID)
|
||||
return NetworkService.currentWifiSSID || "Connected"
|
||||
else
|
||||
return "Not Connected"
|
||||
RotationAnimation {
|
||||
target: wifiLoadingSpinner
|
||||
property: "rotation"
|
||||
running: wifiLoadingSpinner.visible
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
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"
|
||||
DankToggle {
|
||||
id: wifiToggle
|
||||
|
||||
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 = 100
|
||||
NetworkService.wifiNetworks = []
|
||||
NetworkService.savedWifiNetworks = []
|
||||
NetworkService.connectionStatus = ""
|
||||
NetworkService.connectingSSID = ""
|
||||
NetworkService.isScanning = false
|
||||
NetworkService.refreshNetworkStatus()
|
||||
}
|
||||
NetworkService.toggleWifiRadio()
|
||||
if (refreshTimer)
|
||||
refreshTimer.triggered = true
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.7)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: wifiLoadingSpinner
|
||||
MouseArea {
|
||||
id: wifiPreferenceArea
|
||||
|
||||
name: "refresh"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.right: wifiToggle.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.changingPreference
|
||||
&& NetworkService.targetPreference === "wifi"
|
||||
z: 10
|
||||
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) {
|
||||
|
||||
RotationAnimation {
|
||||
target: wifiLoadingSpinner
|
||||
property: "rotation"
|
||||
running: wifiLoadingSpinner.visible
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
if (NetworkService.networkStatus !== "wifi")
|
||||
NetworkService.setNetworkPreference("wifi")
|
||||
else
|
||||
NetworkService.setNetworkPreference("auto")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: wifiToggle
|
||||
|
||||
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 = 100
|
||||
NetworkService.wifiNetworks = []
|
||||
NetworkService.savedWifiNetworks = []
|
||||
NetworkService.connectionStatus = ""
|
||||
NetworkService.connectingSSID = ""
|
||||
NetworkService.isScanning = false
|
||||
NetworkService.refreshNetworkStatus()
|
||||
}
|
||||
NetworkService.toggleWifiRadio()
|
||||
if (refreshTimer)
|
||||
refreshTimer.triggered = true
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,289 +8,293 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: wifiContextMenuWindow
|
||||
id: wifiContextMenuWindow
|
||||
|
||||
property var networkData: null
|
||||
property bool menuVisible: false
|
||||
property var parentItem
|
||||
property var wifiPasswordModalRef
|
||||
property var networkInfoModalRef
|
||||
property var networkData: null
|
||||
property bool menuVisible: false
|
||||
property var parentItem
|
||||
property var wifiPasswordModalRef
|
||||
property var networkInfoModalRef
|
||||
|
||||
function show(x, y) {
|
||||
const menuWidth = 160
|
||||
wifiContextMenuWindow.visible = true
|
||||
Qt.callLater(() => {
|
||||
const menuHeight = wifiMenuColumn.implicitHeight + Theme.spacingS * 2
|
||||
let finalX = x - menuWidth / 2
|
||||
let finalY = y + 4
|
||||
finalX = Math.max(
|
||||
Theme.spacingS,
|
||||
Math.min(finalX,
|
||||
parentItem.width - menuWidth - Theme.spacingS))
|
||||
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 show(x, y) {
|
||||
const menuWidth = 160
|
||||
wifiContextMenuWindow.visible = true
|
||||
Qt.callLater(() => {
|
||||
const menuHeight = wifiMenuColumn.implicitHeight + Theme.spacingS * 2
|
||||
let finalX = x - menuWidth / 2
|
||||
let finalY = y + 4
|
||||
finalX = Math.max(
|
||||
Theme.spacingS, Math.min(
|
||||
finalX,
|
||||
parentItem.width - menuWidth - Theme.spacingS))
|
||||
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
|
||||
})
|
||||
}
|
||||
function hide() {
|
||||
wifiContextMenuWindow.menuVisible = false
|
||||
Qt.callLater(() => {
|
||||
wifiContextMenuWindow.visible = false
|
||||
})
|
||||
}
|
||||
|
||||
visible: false
|
||||
width: 160
|
||||
height: wifiMenuColumn.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
visible: false
|
||||
width: 160
|
||||
height: wifiMenuColumn.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadius
|
||||
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 {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: connectWifiArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.12) : "transparent"
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
Column {
|
||||
id: wifiMenuColumn
|
||||
|
||||
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.show(wifiContextMenuWindow.networkData.ssid)
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
}
|
||||
} else {
|
||||
NetworkService.connectToWifi(
|
||||
wifiContextMenuWindow.networkData.ssid)
|
||||
}
|
||||
}
|
||||
}
|
||||
wifiContextMenuWindow.hide()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
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.show(
|
||||
wifiContextMenuWindow.networkData.ssid)
|
||||
}
|
||||
} else {
|
||||
NetworkService.connectToWifi(
|
||||
wifiContextMenuWindow.networkData.ssid)
|
||||
}
|
||||
}
|
||||
}
|
||||
wifiContextMenuWindow.hide()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: 32
|
||||
radius: Theme.cornerRadius
|
||||
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.cornerRadius
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
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.cornerRadius
|
||||
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 {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,299 +8,303 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property var wifiContextMenuWindow
|
||||
property var sortedWifiNetworks
|
||||
property var wifiPasswordModalRef
|
||||
property var wifiContextMenuWindow
|
||||
property var sortedWifiNetworks
|
||||
property var wifiPasswordModalRef
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 100
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
visible: NetworkService.wifiEnabled
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 100
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
visible: NetworkService.wifiEnabled
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: "Available Networks"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: parent.width - 170
|
||||
height: 1
|
||||
}
|
||||
|
||||
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
|
||||
StyledText {
|
||||
text: "Available Networks"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Behavior on rotation {
|
||||
RotationAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
Item {
|
||||
width: parent.width - 170
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
width: spanningNetworksColumn.width
|
||||
height: 38
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
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: signalIcon2
|
||||
id: refreshIconSpan
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: NetworkService.wifiSignalIcon
|
||||
size: Theme.iconSize - 2
|
||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: signalIcon2.right
|
||||
anchors.leftMargin: Theme.spacingXS
|
||||
anchors.right: rightIcons2.left
|
||||
anchors.rightMargin: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
MouseArea {
|
||||
id: refreshAreaSpan
|
||||
|
||||
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"
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!NetworkService.isScanning) {
|
||||
refreshIconSpan.rotation += 30
|
||||
NetworkService.scanWifi()
|
||||
}
|
||||
}
|
||||
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.show(modelData.ssid)
|
||||
}
|
||||
} else {
|
||||
NetworkService.connectToWifi(modelData.ssid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
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 {
|
||||
width: spanningNetworksColumn.width
|
||||
height: 38
|
||||
radius: Theme.cornerRadius
|
||||
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: NetworkService.wifiSignalIcon
|
||||
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.show(modelData.ssid)
|
||||
}
|
||||
} else {
|
||||
NetworkService.connectToWifi(modelData.ssid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,251 +9,256 @@ import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Network
|
||||
|
||||
Item {
|
||||
id: networkTab
|
||||
id: networkTab
|
||||
|
||||
property var wifiPasswordModalRef: {
|
||||
wifiPasswordModalLoader.active = true
|
||||
return wifiPasswordModalLoader.item
|
||||
}
|
||||
property var networkInfoModalRef: {
|
||||
networkInfoModalLoader.active = true
|
||||
return networkInfoModalLoader.item
|
||||
}
|
||||
|
||||
property var sortedWifiNetworks: {
|
||||
if (!NetworkService.wifiAvailable || !NetworkService.wifiEnabled) {
|
||||
return []
|
||||
property var wifiPasswordModalRef: {
|
||||
wifiPasswordModalLoader.active = true
|
||||
return wifiPasswordModalLoader.item
|
||||
}
|
||||
property var networkInfoModalRef: {
|
||||
networkInfoModalLoader.active = true
|
||||
return networkInfoModalLoader.item
|
||||
}
|
||||
|
||||
var allNetworks = NetworkService.wifiNetworks
|
||||
var savedNetworks = NetworkService.savedWifiNetworks
|
||||
var currentSSID = NetworkService.currentWifiSSID
|
||||
var signalStrength = NetworkService.wifiSignalStrengthStr
|
||||
var refreshTrigger = forceRefresh
|
||||
property var sortedWifiNetworks: {
|
||||
if (!NetworkService.wifiAvailable || !NetworkService.wifiEnabled) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Force recalculation
|
||||
var networks = [...allNetworks]
|
||||
var allNetworks = NetworkService.wifiNetworks
|
||||
var savedNetworks = NetworkService.savedWifiNetworks
|
||||
var currentSSID = NetworkService.currentWifiSSID
|
||||
var signalStrength = NetworkService.wifiSignalStrengthStr
|
||||
var refreshTrigger = 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
|
||||
}
|
||||
})
|
||||
// Force recalculation
|
||||
var networks = [...allNetworks]
|
||||
|
||||
networks.sort(function (a, b) {
|
||||
if (a.connected && !b.connected)
|
||||
return -1
|
||||
if (!a.connected && b.connected)
|
||||
return 1
|
||||
return b.signal - a.signal
|
||||
})
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
return networks
|
||||
}
|
||||
networks.sort(function (a, b) {
|
||||
if (a.connected && !b.connected)
|
||||
return -1
|
||||
if (!a.connected && b.connected)
|
||||
return 1
|
||||
return b.signal - a.signal
|
||||
})
|
||||
|
||||
property int forceRefresh: 0
|
||||
|
||||
Connections {
|
||||
target: NetworkService
|
||||
function onNetworksUpdated() {
|
||||
forceRefresh++
|
||||
return networks
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
NetworkService.addRef()
|
||||
if (NetworkService.wifiEnabled)
|
||||
NetworkService.scanWifi()
|
||||
}
|
||||
property int forceRefresh: 0
|
||||
|
||||
Component.onDestruction: {
|
||||
NetworkService.removeRef()
|
||||
}
|
||||
Connections {
|
||||
target: NetworkService
|
||||
function onNetworksUpdated() {
|
||||
forceRefresh++
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
Component.onCompleted: {
|
||||
NetworkService.addRef()
|
||||
if (NetworkService.wifiEnabled)
|
||||
NetworkService.scanWifi()
|
||||
}
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: parent.height
|
||||
spacing: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Component.onDestruction: {
|
||||
NetworkService.removeRef()
|
||||
}
|
||||
|
||||
Flickable {
|
||||
width: parent.width
|
||||
height: parent.height - 30
|
||||
clip: true
|
||||
contentWidth: width
|
||||
contentHeight: wifiContent.height
|
||||
boundsBehavior: Flickable.DragAndOvershootBounds
|
||||
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
||||
flickDeceleration: 1500
|
||||
maximumFlickVelocity: 2000
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// 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 {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: parent.height
|
||||
spacing: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Flickable {
|
||||
width: parent.width
|
||||
height: parent.height - 30
|
||||
clip: true
|
||||
contentWidth: width
|
||||
contentHeight: wifiContent.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: wifiContent
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
WiFiCard {}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: wifiContent
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: parent.height
|
||||
spacing: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
WiFiCard {}
|
||||
}
|
||||
Flickable {
|
||||
width: parent.width
|
||||
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
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: parent.height
|
||||
spacing: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Flickable {
|
||||
width: parent.width
|
||||
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
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 100
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
color: "transparent"
|
||||
visible: !NetworkService.wifiEnabled
|
||||
|
||||
Column {
|
||||
id: ethernetContent
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
EthernetCard {}
|
||||
DankIcon {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
WiFiNetworksList {
|
||||
wifiContextMenuWindow: wifiContextMenuWindow
|
||||
sortedWifiNetworks: networkTab.sortedWifiNetworks
|
||||
wifiPasswordModalRef: networkTab.wifiPasswordModalRef
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NetworkService
|
||||
function onWifiEnabledChanged() {
|
||||
if (NetworkService.wifiEnabled && visible) {
|
||||
// Trigger a scan when WiFi is enabled
|
||||
NetworkService.scanWifi()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 100
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
color: "transparent"
|
||||
visible: !NetworkService.wifiEnabled
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
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)
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (visible && NetworkService.wifiEnabled
|
||||
&& NetworkService.wifiNetworks.length === 0) {
|
||||
// Scan when tab becomes visible if we don't have networks cached
|
||||
NetworkService.scanWifi()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WiFiNetworksList {
|
||||
wifiContextMenuWindow: wifiContextMenuWindow
|
||||
sortedWifiNetworks: networkTab.sortedWifiNetworks
|
||||
wifiPasswordModalRef: networkTab.wifiPasswordModalRef
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NetworkService
|
||||
function onWifiEnabledChanged() {
|
||||
if (NetworkService.wifiEnabled && visible) {
|
||||
// Trigger a scan when WiFi is enabled
|
||||
NetworkService.scanWifi()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible && NetworkService.wifiEnabled && NetworkService.wifiNetworks.length === 0) {
|
||||
// Scan when tab becomes visible if we don't have networks cached
|
||||
NetworkService.scanWifi()
|
||||
}
|
||||
}
|
||||
|
||||
WiFiContextMenu {
|
||||
id: wifiContextMenuWindow
|
||||
parentItem: networkTab
|
||||
wifiPasswordModalRef: networkTab.wifiPasswordModalRef
|
||||
networkInfoModalRef: networkTab.networkInfoModalRef
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
visible: wifiContextMenuWindow.visible
|
||||
onClicked: {
|
||||
wifiContextMenuWindow.hide()
|
||||
WiFiContextMenu {
|
||||
id: wifiContextMenuWindow
|
||||
parentItem: networkTab
|
||||
wifiPasswordModalRef: networkTab.wifiPasswordModalRef
|
||||
networkInfoModalRef: networkTab.networkInfoModalRef
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
x: wifiContextMenuWindow.x
|
||||
y: wifiContextMenuWindow.y
|
||||
width: wifiContextMenuWindow.width
|
||||
height: wifiContextMenuWindow.height
|
||||
onClicked: {
|
||||
anchors.fill: parent
|
||||
visible: wifiContextMenuWindow.visible
|
||||
onClicked: {
|
||||
wifiContextMenuWindow.hide()
|
||||
}
|
||||
|
||||
}
|
||||
MouseArea {
|
||||
x: wifiContextMenuWindow.x
|
||||
y: wifiContextMenuWindow.y
|
||||
width: wifiContextMenuWindow.width
|
||||
height: wifiContextMenuWindow.height
|
||||
onClicked: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,292 +8,305 @@ import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property bool powerMenuVisible: false
|
||||
signal powerActionRequested(string action, string title, string message)
|
||||
property bool powerMenuVisible: false
|
||||
signal powerActionRequested(string action, string title, string message)
|
||||
|
||||
visible: powerMenuVisible
|
||||
implicitWidth: 400
|
||||
implicitHeight: 320
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
visible: powerMenuVisible
|
||||
implicitWidth: 400
|
||||
implicitHeight: 320
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
powerMenuVisible = false
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
}
|
||||
|
||||
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.cornerRadius
|
||||
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 {
|
||||
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
StyledText {
|
||||
text: "Power Options"
|
||||
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: {
|
||||
anchors.fill: parent
|
||||
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
|
||||
|
||||
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.powerActionRequested("logout", "Log Out", "Are you sure you want to log out?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.powerActionRequested("suspend", "Suspend", "Are you sure you want to suspend the system?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.powerActionRequested("reboot", "Reboot", "Are you sure you want to reboot the system?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.powerActionRequested("poweroff", "Power Off", "Are you sure you want to power off the system?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
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.cornerRadius
|
||||
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
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
MouseArea {
|
||||
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
StyledText {
|
||||
text: "Power Options"
|
||||
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 {
|
||||
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
|
||||
|
||||
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.powerActionRequested(
|
||||
"logout", "Log Out",
|
||||
"Are you sure you want to log out?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.powerActionRequested(
|
||||
"suspend", "Suspend",
|
||||
"Are you sure you want to suspend the system?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.powerActionRequested(
|
||||
"reboot", "Reboot",
|
||||
"Are you sure you want to reboot the system?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.powerActionRequested(
|
||||
"poweroff", "Power Off",
|
||||
"Are you sure you want to power off the system?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,194 +8,196 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
id: dock
|
||||
id: dock
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Top
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.layer: WlrLayershell.Top
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
property var modelData
|
||||
property var contextMenu
|
||||
property var windowsMenu
|
||||
property bool autoHide: SettingsData.dockAutoHide
|
||||
property real backgroundTransparency: SettingsData.dockTransparency
|
||||
property var modelData
|
||||
property var contextMenu
|
||||
property var windowsMenu
|
||||
property bool autoHide: SettingsData.dockAutoHide
|
||||
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
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onDockTransparencyChanged() {
|
||||
dock.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
|
||||
|
||||
screen: modelData
|
||||
visible: SettingsData.showDock
|
||||
color: "transparent"
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onDockTransparencyChanged() {
|
||||
dock.backgroundTransparency = SettingsData.dockTransparency
|
||||
}
|
||||
}
|
||||
|
||||
anchors {
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
screen: modelData
|
||||
visible: SettingsData.showDock
|
||||
color: "transparent"
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockContainer
|
||||
anchors.fill: parent
|
||||
margins {
|
||||
left: 0
|
||||
right: 0
|
||||
}
|
||||
|
||||
transform: Translate {
|
||||
id: dockSlide
|
||||
y: dock.reveal ? 0 : 60
|
||||
implicitHeight: 100
|
||||
exclusiveZone: autoHide ? -1 : 65 - 16
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
mask: Region {
|
||||
item: dockMouseArea
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dockBackground
|
||||
objectName: "dockBackground"
|
||||
MouseArea {
|
||||
id: dockMouseArea
|
||||
height: dock.reveal ? 65 : 12
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
implicitWidth: dock.reveal ? dockBackground.width + 32 : (dockBackground.width + 32)
|
||||
hoverEnabled: true
|
||||
|
||||
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.cornerRadius
|
||||
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
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockContainer
|
||||
anchors.fill: parent
|
||||
|
||||
transform: Translate {
|
||||
id: dockSlide
|
||||
y: dock.reveal ? 0 : 60
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.cornerRadius
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,33 +26,34 @@ Item {
|
||||
property bool showTooltip: mouseArea.containsMouse && !dragging
|
||||
property string tooltipText: {
|
||||
if (!appData)
|
||||
return "";
|
||||
return ""
|
||||
|
||||
// For window type, show app name + window title
|
||||
if (appData.type === "window" && showWindowTitle) {
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId);
|
||||
var appName = desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId;
|
||||
return appName + (windowTitle ? " • " + windowTitle : "");
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||
var appName = desktopEntry
|
||||
&& desktopEntry.name ? desktopEntry.name : appData.appId
|
||||
return appName + (windowTitle ? " • " + windowTitle : "")
|
||||
}
|
||||
// For pinned apps, just show app name
|
||||
if (!appData.appId)
|
||||
return "";
|
||||
return ""
|
||||
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId);
|
||||
return desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId;
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||
return desktopEntry
|
||||
&& desktopEntry.name ? desktopEntry.name : appData.appId
|
||||
}
|
||||
|
||||
width: 40
|
||||
height: 40
|
||||
onIsHoveredChanged: {
|
||||
if (isHovered) {
|
||||
exitAnimation.stop();
|
||||
exitAnimation.stop()
|
||||
if (!bounceAnimation.running)
|
||||
bounceAnimation.restart();
|
||||
|
||||
bounceAnimation.restart()
|
||||
} else {
|
||||
bounceAnimation.stop();
|
||||
exitAnimation.restart();
|
||||
bounceAnimation.stop()
|
||||
exitAnimation.restart()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +79,6 @@ Item {
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasizedDecel
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
@@ -110,8 +110,7 @@ Item {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (appData && appData.isPinned)
|
||||
longPressing = true;
|
||||
|
||||
longPressing = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,97 +122,116 @@ Item {
|
||||
hoverEnabled: true
|
||||
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onPressed: (mouse) => {
|
||||
if (mouse.button === Qt.LeftButton && appData && appData.isPinned) {
|
||||
dragStartPos = Qt.point(mouse.x, mouse.y);
|
||||
longPressTimer.start();
|
||||
}
|
||||
}
|
||||
onReleased: (mouse) => {
|
||||
longPressTimer.stop();
|
||||
if (longPressing) {
|
||||
if (dragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps)
|
||||
dockApps.movePinnedApp(originalIndex, targetIndex);
|
||||
onPressed: mouse => {
|
||||
if (mouse.button === Qt.LeftButton && appData
|
||||
&& appData.isPinned) {
|
||||
dragStartPos = Qt.point(mouse.x, mouse.y)
|
||||
longPressTimer.start()
|
||||
}
|
||||
}
|
||||
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) {
|
||||
targetIndex = newTargetIndex;
|
||||
dragStartPos = Qt.point(mouse.x, mouse.y);
|
||||
longPressing = false
|
||||
dragging = false
|
||||
dragOffset = Qt.point(0, 0)
|
||||
targetIndex = -1
|
||||
originalIndex = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onClicked: (mouse) => {
|
||||
if (!appData || longPressing)
|
||||
return ;
|
||||
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) {
|
||||
targetIndex = newTargetIndex
|
||||
dragStartPos = Qt.point(mouse.x,
|
||||
mouse.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (!appData || longPressing)
|
||||
return
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
// Handle based on type
|
||||
if (appData.type === "pinned") {
|
||||
// Launch the pinned app
|
||||
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 || ""
|
||||
});
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
// Handle based on type
|
||||
if (appData.type === "pinned") {
|
||||
// Launch the pinned app
|
||||
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 (appData.type === "window") {
|
||||
// Focus the specific window
|
||||
if (appData.windowId)
|
||||
NiriService.focusWindow(appData.windowId);
|
||||
Quickshell.execDetached(
|
||||
["gtk-launch", appData.appId])
|
||||
}
|
||||
} else if (appData.type === "window") {
|
||||
// Focus the specific window
|
||||
if (appData.windowId)
|
||||
NiriService.focusWindow(appData.windowId)
|
||||
}
|
||||
} 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
|
||||
|| ""
|
||||
})
|
||||
|
||||
}
|
||||
} 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);
|
||||
|
||||
}
|
||||
}
|
||||
Quickshell.execDetached(
|
||||
["gtk-launch", appData.appId])
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (contextMenu)
|
||||
contextMenu.showForButton(root, appData, 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
@@ -224,14 +242,16 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
source: {
|
||||
if (!appData || !appData.appId)
|
||||
return "";
|
||||
return ""
|
||||
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId);
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||
if (desktopEntry && desktopEntry.icon) {
|
||||
var iconPath = Quickshell.iconPath(desktopEntry.icon, SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme);
|
||||
return iconPath;
|
||||
var iconPath = Quickshell.iconPath(
|
||||
desktopEntry.icon, SettingsData.iconTheme
|
||||
=== "System Default" ? "" : SettingsData.iconTheme)
|
||||
return iconPath
|
||||
}
|
||||
return "";
|
||||
return ""
|
||||
}
|
||||
smooth: true
|
||||
mipmap: true
|
||||
@@ -254,19 +274,18 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (!appData || !appData.appId)
|
||||
return "?";
|
||||
return "?"
|
||||
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId);
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||
if (desktopEntry && desktopEntry.name)
|
||||
return desktopEntry.name.charAt(0).toUpperCase();
|
||||
return desktopEntry.name.charAt(0).toUpperCase()
|
||||
|
||||
return appData.appId.charAt(0).toUpperCase();
|
||||
return appData.appId.charAt(0).toUpperCase()
|
||||
}
|
||||
font.pixelSize: 14
|
||||
color: Theme.primary
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Indicator for running/focused state
|
||||
@@ -280,17 +299,18 @@ Item {
|
||||
visible: appData && (appData.isRunning || appData.type === "window")
|
||||
color: {
|
||||
if (!appData)
|
||||
return "transparent";
|
||||
return "transparent"
|
||||
|
||||
// For window type, check if focused
|
||||
if (appData.type === "window" && appData.isFocused)
|
||||
return Theme.primary;
|
||||
return Theme.primary
|
||||
|
||||
// For running apps, show dimmer indicator
|
||||
if (appData.isRunning || appData.type === "window")
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6);
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.6)
|
||||
|
||||
return "transparent";
|
||||
return "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,5 +319,4 @@ Item {
|
||||
|
||||
y: 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,166 +6,175 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property var contextMenu: null
|
||||
property var windowsMenu: null
|
||||
property bool requestDockShow: false
|
||||
property int pinnedAppCount: 0
|
||||
property var contextMenu: null
|
||||
property var windowsMenu: null
|
||||
property bool requestDockShow: false
|
||||
property int pinnedAppCount: 0
|
||||
|
||||
implicitWidth: row.width
|
||||
implicitHeight: row.height
|
||||
implicitWidth: row.width
|
||||
implicitHeight: row.height
|
||||
|
||||
function movePinnedApp(fromIndex, toIndex) {
|
||||
if (fromIndex === toIndex)
|
||||
return
|
||||
function movePinnedApp(fromIndex, toIndex) {
|
||||
if (fromIndex === toIndex)
|
||||
return
|
||||
|
||||
var currentPinned = [...(SessionData.pinnedApps || [])]
|
||||
if (fromIndex < 0 || fromIndex >= currentPinned.length || toIndex < 0
|
||||
|| toIndex >= currentPinned.length)
|
||||
return
|
||||
var currentPinned = [...(SessionData.pinnedApps || [])]
|
||||
if (fromIndex < 0 || fromIndex >= currentPinned.length || toIndex < 0
|
||||
|| toIndex >= currentPinned.length)
|
||||
return
|
||||
|
||||
var movedApp = currentPinned.splice(fromIndex, 1)[0]
|
||||
currentPinned.splice(toIndex, 0, movedApp)
|
||||
var movedApp = currentPinned.splice(fromIndex, 1)[0]
|
||||
currentPinned.splice(toIndex, 0, movedApp)
|
||||
|
||||
SessionData.setPinnedApps(currentPinned)
|
||||
}
|
||||
SessionData.setPinnedApps(currentPinned)
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row
|
||||
spacing: 2
|
||||
anchors.centerIn: parent
|
||||
height: 40
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: ListModel {
|
||||
id: dockModel
|
||||
|
||||
Component.onCompleted: updateModel()
|
||||
|
||||
function updateModel() {
|
||||
clear()
|
||||
|
||||
var items = []
|
||||
var pinnedApps = [...(SessionData.pinnedApps || [])]
|
||||
|
||||
// First section: Pinned apps (always visible, not representing running windows)
|
||||
pinnedApps.forEach(appId => {
|
||||
items.push({
|
||||
"type": "pinned",
|
||||
"appId": appId,
|
||||
"windowId": -1, // Use -1 instead of null to avoid ListModel warnings
|
||||
"windowTitle": "",
|
||||
"workspaceId": -1, // Use -1 instead of null
|
||||
"isPinned": true,
|
||||
"isRunning": false,
|
||||
"isFocused": false
|
||||
})
|
||||
})
|
||||
|
||||
root.pinnedAppCount = pinnedApps.length
|
||||
|
||||
// Add separator between pinned and running if both exist
|
||||
if (pinnedApps.length > 0 && NiriService.windows.length > 0) {
|
||||
items.push({
|
||||
"type": "separator",
|
||||
"appId": "__SEPARATOR__",
|
||||
"windowId": -1, // Use -1 instead of null
|
||||
"windowTitle": "",
|
||||
"workspaceId": -1, // Use -1 instead of null
|
||||
"isPinned": false,
|
||||
"isRunning": false,
|
||||
"isFocused": false
|
||||
})
|
||||
}
|
||||
|
||||
// Second section: Running windows (sorted by display->workspace->position)
|
||||
// NiriService.windows is already sorted by sortWindowsByLayout
|
||||
NiriService.windows.forEach(window => {
|
||||
// Limit window title length for tooltip
|
||||
var title = window.title || "(Unnamed)"
|
||||
if (title.length > 50) {
|
||||
title = title.substring(0, 47) + "..."
|
||||
}
|
||||
|
||||
// Check if this window is focused - compare as numbers
|
||||
var isFocused = window.id == NiriService.focusedWindowId
|
||||
|
||||
items.push({
|
||||
"type": "window",
|
||||
"appId": window.app_id || "",
|
||||
"windowId": window.id || -1,
|
||||
"windowTitle": title,
|
||||
"workspaceId": window.workspace_id || -1,
|
||||
"isPinned": false,
|
||||
"isRunning": true,
|
||||
"isFocused": isFocused
|
||||
})
|
||||
})
|
||||
|
||||
items.forEach(item => {
|
||||
append(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
property alias dockButton: button
|
||||
|
||||
width: model.type === "separator" ? 16 : 40
|
||||
Row {
|
||||
id: row
|
||||
spacing: 2
|
||||
anchors.centerIn: parent
|
||||
height: 40
|
||||
|
||||
Rectangle {
|
||||
visible: model.type === "separator"
|
||||
width: 2
|
||||
height: 20
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
radius: 1
|
||||
anchors.centerIn: parent
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: ListModel {
|
||||
id: dockModel
|
||||
|
||||
Component.onCompleted: updateModel()
|
||||
|
||||
function updateModel() {
|
||||
clear()
|
||||
|
||||
var items = []
|
||||
var pinnedApps = [...(SessionData.pinnedApps || [])]
|
||||
|
||||
// First section: Pinned apps (always visible, not representing running windows)
|
||||
pinnedApps.forEach(appId => {
|
||||
items.push({
|
||||
"type": "pinned",
|
||||
"appId": appId,
|
||||
"windowId": -1,
|
||||
"windowTitle"// Use -1 instead of null to avoid ListModel warnings
|
||||
: "",
|
||||
"workspaceId": -1,
|
||||
"isPinned"// Use -1 instead of null
|
||||
: true,
|
||||
"isRunning": false,
|
||||
"isFocused": false
|
||||
})
|
||||
})
|
||||
|
||||
root.pinnedAppCount = pinnedApps.length
|
||||
|
||||
// Add separator between pinned and running if both exist
|
||||
if (pinnedApps.length > 0
|
||||
&& NiriService.windows.length > 0) {
|
||||
items.push({
|
||||
"type": "separator",
|
||||
"appId": "__SEPARATOR__",
|
||||
"windowId": -1,
|
||||
"windowTitle"// Use -1 instead of null
|
||||
: "",
|
||||
"workspaceId": -1,
|
||||
"isPinned"// Use -1 instead of null
|
||||
: false,
|
||||
"isRunning": false,
|
||||
"isFocused": false
|
||||
})
|
||||
}
|
||||
|
||||
// Second section: Running windows (sorted by display->workspace->position)
|
||||
// NiriService.windows is already sorted by sortWindowsByLayout
|
||||
NiriService.windows.forEach(window => {
|
||||
// Limit window title length for tooltip
|
||||
var title = window.title
|
||||
|| "(Unnamed)"
|
||||
if (title.length > 50) {
|
||||
title = title.substring(
|
||||
0, 47) + "..."
|
||||
}
|
||||
|
||||
// Check if this window is focused - compare as numbers
|
||||
var isFocused = window.id
|
||||
== NiriService.focusedWindowId
|
||||
|
||||
items.push({
|
||||
"type": "window",
|
||||
"appId": window.app_id
|
||||
|| "",
|
||||
"windowId": window.id || -1,
|
||||
"windowTitle": title,
|
||||
"workspaceId": window.workspace_id || -1,
|
||||
"isPinned": false,
|
||||
"isRunning": true,
|
||||
"isFocused": isFocused
|
||||
})
|
||||
})
|
||||
|
||||
items.forEach(item => {
|
||||
append(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
property alias dockButton: button
|
||||
|
||||
width: model.type === "separator" ? 16 : 40
|
||||
height: 40
|
||||
|
||||
Rectangle {
|
||||
visible: model.type === "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.type !== "separator"
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: 40
|
||||
height: 40
|
||||
|
||||
appData: model
|
||||
contextMenu: root.contextMenu
|
||||
windowsMenu: root.windowsMenu
|
||||
dockApps: root
|
||||
index: model.index
|
||||
|
||||
// Override tooltip for windows to show window title
|
||||
showWindowTitle: model.type === "window"
|
||||
windowTitle: model.windowTitle || ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DockAppButton {
|
||||
id: button
|
||||
visible: model.type !== "separator"
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: 40
|
||||
height: 40
|
||||
|
||||
appData: model
|
||||
contextMenu: root.contextMenu
|
||||
windowsMenu: root.windowsMenu
|
||||
dockApps: root
|
||||
index: model.index
|
||||
|
||||
// Override tooltip for windows to show window title
|
||||
showWindowTitle: model.type === "window"
|
||||
windowTitle: model.windowTitle || ""
|
||||
Connections {
|
||||
target: NiriService
|
||||
function onWindowsChanged() {
|
||||
dockModel.updateModel()
|
||||
}
|
||||
function onWindowOpenedOrChanged() {
|
||||
dockModel.updateModel()
|
||||
}
|
||||
function onFocusedWindowIdChanged() {
|
||||
dockModel.updateModel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NiriService
|
||||
function onWindowsChanged() {
|
||||
dockModel.updateModel()
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onPinnedAppsChanged() {
|
||||
dockModel.updateModel()
|
||||
}
|
||||
}
|
||||
function onWindowOpenedOrChanged() {
|
||||
dockModel.updateModel()
|
||||
}
|
||||
function onFocusedWindowIdChanged() {
|
||||
dockModel.updateModel()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onPinnedAppsChanged() {
|
||||
dockModel.updateModel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,301 +8,308 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property bool showContextMenu: false
|
||||
property var appData: null
|
||||
property var anchorItem: null
|
||||
property real dockVisibleHeight: 40
|
||||
property int margin: 10
|
||||
property bool showContextMenu: false
|
||||
property var appData: null
|
||||
property var anchorItem: null
|
||||
property real dockVisibleHeight: 40
|
||||
property int margin: 10
|
||||
|
||||
function showForButton(button, data, dockHeight) {
|
||||
anchorItem = button
|
||||
appData = data
|
||||
dockVisibleHeight = dockHeight || 40
|
||||
function showForButton(button, data, dockHeight) {
|
||||
anchorItem = button
|
||||
appData = data
|
||||
dockVisibleHeight = dockHeight || 40
|
||||
|
||||
var dockWindow = button.Window.window
|
||||
if (dockWindow) {
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
var s = Quickshell.screens[i]
|
||||
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
|
||||
root.screen = s
|
||||
break
|
||||
var dockWindow = button.Window.window
|
||||
if (dockWindow) {
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
var s = Quickshell.screens[i]
|
||||
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
|
||||
root.screen = s
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showContextMenu = true
|
||||
}
|
||||
function close() {
|
||||
showContextMenu = false
|
||||
}
|
||||
|
||||
showContextMenu = true
|
||||
}
|
||||
function close() {
|
||||
showContextMenu = false
|
||||
}
|
||||
screen: Quickshell.screens[0]
|
||||
|
||||
screen: Quickshell.screens[0]
|
||||
|
||||
visible: showContextMenu
|
||||
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)
|
||||
|
||||
onAnchorItemChanged: updatePosition()
|
||||
onVisibleChanged: if (visible)
|
||||
updatePosition()
|
||||
|
||||
function updatePosition() {
|
||||
if (!anchorItem) {
|
||||
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
|
||||
return
|
||||
visible: showContextMenu
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
var dockWindow = anchorItem.Window.window
|
||||
if (!dockWindow) {
|
||||
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
|
||||
return
|
||||
property point anchorPos: Qt.point(screen.width / 2, screen.height - 100)
|
||||
|
||||
onAnchorItemChanged: updatePosition()
|
||||
onVisibleChanged: if (visible)
|
||||
updatePosition()
|
||||
|
||||
function updatePosition() {
|
||||
if (!anchorItem) {
|
||||
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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.cornerRadius
|
||||
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 {
|
||||
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
|
||||
}
|
||||
id: menuContainer
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Theme.spacingS
|
||||
spacing: 1
|
||||
width: Math.min(400,
|
||||
Math.max(200,
|
||||
menuColumn.implicitWidth + Theme.spacingS * 2))
|
||||
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 28
|
||||
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.cornerRadius
|
||||
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
|
||||
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 {
|
||||
required property var model
|
||||
width: menuColumn.width
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
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.cornerRadius
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
Column {
|
||||
id: menuColumn
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
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.cornerRadius
|
||||
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.cornerRadius
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,207 +8,208 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property bool showWindowsMenu: false
|
||||
property var appData: null
|
||||
property var anchorItem: null
|
||||
property real dockVisibleHeight: 40
|
||||
property int margin: 10
|
||||
property bool showWindowsMenu: false
|
||||
property var appData: null
|
||||
property var anchorItem: null
|
||||
property real dockVisibleHeight: 40
|
||||
property int margin: 10
|
||||
|
||||
function showForButton(button, data, dockHeight) {
|
||||
anchorItem = button
|
||||
appData = data
|
||||
dockVisibleHeight = dockHeight || 40
|
||||
function showForButton(button, data, dockHeight) {
|
||||
anchorItem = button
|
||||
appData = data
|
||||
dockVisibleHeight = dockHeight || 40
|
||||
|
||||
var dockWindow = button.Window.window
|
||||
if (dockWindow) {
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
var s = Quickshell.screens[i]
|
||||
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
|
||||
root.screen = s
|
||||
break
|
||||
var dockWindow = button.Window.window
|
||||
if (dockWindow) {
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
var s = Quickshell.screens[i]
|
||||
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
|
||||
root.screen = s
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showWindowsMenu = true
|
||||
}
|
||||
|
||||
showWindowsMenu = true
|
||||
}
|
||||
|
||||
function close() {
|
||||
showWindowsMenu = false
|
||||
}
|
||||
|
||||
screen: Quickshell.screens[0]
|
||||
|
||||
visible: showWindowsMenu
|
||||
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)
|
||||
|
||||
onAnchorItemChanged: updatePosition()
|
||||
onVisibleChanged: if (visible)
|
||||
updatePosition()
|
||||
|
||||
function updatePosition() {
|
||||
if (!anchorItem) {
|
||||
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
|
||||
return
|
||||
function close() {
|
||||
showWindowsMenu = false
|
||||
}
|
||||
|
||||
var dockWindow = anchorItem.Window.window
|
||||
if (!dockWindow) {
|
||||
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
|
||||
return
|
||||
screen: Quickshell.screens[0]
|
||||
|
||||
visible: showWindowsMenu
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
var buttonPosInDock = anchorItem.mapToItem(dockWindow.contentItem, 0, 0)
|
||||
property point anchorPos: Qt.point(screen.width / 2, screen.height - 100)
|
||||
|
||||
var actualDockHeight = root.dockVisibleHeight // fallback
|
||||
onAnchorItemChanged: updatePosition()
|
||||
onVisibleChanged: if (visible)
|
||||
updatePosition()
|
||||
|
||||
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
|
||||
function updatePosition() {
|
||||
if (!anchorItem) {
|
||||
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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.cornerRadius
|
||||
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 {
|
||||
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
|
||||
}
|
||||
id: menuContainer
|
||||
|
||||
Column {
|
||||
id: windowColumn
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Theme.spacingS
|
||||
spacing: 1
|
||||
width: Math.min(600, Math.max(
|
||||
250,
|
||||
windowColumn.implicitWidth + Theme.spacingS * 2))
|
||||
height: Math.max(60, windowColumn.implicitHeight + Theme.spacingS * 2)
|
||||
|
||||
Repeater {
|
||||
model: root.appData
|
||||
&& root.appData.windows ? root.appData.windows : null
|
||||
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.cornerRadius
|
||||
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 {
|
||||
required property var model
|
||||
width: windowColumn.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
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()
|
||||
}
|
||||
}
|
||||
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: 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.cornerRadius
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
hoverEnabled: false
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,4 +114,4 @@ PanelWindow {
|
||||
mask: Region {
|
||||
item: inhibitorPopup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,148 +8,152 @@ import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property string sid: Quickshell.env("XDG_SESSION_ID") || "self"
|
||||
property string sessionPath: ""
|
||||
id: root
|
||||
property string sid: Quickshell.env("XDG_SESSION_ID") || "self"
|
||||
property string sessionPath: ""
|
||||
|
||||
function activate() {
|
||||
loader.activeAsync = true
|
||||
}
|
||||
function activate() {
|
||||
loader.activeAsync = true
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
getSessionPath.running = true
|
||||
}
|
||||
Component.onCompleted: {
|
||||
getSessionPath.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getSessionPath
|
||||
command: ["gdbus", "call", "--system", "--dest", "org.freedesktop.login1", "--object-path", "/org/freedesktop/login1", "--method", "org.freedesktop.login1.Manager.GetSession", sid]
|
||||
running: false
|
||||
Process {
|
||||
id: getSessionPath
|
||||
command: ["gdbus", "call", "--system", "--dest", "org.freedesktop.login1", "--object-path", "/org/freedesktop/login1", "--method", "org.freedesktop.login1.Manager.GetSession", sid]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const match = text.match(/objectpath '([^']+)'/)
|
||||
if (match) {
|
||||
root.sessionPath = match[1]
|
||||
console.log("Found session path:", root.sessionPath)
|
||||
checkCurrentLockState.running = true
|
||||
lockStateMonitor.running = true
|
||||
} else {
|
||||
console.warn("Could not determine session path")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const match = text.match(/objectpath '([^']+)'/)
|
||||
if (match) {
|
||||
root.sessionPath = match[1]
|
||||
console.log("Found session path:", root.sessionPath)
|
||||
checkCurrentLockState.running = true
|
||||
lockStateMonitor.running = true
|
||||
} else {
|
||||
console.warn("Could not determine session path")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Failed to get session path, exit code:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: checkCurrentLockState
|
||||
command: root.sessionPath ? ["gdbus", "call", "--system", "--dest", "org.freedesktop.login1", "--object-path", root.sessionPath, "--method", "org.freedesktop.DBus.Properties.Get", "org.freedesktop.login1.Session", "LockedHint"] : []
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.includes("true")) {
|
||||
console.log("Session is locked on startup, activating lock screen")
|
||||
LockScreenService.resetState();
|
||||
loader.activeAsync = true
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Failed to get session path, exit code:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Failed to check initial lock state, exit code:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
Process {
|
||||
id: checkCurrentLockState
|
||||
command: root.sessionPath ? ["gdbus", "call", "--system", "--dest", "org.freedesktop.login1", "--object-path", root.sessionPath, "--method", "org.freedesktop.DBus.Properties.Get", "org.freedesktop.login1.Session", "LockedHint"] : []
|
||||
running: false
|
||||
|
||||
Process {
|
||||
id: lockStateMonitor
|
||||
command: root.sessionPath ? ["gdbus", "monitor", "--system", "--dest", "org.freedesktop.login1", "--object-path", root.sessionPath] : []
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
|
||||
onRead: (line) => {
|
||||
if (line.includes("org.freedesktop.login1.Session.Lock")) {
|
||||
console.log("login1: Lock signal received -> show lock")
|
||||
LockScreenService.resetState();
|
||||
loader.activeAsync = true
|
||||
} else if (line.includes("org.freedesktop.login1.Session.Unlock")) {
|
||||
console.log("login1: Unlock signal received -> hide lock")
|
||||
loader.active = false
|
||||
} else if (line.includes("LockedHint") && line.includes("true")) {
|
||||
console.log("login1: LockedHint=true -> show lock")
|
||||
LockScreenService.resetState();
|
||||
loader.activeAsync = true
|
||||
} else if (line.includes("LockedHint") && line.includes("false")) {
|
||||
console.log("login1: LockedHint=false -> hide lock")
|
||||
loader.active = false
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.includes("true")) {
|
||||
console.log(
|
||||
"Session is locked on startup, activating lock screen")
|
||||
LockScreenService.resetState()
|
||||
loader.activeAsync = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("gdbus monitor failed, exit code:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LazyLoader {
|
||||
id: loader
|
||||
|
||||
WlSessionLock {
|
||||
id: sessionLock
|
||||
|
||||
property bool unlocked: false
|
||||
property string sharedPasswordBuffer: ""
|
||||
|
||||
locked: true
|
||||
|
||||
onLockedChanged: {
|
||||
if (!locked)
|
||||
loader.active = false
|
||||
}
|
||||
|
||||
LockSurface {
|
||||
id: lockSurface
|
||||
lock: sessionLock
|
||||
sharedPasswordBuffer: sessionLock.sharedPasswordBuffer
|
||||
onPasswordChanged: newPassword => {
|
||||
sessionLock.sharedPasswordBuffer = newPassword
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Failed to check initial lock state, exit code:",
|
||||
exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LockScreenDemo {
|
||||
id: demoWindow
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "lock"
|
||||
|
||||
function lock(): void {
|
||||
console.log("Lock screen requested via IPC")
|
||||
LockScreenService.resetState();
|
||||
loader.activeAsync = true
|
||||
}
|
||||
|
||||
function demo(): void {
|
||||
console.log("Lock screen DEMO mode requested via IPC")
|
||||
demoWindow.showDemo()
|
||||
Process {
|
||||
id: lockStateMonitor
|
||||
command: root.sessionPath ? ["gdbus", "monitor", "--system", "--dest", "org.freedesktop.login1", "--object-path", root.sessionPath] : []
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
|
||||
onRead: line => {
|
||||
if (line.includes("org.freedesktop.login1.Session.Lock")) {
|
||||
console.log("login1: Lock signal received -> show lock")
|
||||
LockScreenService.resetState()
|
||||
loader.activeAsync = true
|
||||
} else if (line.includes(
|
||||
"org.freedesktop.login1.Session.Unlock")) {
|
||||
console.log("login1: Unlock signal received -> hide lock")
|
||||
loader.active = false
|
||||
} else if (line.includes("LockedHint") && line.includes(
|
||||
"true")) {
|
||||
console.log("login1: LockedHint=true -> show lock")
|
||||
LockScreenService.resetState()
|
||||
loader.activeAsync = true
|
||||
} else if (line.includes("LockedHint") && line.includes(
|
||||
"false")) {
|
||||
console.log("login1: LockedHint=false -> hide lock")
|
||||
loader.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("gdbus monitor failed, exit code:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isLocked(): bool {
|
||||
return loader.active
|
||||
LazyLoader {
|
||||
id: loader
|
||||
|
||||
WlSessionLock {
|
||||
id: sessionLock
|
||||
|
||||
property bool unlocked: false
|
||||
property string sharedPasswordBuffer: ""
|
||||
|
||||
locked: true
|
||||
|
||||
onLockedChanged: {
|
||||
if (!locked)
|
||||
loader.active = false
|
||||
}
|
||||
|
||||
LockSurface {
|
||||
id: lockSurface
|
||||
lock: sessionLock
|
||||
sharedPasswordBuffer: sessionLock.sharedPasswordBuffer
|
||||
onPasswordChanged: newPassword => {
|
||||
sessionLock.sharedPasswordBuffer = newPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LockScreenDemo {
|
||||
id: demoWindow
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "lock"
|
||||
|
||||
function lock(): void {
|
||||
console.log("Lock screen requested via IPC")
|
||||
LockScreenService.resetState()
|
||||
loader.activeAsync = true
|
||||
}
|
||||
|
||||
function demo(): void {
|
||||
console.log("Lock screen DEMO mode requested via IPC")
|
||||
demoWindow.showDemo()
|
||||
}
|
||||
|
||||
function isLocked(): bool {
|
||||
return loader.active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,46 +7,46 @@ import qs.Common
|
||||
import qs.Modals
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property bool demoActive: false
|
||||
property bool demoActive: false
|
||||
|
||||
visible: demoActive
|
||||
visible: demoActive
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
|
||||
color: "transparent"
|
||||
|
||||
function showDemo(): void {
|
||||
console.log("Showing lock screen demo")
|
||||
demoActive = true
|
||||
}
|
||||
|
||||
function hideDemo(): void {
|
||||
console.log("Hiding lock screen demo")
|
||||
demoActive = false
|
||||
}
|
||||
|
||||
PowerConfirmModal {
|
||||
id: powerModal
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: demoActive
|
||||
sourceComponent: LockScreenContent {
|
||||
demoMode: true
|
||||
powerModal: powerModal
|
||||
onUnlockRequested: root.hideDemo()
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
|
||||
color: "transparent"
|
||||
|
||||
function showDemo(): void {
|
||||
console.log("Showing lock screen demo")
|
||||
demoActive = true
|
||||
}
|
||||
|
||||
function hideDemo(): void {
|
||||
console.log("Hiding lock screen demo")
|
||||
demoActive = false
|
||||
}
|
||||
|
||||
PowerConfirmModal {
|
||||
id: powerModal
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: demoActive
|
||||
sourceComponent: LockScreenContent {
|
||||
demoMode: true
|
||||
powerModal: powerModal
|
||||
onUnlockRequested: root.hideDemo()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,60 +7,60 @@ import qs.Common
|
||||
import qs.Modals
|
||||
|
||||
WlSessionLockSurface {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
required property WlSessionLock lock
|
||||
required property string sharedPasswordBuffer
|
||||
required property WlSessionLock lock
|
||||
required property string sharedPasswordBuffer
|
||||
|
||||
signal passwordChanged(string newPassword)
|
||||
signal passwordChanged(string newPassword)
|
||||
|
||||
property bool thisLocked: false
|
||||
readonly property bool locked: thisLocked && lock && !lock.unlocked
|
||||
property bool thisLocked: false
|
||||
readonly property bool locked: thisLocked && lock && !lock.unlocked
|
||||
|
||||
function unlock(): void {
|
||||
console.log("LockSurface.unlock() called")
|
||||
if (lock) {
|
||||
lock.unlocked = true
|
||||
animDelay.start()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
thisLocked = true
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
animDelay.stop()
|
||||
}
|
||||
|
||||
color: "transparent"
|
||||
|
||||
Timer {
|
||||
id: animDelay
|
||||
interval: 1500 // Longer delay for success feedback
|
||||
onTriggered: {
|
||||
if (root.lock) {
|
||||
root.lock.locked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PowerConfirmModal {
|
||||
id: powerConfirmModal
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
sourceComponent: LockScreenContent {
|
||||
demoMode: false
|
||||
powerModal: powerConfirmModal
|
||||
passwordBuffer: root.sharedPasswordBuffer
|
||||
onUnlockRequested: root.unlock()
|
||||
onPasswordBufferChanged: {
|
||||
if (root.sharedPasswordBuffer !== passwordBuffer) {
|
||||
root.passwordChanged(passwordBuffer)
|
||||
function unlock(): void {
|
||||
console.log("LockSurface.unlock() called")
|
||||
if (lock) {
|
||||
lock.unlocked = true
|
||||
animDelay.start()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
thisLocked = true
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
animDelay.stop()
|
||||
}
|
||||
|
||||
color: "transparent"
|
||||
|
||||
Timer {
|
||||
id: animDelay
|
||||
interval: 1500 // Longer delay for success feedback
|
||||
onTriggered: {
|
||||
if (root.lock) {
|
||||
root.lock.locked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PowerConfirmModal {
|
||||
id: powerConfirmModal
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
sourceComponent: LockScreenContent {
|
||||
demoMode: false
|
||||
powerModal: powerConfirmModal
|
||||
passwordBuffer: root.sharedPasswordBuffer
|
||||
onUnlockRequested: root.unlock()
|
||||
onPasswordBufferChanged: {
|
||||
if (root.sharedPasswordBuffer !== passwordBuffer) {
|
||||
root.passwordChanged(passwordBuffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,112 +8,112 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property var modelData
|
||||
property bool micPopupVisible: false
|
||||
property var modelData
|
||||
property bool micPopupVisible: false
|
||||
|
||||
function show() {
|
||||
root.micPopupVisible = true
|
||||
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()
|
||||
function show() {
|
||||
root.micPopupVisible = true
|
||||
hideTimer.restart()
|
||||
}
|
||||
|
||||
target: AudioService
|
||||
}
|
||||
screen: modelData
|
||||
visible: micPopupVisible
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
id: micPopup
|
||||
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
height: Theme.iconSize + Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Theme.spacingM
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 4
|
||||
shadowBlur: 0.8
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
||||
shadowOpacity: 0.3
|
||||
Timer {
|
||||
id: hideTimer
|
||||
|
||||
interval: 2000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.micPopupVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
transform: Translate {
|
||||
y: root.micPopupVisible ? 0 : 20
|
||||
Connections {
|
||||
function onMicMuteChanged() {
|
||||
root.show()
|
||||
}
|
||||
|
||||
target: AudioService
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
Rectangle {
|
||||
id: micPopup
|
||||
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
height: Theme.iconSize + Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Theme.spacingM
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
mask: Region {
|
||||
item: micPopup
|
||||
}
|
||||
|
||||
Behavior on transform {
|
||||
PropertyAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: micPopup
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,97 +5,109 @@ import qs.Widgets
|
||||
|
||||
DankListView {
|
||||
id: listView
|
||||
|
||||
|
||||
property var keyboardController: null
|
||||
property bool keyboardActive: false
|
||||
property bool autoScrollDisabled: false
|
||||
|
||||
|
||||
onIsUserScrollingChanged: {
|
||||
if (isUserScrolling && keyboardController && keyboardController.keyboardNavigationActive) {
|
||||
if (isUserScrolling && keyboardController
|
||||
&& keyboardController.keyboardNavigationActive) {
|
||||
autoScrollDisabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function enableAutoScroll() {
|
||||
autoScrollDisabled = false
|
||||
}
|
||||
|
||||
|
||||
property alias count: listView.count
|
||||
property alias listContentHeight: listView.contentHeight
|
||||
|
||||
|
||||
clip: true
|
||||
model: NotificationService.groupedNotifications
|
||||
spacing: Theme.spacingL
|
||||
|
||||
|
||||
Timer {
|
||||
id: positionPreservationTimer
|
||||
interval: 200
|
||||
running: keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled
|
||||
running: keyboardController
|
||||
&& keyboardController.keyboardNavigationActive
|
||||
&& !autoScrollDisabled
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) {
|
||||
if (keyboardController
|
||||
&& keyboardController.keyboardNavigationActive
|
||||
&& !autoScrollDisabled) {
|
||||
keyboardController.ensureVisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NotificationEmptyState {
|
||||
visible: listView.count === 0
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
|
||||
onModelChanged: {
|
||||
if (keyboardController && keyboardController.keyboardNavigationActive) {
|
||||
keyboardController.rebuildFlatNavigation()
|
||||
Qt.callLater(function() {
|
||||
if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) {
|
||||
Qt.callLater(function () {
|
||||
if (keyboardController
|
||||
&& keyboardController.keyboardNavigationActive
|
||||
&& !autoScrollDisabled) {
|
||||
keyboardController.ensureVisible()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
delegate: Item {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
readonly property bool isExpanded: NotificationService.expandedGroups[modelData?.key] || false
|
||||
|
||||
|
||||
readonly property bool isExpanded: NotificationService.expandedGroups[modelData?.key]
|
||||
|| false
|
||||
|
||||
width: ListView.view.width
|
||||
height: notificationCardWrapper.height
|
||||
|
||||
|
||||
Item {
|
||||
id: notificationCardWrapper
|
||||
width: parent.width
|
||||
height: notificationCard.height
|
||||
|
||||
|
||||
NotificationCard {
|
||||
id: notificationCard
|
||||
width: parent.width
|
||||
notificationGroup: modelData
|
||||
|
||||
|
||||
isGroupSelected: {
|
||||
if (!keyboardController || !keyboardController.keyboardNavigationActive) return false
|
||||
if (!keyboardController
|
||||
|| !keyboardController.keyboardNavigationActive)
|
||||
return false
|
||||
keyboardController.selectionVersion
|
||||
if (!listView.keyboardActive) return false
|
||||
if (!listView.keyboardActive)
|
||||
return false
|
||||
const selection = keyboardController.getCurrentSelection()
|
||||
return selection.type === "group" && selection.groupIndex === index
|
||||
return selection.type === "group"
|
||||
&& selection.groupIndex === index
|
||||
}
|
||||
selectedNotificationIndex: {
|
||||
if (!keyboardController || !keyboardController.keyboardNavigationActive) return -1
|
||||
if (!keyboardController
|
||||
|| !keyboardController.keyboardNavigationActive)
|
||||
return -1
|
||||
keyboardController.selectionVersion
|
||||
if (!listView.keyboardActive) return -1
|
||||
if (!listView.keyboardActive)
|
||||
return -1
|
||||
const selection = keyboardController.getCurrentSelection()
|
||||
return (selection.type === "notification" && selection.groupIndex === index)
|
||||
? selection.notificationIndex : -1
|
||||
return (selection.type === "notification"
|
||||
&& selection.groupIndex === index) ? selection.notificationIndex : -1
|
||||
}
|
||||
keyboardNavigationActive: listView.keyboardActive
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
function onGroupedNotificationsChanged() {
|
||||
@@ -104,11 +116,11 @@ DankListView {
|
||||
keyboardController.rebuildFlatNavigation()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
keyboardController.rebuildFlatNavigation()
|
||||
|
||||
|
||||
if (keyboardController.keyboardNavigationActive) {
|
||||
Qt.callLater(function() {
|
||||
Qt.callLater(function () {
|
||||
if (!autoScrollDisabled) {
|
||||
keyboardController.ensureVisible()
|
||||
}
|
||||
@@ -116,28 +128,29 @@ DankListView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onExpandedGroupsChanged() {
|
||||
if (keyboardController && keyboardController.keyboardNavigationActive) {
|
||||
Qt.callLater(function() {
|
||||
if (keyboardController
|
||||
&& keyboardController.keyboardNavigationActive) {
|
||||
Qt.callLater(function () {
|
||||
if (!autoScrollDisabled) {
|
||||
keyboardController.ensureVisible()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onExpandedMessagesChanged() {
|
||||
if (keyboardController && keyboardController.keyboardNavigationActive) {
|
||||
Qt.callLater(function() {
|
||||
if (keyboardController
|
||||
&& keyboardController.keyboardNavigationActive) {
|
||||
Qt.callLater(function () {
|
||||
if (!autoScrollDisabled) {
|
||||
keyboardController.ensureVisible()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
target: NotificationService
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,193 +10,200 @@ import qs.Widgets
|
||||
import qs.Modules.Notifications.Center
|
||||
|
||||
DankPopout {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property bool notificationHistoryVisible: false
|
||||
property string triggerSection: "right"
|
||||
property var triggerScreen: null
|
||||
|
||||
NotificationKeyboardController {
|
||||
id: keyboardController
|
||||
listView: null
|
||||
isOpen: notificationHistoryVisible
|
||||
onClose: function() { notificationHistoryVisible = false }
|
||||
}
|
||||
|
||||
property bool notificationHistoryVisible: false
|
||||
property string triggerSection: "right"
|
||||
property var triggerScreen: null
|
||||
|
||||
function setTriggerPosition(x, y, width, section, screen) {
|
||||
triggerX = x
|
||||
triggerY = y
|
||||
triggerWidth = width
|
||||
triggerSection = section
|
||||
triggerScreen = screen
|
||||
}
|
||||
|
||||
popupWidth: 400
|
||||
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 400
|
||||
triggerX: Screen.width - 400 - Theme.spacingL
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingXS
|
||||
triggerWidth: 40
|
||||
positioning: "center"
|
||||
WlrLayershell.namespace: "quickshell-notifications"
|
||||
screen: triggerScreen
|
||||
shouldBeVisible: notificationHistoryVisible
|
||||
visible: shouldBeVisible
|
||||
|
||||
onNotificationHistoryVisibleChanged: {
|
||||
if (notificationHistoryVisible) {
|
||||
open()
|
||||
} else {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
onShouldBeVisibleChanged: {
|
||||
if (shouldBeVisible) {
|
||||
NotificationService.disablePopups(true)
|
||||
// Set up keyboard controller when content is loaded
|
||||
Qt.callLater(function() {
|
||||
if (contentLoader.item) {
|
||||
contentLoader.item.externalKeyboardController = keyboardController
|
||||
|
||||
// Find the notification list and set up the connection
|
||||
var notificationList = findChild(contentLoader.item, "notificationList")
|
||||
var notificationHeader = findChild(contentLoader.item, "notificationHeader")
|
||||
|
||||
if (notificationList) {
|
||||
keyboardController.listView = notificationList
|
||||
notificationList.keyboardController = keyboardController
|
||||
}
|
||||
if (notificationHeader) {
|
||||
notificationHeader.keyboardController = keyboardController
|
||||
}
|
||||
|
||||
keyboardController.reset()
|
||||
keyboardController.rebuildFlatNavigation()
|
||||
NotificationKeyboardController {
|
||||
id: keyboardController
|
||||
listView: null
|
||||
isOpen: notificationHistoryVisible
|
||||
onClose: function () {
|
||||
notificationHistoryVisible = false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
NotificationService.disablePopups(false)
|
||||
// Reset keyboard state when closing
|
||||
keyboardController.keyboardNavigationActive = false
|
||||
}
|
||||
}
|
||||
|
||||
function findChild(parent, objectName) {
|
||||
if (parent.objectName === objectName) {
|
||||
return parent
|
||||
|
||||
function setTriggerPosition(x, y, width, section, screen) {
|
||||
triggerX = x
|
||||
triggerY = y
|
||||
triggerWidth = width
|
||||
triggerSection = section
|
||||
triggerScreen = screen
|
||||
}
|
||||
for (var i = 0; i < parent.children.length; i++) {
|
||||
var child = parent.children[i]
|
||||
var result = findChild(child, objectName)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Rectangle {
|
||||
id: notificationContent
|
||||
|
||||
property var externalKeyboardController: null
|
||||
property real cachedHeaderHeight: 32
|
||||
|
||||
implicitHeight: {
|
||||
let baseHeight = Theme.spacingL * 2
|
||||
baseHeight += cachedHeaderHeight
|
||||
baseHeight += (notificationSettings.expanded ? notificationSettings.contentHeight : 0)
|
||||
baseHeight += Theme.spacingM * 2
|
||||
let listHeight = notificationList.listContentHeight
|
||||
if (NotificationService.groupedNotifications.length === 0)
|
||||
listHeight = 200
|
||||
baseHeight += Math.min(listHeight, 600)
|
||||
return Math.max(300, Math.min(baseHeight, root.screen ? root.screen.height * 0.8 : Screen.height * 0.8))
|
||||
}
|
||||
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
focus: true
|
||||
popupWidth: 400
|
||||
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 400
|
||||
triggerX: Screen.width - 400 - Theme.spacingL
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingXS
|
||||
triggerWidth: 40
|
||||
positioning: "center"
|
||||
WlrLayershell.namespace: "quickshell-notifications"
|
||||
screen: triggerScreen
|
||||
shouldBeVisible: notificationHistoryVisible
|
||||
visible: shouldBeVisible
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.shouldBeVisible)
|
||||
forceActiveFocus()
|
||||
}
|
||||
|
||||
Keys.onPressed: function(event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
root.close()
|
||||
event.accepted = true
|
||||
} else if (externalKeyboardController) {
|
||||
externalKeyboardController.handleKey(event)
|
||||
onNotificationHistoryVisibleChanged: {
|
||||
if (notificationHistoryVisible) {
|
||||
open()
|
||||
} else {
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onShouldBeVisibleChanged() {
|
||||
if (root.shouldBeVisible)
|
||||
onShouldBeVisibleChanged: {
|
||||
if (shouldBeVisible) {
|
||||
NotificationService.disablePopups(true)
|
||||
// Set up keyboard controller when content is loaded
|
||||
Qt.callLater(function () {
|
||||
notificationContent.forceActiveFocus()
|
||||
if (contentLoader.item) {
|
||||
contentLoader.item.externalKeyboardController = keyboardController
|
||||
|
||||
// Find the notification list and set up the connection
|
||||
var notificationList = findChild(contentLoader.item,
|
||||
"notificationList")
|
||||
var notificationHeader = findChild(contentLoader.item,
|
||||
"notificationHeader")
|
||||
|
||||
if (notificationList) {
|
||||
keyboardController.listView = notificationList
|
||||
notificationList.keyboardController = keyboardController
|
||||
}
|
||||
if (notificationHeader) {
|
||||
notificationHeader.keyboardController = keyboardController
|
||||
}
|
||||
|
||||
keyboardController.reset()
|
||||
keyboardController.rebuildFlatNavigation()
|
||||
}
|
||||
})
|
||||
else
|
||||
notificationContent.focus = false
|
||||
} else {
|
||||
NotificationService.disablePopups(false)
|
||||
// Reset keyboard state when closing
|
||||
keyboardController.keyboardNavigationActive = false
|
||||
}
|
||||
target: root
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: contentColumn
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
focus: true
|
||||
|
||||
Column {
|
||||
id: contentColumnInner
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
NotificationHeader {
|
||||
id: notificationHeader
|
||||
objectName: "notificationHeader"
|
||||
onHeightChanged: notificationContent.cachedHeaderHeight = height
|
||||
}
|
||||
|
||||
NotificationSettings {
|
||||
id: notificationSettings
|
||||
expanded: notificationHeader.showSettings
|
||||
}
|
||||
|
||||
KeyboardNavigatedNotificationList {
|
||||
id: notificationList
|
||||
objectName: "notificationList"
|
||||
|
||||
width: parent.width
|
||||
height: parent.height - notificationContent.cachedHeaderHeight - notificationSettings.height - contentColumnInner.spacing * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NotificationKeyboardHints {
|
||||
id: keyboardHints
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingL
|
||||
showHints: externalKeyboardController ? externalKeyboardController.showKeyboardHints : false
|
||||
z: 200
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: 180
|
||||
easing.type: Easing.OutQuart
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findChild(parent, objectName) {
|
||||
if (parent.objectName === objectName) {
|
||||
return parent
|
||||
}
|
||||
for (var i = 0; i < parent.children.length; i++) {
|
||||
var child = parent.children[i]
|
||||
var result = findChild(child, objectName)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Rectangle {
|
||||
id: notificationContent
|
||||
|
||||
property var externalKeyboardController: null
|
||||
property real cachedHeaderHeight: 32
|
||||
|
||||
implicitHeight: {
|
||||
let baseHeight = Theme.spacingL * 2
|
||||
baseHeight += cachedHeaderHeight
|
||||
baseHeight += (notificationSettings.expanded ? notificationSettings.contentHeight : 0)
|
||||
baseHeight += Theme.spacingM * 2
|
||||
let listHeight = notificationList.listContentHeight
|
||||
if (NotificationService.groupedNotifications.length === 0)
|
||||
listHeight = 200
|
||||
baseHeight += Math.min(listHeight, 600)
|
||||
return Math.max(
|
||||
300, Math.min(
|
||||
baseHeight,
|
||||
root.screen ? root.screen.height * 0.8 : Screen.height * 0.8))
|
||||
}
|
||||
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
focus: true
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.shouldBeVisible)
|
||||
forceActiveFocus()
|
||||
}
|
||||
|
||||
Keys.onPressed: function (event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
root.close()
|
||||
event.accepted = true
|
||||
} else if (externalKeyboardController) {
|
||||
externalKeyboardController.handleKey(event)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onShouldBeVisibleChanged() {
|
||||
if (root.shouldBeVisible)
|
||||
Qt.callLater(function () {
|
||||
notificationContent.forceActiveFocus()
|
||||
})
|
||||
else
|
||||
notificationContent.focus = false
|
||||
}
|
||||
target: root
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: contentColumn
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
focus: true
|
||||
|
||||
Column {
|
||||
id: contentColumnInner
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
NotificationHeader {
|
||||
id: notificationHeader
|
||||
objectName: "notificationHeader"
|
||||
onHeightChanged: notificationContent.cachedHeaderHeight = height
|
||||
}
|
||||
|
||||
NotificationSettings {
|
||||
id: notificationSettings
|
||||
expanded: notificationHeader.showSettings
|
||||
}
|
||||
|
||||
KeyboardNavigatedNotificationList {
|
||||
id: notificationList
|
||||
objectName: "notificationList"
|
||||
|
||||
width: parent.width
|
||||
height: parent.height - notificationContent.cachedHeaderHeight
|
||||
- notificationSettings.height - contentColumnInner.spacing * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NotificationKeyboardHints {
|
||||
id: keyboardHints
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingL
|
||||
showHints: externalKeyboardController ? externalKeyboardController.showKeyboardHints : false
|
||||
z: 200
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: 180
|
||||
easing.type: Easing.OutQuart
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,33 +4,33 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
height: 200
|
||||
visible: NotificationService.notifications.length === 0
|
||||
width: parent.width
|
||||
height: 200
|
||||
visible: NotificationService.notifications.length === 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
width: parent.width * 0.8
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
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)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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,161 +5,165 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var keyboardController: null
|
||||
property bool showSettings: false
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
height: 32
|
||||
property var keyboardController: null
|
||||
property bool showSettings: false
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
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.cornerRadius
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
// Keyboard help button
|
||||
DankActionButton {
|
||||
id: helpButton
|
||||
iconName: "info"
|
||||
iconColor: keyboardController && keyboardController.showKeyboardHints ? Theme.primary : Theme.surfaceText
|
||||
buttonSize: 28
|
||||
visible: keyboardController !== null
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
if (keyboardController) {
|
||||
keyboardController.showKeyboardHints = !keyboardController.showKeyboardHints
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Settings button
|
||||
DankActionButton {
|
||||
id: settingsButton
|
||||
iconName: "settings"
|
||||
iconColor: root.showSettings ? Theme.primary : Theme.surfaceText
|
||||
buttonSize: 28
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: root.showSettings = !root.showSettings
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clearAllButton
|
||||
|
||||
width: 120
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
width: parent.width
|
||||
height: 32
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "delete_sweep"
|
||||
size: Theme.iconSizeSmall
|
||||
color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Clear All"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
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.cornerRadius
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
MouseArea {
|
||||
id: clearArea
|
||||
// Keyboard help button
|
||||
DankActionButton {
|
||||
id: helpButton
|
||||
iconName: "info"
|
||||
iconColor: keyboardController
|
||||
&& keyboardController.showKeyboardHints ? Theme.primary : Theme.surfaceText
|
||||
buttonSize: 28
|
||||
visible: keyboardController !== null
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
if (keyboardController) {
|
||||
keyboardController.showKeyboardHints = !keyboardController.showKeyboardHints
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationService.clearAllNotifications()
|
||||
}
|
||||
// Settings button
|
||||
DankActionButton {
|
||||
id: settingsButton
|
||||
iconName: "settings"
|
||||
iconColor: root.showSettings ? Theme.primary : Theme.surfaceText
|
||||
buttonSize: 28
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: root.showSettings = !root.showSettings
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
id: clearAllButton
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
width: 120
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,114 +4,124 @@ import qs.Services
|
||||
|
||||
QtObject {
|
||||
id: controller
|
||||
|
||||
|
||||
property var listView: null
|
||||
property bool isOpen: false
|
||||
property var onClose: null
|
||||
|
||||
|
||||
property int selectionVersion: 0
|
||||
|
||||
|
||||
property bool keyboardNavigationActive: false
|
||||
property int selectedFlatIndex: 0
|
||||
property var flatNavigation: []
|
||||
property bool showKeyboardHints: false
|
||||
|
||||
|
||||
property string selectedNotificationId: ""
|
||||
property string selectedGroupKey: ""
|
||||
property string selectedItemType: ""
|
||||
property bool isTogglingGroup: false
|
||||
property bool isRebuilding: false
|
||||
|
||||
|
||||
function rebuildFlatNavigation() {
|
||||
isRebuilding = true
|
||||
|
||||
|
||||
var nav = []
|
||||
var groups = NotificationService.groupedNotifications
|
||||
|
||||
|
||||
for (var i = 0; i < groups.length; i++) {
|
||||
var group = groups[i]
|
||||
var isExpanded = NotificationService.expandedGroups[group.key] || false
|
||||
|
||||
var isExpanded = NotificationService.expandedGroups[group.key]
|
||||
|| false
|
||||
|
||||
// Add the group itself
|
||||
nav.push({
|
||||
type: "group",
|
||||
groupIndex: i,
|
||||
notificationIndex: -1,
|
||||
groupKey: group.key,
|
||||
notificationId: ""
|
||||
})
|
||||
|
||||
"type": "group",
|
||||
"groupIndex": i,
|
||||
"notificationIndex": -1,
|
||||
"groupKey": group.key,
|
||||
"notificationId": ""
|
||||
})
|
||||
|
||||
// If expanded, add individual notifications
|
||||
if (isExpanded) {
|
||||
var notifications = group.notifications || []
|
||||
for (var j = 0; j < notifications.length; j++) {
|
||||
var notifId = String(notifications[j]?.notification?.id || "")
|
||||
var notifId = String(
|
||||
notifications[j]?.notification?.id || "")
|
||||
nav.push({
|
||||
type: "notification",
|
||||
groupIndex: i,
|
||||
notificationIndex: j,
|
||||
groupKey: group.key,
|
||||
notificationId: notifId
|
||||
})
|
||||
"type": "notification",
|
||||
"groupIndex": i,
|
||||
"notificationIndex": j,
|
||||
"groupKey": group.key,
|
||||
"notificationId": notifId
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
flatNavigation = nav
|
||||
updateSelectedIndexFromId()
|
||||
isRebuilding = false
|
||||
}
|
||||
|
||||
|
||||
function updateSelectedIndexFromId() {
|
||||
if (!keyboardNavigationActive) return
|
||||
|
||||
if (!keyboardNavigationActive)
|
||||
return
|
||||
|
||||
// Find the index that matches our selected ID/key
|
||||
for (var i = 0; i < flatNavigation.length; i++) {
|
||||
var item = flatNavigation[i]
|
||||
|
||||
if (selectedItemType === "group" && item.type === "group" && item.groupKey === selectedGroupKey) {
|
||||
|
||||
if (selectedItemType === "group" && item.type === "group"
|
||||
&& item.groupKey === selectedGroupKey) {
|
||||
selectedFlatIndex = i
|
||||
selectionVersion++ // Trigger UI update
|
||||
selectionVersion++ // Trigger UI update
|
||||
return
|
||||
} else if (selectedItemType === "notification" && item.type === "notification" && String(item.notificationId) === String(selectedNotificationId)) {
|
||||
} else if (selectedItemType === "notification"
|
||||
&& item.type === "notification" && String(
|
||||
item.notificationId) === String(
|
||||
selectedNotificationId)) {
|
||||
selectedFlatIndex = i
|
||||
selectionVersion++ // Trigger UI update
|
||||
selectionVersion++ // Trigger UI update
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If not found, try to find the same group but select the group header instead
|
||||
if (selectedItemType === "notification") {
|
||||
for (var j = 0; j < flatNavigation.length; j++) {
|
||||
var groupItem = flatNavigation[j]
|
||||
if (groupItem.type === "group" && groupItem.groupKey === selectedGroupKey) {
|
||||
if (groupItem.type === "group"
|
||||
&& groupItem.groupKey === selectedGroupKey) {
|
||||
selectedFlatIndex = j
|
||||
selectedItemType = "group"
|
||||
selectedNotificationId = ""
|
||||
selectionVersion++ // Trigger UI update
|
||||
selectionVersion++ // Trigger UI update
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If still not found, clamp to valid range and update
|
||||
if (flatNavigation.length > 0) {
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1)
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex,
|
||||
flatNavigation.length - 1)
|
||||
selectedFlatIndex = Math.max(selectedFlatIndex, 0)
|
||||
updateSelectedIdFromIndex()
|
||||
selectionVersion++ // Trigger UI update
|
||||
selectionVersion++ // Trigger UI update
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateSelectedIdFromIndex() {
|
||||
if (selectedFlatIndex >= 0 && selectedFlatIndex < flatNavigation.length) {
|
||||
if (selectedFlatIndex >= 0
|
||||
&& selectedFlatIndex < flatNavigation.length) {
|
||||
var item = flatNavigation[selectedFlatIndex]
|
||||
selectedItemType = item.type
|
||||
selectedGroupKey = item.groupKey
|
||||
selectedNotificationId = item.notificationId
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function reset() {
|
||||
selectedFlatIndex = 0
|
||||
keyboardNavigationActive = false
|
||||
@@ -122,87 +132,100 @@ QtObject {
|
||||
}
|
||||
rebuildFlatNavigation()
|
||||
}
|
||||
|
||||
|
||||
function selectNext() {
|
||||
keyboardNavigationActive = true
|
||||
if (flatNavigation.length === 0) return
|
||||
|
||||
if (flatNavigation.length === 0)
|
||||
return
|
||||
|
||||
// Re-enable auto-scrolling when arrow keys are used
|
||||
if (listView && listView.enableAutoScroll) {
|
||||
listView.enableAutoScroll()
|
||||
}
|
||||
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex + 1, flatNavigation.length - 1)
|
||||
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex + 1,
|
||||
flatNavigation.length - 1)
|
||||
updateSelectedIdFromIndex()
|
||||
selectionVersion++
|
||||
ensureVisible()
|
||||
}
|
||||
|
||||
|
||||
function selectPrevious() {
|
||||
keyboardNavigationActive = true
|
||||
if (flatNavigation.length === 0) return
|
||||
|
||||
if (flatNavigation.length === 0)
|
||||
return
|
||||
|
||||
// Re-enable auto-scrolling when arrow keys are used
|
||||
if (listView && listView.enableAutoScroll) {
|
||||
listView.enableAutoScroll()
|
||||
}
|
||||
|
||||
|
||||
selectedFlatIndex = Math.max(selectedFlatIndex - 1, 0)
|
||||
updateSelectedIdFromIndex()
|
||||
selectionVersion++
|
||||
ensureVisible()
|
||||
}
|
||||
|
||||
|
||||
function toggleGroupExpanded() {
|
||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) return
|
||||
|
||||
if (flatNavigation.length === 0
|
||||
|| selectedFlatIndex >= flatNavigation.length)
|
||||
return
|
||||
|
||||
const currentItem = flatNavigation[selectedFlatIndex]
|
||||
const groups = NotificationService.groupedNotifications
|
||||
const group = groups[currentItem.groupIndex]
|
||||
if (!group) return
|
||||
|
||||
if (!group)
|
||||
return
|
||||
|
||||
// Prevent expanding groups with < 2 notifications
|
||||
const notificationCount = group.notifications ? group.notifications.length : 0
|
||||
if (notificationCount < 2) return
|
||||
|
||||
const wasExpanded = NotificationService.expandedGroups[group.key] || false
|
||||
if (notificationCount < 2)
|
||||
return
|
||||
|
||||
const wasExpanded = NotificationService.expandedGroups[group.key]
|
||||
|| false
|
||||
const groupIndex = currentItem.groupIndex
|
||||
|
||||
|
||||
isTogglingGroup = true
|
||||
NotificationService.toggleGroupExpansion(group.key)
|
||||
rebuildFlatNavigation()
|
||||
|
||||
|
||||
// Smart selection after toggle
|
||||
if (!wasExpanded) {
|
||||
// Just expanded - move to first notification in the group
|
||||
for (let i = 0; i < flatNavigation.length; i++) {
|
||||
if (flatNavigation[i].type === "notification" && flatNavigation[i].groupIndex === groupIndex) {
|
||||
for (var i = 0; i < flatNavigation.length; i++) {
|
||||
if (flatNavigation[i].type === "notification"
|
||||
&& flatNavigation[i].groupIndex === groupIndex) {
|
||||
selectedFlatIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Just collapsed - stay on the group header
|
||||
for (let i = 0; i < flatNavigation.length; i++) {
|
||||
if (flatNavigation[i].type === "group" && flatNavigation[i].groupIndex === groupIndex) {
|
||||
for (var i = 0; i < flatNavigation.length; i++) {
|
||||
if (flatNavigation[i].type === "group"
|
||||
&& flatNavigation[i].groupIndex === groupIndex) {
|
||||
selectedFlatIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
isTogglingGroup = false
|
||||
ensureVisible()
|
||||
}
|
||||
|
||||
|
||||
function handleEnterKey() {
|
||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) return
|
||||
|
||||
if (flatNavigation.length === 0
|
||||
|| selectedFlatIndex >= flatNavigation.length)
|
||||
return
|
||||
|
||||
const currentItem = flatNavigation[selectedFlatIndex]
|
||||
const groups = NotificationService.groupedNotifications
|
||||
const group = groups[currentItem.groupIndex]
|
||||
if (!group) return
|
||||
|
||||
if (!group)
|
||||
return
|
||||
|
||||
if (currentItem.type === "group") {
|
||||
const notificationCount = group.notifications ? group.notifications.length : 0
|
||||
if (notificationCount >= 2) {
|
||||
@@ -214,100 +237,117 @@ QtObject {
|
||||
executeAction(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function toggleTextExpanded() {
|
||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) return
|
||||
|
||||
if (flatNavigation.length === 0
|
||||
|| selectedFlatIndex >= flatNavigation.length)
|
||||
return
|
||||
|
||||
const currentItem = flatNavigation[selectedFlatIndex]
|
||||
const groups = NotificationService.groupedNotifications
|
||||
const group = groups[currentItem.groupIndex]
|
||||
if (!group) return
|
||||
|
||||
if (!group)
|
||||
return
|
||||
|
||||
var messageId = ""
|
||||
|
||||
|
||||
if (currentItem.type === "group") {
|
||||
messageId = group.latestNotification?.notification?.id + "_desc"
|
||||
} else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) {
|
||||
messageId = group.notifications[currentItem.notificationIndex]?.notification?.id + "_desc"
|
||||
} else if (currentItem.type === "notification"
|
||||
&& currentItem.notificationIndex >= 0
|
||||
&& currentItem.notificationIndex < group.notifications.length) {
|
||||
messageId = group.notifications[currentItem.notificationIndex]?.notification?.id
|
||||
+ "_desc"
|
||||
}
|
||||
|
||||
|
||||
if (messageId) {
|
||||
NotificationService.toggleMessageExpansion(messageId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function executeAction(actionIndex) {
|
||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) return
|
||||
|
||||
if (flatNavigation.length === 0
|
||||
|| selectedFlatIndex >= flatNavigation.length)
|
||||
return
|
||||
|
||||
const currentItem = flatNavigation[selectedFlatIndex]
|
||||
const groups = NotificationService.groupedNotifications
|
||||
const group = groups[currentItem.groupIndex]
|
||||
if (!group) return
|
||||
|
||||
if (!group)
|
||||
return
|
||||
|
||||
var actions = []
|
||||
|
||||
|
||||
if (currentItem.type === "group") {
|
||||
actions = group.latestNotification?.actions || []
|
||||
} else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) {
|
||||
actions = group.notifications[currentItem.notificationIndex]?.actions || []
|
||||
} else if (currentItem.type === "notification"
|
||||
&& currentItem.notificationIndex >= 0
|
||||
&& currentItem.notificationIndex < group.notifications.length) {
|
||||
actions = group.notifications[currentItem.notificationIndex]?.actions
|
||||
|| []
|
||||
}
|
||||
|
||||
|
||||
if (actionIndex >= 0 && actionIndex < actions.length) {
|
||||
const action = actions[actionIndex]
|
||||
if (action.invoke) {
|
||||
action.invoke()
|
||||
if (onClose) onClose()
|
||||
if (onClose)
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function clearSelected() {
|
||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) return
|
||||
|
||||
if (flatNavigation.length === 0
|
||||
|| selectedFlatIndex >= flatNavigation.length)
|
||||
return
|
||||
|
||||
const currentItem = flatNavigation[selectedFlatIndex]
|
||||
const groups = NotificationService.groupedNotifications
|
||||
const group = groups[currentItem.groupIndex]
|
||||
if (!group) return
|
||||
|
||||
if (!group)
|
||||
return
|
||||
|
||||
// Save current state for smart navigation
|
||||
const currentGroupKey = group.key
|
||||
const isNotification = currentItem.type === "notification"
|
||||
const notificationIndex = currentItem.notificationIndex
|
||||
const totalNotificationsInGroup = group.notifications ? group.notifications.length : 0
|
||||
const isLastNotificationInGroup = isNotification && totalNotificationsInGroup === 1
|
||||
const isLastNotificationInList = isNotification && notificationIndex === totalNotificationsInGroup - 1
|
||||
|
||||
const isLastNotificationInGroup = isNotification
|
||||
&& totalNotificationsInGroup === 1
|
||||
const isLastNotificationInList = isNotification
|
||||
&& notificationIndex === totalNotificationsInGroup - 1
|
||||
|
||||
// Store what to select next BEFORE clearing
|
||||
let nextTargetType = ""
|
||||
let nextTargetGroupKey = ""
|
||||
let nextTargetNotificationIndex = -1
|
||||
|
||||
|
||||
if (currentItem.type === "group") {
|
||||
NotificationService.dismissGroup(group.key)
|
||||
|
||||
|
||||
// Look for next group
|
||||
for (let i = currentItem.groupIndex + 1; i < groups.length; i++) {
|
||||
for (var i = currentItem.groupIndex + 1; i < groups.length; i++) {
|
||||
nextTargetType = "group"
|
||||
nextTargetGroupKey = groups[i].key
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
if (!nextTargetGroupKey && currentItem.groupIndex > 0) {
|
||||
nextTargetType = "group"
|
||||
nextTargetGroupKey = groups[currentItem.groupIndex - 1].key
|
||||
}
|
||||
|
||||
} else if (isNotification) {
|
||||
const notification = group.notifications[notificationIndex]
|
||||
NotificationService.dismissNotification(notification)
|
||||
|
||||
|
||||
if (isLastNotificationInGroup) {
|
||||
for (let i = currentItem.groupIndex + 1; i < groups.length; i++) {
|
||||
for (var i = currentItem.groupIndex + 1; i < groups.length; i++) {
|
||||
nextTargetType = "group"
|
||||
nextTargetGroupKey = groups[i].key
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
if (!nextTargetGroupKey && currentItem.groupIndex > 0) {
|
||||
nextTargetType = "group"
|
||||
nextTargetGroupKey = groups[currentItem.groupIndex - 1].key
|
||||
@@ -331,66 +371,77 @@ QtObject {
|
||||
nextTargetNotificationIndex = notificationIndex
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
rebuildFlatNavigation()
|
||||
|
||||
|
||||
// Find and select the target we identified
|
||||
if (flatNavigation.length === 0) {
|
||||
selectedFlatIndex = 0
|
||||
updateSelectedIdFromIndex()
|
||||
} else if (nextTargetGroupKey) {
|
||||
let found = false
|
||||
for (let i = 0; i < flatNavigation.length; i++) {
|
||||
for (var i = 0; i < flatNavigation.length; i++) {
|
||||
const item = flatNavigation[i]
|
||||
|
||||
if (nextTargetType === "group" && item.type === "group" && item.groupKey === nextTargetGroupKey) {
|
||||
|
||||
if (nextTargetType === "group" && item.type === "group"
|
||||
&& item.groupKey === nextTargetGroupKey) {
|
||||
selectedFlatIndex = i
|
||||
found = true
|
||||
break
|
||||
} else if (nextTargetType === "notification" && item.type === "notification" &&
|
||||
item.groupKey === nextTargetGroupKey && item.notificationIndex === nextTargetNotificationIndex) {
|
||||
} else if (nextTargetType === "notification"
|
||||
&& item.type === "notification"
|
||||
&& item.groupKey === nextTargetGroupKey
|
||||
&& item.notificationIndex === nextTargetNotificationIndex) {
|
||||
selectedFlatIndex = i
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!found) {
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1)
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex,
|
||||
flatNavigation.length - 1)
|
||||
}
|
||||
|
||||
|
||||
updateSelectedIdFromIndex()
|
||||
} else {
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1)
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex,
|
||||
flatNavigation.length - 1)
|
||||
updateSelectedIdFromIndex()
|
||||
}
|
||||
|
||||
|
||||
ensureVisible()
|
||||
}
|
||||
|
||||
|
||||
function ensureVisible() {
|
||||
if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length || !listView) return
|
||||
|
||||
if (flatNavigation.length === 0
|
||||
|| selectedFlatIndex >= flatNavigation.length || !listView)
|
||||
return
|
||||
|
||||
const currentItem = flatNavigation[selectedFlatIndex]
|
||||
|
||||
if (keyboardNavigationActive && currentItem && currentItem.groupIndex >= 0) {
|
||||
|
||||
if (keyboardNavigationActive && currentItem
|
||||
&& currentItem.groupIndex >= 0) {
|
||||
// Always center the selected item for better visibility
|
||||
// This ensures the selected item stays in view even when new notifications arrive
|
||||
if (currentItem.type === "notification") {
|
||||
// For individual notifications, center on the group but bias towards the notification
|
||||
listView.positionViewAtIndex(currentItem.groupIndex, ListView.Center)
|
||||
listView.positionViewAtIndex(currentItem.groupIndex,
|
||||
ListView.Center)
|
||||
} else {
|
||||
// For group headers, center on the group
|
||||
listView.positionViewAtIndex(currentItem.groupIndex, ListView.Center)
|
||||
listView.positionViewAtIndex(currentItem.groupIndex,
|
||||
ListView.Center)
|
||||
}
|
||||
|
||||
|
||||
// Force immediate update
|
||||
listView.forceLayout()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleKey(event) {
|
||||
if ((event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) && (event.modifiers & Qt.ShiftModifier)) {
|
||||
if ((event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace)
|
||||
&& (event.modifiers & Qt.ShiftModifier)) {
|
||||
NotificationService.clearAllNotifications()
|
||||
rebuildFlatNavigation()
|
||||
if (flatNavigation.length === 0) {
|
||||
@@ -406,19 +457,20 @@ QtObject {
|
||||
event.accepted = true
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
if (keyboardNavigationActive) {
|
||||
keyboardNavigationActive = false
|
||||
event.accepted = true
|
||||
} else {
|
||||
if (onClose) onClose()
|
||||
if (onClose)
|
||||
onClose()
|
||||
event.accepted = true
|
||||
}
|
||||
} else if (event.key === Qt.Key_Down || event.key === 16777237) {
|
||||
if (!keyboardNavigationActive) {
|
||||
keyboardNavigationActive = true
|
||||
rebuildFlatNavigation() // Ensure we have fresh navigation data
|
||||
rebuildFlatNavigation() // Ensure we have fresh navigation data
|
||||
selectedFlatIndex = 0
|
||||
updateSelectedIdFromIndex()
|
||||
// Set keyboardActive on listView to show highlight
|
||||
@@ -435,7 +487,7 @@ QtObject {
|
||||
} else if (event.key === Qt.Key_Up || event.key === 16777235) {
|
||||
if (!keyboardNavigationActive) {
|
||||
keyboardNavigationActive = true
|
||||
rebuildFlatNavigation() // Ensure we have fresh navigation data
|
||||
rebuildFlatNavigation() // Ensure we have fresh navigation data
|
||||
selectedFlatIndex = 0
|
||||
updateSelectedIdFromIndex()
|
||||
// Set keyboardActive on listView to show highlight
|
||||
@@ -462,13 +514,15 @@ QtObject {
|
||||
if (event.key === Qt.Key_Space) {
|
||||
toggleGroupExpanded()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
} else if (event.key === Qt.Key_Return
|
||||
|| event.key === Qt.Key_Enter) {
|
||||
handleEnterKey()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_E) {
|
||||
toggleTextExpanded()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) {
|
||||
} else if (event.key === Qt.Key_Delete
|
||||
|| event.key === Qt.Key_Backspace) {
|
||||
clearSelected()
|
||||
event.accepted = true
|
||||
} else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) {
|
||||
@@ -477,19 +531,28 @@ QtObject {
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (event.key === Qt.Key_F10) {
|
||||
showKeyboardHints = !showKeyboardHints
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get current selection info for UI
|
||||
function getCurrentSelection() {
|
||||
if (!keyboardNavigationActive || selectedFlatIndex < 0 || selectedFlatIndex >= flatNavigation.length) {
|
||||
return { type: "", groupIndex: -1, notificationIndex: -1 }
|
||||
if (!keyboardNavigationActive || selectedFlatIndex < 0
|
||||
|| selectedFlatIndex >= flatNavigation.length) {
|
||||
return {
|
||||
"type": "",
|
||||
"groupIndex": -1,
|
||||
"notificationIndex": -1
|
||||
}
|
||||
}
|
||||
const result = flatNavigation[selectedFlatIndex] || {
|
||||
"type": "",
|
||||
"groupIndex": -1,
|
||||
"notificationIndex": -1
|
||||
}
|
||||
const result = flatNavigation[selectedFlatIndex] || { type: "", groupIndex: -1, notificationIndex: -1 }
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ Rectangle {
|
||||
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||
Theme.surfaceContainer.b, 0.95)
|
||||
border.color: Theme.primary
|
||||
border.width: 2
|
||||
opacity: showHints ? 1 : 0
|
||||
@@ -39,7 +40,6 @@ Rectangle {
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -47,7 +47,5 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,19 +6,21 @@ import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
|
||||
property bool expanded: false
|
||||
readonly property real contentHeight: contentColumn.height + Theme.spacingL * 2
|
||||
|
||||
|
||||
width: parent.width
|
||||
height: expanded ? Math.min(contentHeight, 400) : 0
|
||||
visible: expanded
|
||||
clip: true
|
||||
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)
|
||||
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
|
||||
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
@@ -26,48 +28,74 @@ Rectangle {
|
||||
easing.bezierCurve: Anims.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Ensure smooth opacity transition
|
||||
opacity: expanded ? 1 : 0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var timeoutOptions: [
|
||||
{ text: "Never", value: 0 },
|
||||
{ text: "1 second", value: 1000 },
|
||||
{ text: "3 seconds", value: 3000 },
|
||||
{ text: "5 seconds", value: 5000 },
|
||||
{ text: "8 seconds", value: 8000 },
|
||||
{ text: "10 seconds", value: 10000 },
|
||||
{ text: "15 seconds", value: 15000 },
|
||||
{ text: "30 seconds", value: 30000 },
|
||||
{ text: "1 minute", value: 60000 },
|
||||
{ text: "2 minutes", value: 120000 },
|
||||
{ text: "5 minutes", value: 300000 },
|
||||
{ text: "10 minutes", value: 600000 }
|
||||
]
|
||||
|
||||
|
||||
readonly property var timeoutOptions: [{
|
||||
"text": "Never",
|
||||
"value": 0
|
||||
}, {
|
||||
"text": "1 second",
|
||||
"value": 1000
|
||||
}, {
|
||||
"text": "3 seconds",
|
||||
"value": 3000
|
||||
}, {
|
||||
"text": "5 seconds",
|
||||
"value": 5000
|
||||
}, {
|
||||
"text": "8 seconds",
|
||||
"value": 8000
|
||||
}, {
|
||||
"text": "10 seconds",
|
||||
"value": 10000
|
||||
}, {
|
||||
"text": "15 seconds",
|
||||
"value": 15000
|
||||
}, {
|
||||
"text": "30 seconds",
|
||||
"value": 30000
|
||||
}, {
|
||||
"text": "1 minute",
|
||||
"value": 60000
|
||||
}, {
|
||||
"text": "2 minutes",
|
||||
"value": 120000
|
||||
}, {
|
||||
"text": "5 minutes",
|
||||
"value": 300000
|
||||
}, {
|
||||
"text": "10 minutes",
|
||||
"value": 600000
|
||||
}]
|
||||
|
||||
function getTimeoutText(value) {
|
||||
if (value === undefined || value === null || isNaN(value)) {
|
||||
return "5 seconds"
|
||||
}
|
||||
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
|
||||
for (var i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].value === value) {
|
||||
return timeoutOptions[i].text
|
||||
}
|
||||
}
|
||||
if (value === 0) return "Never"
|
||||
if (value < 1000) return value + "ms"
|
||||
if (value < 60000) return Math.round(value / 1000) + " seconds"
|
||||
if (value === 0)
|
||||
return "Never"
|
||||
if (value < 1000)
|
||||
return value + "ms"
|
||||
if (value < 60000)
|
||||
return Math.round(value / 1000) + " seconds"
|
||||
return Math.round(value / 60000) + " minutes"
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
anchors.top: parent.top
|
||||
@@ -75,30 +103,30 @@ Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
|
||||
StyledText {
|
||||
text: "Notification Settings"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 36
|
||||
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
|
||||
DankIcon {
|
||||
name: SessionData.doNotDisturb ? "notifications_off" : "notifications"
|
||||
size: Theme.iconSizeSmall
|
||||
color: SessionData.doNotDisturb ? Theme.error : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: "Do Not Disturb"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -106,28 +134,30 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DankToggle {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SessionData.doNotDisturb
|
||||
onToggled: SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
||||
onToggled: SessionData.setDoNotDisturb(
|
||||
!SessionData.doNotDisturb)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.1)
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: "Notification Timeouts"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
text: "Low Priority"
|
||||
@@ -135,15 +165,16 @@ Rectangle {
|
||||
currentValue: getTimeoutText(SettingsData.notificationTimeoutLow)
|
||||
options: timeoutOptions.map(opt => opt.text)
|
||||
onValueChanged: value => {
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.setNotificationTimeoutLow(timeoutOptions[i].value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.setNotificationTimeoutLow(
|
||||
timeoutOptions[i].value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
text: "Normal Priority"
|
||||
@@ -151,63 +182,67 @@ Rectangle {
|
||||
currentValue: getTimeoutText(SettingsData.notificationTimeoutNormal)
|
||||
options: timeoutOptions.map(opt => opt.text)
|
||||
onValueChanged: value => {
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.setNotificationTimeoutNormal(timeoutOptions[i].value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.setNotificationTimeoutNormal(
|
||||
timeoutOptions[i].value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
text: "Critical Priority"
|
||||
description: "Timeout for critical priority notifications"
|
||||
currentValue: getTimeoutText(SettingsData.notificationTimeoutCritical)
|
||||
currentValue: getTimeoutText(
|
||||
SettingsData.notificationTimeoutCritical)
|
||||
options: timeoutOptions.map(opt => opt.text)
|
||||
onValueChanged: value => {
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.setNotificationTimeoutCritical(timeoutOptions[i].value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.setNotificationTimeoutCritical(
|
||||
timeoutOptions[i].value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.1)
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 36
|
||||
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
|
||||
DankIcon {
|
||||
name: "notifications_active"
|
||||
size: Theme.iconSizeSmall
|
||||
color: SettingsData.notificationOverlayEnabled ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
|
||||
StyledText {
|
||||
text: "Notification Overlay"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: "Display all priorities over fullscreen apps"
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
@@ -215,13 +250,14 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DankToggle {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.notificationOverlayEnabled
|
||||
onToggled: (toggled) => SettingsData.setNotificationOverlayEnabled(toggled)
|
||||
onToggled: toggled => SettingsData.setNotificationOverlayEnabled(
|
||||
toggled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,280 +4,290 @@ import qs.Common
|
||||
import qs.Services
|
||||
|
||||
QtObject {
|
||||
id: manager
|
||||
id: manager
|
||||
|
||||
property var modelData
|
||||
property int topMargin: 0
|
||||
property int baseNotificationHeight: 120
|
||||
property int maxTargetNotifications: 3
|
||||
property var popupWindows: [] // strong refs to windows (live until exitFinished)
|
||||
property var destroyingWindows: new Set()
|
||||
property Component popupComponent
|
||||
property var modelData
|
||||
property int topMargin: 0
|
||||
property int baseNotificationHeight: 120
|
||||
property int maxTargetNotifications: 3
|
||||
property var popupWindows: [] // strong refs to windows (live until exitFinished)
|
||||
property var destroyingWindows: new Set()
|
||||
property Component popupComponent
|
||||
|
||||
popupComponent: Component {
|
||||
NotificationPopup {
|
||||
onEntered: manager._onPopupEntered(this)
|
||||
onExitFinished: manager._onPopupExitFinished(this)
|
||||
}
|
||||
}
|
||||
|
||||
property Connections notificationConnections
|
||||
|
||||
notificationConnections: Connections {
|
||||
function onVisibleNotificationsChanged() {
|
||||
manager._sync(NotificationService.visibleNotifications)
|
||||
}
|
||||
|
||||
target: NotificationService
|
||||
}
|
||||
|
||||
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
|
||||
popupComponent: Component {
|
||||
NotificationPopup {
|
||||
onEntered: manager._onPopupEntered(this)
|
||||
onExitFinished: manager._onPopupExitFinished(this)
|
||||
}
|
||||
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) {
|
||||
property Connections notificationConnections
|
||||
|
||||
notificationConnections: Connections {
|
||||
function onVisibleNotificationsChanged() {
|
||||
manager._sync(NotificationService.visibleNotifications)
|
||||
}
|
||||
|
||||
target: NotificationService
|
||||
}
|
||||
|
||||
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 (var k = 0; k < survivors.length; ++k) {
|
||||
survivors[k].screenY = topMargin + k * baseNotificationHeight
|
||||
}
|
||||
}
|
||||
if (popupWindows.length === 0)
|
||||
sweeper.stop()
|
||||
}
|
||||
}
|
||||
if (toRemove.length > 0) {
|
||||
for (let zombie of toRemove) {
|
||||
const i = popupWindows.indexOf(zombie)
|
||||
if (i !== -1)
|
||||
}
|
||||
|
||||
function _hasWindowFor(w) {
|
||||
return popupWindows.some(p => {
|
||||
return p && p.notificationData === w
|
||||
&& !p._isDestroying
|
||||
&& p.status !== Component.Null
|
||||
})
|
||||
}
|
||||
|
||||
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()) {
|
||||
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 expiredCandidates = activeWindows.filter(p => {
|
||||
if (!p.notificationData
|
||||
|| !p.notificationData.notification)
|
||||
return false
|
||||
if (p.notificationData.notification.urgency === 2)
|
||||
return false
|
||||
|
||||
const timeoutMs = p.notificationData.timer ? p.notificationData.timer.interval : 5000
|
||||
if (timeoutMs === 0)
|
||||
return false
|
||||
|
||||
return !p.notificationData.timer.running
|
||||
}).sort(
|
||||
(a, b) => b.screenY - a.screenY)
|
||||
|
||||
if (expiredCandidates.length > 0) {
|
||||
const toRemove = expiredCandidates[0]
|
||||
if (toRemove && !toRemove.exiting) {
|
||||
toRemove.notificationData.removedByLimit = true
|
||||
toRemove.notificationData.popup = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const timeoutCandidates = activeWindows.filter(p => {
|
||||
if (!p.notificationData
|
||||
|| !p.notificationData.notification)
|
||||
return false
|
||||
if (p.notificationData.notification.urgency === 2)
|
||||
return false
|
||||
|
||||
const timeoutMs = p.notificationData.timer ? p.notificationData.timer.interval : 5000
|
||||
return timeoutMs > 0
|
||||
}).sort((a, b) => {
|
||||
const aTimeout = a.notificationData.timer ? a.notificationData.timer.interval : 5000
|
||||
const bTimeout = b.notificationData.timer ? b.notificationData.timer.interval : 5000
|
||||
if (aTimeout !== bTimeout)
|
||||
return aTimeout - bTimeout
|
||||
return b.screenY - a.screenY
|
||||
})
|
||||
|
||||
if (timeoutCandidates.length > 0) {
|
||||
const toRemove = timeoutCandidates[0]
|
||||
if (toRemove && !toRemove.exiting) {
|
||||
toRemove.notificationData.removedByLimit = true
|
||||
toRemove.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()
|
||||
}
|
||||
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
|
||||
return a.screenY - b.screenY
|
||||
})
|
||||
for (var k = 0; k < survivors.length; ++k) {
|
||||
survivors[k].screenY = topMargin + k * baseNotificationHeight
|
||||
survivors[k].screenY = topMargin + k * baseNotificationHeight
|
||||
}
|
||||
}
|
||||
if (popupWindows.length === 0)
|
||||
_maybeStartOverflow()
|
||||
}
|
||||
|
||||
function cleanupAllWindows() {
|
||||
sweeper.stop()
|
||||
}
|
||||
}
|
||||
|
||||
function _hasWindowFor(w) {
|
||||
return popupWindows.some(p => {
|
||||
return p && p.notificationData === w
|
||||
&& !p._isDestroying
|
||||
&& p.status !== Component.Null
|
||||
})
|
||||
}
|
||||
|
||||
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()) {
|
||||
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 expiredCandidates = activeWindows.filter(p => {
|
||||
if (!p.notificationData || !p.notificationData.notification) return false
|
||||
if (p.notificationData.notification.urgency === 2) return false
|
||||
|
||||
const timeoutMs = p.notificationData.timer ? p.notificationData.timer.interval : 5000
|
||||
if (timeoutMs === 0) return false
|
||||
|
||||
return !p.notificationData.timer.running
|
||||
}).sort((a, b) => b.screenY - a.screenY)
|
||||
|
||||
if (expiredCandidates.length > 0) {
|
||||
const toRemove = expiredCandidates[0]
|
||||
if (toRemove && !toRemove.exiting) {
|
||||
toRemove.notificationData.removedByLimit = true
|
||||
toRemove.notificationData.popup = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const timeoutCandidates = activeWindows.filter(p => {
|
||||
if (!p.notificationData || !p.notificationData.notification) return false
|
||||
if (p.notificationData.notification.urgency === 2) return false
|
||||
|
||||
const timeoutMs = p.notificationData.timer ? p.notificationData.timer.interval : 5000
|
||||
return timeoutMs > 0
|
||||
}).sort((a, b) => {
|
||||
const aTimeout = a.notificationData.timer ? a.notificationData.timer.interval : 5000
|
||||
const bTimeout = b.notificationData.timer ? b.notificationData.timer.interval : 5000
|
||||
if (aTimeout !== bTimeout) return aTimeout - bTimeout
|
||||
return b.screenY - a.screenY
|
||||
})
|
||||
|
||||
if (timeoutCandidates.length > 0) {
|
||||
const toRemove = timeoutCandidates[0]
|
||||
if (toRemove && !toRemove.exiting) {
|
||||
toRemove.notificationData.removedByLimit = true
|
||||
toRemove.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) {
|
||||
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: {
|
||||
if (popupWindows.length > 0 && !sweeper.running)
|
||||
sweeper.start()
|
||||
else if (popupWindows.length === 0 && sweeper.running)
|
||||
sweeper.stop()
|
||||
}
|
||||
onPopupWindowsChanged: {
|
||||
if (popupWindows.length > 0 && !sweeper.running)
|
||||
sweeper.start()
|
||||
else if (popupWindows.length === 0 && sweeper.running)
|
||||
sweeper.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,474 +5,479 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
function formatNetworkSpeed(bytesPerSec) {
|
||||
if (bytesPerSec < 1024)
|
||||
return bytesPerSec.toFixed(0) + " B/s"
|
||||
else if (bytesPerSec < 1024 * 1024)
|
||||
return (bytesPerSec / 1024).toFixed(1) + " KB/s"
|
||||
else if (bytesPerSec < 1024 * 1024 * 1024)
|
||||
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s"
|
||||
else
|
||||
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s"
|
||||
}
|
||||
function formatNetworkSpeed(bytesPerSec) {
|
||||
if (bytesPerSec < 1024)
|
||||
return bytesPerSec.toFixed(0) + " B/s"
|
||||
else if (bytesPerSec < 1024 * 1024)
|
||||
return (bytesPerSec / 1024).toFixed(1) + " KB/s"
|
||||
else if (bytesPerSec < 1024 * 1024 * 1024)
|
||||
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s"
|
||||
else
|
||||
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s"
|
||||
}
|
||||
|
||||
function formatDiskSpeed(bytesPerSec) {
|
||||
if (bytesPerSec < 1024 * 1024)
|
||||
return (bytesPerSec / 1024).toFixed(1) + " KB/s"
|
||||
else if (bytesPerSec < 1024 * 1024 * 1024)
|
||||
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s"
|
||||
else
|
||||
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s"
|
||||
}
|
||||
function formatDiskSpeed(bytesPerSec) {
|
||||
if (bytesPerSec < 1024 * 1024)
|
||||
return (bytesPerSec / 1024).toFixed(1) + " KB/s"
|
||||
else if (bytesPerSec < 1024 * 1024 * 1024)
|
||||
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s"
|
||||
else
|
||||
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s"
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["cpu", "memory", "network", "disk"])
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["cpu", "memory", "network", "disk"])
|
||||
}
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["cpu", "memory", "network", "disk"])
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["cpu", "memory", "network", "disk"])
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 200
|
||||
radius: Theme.cornerRadius
|
||||
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 {
|
||||
Rectangle {
|
||||
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.cornerRadius
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||
Theme.primary.b, 0.12)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: DgopService.cpuUsage.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: DgopService.cpuCores + " 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
|
||||
height: 200
|
||||
radius: Theme.cornerRadius
|
||||
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 {
|
||||
width: parent.width
|
||||
spacing: 6
|
||||
|
||||
Repeater {
|
||||
model: DgopService.perCoreCpuUsage
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 20
|
||||
spacing: Theme.spacingS
|
||||
width: parent.width
|
||||
height: 32
|
||||
spacing: Theme.spacingM
|
||||
|
||||
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
|
||||
StyledText {
|
||||
text: "CPU"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
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
|
||||
width: 80
|
||||
height: 24
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||
Theme.primary.b, 0.12)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
if (usage > 60)
|
||||
return Theme.warning
|
||||
|
||||
return Theme.primary
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
StyledText {
|
||||
text: DgopService.cpuUsage.toFixed(1) + "%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Item {
|
||||
width: parent.width - 280
|
||||
height: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.cpuCores + " cores"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
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: DgopService.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.cornerRadius
|
||||
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: DgopService.formatSystemMemory(
|
||||
DgopService.usedMemoryKB) + " / " + DgopService.formatSystemMemory(
|
||||
DgopService.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: DgopService.totalMemoryKB
|
||||
> 0 ? parent.width * (DgopService.usedMemoryKB
|
||||
/ DgopService.totalMemoryKB) : 0
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: {
|
||||
const usage = DgopService.totalMemoryKB
|
||||
> 0 ? (DgopService.usedMemoryKB
|
||||
/ DgopService.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: DgopService.totalMemoryKB
|
||||
> 0 ? ((DgopService.usedMemoryKB
|
||||
/ DgopService.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: DgopService.totalSwapKB
|
||||
> 0 ? DgopService.formatSystemMemory(
|
||||
DgopService.usedSwapKB) + " / "
|
||||
+ DgopService.formatSystemMemory(
|
||||
DgopService.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: DgopService.totalSwapKB
|
||||
> 0 ? parent.width * (DgopService.usedSwapKB
|
||||
/ DgopService.totalSwapKB) : 0
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: {
|
||||
if (!DgopService.totalSwapKB)
|
||||
return Qt.rgba(Theme.surfaceText.r,
|
||||
Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.3)
|
||||
|
||||
const usage = DgopService.usedSwapKB / DgopService.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: DgopService.totalSwapKB
|
||||
> 0 ? ((DgopService.usedSwapKB / DgopService.totalSwapKB) * 100).toFixed(
|
||||
1) + "% used" : "Not available"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: DgopService.formatSystemMemory(
|
||||
DgopService.usedMemoryKB) + " / " + DgopService.formatSystemMemory(
|
||||
DgopService.totalMemoryKB)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Theme.spacingL
|
||||
height: 1
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 4
|
||||
width: 200
|
||||
width: parent.width
|
||||
height: 80
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 16
|
||||
radius: 8
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
|
||||
Rectangle {
|
||||
width: DgopService.totalMemoryKB
|
||||
> 0 ? parent.width * (DgopService.usedMemoryKB
|
||||
/ DgopService.totalMemoryKB) : 0
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: {
|
||||
const usage = DgopService.totalMemoryKB
|
||||
> 0 ? (DgopService.usedMemoryKB
|
||||
/ DgopService.totalMemoryKB) : 0
|
||||
if (usage > 0.9)
|
||||
return Theme.error
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
if (usage > 0.7)
|
||||
return Theme.warning
|
||||
StyledText {
|
||||
text: "Network"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
return Theme.secondary
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Row {
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: "↓"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.info
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.networkRxRate
|
||||
> 0 ? formatNetworkSpeed(
|
||||
DgopService.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: DgopService.networkTxRate
|
||||
> 0 ? formatNetworkSpeed(
|
||||
DgopService.networkTxRate) : "0 B/s"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.totalMemoryKB
|
||||
> 0 ? ((DgopService.usedMemoryKB
|
||||
/ DgopService.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: DgopService.totalSwapKB
|
||||
> 0 ? DgopService.formatSystemMemory(
|
||||
DgopService.usedSwapKB) + " / "
|
||||
+ DgopService.formatSystemMemory(
|
||||
DgopService.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)
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
|
||||
Rectangle {
|
||||
width: DgopService.totalSwapKB
|
||||
> 0 ? parent.width * (DgopService.usedSwapKB
|
||||
/ DgopService.totalSwapKB) : 0
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: {
|
||||
if (!DgopService.totalSwapKB)
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.3)
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
const usage = DgopService.usedSwapKB / DgopService.totalSwapKB
|
||||
if (usage > 0.9)
|
||||
return Theme.error
|
||||
StyledText {
|
||||
text: "Disk"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
if (usage > 0.7)
|
||||
return Theme.warning
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
return Theme.info
|
||||
Row {
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: "R"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: formatDiskSpeed(DgopService.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(DgopService.diskWriteRate)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.totalSwapKB
|
||||
> 0 ? ((DgopService.usedSwapKB
|
||||
/ DgopService.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.cornerRadius
|
||||
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: DgopService.networkRxRate
|
||||
> 0 ? formatNetworkSpeed(
|
||||
DgopService.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: DgopService.networkTxRate
|
||||
> 0 ? formatNetworkSpeed(
|
||||
DgopService.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.cornerRadius
|
||||
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(DgopService.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(DgopService.diskWriteRate)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,238 +7,246 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Popup {
|
||||
id: processContextMenu
|
||||
id: processContextMenu
|
||||
|
||||
property var processData: null
|
||||
property var processData: null
|
||||
|
||||
function show(x, y) {
|
||||
if (!processContextMenu.parent && typeof Overlay !== "undefined"
|
||||
&& Overlay.overlay)
|
||||
processContextMenu.parent = Overlay.overlay
|
||||
function show(x, y) {
|
||||
if (!processContextMenu.parent && typeof Overlay !== "undefined"
|
||||
&& Overlay.overlay)
|
||||
processContextMenu.parent = Overlay.overlay
|
||||
|
||||
const menuWidth = 180
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
const screenWidth = Screen.width
|
||||
const screenHeight = Screen.height
|
||||
let finalX = x
|
||||
let finalY = y
|
||||
if (x + menuWidth > screenWidth - 20)
|
||||
finalX = x - menuWidth
|
||||
const menuWidth = 180
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
const screenWidth = Screen.width
|
||||
const screenHeight = Screen.height
|
||||
let finalX = x
|
||||
let finalY = y
|
||||
if (x + menuWidth > screenWidth - 20)
|
||||
finalX = x - menuWidth
|
||||
|
||||
if (y + menuHeight > screenHeight - 20)
|
||||
finalY = y - menuHeight
|
||||
if (y + menuHeight > screenHeight - 20)
|
||||
finalY = y - menuHeight
|
||||
|
||||
processContextMenu.x = Math.max(20, finalX)
|
||||
processContextMenu.y = Math.max(20, finalY)
|
||||
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
|
||||
processContextMenu.x = Math.max(20, finalX)
|
||||
processContextMenu.y = Math.max(20, finalY)
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
width: 180
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
padding: 0
|
||||
modal: false
|
||||
closePolicy: Popup.CloseOnEscape
|
||||
onClosed: {
|
||||
closePolicy = Popup.CloseOnEscape
|
||||
}
|
||||
onOpened: {
|
||||
outsideClickTimer.start()
|
||||
}
|
||||
|
||||
contentItem: Rectangle {
|
||||
id: menuContent
|
||||
Timer {
|
||||
id: outsideClickTimer
|
||||
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
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.cornerRadius
|
||||
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
|
||||
interval: 100
|
||||
onTriggered: {
|
||||
processContextMenu.closePolicy = Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
}
|
||||
}
|
||||
|
||||
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.cornerRadius
|
||||
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
|
||||
background: Rectangle {
|
||||
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.cornerRadius
|
||||
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.cornerRadius
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Rectangle {
|
||||
id: menuContent
|
||||
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
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.cornerRadius
|
||||
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.cornerRadius
|
||||
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.cornerRadius
|
||||
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.cornerRadius
|
||||
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,227 +4,229 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: processItem
|
||||
id: processItem
|
||||
|
||||
property var process: null
|
||||
property var contextMenu: null
|
||||
property var process: null
|
||||
property var contextMenu: null
|
||||
|
||||
width: parent ? parent.width : 0
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
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 {
|
||||
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: DgopService.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: DgopService.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: DgopService.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,
|
||||
width: parent ? parent.width : 0
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.08) : "transparent"
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
border.color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.12) : "transparent"
|
||||
border.width: 1
|
||||
|
||||
DankIcon {
|
||||
name: "more_vert"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.6
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: menuButtonArea
|
||||
MouseArea {
|
||||
id: processMouseArea
|
||||
|
||||
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)
|
||||
}
|
||||
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: DgopService.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: DgopService.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: DgopService.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,125 +12,127 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankPopout {
|
||||
id: processListPopout
|
||||
id: processListPopout
|
||||
|
||||
property var parentWidget: null
|
||||
property string triggerSection: "right"
|
||||
property var triggerScreen: null
|
||||
property var parentWidget: null
|
||||
property string triggerSection: "right"
|
||||
property var triggerScreen: null
|
||||
|
||||
function setTriggerPosition(x, y, width, section, screen) {
|
||||
triggerX = x
|
||||
triggerY = y
|
||||
triggerWidth = width
|
||||
triggerSection = section
|
||||
triggerScreen = screen
|
||||
}
|
||||
|
||||
function hide() {
|
||||
close()
|
||||
if (processContextMenu.visible)
|
||||
processContextMenu.close()
|
||||
}
|
||||
|
||||
function show() {
|
||||
open()
|
||||
}
|
||||
|
||||
popupWidth: 600
|
||||
popupHeight: 600
|
||||
triggerX: Screen.width - 600 - Theme.spacingL
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingXS
|
||||
triggerWidth: 55
|
||||
positioning: "center"
|
||||
WlrLayershell.namespace: "quickshell-processlist"
|
||||
screen: triggerScreen
|
||||
visible: shouldBeVisible
|
||||
shouldBeVisible: false
|
||||
|
||||
Ref {
|
||||
service: DgopService
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Rectangle {
|
||||
id: processListContent
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
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.shouldBeVisible)
|
||||
forceActiveFocus()
|
||||
}
|
||||
|
||||
Keys.onPressed: function (event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
processListPopout.close()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onShouldBeVisibleChanged() {
|
||||
if (processListPopout.shouldBeVisible)
|
||||
Qt.callLater(function () {
|
||||
processListContent.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.cornerRadius
|
||||
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.cornerRadius
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
function setTriggerPosition(x, y, width, section, screen) {
|
||||
triggerX = x
|
||||
triggerY = y
|
||||
triggerWidth = width
|
||||
triggerSection = section
|
||||
triggerScreen = screen
|
||||
}
|
||||
}
|
||||
|
||||
ProcessContextMenu {
|
||||
id: processContextMenu
|
||||
}
|
||||
}
|
||||
function hide() {
|
||||
close()
|
||||
if (processContextMenu.visible)
|
||||
processContextMenu.close()
|
||||
}
|
||||
|
||||
function show() {
|
||||
open()
|
||||
}
|
||||
|
||||
popupWidth: 600
|
||||
popupHeight: 600
|
||||
triggerX: Screen.width - 600 - Theme.spacingL
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingXS
|
||||
triggerWidth: 55
|
||||
positioning: "center"
|
||||
WlrLayershell.namespace: "quickshell-processlist"
|
||||
screen: triggerScreen
|
||||
visible: shouldBeVisible
|
||||
shouldBeVisible: false
|
||||
|
||||
Ref {
|
||||
service: DgopService
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Rectangle {
|
||||
id: processListContent
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
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.shouldBeVisible)
|
||||
forceActiveFocus()
|
||||
}
|
||||
|
||||
Keys.onPressed: function (event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
processListPopout.close()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onShouldBeVisibleChanged() {
|
||||
if (processListPopout.shouldBeVisible)
|
||||
Qt.callLater(function () {
|
||||
processListContent.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.cornerRadius
|
||||
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.cornerRadius
|
||||
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,257 +5,266 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property var contextMenu: null
|
||||
property var contextMenu: null
|
||||
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["processes"])
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["processes"])
|
||||
}
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["processes"])
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["processes"])
|
||||
}
|
||||
|
||||
Item {
|
||||
id: columnHeaders
|
||||
Item {
|
||||
id: columnHeaders
|
||||
|
||||
width: parent.width
|
||||
anchors.leftMargin: 8
|
||||
height: 24
|
||||
width: parent.width
|
||||
anchors.leftMargin: 8
|
||||
height: 24
|
||||
|
||||
Rectangle {
|
||||
width: 60
|
||||
height: 20
|
||||
color: {
|
||||
if (DgopService.currentSort === "name") {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
Rectangle {
|
||||
width: 60
|
||||
height: 20
|
||||
color: {
|
||||
if (DgopService.currentSort === "name") {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||
Theme.primary.b, 0.12)
|
||||
}
|
||||
return 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: DgopService.currentSort === "name" ? Font.Bold : Font.Medium
|
||||
color: Theme.surfaceText
|
||||
opacity: DgopService.currentSort === "name" ? 1 : 0.7
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: processHeaderArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
DgopService.setSortBy("name")
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
return processHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r,
|
||||
|
||||
Rectangle {
|
||||
width: 80
|
||||
height: 20
|
||||
color: {
|
||||
if (DgopService.currentSort === "cpu") {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||
Theme.primary.b, 0.12)
|
||||
}
|
||||
return cpuHeaderArea.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
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 200
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: "Process"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: SettingsData.monoFontFamily
|
||||
font.weight: DgopService.currentSort === "name" ? Font.Bold : Font.Medium
|
||||
color: Theme.surfaceText
|
||||
opacity: DgopService.currentSort === "name" ? 1 : 0.7
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
StyledText {
|
||||
text: "CPU"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: SettingsData.monoFontFamily
|
||||
font.weight: DgopService.currentSort === "cpu" ? Font.Bold : Font.Medium
|
||||
color: Theme.surfaceText
|
||||
opacity: DgopService.currentSort === "cpu" ? 1 : 0.7
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: processHeaderArea
|
||||
MouseArea {
|
||||
id: cpuHeaderArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
DgopService.setSortBy("name")
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
DgopService.setSortBy("cpu")
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
Rectangle {
|
||||
width: 80
|
||||
height: 20
|
||||
color: {
|
||||
if (DgopService.currentSort === "memory") {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||
Theme.primary.b, 0.12)
|
||||
}
|
||||
return 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: DgopService.currentSort === "memory" ? Font.Bold : Font.Medium
|
||||
color: Theme.surfaceText
|
||||
opacity: DgopService.currentSort === "memory" ? 1 : 0.7
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: memoryHeaderArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
DgopService.setSortBy("memory")
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 50
|
||||
height: 20
|
||||
color: {
|
||||
if (DgopService.currentSort === "pid") {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||
Theme.primary.b, 0.12)
|
||||
}
|
||||
return 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: DgopService.currentSort === "pid" ? Font.Bold : Font.Medium
|
||||
color: Theme.surfaceText
|
||||
opacity: DgopService.currentSort === "pid" ? 1 : 0.7
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: pidHeaderArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
DgopService.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: DgopService.sortDescending ? "↓" : "↑"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: sortOrderArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
|
||||
// ! TODO - we lost this with dgop
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 80
|
||||
height: 20
|
||||
color: {
|
||||
if (DgopService.currentSort === "cpu") {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
DankListView {
|
||||
id: processListView
|
||||
|
||||
property string keyRoleName: "pid"
|
||||
|
||||
width: parent.width
|
||||
height: parent.height - columnHeaders.height
|
||||
clip: true
|
||||
spacing: 4
|
||||
model: DgopService.processes
|
||||
|
||||
delegate: ProcessListItem {
|
||||
process: modelData
|
||||
contextMenu: root.contextMenu
|
||||
}
|
||||
return 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: DgopService.currentSort === "cpu" ? Font.Bold : Font.Medium
|
||||
color: Theme.surfaceText
|
||||
opacity: DgopService.currentSort === "cpu" ? 1 : 0.7
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cpuHeaderArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
DgopService.setSortBy("cpu")
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 80
|
||||
height: 20
|
||||
color: {
|
||||
if (DgopService.currentSort === "memory") {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
}
|
||||
return 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: DgopService.currentSort === "memory" ? Font.Bold : Font.Medium
|
||||
color: Theme.surfaceText
|
||||
opacity: DgopService.currentSort === "memory" ? 1 : 0.7
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: memoryHeaderArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
DgopService.setSortBy("memory")
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 50
|
||||
height: 20
|
||||
color: {
|
||||
if (DgopService.currentSort === "pid") {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
}
|
||||
return 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: DgopService.currentSort === "pid" ? Font.Bold : Font.Medium
|
||||
color: Theme.surfaceText
|
||||
opacity: DgopService.currentSort === "pid" ? 1 : 0.7
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: pidHeaderArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
DgopService.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: DgopService.sortDescending ? "↓" : "↑"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: sortOrderArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
// ! TODO - we lost this with dgop
|
||||
}
|
||||
}
|
||||
|
||||
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: DgopService.processes
|
||||
|
||||
delegate: ProcessListItem {
|
||||
process: modelData
|
||||
contextMenu: root.contextMenu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,24 +5,24 @@ import qs.Modules.ProcessList
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: processesTab
|
||||
id: processesTab
|
||||
|
||||
property var contextMenu: null
|
||||
property var contextMenu: null
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
SystemOverview {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
SystemOverview {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ProcessListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
contextMenu: processesTab.contextMenu || localContextMenu
|
||||
}
|
||||
ProcessListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
contextMenu: processesTab.contextMenu || localContextMenu
|
||||
}
|
||||
|
||||
ProcessContextMenu {
|
||||
id: localContextMenu
|
||||
}
|
||||
ProcessContextMenu {
|
||||
id: localContextMenu
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["cpu", "memory", "system"]);
|
||||
DgopService.addRef(["cpu", "memory", "system"])
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["cpu", "memory", "system"]);
|
||||
DgopService.removeRef(["cpu", "memory", "system"])
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -20,13 +20,22 @@ Row {
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (DgopService.sortBy === "cpu")
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16);
|
||||
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);
|
||||
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);
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||
Theme.primary.b, 0.08)
|
||||
}
|
||||
border.color: DgopService.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.color: DgopService.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: DgopService.sortBy === "cpu" ? 2 : 1
|
||||
|
||||
MouseArea {
|
||||
@@ -57,7 +66,8 @@ Row {
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null)
|
||||
if (DgopService.cpuUsage === undefined
|
||||
|| DgopService.cpuUsage === null)
|
||||
return "--%"
|
||||
return DgopService.cpuUsage.toFixed(1) + "%"
|
||||
}
|
||||
@@ -71,32 +81,34 @@ Row {
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: 20
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.3)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature <= 0)
|
||||
return "--°";
|
||||
if (DgopService.cpuTemperature === undefined
|
||||
|| DgopService.cpuTemperature === null
|
||||
|| DgopService.cpuTemperature <= 0)
|
||||
return "--°"
|
||||
|
||||
return Math.round(DgopService.cpuTemperature) + "°";
|
||||
return Math.round(DgopService.cpuTemperature) + "°"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.family: SettingsData.monoFontFamily
|
||||
font.weight: Font.Medium
|
||||
color: {
|
||||
if (DgopService.cpuTemperature > 80)
|
||||
return Theme.error;
|
||||
return Theme.error
|
||||
|
||||
if (DgopService.cpuTemperature > 60)
|
||||
return Theme.warning;
|
||||
return Theme.warning
|
||||
|
||||
return Theme.surfaceText;
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -106,23 +118,19 @@ Row {
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -131,13 +139,24 @@ Row {
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (DgopService.sortBy === "memory")
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16);
|
||||
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);
|
||||
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);
|
||||
return Qt.rgba(Theme.secondary.r, Theme.secondary.g,
|
||||
Theme.secondary.b, 0.08)
|
||||
}
|
||||
border.color: DgopService.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.color: DgopService.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: DgopService.sortBy === "memory" ? 2 : 1
|
||||
|
||||
MouseArea {
|
||||
@@ -167,7 +186,8 @@ Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: DgopService.formatSystemMemory(DgopService.usedMemoryKB)
|
||||
text: DgopService.formatSystemMemory(
|
||||
DgopService.usedMemoryKB)
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.family: SettingsData.monoFontFamily
|
||||
font.weight: Font.Bold
|
||||
@@ -178,13 +198,15 @@ Row {
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: 20
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
|
||||
Theme.surfaceText.b, 0.3)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: DgopService.totalSwapKB > 0
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.totalSwapKB > 0 ? DgopService.formatSystemMemory(DgopService.usedSwapKB) : ""
|
||||
text: DgopService.totalSwapKB > 0 ? DgopService.formatSystemMemory(
|
||||
DgopService.usedSwapKB) : ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.family: SettingsData.monoFontFamily
|
||||
font.weight: Font.Medium
|
||||
@@ -192,38 +214,35 @@ Row {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: DgopService.totalSwapKB > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.totalSwapKB > 0)
|
||||
return "of " + DgopService.formatSystemMemory(DgopService.totalMemoryKB) + " + swap";
|
||||
return "of " + DgopService.formatSystemMemory(
|
||||
DgopService.totalMemoryKB) + " + swap"
|
||||
|
||||
return "of " + DgopService.formatSystemMemory(DgopService.totalMemoryKB);
|
||||
return "of " + DgopService.formatSystemMemory(
|
||||
DgopService.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 {
|
||||
@@ -231,48 +250,74 @@ Row {
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
|
||||
if (gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.16);
|
||||
if (!DgopService.availableGpus
|
||||
|| DgopService.availableGpus.length === 0) {
|
||||
if (gpuCardMouseArea.containsMouse
|
||||
&& DgopService.availableGpus.length > 1)
|
||||
return Qt.rgba(Theme.surfaceVariant.r,
|
||||
Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b, 0.16)
|
||||
else
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
|
||||
return Qt.rgba(Theme.surfaceVariant.r,
|
||||
Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b, 0.08)
|
||||
}
|
||||
var gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
|
||||
var vendor = gpu.vendor.toLowerCase();
|
||||
var gpu = DgopService.availableGpus[Math.min(
|
||||
SessionData.selectedGpuIndex,
|
||||
DgopService.availableGpus.length - 1)]
|
||||
var vendor = gpu.vendor.toLowerCase()
|
||||
if (vendor.includes("nvidia")) {
|
||||
if (gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1)
|
||||
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.2);
|
||||
if (gpuCardMouseArea.containsMouse
|
||||
&& DgopService.availableGpus.length > 1)
|
||||
return Qt.rgba(Theme.success.r, Theme.success.g,
|
||||
Theme.success.b, 0.2)
|
||||
else
|
||||
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.12);
|
||||
return Qt.rgba(Theme.success.r, Theme.success.g,
|
||||
Theme.success.b, 0.12)
|
||||
} else if (vendor.includes("amd")) {
|
||||
if (gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1)
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.2);
|
||||
if (gpuCardMouseArea.containsMouse
|
||||
&& DgopService.availableGpus.length > 1)
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g,
|
||||
Theme.error.b, 0.2)
|
||||
else
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g,
|
||||
Theme.error.b, 0.12)
|
||||
} else if (vendor.includes("intel")) {
|
||||
if (gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1)
|
||||
return Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.2);
|
||||
if (gpuCardMouseArea.containsMouse
|
||||
&& DgopService.availableGpus.length > 1)
|
||||
return Qt.rgba(Theme.info.r, Theme.info.g,
|
||||
Theme.info.b, 0.2)
|
||||
else
|
||||
return Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.12);
|
||||
return Qt.rgba(Theme.info.r, Theme.info.g,
|
||||
Theme.info.b, 0.12)
|
||||
}
|
||||
if (gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.16);
|
||||
if (gpuCardMouseArea.containsMouse
|
||||
&& DgopService.availableGpus.length > 1)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b, 0.16)
|
||||
else
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b, 0.08)
|
||||
}
|
||||
border.color: {
|
||||
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2);
|
||||
if (!DgopService.availableGpus
|
||||
|| DgopService.availableGpus.length === 0)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b, 0.2)
|
||||
|
||||
var gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
|
||||
var vendor = gpu.vendor.toLowerCase();
|
||||
var gpu = DgopService.availableGpus[Math.min(
|
||||
SessionData.selectedGpuIndex,
|
||||
DgopService.availableGpus.length - 1)]
|
||||
var vendor = gpu.vendor.toLowerCase()
|
||||
if (vendor.includes("nvidia"))
|
||||
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.3);
|
||||
return Qt.rgba(Theme.success.r, Theme.success.g,
|
||||
Theme.success.b, 0.3)
|
||||
else if (vendor.includes("amd"))
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3);
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3)
|
||||
else if (vendor.includes("intel"))
|
||||
return Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.3);
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2);
|
||||
return Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.3)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b, 0.2)
|
||||
}
|
||||
border.width: 1
|
||||
|
||||
@@ -282,17 +327,19 @@ Row {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: DgopService.availableGpus.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (DgopService.availableGpus.length > 1) {
|
||||
var nextIndex = (SessionData.selectedGpuIndex + 1) % DgopService.availableGpus.length;
|
||||
SessionData.setSelectedGpuIndex(nextIndex);
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
gpuContextMenu.popup();
|
||||
}
|
||||
}
|
||||
cursorShape: DgopService.availableGpus.length
|
||||
> 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (DgopService.availableGpus.length > 1) {
|
||||
var nextIndex = (SessionData.selectedGpuIndex + 1)
|
||||
% DgopService.availableGpus.length
|
||||
SessionData.setSelectedGpuIndex(nextIndex)
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
gpuContextMenu.popup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -311,52 +358,69 @@ Row {
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0)
|
||||
return "No GPU";
|
||||
if (!DgopService.availableGpus
|
||||
|| DgopService.availableGpus.length === 0)
|
||||
return "No GPU"
|
||||
|
||||
var gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
|
||||
var gpu = DgopService.availableGpus[Math.min(
|
||||
SessionData.selectedGpuIndex,
|
||||
DgopService.availableGpus.length - 1)]
|
||||
// Check if temperature monitoring is enabled for this GPU
|
||||
var tempEnabled = SessionData.enabledGpuPciIds && SessionData.enabledGpuPciIds.indexOf(gpu.pciId) !== -1;
|
||||
var temp = gpu.temperature;
|
||||
var hasTemp = tempEnabled && temp !== undefined && temp !== null && temp !== 0;
|
||||
var tempEnabled = SessionData.enabledGpuPciIds
|
||||
&& SessionData.enabledGpuPciIds.indexOf(
|
||||
gpu.pciId) !== -1
|
||||
var temp = gpu.temperature
|
||||
var hasTemp = tempEnabled && temp !== undefined
|
||||
&& temp !== null && temp !== 0
|
||||
if (hasTemp)
|
||||
return Math.round(temp) + "°";
|
||||
return Math.round(temp) + "°"
|
||||
else
|
||||
return gpu.vendor;
|
||||
return gpu.vendor
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.family: SettingsData.monoFontFamily
|
||||
font.weight: Font.Bold
|
||||
color: {
|
||||
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0)
|
||||
return Theme.surfaceText;
|
||||
if (!DgopService.availableGpus
|
||||
|| DgopService.availableGpus.length === 0)
|
||||
return Theme.surfaceText
|
||||
|
||||
var gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
|
||||
var tempEnabled = SessionData.enabledGpuPciIds && SessionData.enabledGpuPciIds.indexOf(gpu.pciId) !== -1;
|
||||
var temp = gpu.temperature || 0;
|
||||
var gpu = DgopService.availableGpus[Math.min(
|
||||
SessionData.selectedGpuIndex,
|
||||
DgopService.availableGpus.length - 1)]
|
||||
var tempEnabled = SessionData.enabledGpuPciIds
|
||||
&& SessionData.enabledGpuPciIds.indexOf(
|
||||
gpu.pciId) !== -1
|
||||
var temp = gpu.temperature || 0
|
||||
if (tempEnabled && temp > 80)
|
||||
return Theme.error;
|
||||
return Theme.error
|
||||
|
||||
if (tempEnabled && temp > 60)
|
||||
return Theme.warning;
|
||||
return Theme.warning
|
||||
|
||||
return Theme.surfaceText;
|
||||
return Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0)
|
||||
return "No GPUs detected";
|
||||
if (!DgopService.availableGpus
|
||||
|| DgopService.availableGpus.length === 0)
|
||||
return "No GPUs detected"
|
||||
|
||||
var gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
|
||||
var tempEnabled = SessionData.enabledGpuPciIds && SessionData.enabledGpuPciIds.indexOf(gpu.pciId) !== -1;
|
||||
var temp = gpu.temperature;
|
||||
var hasTemp = tempEnabled && temp !== undefined && temp !== null && temp !== 0;
|
||||
var gpu = DgopService.availableGpus[Math.min(
|
||||
SessionData.selectedGpuIndex,
|
||||
DgopService.availableGpus.length - 1)]
|
||||
var tempEnabled = SessionData.enabledGpuPciIds
|
||||
&& SessionData.enabledGpuPciIds.indexOf(
|
||||
gpu.pciId) !== -1
|
||||
var temp = gpu.temperature
|
||||
var hasTemp = tempEnabled && temp !== undefined
|
||||
&& temp !== null && temp !== 0
|
||||
if (hasTemp)
|
||||
return gpu.vendor + " " + gpu.displayName;
|
||||
return gpu.vendor + " " + gpu.displayName
|
||||
else
|
||||
return gpu.displayName;
|
||||
return gpu.displayName
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: SettingsData.monoFontFamily
|
||||
@@ -366,7 +430,6 @@ Row {
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Menu {
|
||||
@@ -376,26 +439,36 @@ Row {
|
||||
text: "Enable GPU Temperature"
|
||||
checkable: true
|
||||
checked: {
|
||||
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
|
||||
if (!DgopService.availableGpus
|
||||
|| DgopService.availableGpus.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
var gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)]
|
||||
if (!gpu.pciId) return false
|
||||
|
||||
return SessionData.enabledGpuPciIds ? SessionData.enabledGpuPciIds.indexOf(gpu.pciId) !== -1 : false
|
||||
|
||||
var gpu = DgopService.availableGpus[Math.min(
|
||||
SessionData.selectedGpuIndex,
|
||||
DgopService.availableGpus.length - 1)]
|
||||
if (!gpu.pciId)
|
||||
return false
|
||||
|
||||
return SessionData.enabledGpuPciIds ? SessionData.enabledGpuPciIds.indexOf(
|
||||
gpu.pciId) !== -1 : false
|
||||
}
|
||||
onTriggered: {
|
||||
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
|
||||
if (!DgopService.availableGpus
|
||||
|| DgopService.availableGpus.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
var gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)]
|
||||
if (!gpu.pciId) return
|
||||
|
||||
var enabledIds = SessionData.enabledGpuPciIds ? SessionData.enabledGpuPciIds.slice() : []
|
||||
|
||||
var gpu = DgopService.availableGpus[Math.min(
|
||||
SessionData.selectedGpuIndex,
|
||||
DgopService.availableGpus.length - 1)]
|
||||
if (!gpu.pciId)
|
||||
return
|
||||
|
||||
var enabledIds = SessionData.enabledGpuPciIds ? SessionData.enabledGpuPciIds.slice(
|
||||
) : []
|
||||
var index = enabledIds.indexOf(gpu.pciId)
|
||||
|
||||
|
||||
if (checked && index === -1) {
|
||||
enabledIds.push(gpu.pciId)
|
||||
DgopService.addGpuPciId(gpu.pciId)
|
||||
@@ -403,7 +476,7 @@ Row {
|
||||
enabledIds.splice(index, 1)
|
||||
DgopService.removeGpuPciId(gpu.pciId)
|
||||
}
|
||||
|
||||
|
||||
SessionData.setEnabledGpuPciIds(enabledIds)
|
||||
}
|
||||
}
|
||||
@@ -413,9 +486,6 @@ Row {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,8 +24,10 @@ Item {
|
||||
width: parent.width
|
||||
height: asciiSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -41,7 +43,7 @@ Item {
|
||||
|
||||
StyledText {
|
||||
id: asciiText
|
||||
|
||||
|
||||
text: "██████╗ █████╗ ███╗ ██╗██╗ ██╗\n██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝\n██║ ██║███████║██╔██╗ ██║█████╔╝ \n██║ ██║██╔══██║██║╚██╗██║██╔═██╗ \n██████╔╝██║ ██║██║ ╚████║██║ ██╗\n╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝"
|
||||
isMonospace: true
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -63,7 +65,7 @@ Item {
|
||||
text: "A desktop shell built with <a href=\"https://quickshell.org\">Quickshell</a>"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
linkColor: Theme.primary
|
||||
onLinkActivated: (url) => Qt.openUrlExternally(url)
|
||||
onLinkActivated: url => Qt.openUrlExternally(url)
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
width: parent.width
|
||||
@@ -76,9 +78,7 @@ Item {
|
||||
propagateComposedEvents: true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Project Information
|
||||
@@ -86,8 +86,10 @@ Item {
|
||||
width: parent.width
|
||||
height: projectSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -115,16 +117,15 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `DankMaterialShell is a modern desktop inspired by <a href="https://m3.material.io/">MUI 3</a>.
|
||||
<br /><br/>The goal is to provide a high level of functionality and customization so that it can be a suitable replacement for complete desktop environments like Gnome, KDE, or Cosmic.
|
||||
<br /><br/>The goal is to provide a high level of functionality and customization so that it can be a suitable replacement for complete desktop environments like Gnome, KDE, or Cosmic.
|
||||
`
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
linkColor: Theme.primary
|
||||
onLinkActivated: (url) => Qt.openUrlExternally(url)
|
||||
onLinkActivated: url => Qt.openUrlExternally(url)
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
@@ -137,7 +138,6 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Technical Details
|
||||
@@ -145,8 +145,10 @@ Item {
|
||||
width: parent.width
|
||||
height: techSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -174,7 +176,6 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Grid {
|
||||
@@ -234,7 +235,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
linkColor: Theme.primary
|
||||
onLinkActivated: (url) => Qt.openUrlExternally(url)
|
||||
onLinkActivated: url => Qt.openUrlExternally(url)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -256,7 +257,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
linkColor: Theme.primary
|
||||
onLinkActivated: (url) => Qt.openUrlExternally(url)
|
||||
onLinkActivated: url => Qt.openUrlExternally(url)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -265,15 +266,9 @@ Item {
|
||||
propagateComposedEvents: true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,228 +5,219 @@ import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: dockTab
|
||||
id: dockTab
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Theme.spacingL
|
||||
clip: true
|
||||
contentHeight: mainColumn.height
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
|
||||
// Enable Dock
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: enableDockSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Theme.spacingL
|
||||
clip: true
|
||||
contentHeight: mainColumn.height
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: enableDockSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
id: mainColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
DankIcon {
|
||||
name: "dock_to_bottom"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - enableToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: "Show Dock"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Display a dock at the bottom of the screen with pinned and running applications"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
// Enable Dock
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
}
|
||||
height: enableDockSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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: enableDockSection
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
- enableToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: "Show Dock"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Display a dock at the bottom of the screen with pinned and running applications"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: enableToggle
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.showDock
|
||||
onToggled: checked => {
|
||||
SettingsData.setShowDock(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: enableToggle
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.showDock
|
||||
onToggled: checked => {
|
||||
SettingsData.setShowDock(checked)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Auto-hide Dock
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: autoHideSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
visible: SettingsData.showDock
|
||||
opacity: visible ? 1 : 0
|
||||
|
||||
Column {
|
||||
id: autoHideSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "visibility_off"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - autoHideToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: "Auto-hide Dock"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Hide the dock when not in use and reveal it when hovering near the bottom of the screen"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
// Auto-hide Dock
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
}
|
||||
height: autoHideSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
visible: SettingsData.showDock
|
||||
opacity: visible ? 1 : 0
|
||||
|
||||
Column {
|
||||
id: autoHideSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "visibility_off"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
- autoHideToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: "Auto-hide Dock"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Hide the dock when not in use and reveal it when hovering near the bottom of the screen"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: autoHideToggle
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.dockAutoHide
|
||||
onToggled: checked => {
|
||||
SettingsData.setDockAutoHide(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: autoHideToggle
|
||||
// Dock Transparency Section
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: transparencySection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
visible: SettingsData.showDock
|
||||
opacity: visible ? 1 : 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.dockAutoHide
|
||||
onToggled: checked => {
|
||||
SettingsData.setDockAutoHide(checked)
|
||||
}
|
||||
Column {
|
||||
id: transparencySection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "opacity"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Dock Transparency"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
width: parent.width
|
||||
height: 32
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Dock Transparency Section
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: transparencySection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
visible: SettingsData.showDock
|
||||
opacity: visible ? 1 : 0
|
||||
|
||||
Column {
|
||||
id: transparencySection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "opacity"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Dock Transparency"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
width: parent.width
|
||||
height: 32
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,56 +17,75 @@ Item {
|
||||
property bool fontsEnumerated: false
|
||||
|
||||
function enumerateFonts() {
|
||||
var fonts = ["Default"];
|
||||
var availableFonts = Qt.fontFamilies();
|
||||
var rootFamilies = [];
|
||||
var seenFamilies = new Set();
|
||||
var fonts = ["Default"]
|
||||
var availableFonts = Qt.fontFamilies()
|
||||
var rootFamilies = []
|
||||
var seenFamilies = new Set()
|
||||
for (var i = 0; i < availableFonts.length; i++) {
|
||||
var fontName = availableFonts[i];
|
||||
var fontName = availableFonts[i]
|
||||
if (fontName.startsWith("."))
|
||||
continue;
|
||||
continue
|
||||
|
||||
if (fontName === SettingsData.defaultFontFamily)
|
||||
continue;
|
||||
continue
|
||||
|
||||
var rootName = fontName.replace(/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i, "").replace(/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i, "").replace(/ (UI|Display|Text|Mono|Sans|Serif)$/i, function(match, suffix) {
|
||||
return match;
|
||||
}).trim();
|
||||
var rootName = fontName.replace(
|
||||
/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i,
|
||||
"").replace(
|
||||
/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i,
|
||||
"").replace(/ (UI|Display|Text|Mono|Sans|Serif)$/i,
|
||||
function (match, suffix) {
|
||||
return match
|
||||
}).trim()
|
||||
if (!seenFamilies.has(rootName) && rootName !== "") {
|
||||
seenFamilies.add(rootName);
|
||||
rootFamilies.push(rootName);
|
||||
seenFamilies.add(rootName)
|
||||
rootFamilies.push(rootName)
|
||||
}
|
||||
}
|
||||
cachedFontFamilies = fonts.concat(rootFamilies.sort());
|
||||
var monoFonts = ["Default"];
|
||||
var monoFamilies = [];
|
||||
var seenMonoFamilies = new Set();
|
||||
cachedFontFamilies = fonts.concat(rootFamilies.sort())
|
||||
var monoFonts = ["Default"]
|
||||
var monoFamilies = []
|
||||
var seenMonoFamilies = new Set()
|
||||
for (var j = 0; j < availableFonts.length; j++) {
|
||||
var fontName2 = availableFonts[j];
|
||||
var fontName2 = availableFonts[j]
|
||||
if (fontName2.startsWith("."))
|
||||
continue;
|
||||
continue
|
||||
|
||||
if (fontName2 === SettingsData.defaultMonoFontFamily)
|
||||
continue;
|
||||
continue
|
||||
|
||||
var lowerName = fontName2.toLowerCase();
|
||||
if (lowerName.includes("mono") || lowerName.includes("code") || lowerName.includes("console") || lowerName.includes("terminal") || lowerName.includes("courier") || lowerName.includes("dejavu sans mono") || lowerName.includes("jetbrains") || lowerName.includes("fira") || lowerName.includes("hack") || lowerName.includes("source code") || lowerName.includes("ubuntu mono") || lowerName.includes("cascadia")) {
|
||||
var rootName2 = fontName2.replace(/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i, "").replace(/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i, "").trim();
|
||||
var lowerName = fontName2.toLowerCase()
|
||||
if (lowerName.includes("mono") || lowerName.includes(
|
||||
"code") || lowerName.includes(
|
||||
"console") || lowerName.includes(
|
||||
"terminal") || lowerName.includes(
|
||||
"courier") || lowerName.includes(
|
||||
"dejavu sans mono") || lowerName.includes(
|
||||
"jetbrains") || lowerName.includes(
|
||||
"fira") || lowerName.includes(
|
||||
"hack") || lowerName.includes(
|
||||
"source code") || lowerName.includes(
|
||||
"ubuntu mono") || lowerName.includes("cascadia")) {
|
||||
var rootName2 = fontName2.replace(
|
||||
/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i,
|
||||
"").replace(
|
||||
/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i,
|
||||
"").trim()
|
||||
if (!seenMonoFamilies.has(rootName2) && rootName2 !== "") {
|
||||
seenMonoFamilies.add(rootName2);
|
||||
monoFamilies.push(rootName2);
|
||||
seenMonoFamilies.add(rootName2)
|
||||
monoFamilies.push(rootName2)
|
||||
}
|
||||
}
|
||||
}
|
||||
cachedMonoFamilies = monoFonts.concat(monoFamilies.sort());
|
||||
cachedMonoFamilies = monoFonts.concat(monoFamilies.sort())
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Access WallpaperCyclingService to ensure it's initialized
|
||||
WallpaperCyclingService.cyclingActive;
|
||||
WallpaperCyclingService.cyclingActive
|
||||
if (!fontsEnumerated) {
|
||||
enumerateFonts();
|
||||
fontsEnumerated = true;
|
||||
enumerateFonts()
|
||||
fontsEnumerated = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,14 +102,15 @@ Item {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
|
||||
// Wallpaper Section
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: wallpaperSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -118,7 +138,6 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -148,7 +167,6 @@ Item {
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -169,7 +187,6 @@ Item {
|
||||
color: Theme.surfaceVariantText
|
||||
visible: SessionData.wallpaperPath === ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -178,7 +195,9 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: SessionData.wallpaperPath ? SessionData.wallpaperPath.split('/').pop() : "No wallpaper selected"
|
||||
text: SessionData.wallpaperPath ? SessionData.wallpaperPath.split(
|
||||
'/').pop(
|
||||
) : "No wallpaper selected"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideMiddle
|
||||
@@ -222,7 +241,6 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -230,13 +248,12 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (parentModal) {
|
||||
parentModal.allowFocusOverride = true;
|
||||
parentModal.shouldHaveFocus = false;
|
||||
parentModal.allowFocusOverride = true
|
||||
parentModal.shouldHaveFocus = false
|
||||
}
|
||||
wallpaperBrowser.open();
|
||||
wallpaperBrowser.open()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
@@ -263,7 +280,6 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -271,16 +287,12 @@ Item {
|
||||
enabled: SessionData.wallpaperPath !== ""
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
SessionData.setWallpaper("");
|
||||
SessionData.setWallpaper("")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Wallpaper Cycling Section - Full Width
|
||||
@@ -309,7 +321,8 @@ Item {
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - controlsRow.width - Theme.spacingM
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
- controlsRow.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -326,7 +339,6 @@ Item {
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -339,7 +351,11 @@ Item {
|
||||
width: 60
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: prevButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8) : Theme.primary
|
||||
color: prevButtonArea.containsMouse ? Qt.rgba(
|
||||
Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.8) : Theme.primary
|
||||
opacity: SessionData.wallpaperPath ? 1 : 0.5
|
||||
|
||||
Row {
|
||||
@@ -359,7 +375,6 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -370,17 +385,20 @@ Item {
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
WallpaperCyclingService.cyclePrevManually();
|
||||
WallpaperCyclingService.cyclePrevManually()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: 60
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: nextButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8) : Theme.primary
|
||||
color: nextButtonArea.containsMouse ? Qt.rgba(
|
||||
Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.8) : Theme.primary
|
||||
opacity: SessionData.wallpaperPath ? 1 : 0.5
|
||||
|
||||
Row {
|
||||
@@ -400,7 +418,6 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -411,10 +428,9 @@ Item {
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
WallpaperCyclingService.cycleNextManually();
|
||||
WallpaperCyclingService.cycleNextManually()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
@@ -422,13 +438,12 @@ Item {
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SessionData.wallpaperCyclingEnabled
|
||||
onToggled: (toggled) => {
|
||||
return SessionData.setWallpaperCyclingEnabled(toggled);
|
||||
}
|
||||
onToggled: toggled => {
|
||||
return SessionData.setWallpaperCyclingEnabled(
|
||||
toggled)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Cycling mode and settings
|
||||
@@ -455,16 +470,17 @@ Item {
|
||||
width: 200
|
||||
height: 32
|
||||
model: [{
|
||||
"text": "Interval"
|
||||
}, {
|
||||
"text": "Time"
|
||||
}]
|
||||
currentIndex: SessionData.wallpaperCyclingMode === "time" ? 1 : 0
|
||||
onTabClicked: (index) => {
|
||||
SessionData.setWallpaperCyclingMode(index === 1 ? "time" : "interval");
|
||||
}
|
||||
"text": "Interval"
|
||||
}, {
|
||||
"text": "Time"
|
||||
}]
|
||||
currentIndex: SessionData.wallpaperCyclingMode
|
||||
=== "time" ? 1 : 0
|
||||
onTabClicked: index => {
|
||||
SessionData.setWallpaperCyclingMode(
|
||||
index === 1 ? "time" : "interval")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Interval settings
|
||||
@@ -478,16 +494,18 @@ Item {
|
||||
description: "How often to change wallpaper"
|
||||
options: intervalOptions
|
||||
currentValue: {
|
||||
const currentSeconds = SessionData.wallpaperCyclingInterval;
|
||||
const index = intervalValues.indexOf(currentSeconds);
|
||||
return index >= 0 ? intervalOptions[index] : "5 minutes";
|
||||
}
|
||||
onValueChanged: (value) => {
|
||||
const index = intervalOptions.indexOf(value);
|
||||
if (index >= 0)
|
||||
SessionData.setWallpaperCyclingInterval(intervalValues[index]);
|
||||
|
||||
const currentSeconds = SessionData.wallpaperCyclingInterval
|
||||
const index = intervalValues.indexOf(
|
||||
currentSeconds)
|
||||
return index >= 0 ? intervalOptions[index] : "5 minutes"
|
||||
}
|
||||
onValueChanged: value => {
|
||||
const index = intervalOptions.indexOf(
|
||||
value)
|
||||
if (index >= 0)
|
||||
SessionData.setWallpaperCyclingInterval(
|
||||
intervalValues[index])
|
||||
}
|
||||
}
|
||||
|
||||
// Time settings
|
||||
@@ -512,25 +530,28 @@ Item {
|
||||
topPadding: Theme.spacingS
|
||||
bottomPadding: Theme.spacingS
|
||||
onAccepted: {
|
||||
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text);
|
||||
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(
|
||||
text)
|
||||
if (isValid)
|
||||
SessionData.setWallpaperCyclingTime(text);
|
||||
SessionData.setWallpaperCyclingTime(
|
||||
text)
|
||||
else
|
||||
text = SessionData.wallpaperCyclingTime;
|
||||
text = SessionData.wallpaperCyclingTime
|
||||
}
|
||||
onEditingFinished: {
|
||||
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text);
|
||||
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(
|
||||
text)
|
||||
if (isValid)
|
||||
SessionData.setWallpaperCyclingTime(text);
|
||||
SessionData.setWallpaperCyclingTime(
|
||||
text)
|
||||
else
|
||||
text = SessionData.wallpaperCyclingTime;
|
||||
text = SessionData.wallpaperCyclingTime
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -539,15 +560,10 @@ Item {
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Dynamic Theme Section
|
||||
@@ -555,8 +571,10 @@ Item {
|
||||
width: parent.width
|
||||
height: dynamicThemeSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -578,7 +596,8 @@ Item {
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - toggle.width - Theme.spacingM
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
- toggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -596,7 +615,6 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
@@ -605,14 +623,13 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: Theme.isDynamicTheme
|
||||
enabled: ToastService.wallpaperErrorStatus !== "matugen_missing"
|
||||
onToggled: (toggled) => {
|
||||
if (toggled)
|
||||
Theme.switchTheme(10, true);
|
||||
else
|
||||
Theme.switchTheme(0);
|
||||
}
|
||||
onToggled: toggled => {
|
||||
if (toggled)
|
||||
Theme.switchTheme(10, true)
|
||||
else
|
||||
Theme.switchTheme(0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -623,9 +640,7 @@ Item {
|
||||
width: parent.width
|
||||
leftPadding: Theme.iconSize + Theme.spacingM
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Display Settings
|
||||
@@ -633,8 +648,10 @@ Item {
|
||||
width: parent.width
|
||||
height: displaySection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -662,7 +679,6 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
@@ -672,23 +688,22 @@ Item {
|
||||
text: "Night Mode"
|
||||
description: "Apply warm color temperature to reduce eye strain"
|
||||
checked: BrightnessService.nightModeActive
|
||||
onToggled: (checked) => {
|
||||
if (checked !== BrightnessService.nightModeActive) {
|
||||
if (checked)
|
||||
BrightnessService.enableNightMode();
|
||||
else
|
||||
BrightnessService.disableNightMode();
|
||||
}
|
||||
}
|
||||
onToggled: checked => {
|
||||
if (checked !== BrightnessService.nightModeActive) {
|
||||
if (checked)
|
||||
BrightnessService.enableNightMode()
|
||||
else
|
||||
BrightnessService.disableNightMode()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onNightModeActiveChanged() {
|
||||
nightModeToggle.checked = BrightnessService.nightModeActive;
|
||||
nightModeToggle.checked = BrightnessService.nightModeActive
|
||||
}
|
||||
|
||||
target: BrightnessService
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
@@ -699,16 +714,18 @@ Item {
|
||||
opacity: !BrightnessService.nightModeActive ? 1 : 0.6
|
||||
currentValue: SessionData.nightModeTemperature + "K"
|
||||
options: {
|
||||
var temps = [];
|
||||
var temps = []
|
||||
for (var i = 2500; i <= 6000; i += 500) {
|
||||
temps.push(i + "K");
|
||||
temps.push(i + "K")
|
||||
}
|
||||
return temps;
|
||||
}
|
||||
onValueChanged: (value) => {
|
||||
var temp = parseInt(value.replace("K", ""));
|
||||
SessionData.setNightModeTemperature(temp);
|
||||
return temps
|
||||
}
|
||||
onValueChanged: value => {
|
||||
var temp = parseInt(
|
||||
value.replace("K", ""))
|
||||
SessionData.setNightModeTemperature(
|
||||
temp)
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
@@ -716,15 +733,13 @@ Item {
|
||||
text: "Light Mode"
|
||||
description: "Use light theme instead of dark theme"
|
||||
checked: SessionData.isLightMode
|
||||
onToggled: (checked) => {
|
||||
SessionData.setLightMode(checked);
|
||||
Theme.isLightMode = checked;
|
||||
PortalService.setLightMode(checked);
|
||||
}
|
||||
onToggled: checked => {
|
||||
SessionData.setLightMode(checked)
|
||||
Theme.isLightMode = checked
|
||||
PortalService.setLightMode(checked)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Font Settings
|
||||
@@ -732,8 +747,10 @@ Item {
|
||||
width: parent.width
|
||||
height: fontSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -761,7 +778,6 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
@@ -770,20 +786,21 @@ Item {
|
||||
description: "Select system font family"
|
||||
currentValue: {
|
||||
if (SettingsData.fontFamily === SettingsData.defaultFontFamily)
|
||||
return "Default";
|
||||
return "Default"
|
||||
else
|
||||
return SettingsData.fontFamily || "Default";
|
||||
return SettingsData.fontFamily || "Default"
|
||||
}
|
||||
enableFuzzySearch: true
|
||||
popupWidthOffset: 100
|
||||
maxPopupHeight: 400
|
||||
options: cachedFontFamilies
|
||||
onValueChanged: (value) => {
|
||||
if (value.startsWith("Default"))
|
||||
SettingsData.setFontFamily(SettingsData.defaultFontFamily);
|
||||
else
|
||||
SettingsData.setFontFamily(value);
|
||||
}
|
||||
onValueChanged: value => {
|
||||
if (value.startsWith("Default"))
|
||||
SettingsData.setFontFamily(
|
||||
SettingsData.defaultFontFamily)
|
||||
else
|
||||
SettingsData.setFontFamily(value)
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
@@ -793,64 +810,64 @@ Item {
|
||||
currentValue: {
|
||||
switch (SettingsData.fontWeight) {
|
||||
case Font.Thin:
|
||||
return "Thin";
|
||||
return "Thin"
|
||||
case Font.ExtraLight:
|
||||
return "Extra Light";
|
||||
return "Extra Light"
|
||||
case Font.Light:
|
||||
return "Light";
|
||||
return "Light"
|
||||
case Font.Normal:
|
||||
return "Regular";
|
||||
return "Regular"
|
||||
case Font.Medium:
|
||||
return "Medium";
|
||||
return "Medium"
|
||||
case Font.DemiBold:
|
||||
return "Demi Bold";
|
||||
return "Demi Bold"
|
||||
case Font.Bold:
|
||||
return "Bold";
|
||||
return "Bold"
|
||||
case Font.ExtraBold:
|
||||
return "Extra Bold";
|
||||
return "Extra Bold"
|
||||
case Font.Black:
|
||||
return "Black";
|
||||
return "Black"
|
||||
default:
|
||||
return "Regular";
|
||||
return "Regular"
|
||||
}
|
||||
}
|
||||
options: ["Thin", "Extra Light", "Light", "Regular", "Medium", "Demi Bold", "Bold", "Extra Bold", "Black"]
|
||||
onValueChanged: (value) => {
|
||||
var weight;
|
||||
switch (value) {
|
||||
case "Thin":
|
||||
weight = Font.Thin;
|
||||
break;
|
||||
case "Extra Light":
|
||||
weight = Font.ExtraLight;
|
||||
break;
|
||||
case "Light":
|
||||
weight = Font.Light;
|
||||
break;
|
||||
case "Regular":
|
||||
weight = Font.Normal;
|
||||
break;
|
||||
case "Medium":
|
||||
weight = Font.Medium;
|
||||
break;
|
||||
case "Demi Bold":
|
||||
weight = Font.DemiBold;
|
||||
break;
|
||||
case "Bold":
|
||||
weight = Font.Bold;
|
||||
break;
|
||||
case "Extra Bold":
|
||||
weight = Font.ExtraBold;
|
||||
break;
|
||||
case "Black":
|
||||
weight = Font.Black;
|
||||
break;
|
||||
default:
|
||||
weight = Font.Normal;
|
||||
break;
|
||||
}
|
||||
SettingsData.setFontWeight(weight);
|
||||
}
|
||||
onValueChanged: value => {
|
||||
var weight
|
||||
switch (value) {
|
||||
case "Thin":
|
||||
weight = Font.Thin
|
||||
break
|
||||
case "Extra Light":
|
||||
weight = Font.ExtraLight
|
||||
break
|
||||
case "Light":
|
||||
weight = Font.Light
|
||||
break
|
||||
case "Regular":
|
||||
weight = Font.Normal
|
||||
break
|
||||
case "Medium":
|
||||
weight = Font.Medium
|
||||
break
|
||||
case "Demi Bold":
|
||||
weight = Font.DemiBold
|
||||
break
|
||||
case "Bold":
|
||||
weight = Font.Bold
|
||||
break
|
||||
case "Extra Bold":
|
||||
weight = Font.ExtraBold
|
||||
break
|
||||
case "Black":
|
||||
weight = Font.Black
|
||||
break
|
||||
default:
|
||||
weight = Font.Normal
|
||||
break
|
||||
}
|
||||
SettingsData.setFontWeight(weight)
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
@@ -859,24 +876,24 @@ Item {
|
||||
description: "Select monospace font for process list and technical displays"
|
||||
currentValue: {
|
||||
if (SettingsData.monoFontFamily === SettingsData.defaultMonoFontFamily)
|
||||
return "Default";
|
||||
return "Default"
|
||||
|
||||
return SettingsData.monoFontFamily || "Default";
|
||||
return SettingsData.monoFontFamily || "Default"
|
||||
}
|
||||
enableFuzzySearch: true
|
||||
popupWidthOffset: 100
|
||||
maxPopupHeight: 400
|
||||
options: cachedMonoFamilies
|
||||
onValueChanged: (value) => {
|
||||
if (value === "Default")
|
||||
SettingsData.setMonoFontFamily(SettingsData.defaultMonoFontFamily);
|
||||
else
|
||||
SettingsData.setMonoFontFamily(value);
|
||||
}
|
||||
onValueChanged: value => {
|
||||
if (value === "Default")
|
||||
SettingsData.setMonoFontFamily(
|
||||
SettingsData.defaultMonoFontFamily)
|
||||
else
|
||||
SettingsData.setMonoFontFamily(
|
||||
value)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Corner Radius
|
||||
@@ -884,8 +901,10 @@ Item {
|
||||
width: parent.width
|
||||
height: cornerRadiusSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -934,19 +953,17 @@ Item {
|
||||
maximum: 32
|
||||
unit: ""
|
||||
showValue: true
|
||||
onSliderValueChanged: (newValue) => {
|
||||
SettingsData.setCornerRadius(newValue);
|
||||
}
|
||||
onSliderValueChanged: newValue => {
|
||||
SettingsData.setCornerRadius(
|
||||
newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
FileBrowserModal {
|
||||
id: wallpaperBrowser
|
||||
|
||||
@@ -954,18 +971,17 @@ Item {
|
||||
browserIcon: "wallpaper"
|
||||
browserType: "wallpaper"
|
||||
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
|
||||
onFileSelected: (path) => {
|
||||
SessionData.setWallpaper(path);
|
||||
close();
|
||||
}
|
||||
onFileSelected: path => {
|
||||
SessionData.setWallpaper(path)
|
||||
close()
|
||||
}
|
||||
onDialogClosed: {
|
||||
if (parentModal) {
|
||||
parentModal.allowFocusOverride = false;
|
||||
parentModal.allowFocusOverride = false
|
||||
parentModal.shouldHaveFocus = Qt.binding(() => {
|
||||
return parentModal.shouldBeVisible;
|
||||
});
|
||||
return parentModal.shouldBeVisible
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,236 +5,247 @@ import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: recentAppsTab
|
||||
id: recentAppsTab
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Theme.spacingL
|
||||
clip: true
|
||||
contentHeight: mainColumn.height
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: recentlyUsedSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Theme.spacingL
|
||||
clip: true
|
||||
contentHeight: mainColumn.height
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: recentlyUsedSection
|
||||
|
||||
property var rankedAppsModel: {
|
||||
var apps = []
|
||||
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)
|
||||
})
|
||||
return apps.slice(0, 20)
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
id: mainColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
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 {
|
||||
width: parent.width
|
||||
text: "Apps are ordered by usage frequency, then last used, then alphabetically."
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Column {
|
||||
id: rankedAppsList
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: recentlyUsedSection.rankedAppsModel
|
||||
|
||||
delegate: Rectangle {
|
||||
width: rankedAppsList.width
|
||||
height: 48
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: recentlyUsedSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceContainer.r,
|
||||
Theme.surfaceContainer.g,
|
||||
Theme.surfaceContainer.b, 0.3)
|
||||
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.1)
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
Column {
|
||||
id: recentlyUsedSection
|
||||
|
||||
StyledText {
|
||||
text: (index + 1).toString()
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.primary
|
||||
width: 20
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
property var rankedAppsModel: {
|
||||
var apps = []
|
||||
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
|
||||
|
||||
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"
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
return apps.slice(0, 20)
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: modelData.name || "Unknown App"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
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 {
|
||||
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
|
||||
width: parent.width
|
||||
text: "Apps are ordered by usage frequency, then last used, then alphabetically."
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Column {
|
||||
id: rankedAppsList
|
||||
|
||||
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
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: recentlyUsedSection.rankedAppsModel
|
||||
|
||||
delegate: Rectangle {
|
||||
width: rankedAppsList.width
|
||||
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
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: (index + 1).toString()
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.primary
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,103 +5,104 @@ import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property string title: ""
|
||||
property string iconName: ""
|
||||
property alias content: contentLoader.sourceComponent
|
||||
property bool expanded: false
|
||||
property bool collapsible: true
|
||||
property bool lazyLoad: true
|
||||
property string title: ""
|
||||
property string iconName: ""
|
||||
property alias content: contentLoader.sourceComponent
|
||||
property bool expanded: false
|
||||
property bool collapsible: true
|
||||
property bool lazyLoad: true
|
||||
|
||||
width: parent.width
|
||||
spacing: expanded ? Theme.spacingM : 0
|
||||
Component.onCompleted: {
|
||||
if (!collapsible)
|
||||
expanded = true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
width: parent.width
|
||||
height: headerRow.height
|
||||
enabled: collapsible
|
||||
hoverEnabled: collapsible
|
||||
onClicked: {
|
||||
if (collapsible)
|
||||
expanded = !expanded
|
||||
spacing: expanded ? Theme.spacingM : 0
|
||||
Component.onCompleted: {
|
||||
if (!collapsible)
|
||||
expanded = true
|
||||
}
|
||||
|
||||
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 {
|
||||
anchors.fill: parent
|
||||
color: parent.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.08) : "transparent"
|
||||
radius: Theme.radiusS
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
visible: expanded || !collapsible
|
||||
}
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
Loader {
|
||||
id: contentLoader
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
topPadding: Theme.spacingS
|
||||
bottomPadding: Theme.spacingS
|
||||
width: parent.width
|
||||
active: lazyLoad ? expanded || !collapsible : true
|
||||
visible: expanded || !collapsible
|
||||
asynchronous: true
|
||||
opacity: visible ? 1 : 0
|
||||
|
||||
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
|
||||
}
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,54 +12,73 @@ Item {
|
||||
property bool fontsEnumerated: false
|
||||
|
||||
function enumerateFonts() {
|
||||
var fonts = ["Default"];
|
||||
var availableFonts = Qt.fontFamilies();
|
||||
var rootFamilies = [];
|
||||
var seenFamilies = new Set();
|
||||
var fonts = ["Default"]
|
||||
var availableFonts = Qt.fontFamilies()
|
||||
var rootFamilies = []
|
||||
var seenFamilies = new Set()
|
||||
for (var i = 0; i < availableFonts.length; i++) {
|
||||
var fontName = availableFonts[i];
|
||||
var fontName = availableFonts[i]
|
||||
if (fontName.startsWith("."))
|
||||
continue;
|
||||
continue
|
||||
|
||||
if (fontName === SettingsData.defaultFontFamily)
|
||||
continue;
|
||||
continue
|
||||
|
||||
var rootName = fontName.replace(/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i, "").replace(/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i, "").replace(/ (UI|Display|Text|Mono|Sans|Serif)$/i, function(match, suffix) {
|
||||
return match;
|
||||
}).trim();
|
||||
var rootName = fontName.replace(
|
||||
/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i,
|
||||
"").replace(
|
||||
/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i,
|
||||
"").replace(/ (UI|Display|Text|Mono|Sans|Serif)$/i,
|
||||
function (match, suffix) {
|
||||
return match
|
||||
}).trim()
|
||||
if (!seenFamilies.has(rootName) && rootName !== "") {
|
||||
seenFamilies.add(rootName);
|
||||
rootFamilies.push(rootName);
|
||||
seenFamilies.add(rootName)
|
||||
rootFamilies.push(rootName)
|
||||
}
|
||||
}
|
||||
cachedFontFamilies = fonts.concat(rootFamilies.sort());
|
||||
var monoFonts = ["Default"];
|
||||
var monoFamilies = [];
|
||||
var seenMonoFamilies = new Set();
|
||||
cachedFontFamilies = fonts.concat(rootFamilies.sort())
|
||||
var monoFonts = ["Default"]
|
||||
var monoFamilies = []
|
||||
var seenMonoFamilies = new Set()
|
||||
for (var j = 0; j < availableFonts.length; j++) {
|
||||
var fontName2 = availableFonts[j];
|
||||
var fontName2 = availableFonts[j]
|
||||
if (fontName2.startsWith("."))
|
||||
continue;
|
||||
continue
|
||||
|
||||
if (fontName2 === SettingsData.defaultMonoFontFamily)
|
||||
continue;
|
||||
continue
|
||||
|
||||
var lowerName = fontName2.toLowerCase();
|
||||
if (lowerName.includes("mono") || lowerName.includes("code") || lowerName.includes("console") || lowerName.includes("terminal") || lowerName.includes("courier") || lowerName.includes("dejavu sans mono") || lowerName.includes("jetbrains") || lowerName.includes("fira") || lowerName.includes("hack") || lowerName.includes("source code") || lowerName.includes("ubuntu mono") || lowerName.includes("cascadia")) {
|
||||
var rootName2 = fontName2.replace(/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i, "").replace(/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i, "").trim();
|
||||
var lowerName = fontName2.toLowerCase()
|
||||
if (lowerName.includes("mono") || lowerName.includes(
|
||||
"code") || lowerName.includes(
|
||||
"console") || lowerName.includes(
|
||||
"terminal") || lowerName.includes(
|
||||
"courier") || lowerName.includes(
|
||||
"dejavu sans mono") || lowerName.includes(
|
||||
"jetbrains") || lowerName.includes(
|
||||
"fira") || lowerName.includes(
|
||||
"hack") || lowerName.includes(
|
||||
"source code") || lowerName.includes(
|
||||
"ubuntu mono") || lowerName.includes("cascadia")) {
|
||||
var rootName2 = fontName2.replace(
|
||||
/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i,
|
||||
"").replace(
|
||||
/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i,
|
||||
"").trim()
|
||||
if (!seenMonoFamilies.has(rootName2) && rootName2 !== "") {
|
||||
seenMonoFamilies.add(rootName2);
|
||||
monoFamilies.push(rootName2);
|
||||
seenMonoFamilies.add(rootName2)
|
||||
monoFamilies.push(rootName2)
|
||||
}
|
||||
}
|
||||
}
|
||||
cachedMonoFamilies = monoFonts.concat(monoFamilies.sort());
|
||||
cachedMonoFamilies = monoFonts.concat(monoFamilies.sort())
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!fontsEnumerated) {
|
||||
enumerateFonts();
|
||||
fontsEnumerated = true;
|
||||
enumerateFonts()
|
||||
fontsEnumerated = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +100,10 @@ Item {
|
||||
width: parent.width
|
||||
height: themeSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -110,7 +131,6 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -128,10 +148,11 @@ Item {
|
||||
StyledText {
|
||||
text: {
|
||||
if (Theme.isDynamicTheme)
|
||||
return "Wallpaper-based dynamic colors";
|
||||
return "Wallpaper-based dynamic colors"
|
||||
|
||||
var descriptions = ["Material blue inspired by modern interfaces", "Deep blue inspired by material 3", "Rich purple tones for BB elegance", "Natural green for productivity", "Energetic orange for creativity", "Bold red for impact", "Cool cyan for tranquility", "Vibrant pink for expression", "Warm amber for comfort", "Soft coral for gentle warmth"];
|
||||
return descriptions[Theme.currentThemeIndex] || "Select a theme";
|
||||
var descriptions = ["Material blue inspired by modern interfaces", "Deep blue inspired by material 3", "Rich purple tones for BB elegance", "Natural green for productivity", "Energetic orange for creativity", "Bold red for impact", "Cool cyan for tranquility", "Vibrant pink for expression", "Warm amber for comfort", "Soft coral for gentle warmth"]
|
||||
return descriptions[Theme.currentThemeIndex]
|
||||
|| "Select a theme"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
@@ -140,7 +161,6 @@ Item {
|
||||
width: Math.min(parent.width, 400)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -160,8 +180,10 @@ Item {
|
||||
radius: 16
|
||||
color: Theme.themes[index].primary
|
||||
border.color: Theme.outline
|
||||
border.width: (Theme.currentThemeIndex === index && !Theme.isDynamicTheme) ? 2 : 1
|
||||
scale: (Theme.currentThemeIndex === index && !Theme.isDynamicTheme) ? 1.1 : 1
|
||||
border.width: (Theme.currentThemeIndex === index
|
||||
&& !Theme.isDynamicTheme) ? 2 : 1
|
||||
scale: (Theme.currentThemeIndex === index
|
||||
&& !Theme.isDynamicTheme) ? 1.1 : 1
|
||||
|
||||
Rectangle {
|
||||
width: nameText.contentWidth + Theme.spacingS * 2
|
||||
@@ -183,7 +205,6 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -193,7 +214,7 @@ Item {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Theme.switchTheme(index, false);
|
||||
Theme.switchTheme(index, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +223,6 @@ Item {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
@@ -210,13 +230,9 @@ Item {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -248,7 +264,8 @@ Item {
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: Theme.spacingXS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: mouseArea2.containsMouse && themeIndex < Theme.themes.length
|
||||
visible: mouseArea2.containsMouse
|
||||
&& themeIndex < Theme.themes.length
|
||||
|
||||
StyledText {
|
||||
id: nameText2
|
||||
@@ -258,7 +275,6 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -269,8 +285,7 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (themeIndex < Theme.themes.length)
|
||||
Theme.switchTheme(themeIndex);
|
||||
|
||||
Theme.switchTheme(themeIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +294,6 @@ Item {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
@@ -287,13 +301,9 @@ Item {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -307,18 +317,26 @@ Item {
|
||||
radius: 20
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: {
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
|
||||
if (ToastService.wallpaperErrorStatus === "error"
|
||||
|| ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return Qt.rgba(Theme.error.r,
|
||||
Theme.error.g,
|
||||
Theme.error.b, 0.12)
|
||||
else
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3);
|
||||
return Qt.rgba(Theme.surfaceVariant.r,
|
||||
Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b, 0.3)
|
||||
}
|
||||
border.color: {
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.5);
|
||||
if (ToastService.wallpaperErrorStatus === "error"
|
||||
|| ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return Qt.rgba(Theme.error.r,
|
||||
Theme.error.g,
|
||||
Theme.error.b, 0.5)
|
||||
else if (Theme.isDynamicTheme)
|
||||
return Theme.primary;
|
||||
return Theme.primary
|
||||
else
|
||||
return Theme.outline;
|
||||
return Theme.outline
|
||||
}
|
||||
border.width: Theme.isDynamicTheme ? 2 : 1
|
||||
scale: Theme.isDynamicTheme ? 1.1 : (autoMouseArea.containsMouse ? 1.02 : 1)
|
||||
@@ -329,17 +347,21 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return "error";
|
||||
if (ToastService.wallpaperErrorStatus === "error"
|
||||
|| ToastService.wallpaperErrorStatus
|
||||
=== "matugen_missing")
|
||||
return "error"
|
||||
else
|
||||
return "palette";
|
||||
return "palette"
|
||||
}
|
||||
size: 16
|
||||
color: {
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return Theme.error;
|
||||
if (ToastService.wallpaperErrorStatus === "error"
|
||||
|| ToastService.wallpaperErrorStatus
|
||||
=== "matugen_missing")
|
||||
return Theme.error
|
||||
else
|
||||
return Theme.surfaceText;
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
@@ -347,23 +369,25 @@ Item {
|
||||
StyledText {
|
||||
text: {
|
||||
if (ToastService.wallpaperErrorStatus === "error")
|
||||
return "Error";
|
||||
else if (ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return "No matugen";
|
||||
return "Error"
|
||||
else if (ToastService.wallpaperErrorStatus
|
||||
=== "matugen_missing")
|
||||
return "No matugen"
|
||||
else
|
||||
return "Auto";
|
||||
return "Auto"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: {
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return Theme.error;
|
||||
if (ToastService.wallpaperErrorStatus === "error"
|
||||
|| ToastService.wallpaperErrorStatus
|
||||
=== "matugen_missing")
|
||||
return Theme.error
|
||||
else
|
||||
return Theme.surfaceText;
|
||||
return Theme.surfaceText
|
||||
}
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -374,11 +398,13 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
ToastService.showError("matugen not found - install matugen package for dynamic theming");
|
||||
ToastService.showError(
|
||||
"matugen not found - install matugen package for dynamic theming")
|
||||
else if (ToastService.wallpaperErrorStatus === "error")
|
||||
ToastService.showError("Wallpaper processing failed - check wallpaper path");
|
||||
ToastService.showError(
|
||||
"Wallpaper processing failed - check wallpaper path")
|
||||
else
|
||||
Theme.switchTheme(10, true);
|
||||
Theme.switchTheme(10, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,25 +418,30 @@ Item {
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: autoMouseArea.containsMouse && (!Theme.isDynamicTheme || ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
visible: autoMouseArea.containsMouse
|
||||
&& (!Theme.isDynamicTheme
|
||||
|| ToastService.wallpaperErrorStatus === "error"
|
||||
|| ToastService.wallpaperErrorStatus
|
||||
=== "matugen_missing")
|
||||
|
||||
StyledText {
|
||||
id: autoTooltipText
|
||||
|
||||
text: {
|
||||
if (ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return "Install matugen package for dynamic themes";
|
||||
return "Install matugen package for dynamic themes"
|
||||
else
|
||||
return "Dynamic wallpaper-based colors";
|
||||
return "Dynamic wallpaper-based colors"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") ? Theme.error : Theme.surfaceText
|
||||
color: (ToastService.wallpaperErrorStatus === "error"
|
||||
|| ToastService.wallpaperErrorStatus
|
||||
=== "matugen_missing") ? Theme.error : Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
wrapMode: Text.WordWrap
|
||||
width: Math.min(implicitWidth, 250)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
@@ -418,7 +449,6 @@ Item {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
@@ -426,7 +456,6 @@ Item {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
@@ -434,15 +463,10 @@ Item {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Transparency Settings
|
||||
@@ -450,8 +474,10 @@ Item {
|
||||
width: parent.width
|
||||
height: transparencySection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -479,7 +505,6 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -496,16 +521,17 @@ Item {
|
||||
DankSlider {
|
||||
width: parent.width
|
||||
height: 24
|
||||
value: Math.round(SettingsData.topBarTransparency * 100)
|
||||
value: Math.round(
|
||||
SettingsData.topBarTransparency * 100)
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
unit: ""
|
||||
showValue: true
|
||||
onSliderValueChanged: (newValue) => {
|
||||
SettingsData.setTopBarTransparency(newValue / 100);
|
||||
}
|
||||
onSliderValueChanged: newValue => {
|
||||
SettingsData.setTopBarTransparency(
|
||||
newValue / 100)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -522,16 +548,17 @@ Item {
|
||||
DankSlider {
|
||||
width: parent.width
|
||||
height: 24
|
||||
value: Math.round(SettingsData.topBarWidgetTransparency * 100)
|
||||
value: Math.round(
|
||||
SettingsData.topBarWidgetTransparency * 100)
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
unit: ""
|
||||
showValue: true
|
||||
onSliderValueChanged: (newValue) => {
|
||||
SettingsData.setTopBarWidgetTransparency(newValue / 100);
|
||||
}
|
||||
onSliderValueChanged: newValue => {
|
||||
SettingsData.setTopBarWidgetTransparency(
|
||||
newValue / 100)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -548,20 +575,19 @@ Item {
|
||||
DankSlider {
|
||||
width: parent.width
|
||||
height: 24
|
||||
value: Math.round(SettingsData.popupTransparency * 100)
|
||||
value: Math.round(
|
||||
SettingsData.popupTransparency * 100)
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
unit: ""
|
||||
showValue: true
|
||||
onSliderValueChanged: (newValue) => {
|
||||
SettingsData.setPopupTransparency(newValue / 100);
|
||||
}
|
||||
onSliderValueChanged: newValue => {
|
||||
SettingsData.setPopupTransparency(
|
||||
newValue / 100)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// System Configuration Warning
|
||||
@@ -569,8 +595,10 @@ Item {
|
||||
width: parent.width
|
||||
height: warningText.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
|
||||
border.color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3)
|
||||
color: Qt.rgba(Theme.warning.r, Theme.warning.g,
|
||||
Theme.warning.b, 0.12)
|
||||
border.color: Qt.rgba(Theme.warning.r, Theme.warning.g,
|
||||
Theme.warning.b, 0.3)
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
@@ -595,9 +623,7 @@ Item {
|
||||
width: parent.width - Theme.iconSizeSmall - Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Icon Theme
|
||||
@@ -605,8 +631,10 @@ Item {
|
||||
width: parent.width
|
||||
height: iconThemeSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -637,21 +665,20 @@ Item {
|
||||
popupWidthOffset: 100
|
||||
maxPopupHeight: 236
|
||||
options: {
|
||||
SettingsData.detectAvailableIconThemes();
|
||||
return SettingsData.availableIconThemes;
|
||||
}
|
||||
onValueChanged: (value) => {
|
||||
SettingsData.setIconTheme(value);
|
||||
if (value !== "System Default" && !SettingsData.qt5ctAvailable && !SettingsData.qt6ctAvailable)
|
||||
ToastService.showWarning("qt5ct or qt6ct not found - Qt app themes may not update without these tools");
|
||||
|
||||
SettingsData.detectAvailableIconThemes()
|
||||
return SettingsData.availableIconThemes
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SettingsData.setIconTheme(value)
|
||||
if (value !== "System Default"
|
||||
&& !SettingsData.qt5ctAvailable
|
||||
&& !SettingsData.qt6ctAvailable)
|
||||
ToastService.showWarning(
|
||||
"qt5ct or qt6ct not found - Qt app themes may not update without these tools")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// System App Theming
|
||||
@@ -659,8 +686,10 @@ Item {
|
||||
width: parent.width
|
||||
height: systemThemingSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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
|
||||
visible: Theme.isDynamicTheme && Colors.matugenAvailable
|
||||
|
||||
@@ -689,7 +718,6 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
@@ -697,9 +725,10 @@ Item {
|
||||
text: "Theme GTK Applications"
|
||||
description: Colors.gtkThemingEnabled ? "File managers, text editors, and system dialogs will match your theme" : "GTK theming not available (install gsettings)"
|
||||
enabled: Colors.gtkThemingEnabled
|
||||
checked: Colors.gtkThemingEnabled && SettingsData.gtkThemingEnabled
|
||||
onToggled: function(checked) {
|
||||
SettingsData.setGtkThemingEnabled(checked);
|
||||
checked: Colors.gtkThemingEnabled
|
||||
&& SettingsData.gtkThemingEnabled
|
||||
onToggled: function (checked) {
|
||||
SettingsData.setGtkThemingEnabled(checked)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -708,18 +737,14 @@ Item {
|
||||
text: "Theme Qt Applications"
|
||||
description: Colors.qtThemingEnabled ? "Qt applications will match your theme colors" : "Qt theming not available (install qt5ct or qt6ct)"
|
||||
enabled: Colors.qtThemingEnabled
|
||||
checked: Colors.qtThemingEnabled && SettingsData.qtThemingEnabled
|
||||
onToggled: function(checked) {
|
||||
SettingsData.setQtThemingEnabled(checked);
|
||||
checked: Colors.qtThemingEnabled
|
||||
&& SettingsData.qtThemingEnabled
|
||||
onToggled: function (checked) {
|
||||
SettingsData.setQtThemingEnabled(checked)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,10 @@ Item {
|
||||
width: parent.width
|
||||
height: timeSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -47,7 +49,8 @@ Item {
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - toggle.width - Theme.spacingM
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
- toggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -65,7 +68,6 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
@@ -73,15 +75,13 @@ Item {
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.use24HourClock
|
||||
onToggled: (checked) => {
|
||||
return SettingsData.setClockFormat(checked);
|
||||
}
|
||||
onToggled: checked => {
|
||||
return SettingsData.setClockFormat(
|
||||
checked)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Date Format Section
|
||||
@@ -89,8 +89,10 @@ Item {
|
||||
width: parent.width
|
||||
height: dateSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -118,123 +120,130 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
height: 50
|
||||
text: "Top Bar Format"
|
||||
description: "Preview: " + Qt.formatDate(new Date(), SettingsData.clockDateFormat)
|
||||
description: "Preview: " + Qt.formatDate(
|
||||
new Date(),
|
||||
SettingsData.clockDateFormat)
|
||||
currentValue: {
|
||||
// Find matching preset or show "Custom"
|
||||
const presets = [{
|
||||
"format": "ddd d",
|
||||
"label": "Day Date"
|
||||
}, {
|
||||
"format": "ddd MMM d",
|
||||
"label": "Day Month Date"
|
||||
}, {
|
||||
"format": "MMM d",
|
||||
"label": "Month Date"
|
||||
}, {
|
||||
"format": "M/d",
|
||||
"label": "Numeric (M/D)"
|
||||
}, {
|
||||
"format": "d/M",
|
||||
"label": "Numeric (D/M)"
|
||||
}, {
|
||||
"format": "ddd d MMM yyyy",
|
||||
"label": "Full with Year"
|
||||
}, {
|
||||
"format": "yyyy-MM-dd",
|
||||
"label": "ISO Date"
|
||||
}, {
|
||||
"format": "dddd, MMMM d",
|
||||
"label": "Full Day & Month"
|
||||
}];
|
||||
const match = presets.find((p) => {
|
||||
return p.format === SettingsData.clockDateFormat;
|
||||
});
|
||||
return match ? match.label : "Custom: " + SettingsData.clockDateFormat;
|
||||
"format": "ddd d",
|
||||
"label": "Day Date"
|
||||
}, {
|
||||
"format": "ddd MMM d",
|
||||
"label": "Day Month Date"
|
||||
}, {
|
||||
"format": "MMM d",
|
||||
"label": "Month Date"
|
||||
}, {
|
||||
"format": "M/d",
|
||||
"label": "Numeric (M/D)"
|
||||
}, {
|
||||
"format": "d/M",
|
||||
"label": "Numeric (D/M)"
|
||||
}, {
|
||||
"format": "ddd d MMM yyyy",
|
||||
"label": "Full with Year"
|
||||
}, {
|
||||
"format": "yyyy-MM-dd",
|
||||
"label": "ISO Date"
|
||||
}, {
|
||||
"format": "dddd, MMMM d",
|
||||
"label": "Full Day & Month"
|
||||
}]
|
||||
const match = presets.find(p => {
|
||||
return p.format
|
||||
=== SettingsData.clockDateFormat
|
||||
})
|
||||
return match ? match.label : "Custom: " + SettingsData.clockDateFormat
|
||||
}
|
||||
options: ["Day Date", "Day Month Date", "Month Date", "Numeric (M/D)", "Numeric (D/M)", "Full with Year", "ISO Date", "Full Day & Month", "Custom..."]
|
||||
onValueChanged: (value) => {
|
||||
const formatMap = {
|
||||
"Day Date": "ddd d",
|
||||
"Day Month Date": "ddd MMM d",
|
||||
"Month Date": "MMM d",
|
||||
"Numeric (M/D)": "M/d",
|
||||
"Numeric (D/M)": "d/M",
|
||||
"Full with Year": "ddd d MMM yyyy",
|
||||
"ISO Date": "yyyy-MM-dd",
|
||||
"Full Day & Month": "dddd, MMMM d"
|
||||
};
|
||||
if (value === "Custom...") {
|
||||
customFormatInput.visible = true;
|
||||
} else {
|
||||
customFormatInput.visible = false;
|
||||
SettingsData.setClockDateFormat(formatMap[value]);
|
||||
}
|
||||
}
|
||||
onValueChanged: value => {
|
||||
const formatMap = {
|
||||
"Day Date": "ddd d",
|
||||
"Day Month Date": "ddd MMM d",
|
||||
"Month Date": "MMM d",
|
||||
"Numeric (M/D)": "M/d",
|
||||
"Numeric (D/M)": "d/M",
|
||||
"Full with Year": "ddd d MMM yyyy",
|
||||
"ISO Date": "yyyy-MM-dd",
|
||||
"Full Day & Month": "dddd, MMMM d"
|
||||
}
|
||||
if (value === "Custom...") {
|
||||
customFormatInput.visible = true
|
||||
} else {
|
||||
customFormatInput.visible = false
|
||||
SettingsData.setClockDateFormat(
|
||||
formatMap[value])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
height: 50
|
||||
text: "Lock Screen Format"
|
||||
description: "Preview: " + Qt.formatDate(new Date(), SettingsData.lockDateFormat)
|
||||
description: "Preview: " + Qt.formatDate(
|
||||
new Date(),
|
||||
SettingsData.lockDateFormat)
|
||||
currentValue: {
|
||||
// Find matching preset or show "Custom"
|
||||
const presets = [{
|
||||
"format": "ddd d",
|
||||
"label": "Day Date"
|
||||
}, {
|
||||
"format": "ddd MMM d",
|
||||
"label": "Day Month Date"
|
||||
}, {
|
||||
"format": "MMM d",
|
||||
"label": "Month Date"
|
||||
}, {
|
||||
"format": "M/d",
|
||||
"label": "Numeric (M/D)"
|
||||
}, {
|
||||
"format": "d/M",
|
||||
"label": "Numeric (D/M)"
|
||||
}, {
|
||||
"format": "ddd d MMM yyyy",
|
||||
"label": "Full with Year"
|
||||
}, {
|
||||
"format": "yyyy-MM-dd",
|
||||
"label": "ISO Date"
|
||||
}, {
|
||||
"format": "dddd, MMMM d",
|
||||
"label": "Full Day & Month"
|
||||
}];
|
||||
const match = presets.find((p) => {
|
||||
return p.format === SettingsData.lockDateFormat;
|
||||
});
|
||||
return match ? match.label : "Custom: " + SettingsData.lockDateFormat;
|
||||
"format": "ddd d",
|
||||
"label": "Day Date"
|
||||
}, {
|
||||
"format": "ddd MMM d",
|
||||
"label": "Day Month Date"
|
||||
}, {
|
||||
"format": "MMM d",
|
||||
"label": "Month Date"
|
||||
}, {
|
||||
"format": "M/d",
|
||||
"label": "Numeric (M/D)"
|
||||
}, {
|
||||
"format": "d/M",
|
||||
"label": "Numeric (D/M)"
|
||||
}, {
|
||||
"format": "ddd d MMM yyyy",
|
||||
"label": "Full with Year"
|
||||
}, {
|
||||
"format": "yyyy-MM-dd",
|
||||
"label": "ISO Date"
|
||||
}, {
|
||||
"format": "dddd, MMMM d",
|
||||
"label": "Full Day & Month"
|
||||
}]
|
||||
const match = presets.find(p => {
|
||||
return p.format
|
||||
=== SettingsData.lockDateFormat
|
||||
})
|
||||
return match ? match.label : "Custom: " + SettingsData.lockDateFormat
|
||||
}
|
||||
options: ["Day Date", "Day Month Date", "Month Date", "Numeric (M/D)", "Numeric (D/M)", "Full with Year", "ISO Date", "Full Day & Month", "Custom..."]
|
||||
onValueChanged: (value) => {
|
||||
const formatMap = {
|
||||
"Day Date": "ddd d",
|
||||
"Day Month Date": "ddd MMM d",
|
||||
"Month Date": "MMM d",
|
||||
"Numeric (M/D)": "M/d",
|
||||
"Numeric (D/M)": "d/M",
|
||||
"Full with Year": "ddd d MMM yyyy",
|
||||
"ISO Date": "yyyy-MM-dd",
|
||||
"Full Day & Month": "dddd, MMMM d"
|
||||
};
|
||||
if (value === "Custom...") {
|
||||
customLockFormatInput.visible = true;
|
||||
} else {
|
||||
customLockFormatInput.visible = false;
|
||||
SettingsData.setLockDateFormat(formatMap[value]);
|
||||
}
|
||||
}
|
||||
onValueChanged: value => {
|
||||
const formatMap = {
|
||||
"Day Date": "ddd d",
|
||||
"Day Month Date": "ddd MMM d",
|
||||
"Month Date": "MMM d",
|
||||
"Numeric (M/D)": "M/d",
|
||||
"Numeric (D/M)": "d/M",
|
||||
"Full with Year": "ddd d MMM yyyy",
|
||||
"ISO Date": "yyyy-MM-dd",
|
||||
"Full Day & Month": "dddd, MMMM d"
|
||||
}
|
||||
if (value === "Custom...") {
|
||||
customLockFormatInput.visible = true
|
||||
} else {
|
||||
customLockFormatInput.visible = false
|
||||
SettingsData.setLockDateFormat(
|
||||
formatMap[value])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
@@ -246,8 +255,7 @@ Item {
|
||||
text: SettingsData.clockDateFormat
|
||||
onTextChanged: {
|
||||
if (visible && text)
|
||||
SettingsData.setClockDateFormat(text);
|
||||
|
||||
SettingsData.setClockDateFormat(text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,8 +268,7 @@ Item {
|
||||
text: SettingsData.lockDateFormat
|
||||
onTextChanged: {
|
||||
if (visible && text)
|
||||
SettingsData.setLockDateFormat(text);
|
||||
|
||||
SettingsData.setLockDateFormat(text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,8 +276,11 @@ Item {
|
||||
width: parent.width
|
||||
height: formatHelp.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
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.1)
|
||||
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.1)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
@@ -324,7 +334,6 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -360,21 +369,12 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,8 +24,10 @@ Item {
|
||||
width: parent.width
|
||||
height: enableWeatherSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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 {
|
||||
@@ -47,7 +49,8 @@ Item {
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - enableToggle.width - Theme.spacingM
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
- enableToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -65,7 +68,6 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
@@ -73,15 +75,13 @@ Item {
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.weatherEnabled
|
||||
onToggled: (checked) => {
|
||||
return SettingsData.setWeatherEnabled(checked);
|
||||
}
|
||||
onToggled: checked => {
|
||||
return SettingsData.setWeatherEnabled(
|
||||
checked)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Temperature Unit
|
||||
@@ -89,8 +89,10 @@ Item {
|
||||
width: parent.width
|
||||
height: temperatureSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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
|
||||
visible: SettingsData.weatherEnabled
|
||||
opacity: visible ? 1 : 0
|
||||
@@ -114,7 +116,8 @@ Item {
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - temperatureToggle.width - Theme.spacingM
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
- temperatureToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -132,7 +135,6 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
@@ -140,13 +142,12 @@ Item {
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.useFahrenheit
|
||||
onToggled: (checked) => {
|
||||
return SettingsData.setTemperatureUnit(checked);
|
||||
}
|
||||
onToggled: checked => {
|
||||
return SettingsData.setTemperatureUnit(
|
||||
checked)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -155,7 +156,6 @@ Item {
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Location Settings
|
||||
@@ -163,8 +163,10 @@ Item {
|
||||
width: parent.width
|
||||
height: locationSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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)
|
||||
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
|
||||
visible: SettingsData.weatherEnabled
|
||||
opacity: visible ? 1 : 0
|
||||
@@ -188,7 +190,8 @@ Item {
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - autoLocationToggle.width - Theme.spacingM
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
- autoLocationToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -206,7 +209,6 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
@@ -214,11 +216,11 @@ Item {
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.useAutoLocation
|
||||
onToggled: (checked) => {
|
||||
return SettingsData.setAutoLocation(checked);
|
||||
}
|
||||
onToggled: checked => {
|
||||
return SettingsData.setAutoLocation(
|
||||
checked)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -245,12 +247,12 @@ Item {
|
||||
currentLocation: SettingsData.weatherLocation
|
||||
placeholderText: "New York, NY"
|
||||
onLocationSelected: (displayName, coordinates) => {
|
||||
SettingsData.setWeatherLocation(displayName, coordinates);
|
||||
}
|
||||
SettingsData.setWeatherLocation(
|
||||
displayName,
|
||||
coordinates)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -259,11 +261,7 @@ Item {
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,373 +5,391 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: widgetTweaksTab
|
||||
id: widgetTweaksTab
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Theme.spacingL
|
||||
clip: true
|
||||
contentHeight: mainColumn.height
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
// Launcher Button Section
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: launcherButtonSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Theme.spacingL
|
||||
clip: true
|
||||
contentHeight: mainColumn.height
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: launcherButtonSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
id: mainColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
DankIcon {
|
||||
name: "apps"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
// Launcher Button Section
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: launcherButtonSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
|
||||
StyledText {
|
||||
text: "Launcher Button"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: workspaceSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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: workspaceSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "view_module"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Workspace Settings"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: "Workspace Index Numbers"
|
||||
description: "Show workspace index numbers in the top bar workspace switcher"
|
||||
checked: SettingsData.showWorkspaceIndex
|
||||
onToggled: checked => {
|
||||
return SettingsData.setShowWorkspaceIndex(checked)
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: "Workspace Padding"
|
||||
description: "Always show a minimum of 3 workspaces, even if fewer are available"
|
||||
checked: SettingsData.showWorkspacePadding
|
||||
onToggled: checked => {
|
||||
return SettingsData.setShowWorkspacePadding(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: workspaceIconsSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
visible: SettingsData.hasNamedWorkspaces()
|
||||
|
||||
Column {
|
||||
id: workspaceIconsSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "label"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Named Workspace Icons"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: "Configure icons for named workspaces. Icons take priority over numbers when both are enabled."
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.outline
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: SettingsData.getNamedWorkspaces()
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: workspaceIconRow.implicitHeight + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
|
||||
Theme.surfaceContainer.b, 0.5)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.3)
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
id: workspaceIconRow
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: "\"" + modelData + "\""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 150
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
DankIconPicker {
|
||||
id: iconPicker
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Component.onCompleted: {
|
||||
var iconData = SettingsData.getWorkspaceNameIcon(modelData)
|
||||
if (iconData) {
|
||||
setIcon(iconData.value, iconData.type)
|
||||
}
|
||||
}
|
||||
|
||||
onIconSelected: (iconName, iconType) => {
|
||||
SettingsData.setWorkspaceNameIcon(modelData, {
|
||||
type: iconType,
|
||||
value: iconName
|
||||
})
|
||||
setIcon(iconName, iconType)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onWorkspaceIconsUpdated() {
|
||||
var iconData = SettingsData.getWorkspaceNameIcon(modelData)
|
||||
if (iconData) {
|
||||
iconPicker.setIcon(iconData.value, iconData.type)
|
||||
} else {
|
||||
iconPicker.setIcon("", "icon")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
color: clearMouseArea.containsMouse ? Theme.errorHover : Theme.surfaceContainer
|
||||
border.color: clearMouseArea.containsMouse ? Theme.error : Theme.outline
|
||||
border.width: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "close"
|
||||
size: 16
|
||||
color: clearMouseArea.containsMouse ? Theme.error : Theme.outline
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clearMouseArea
|
||||
Column {
|
||||
id: launcherButtonSection
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
SettingsData.removeWorkspaceNameIcon(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Item {
|
||||
width: parent.width - 150 - 240 - 28 - Theme.spacingM * 4
|
||||
height: 1
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "apps"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Launcher Button"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: workspaceSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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: workspaceSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "view_module"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Workspace Settings"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: "Workspace Index Numbers"
|
||||
description: "Show workspace index numbers in the top bar workspace switcher"
|
||||
checked: SettingsData.showWorkspaceIndex
|
||||
onToggled: checked => {
|
||||
return SettingsData.setShowWorkspaceIndex(
|
||||
checked)
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: "Workspace Padding"
|
||||
description: "Always show a minimum of 3 workspaces, even if fewer are available"
|
||||
checked: SettingsData.showWorkspacePadding
|
||||
onToggled: checked => {
|
||||
return SettingsData.setShowWorkspacePadding(
|
||||
checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: workspaceIconsSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
visible: SettingsData.hasNamedWorkspaces()
|
||||
|
||||
Column {
|
||||
id: workspaceIconsSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "label"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Named Workspace Icons"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: "Configure icons for named workspaces. Icons take priority over numbers when both are enabled."
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.outline
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: SettingsData.getNamedWorkspaces()
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: workspaceIconRow.implicitHeight + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceContainer.r,
|
||||
Theme.surfaceContainer.g,
|
||||
Theme.surfaceContainer.b, 0.5)
|
||||
border.color: Qt.rgba(Theme.outline.r,
|
||||
Theme.outline.g,
|
||||
Theme.outline.b, 0.3)
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
id: workspaceIconRow
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: "\"" + modelData + "\""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 150
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
DankIconPicker {
|
||||
id: iconPicker
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Component.onCompleted: {
|
||||
var iconData = SettingsData.getWorkspaceNameIcon(
|
||||
modelData)
|
||||
if (iconData) {
|
||||
setIcon(iconData.value,
|
||||
iconData.type)
|
||||
}
|
||||
}
|
||||
|
||||
onIconSelected: (iconName, iconType) => {
|
||||
SettingsData.setWorkspaceNameIcon(
|
||||
modelData, {
|
||||
"type": iconType,
|
||||
"value": iconName
|
||||
})
|
||||
setIcon(iconName,
|
||||
iconType)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onWorkspaceIconsUpdated() {
|
||||
var iconData = SettingsData.getWorkspaceNameIcon(
|
||||
modelData)
|
||||
if (iconData) {
|
||||
iconPicker.setIcon(
|
||||
iconData.value,
|
||||
iconData.type)
|
||||
} else {
|
||||
iconPicker.setIcon("", "icon")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
color: clearMouseArea.containsMouse ? Theme.errorHover : Theme.surfaceContainer
|
||||
border.color: clearMouseArea.containsMouse ? Theme.error : Theme.outline
|
||||
border.width: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "close"
|
||||
size: 16
|
||||
color: clearMouseArea.containsMouse ? Theme.error : Theme.outline
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clearMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
SettingsData.removeWorkspaceNameIcon(
|
||||
modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - 150 - 240 - 28 - Theme.spacingM * 4
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -9,256 +9,257 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property var modelData
|
||||
property var modelData
|
||||
|
||||
screen: modelData
|
||||
visible: ToastService.toastVisible
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
screen: modelData
|
||||
visible: ToastService.toastVisible
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: toast
|
||||
|
||||
property bool expanded: false
|
||||
|
||||
Connections {
|
||||
target: ToastService
|
||||
function onResetToastState() {
|
||||
toast.expanded = false
|
||||
}
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
width: ToastService.hasDetails ? 380 : messageText.implicitWidth + Theme.iconSize + Theme.spacingM * 3 + Theme.spacingL * 2
|
||||
height: toastContent.height + Theme.spacingL * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: Theme.barHeight - 4 + SettingsData.topBarSpacing + 2
|
||||
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.cornerRadius
|
||||
layer.enabled: true
|
||||
opacity: ToastService.toastVisible ? 0.9 : 0
|
||||
Rectangle {
|
||||
id: toast
|
||||
|
||||
Column {
|
||||
id: toastContent
|
||||
property bool expanded: false
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: Theme.spacingL
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
spacing: Theme.spacingS
|
||||
Connections {
|
||||
target: ToastService
|
||||
function onResetToastState() {
|
||||
toast.expanded = false
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Theme.iconSize + 8
|
||||
|
||||
DankIcon {
|
||||
id: statusIcon
|
||||
name: {
|
||||
width: ToastService.hasDetails ? 380 : messageText.implicitWidth + Theme.iconSize
|
||||
+ Theme.spacingM * 3 + Theme.spacingL * 2
|
||||
height: toastContent.height + Theme.spacingL * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: Theme.barHeight - 4 + SettingsData.topBarSpacing + 2
|
||||
color: {
|
||||
switch (ToastService.currentLevel) {
|
||||
case ToastService.levelError:
|
||||
return "error"
|
||||
return Theme.error
|
||||
case ToastService.levelWarn:
|
||||
return "warning"
|
||||
return Theme.warning
|
||||
case ToastService.levelInfo:
|
||||
return "info"
|
||||
return Theme.primary
|
||||
default:
|
||||
return "info"
|
||||
return Theme.primary
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: Theme.background
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
layer.enabled: true
|
||||
opacity: ToastService.toastVisible ? 0.9 : 0
|
||||
|
||||
StyledText {
|
||||
id: messageText
|
||||
text: ToastService.currentMessage
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.background
|
||||
font.weight: Font.Medium
|
||||
anchors.left: statusIcon.right
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
Column {
|
||||
id: toastContent
|
||||
|
||||
DankActionButton {
|
||||
iconName: toast.expanded ? "expand_less" : "expand_more"
|
||||
iconSize: Theme.iconSize
|
||||
iconColor: Theme.background
|
||||
buttonSize: Theme.iconSize + 8
|
||||
anchors.right: closeButton.left
|
||||
anchors.rightMargin: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: ToastService.hasDetails
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: Theme.spacingL
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
spacing: Theme.spacingS
|
||||
|
||||
onClicked: {
|
||||
toast.expanded = !toast.expanded
|
||||
if (toast.expanded) {
|
||||
ToastService.stopTimer()
|
||||
} else {
|
||||
ToastService.restartTimer()
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Theme.iconSize + 8
|
||||
|
||||
DankIcon {
|
||||
id: statusIcon
|
||||
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.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: messageText
|
||||
text: ToastService.currentMessage
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.background
|
||||
font.weight: Font.Medium
|
||||
anchors.left: statusIcon.right
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: toast.expanded ? "expand_less" : "expand_more"
|
||||
iconSize: Theme.iconSize
|
||||
iconColor: Theme.background
|
||||
buttonSize: Theme.iconSize + 8
|
||||
anchors.right: closeButton.left
|
||||
anchors.rightMargin: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: ToastService.hasDetails
|
||||
|
||||
onClicked: {
|
||||
toast.expanded = !toast.expanded
|
||||
if (toast.expanded) {
|
||||
ToastService.stopTimer()
|
||||
} else {
|
||||
ToastService.restartTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: closeButton
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize
|
||||
iconColor: Theme.background
|
||||
buttonSize: Theme.iconSize + 8
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: ToastService.hasDetails
|
||||
|
||||
onClicked: {
|
||||
ToastService.hideToast()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: closeButton
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize
|
||||
iconColor: Theme.background
|
||||
buttonSize: Theme.iconSize + 8
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: ToastService.hasDetails
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: detailsText.height + Theme.spacingS * 2
|
||||
color: Qt.rgba(0, 0, 0, 0.2)
|
||||
radius: Theme.cornerRadius / 2
|
||||
visible: toast.expanded && ToastService.hasDetails
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
onClicked: {
|
||||
ToastService.hideToast()
|
||||
}
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
id: detailsText
|
||||
text: ToastService.currentDetails
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.background
|
||||
isMonospace: true
|
||||
anchors.left: parent.left
|
||||
anchors.right: copyButton.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: detailsText.height + Theme.spacingS * 2
|
||||
color: Qt.rgba(0, 0, 0, 0.2)
|
||||
radius: Theme.cornerRadius / 2
|
||||
visible: toast.expanded && ToastService.hasDetails
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
DankActionButton {
|
||||
id: copyButton
|
||||
iconName: "content_copy"
|
||||
iconSize: Theme.iconSizeSmall
|
||||
iconColor: Theme.background
|
||||
buttonSize: Theme.iconSizeSmall + 8
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
id: detailsText
|
||||
text: ToastService.currentDetails
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.background
|
||||
isMonospace: true
|
||||
anchors.left: parent.left
|
||||
anchors.right: copyButton.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
property bool showTooltip: false
|
||||
|
||||
DankActionButton {
|
||||
id: copyButton
|
||||
iconName: "content_copy"
|
||||
iconSize: Theme.iconSizeSmall
|
||||
iconColor: Theme.background
|
||||
buttonSize: Theme.iconSizeSmall + 8
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
onClicked: {
|
||||
Quickshell.execDetached(
|
||||
["wl-copy", ToastService.currentDetails])
|
||||
showTooltip = true
|
||||
tooltipTimer.start()
|
||||
}
|
||||
|
||||
property bool showTooltip: false
|
||||
Timer {
|
||||
id: tooltipTimer
|
||||
interval: 1500
|
||||
onTriggered: copyButton.showTooltip = false
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["wl-copy", ToastService.currentDetails])
|
||||
showTooltip = true
|
||||
tooltipTimer.start()
|
||||
}
|
||||
Rectangle {
|
||||
visible: copyButton.showTooltip
|
||||
width: tooltipLabel.implicitWidth + 16
|
||||
height: tooltipLabel.implicitHeight + 8
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.color: Theme.outlineMedium
|
||||
y: -height - 4
|
||||
x: -width / 2 + copyButton.width / 2
|
||||
|
||||
Timer {
|
||||
id: tooltipTimer
|
||||
interval: 1500
|
||||
onTriggered: copyButton.showTooltip = false
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: copyButton.showTooltip
|
||||
width: tooltipLabel.implicitWidth + 16
|
||||
height: tooltipLabel.implicitHeight + 8
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.color: Theme.outlineMedium
|
||||
y: -height - 4
|
||||
x: -width / 2 + copyButton.width / 2
|
||||
|
||||
StyledText {
|
||||
id: tooltipLabel
|
||||
anchors.centerIn: parent
|
||||
text: "Copied!"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
StyledText {
|
||||
id: tooltipLabel
|
||||
anchors.centerIn: parent
|
||||
text: "Copied!"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
visible: !ToastService.hasDetails
|
||||
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 color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
enabled: ToastService.toastVisible
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
visible: !ToastService.hasDetails
|
||||
onClicked: ToastService.hideToast()
|
||||
mask: Region {
|
||||
item: toast
|
||||
}
|
||||
|
||||
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 color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
enabled: ToastService.toastVisible
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: toast
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,71 +4,72 @@ import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
readonly property MprisPlayer activePlayer: MprisController.activePlayer
|
||||
readonly property bool hasActiveMedia: activePlayer !== null
|
||||
readonly property bool isPlaying: hasActiveMedia && activePlayer
|
||||
&& activePlayer.playbackState === MprisPlaybackState.Playing
|
||||
readonly property MprisPlayer activePlayer: MprisController.activePlayer
|
||||
readonly property bool hasActiveMedia: activePlayer !== null
|
||||
readonly property bool isPlaying: hasActiveMedia && activePlayer
|
||||
&& activePlayer.playbackState === MprisPlaybackState.Playing
|
||||
|
||||
width: 20
|
||||
height: Theme.iconSize
|
||||
width: 20
|
||||
height: Theme.iconSize
|
||||
|
||||
Loader {
|
||||
active: isPlaying
|
||||
sourceComponent: Component {
|
||||
Ref {
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
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
|
||||
Loader {
|
||||
active: isPlaying
|
||||
sourceComponent: Component {
|
||||
Ref {
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,202 +5,202 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: battery
|
||||
id: battery
|
||||
|
||||
property bool batteryPopupVisible: false
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property bool batteryPopupVisible: false
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
|
||||
signal toggleBatteryPopup
|
||||
signal toggleBatteryPopup
|
||||
|
||||
width: BatteryService.batteryAvailable ? 70 : 40
|
||||
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: 2
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (!BatteryService.batteryAvailable)
|
||||
return "power"
|
||||
|
||||
if (BatteryService.isCharging) {
|
||||
if (BatteryService.batteryLevel >= 90)
|
||||
return "battery_charging_full"
|
||||
if (BatteryService.batteryLevel >= 80)
|
||||
return "battery_charging_90"
|
||||
if (BatteryService.batteryLevel >= 60)
|
||||
return "battery_charging_80"
|
||||
if (BatteryService.batteryLevel >= 50)
|
||||
return "battery_charging_60"
|
||||
if (BatteryService.batteryLevel >= 30)
|
||||
return "battery_charging_50"
|
||||
if (BatteryService.batteryLevel >= 20)
|
||||
return "battery_charging_30"
|
||||
return "battery_charging_20"
|
||||
}
|
||||
|
||||
// Check if plugged in but not charging (like at 80% charge limit)
|
||||
if (BatteryService.isPluggedIn) {
|
||||
if (BatteryService.batteryLevel >= 90)
|
||||
return "battery_charging_full"
|
||||
if (BatteryService.batteryLevel >= 80)
|
||||
return "battery_charging_90"
|
||||
if (BatteryService.batteryLevel >= 60)
|
||||
return "battery_charging_80"
|
||||
if (BatteryService.batteryLevel >= 50)
|
||||
return "battery_charging_60"
|
||||
if (BatteryService.batteryLevel >= 30)
|
||||
return "battery_charging_50"
|
||||
if (BatteryService.batteryLevel >= 20)
|
||||
return "battery_charging_30"
|
||||
return "battery_charging_20"
|
||||
}
|
||||
|
||||
// On battery power
|
||||
if (BatteryService.batteryLevel >= 95)
|
||||
return "battery_full"
|
||||
if (BatteryService.batteryLevel >= 85)
|
||||
return "battery_6_bar"
|
||||
if (BatteryService.batteryLevel >= 70)
|
||||
return "battery_5_bar"
|
||||
if (BatteryService.batteryLevel >= 55)
|
||||
return "battery_4_bar"
|
||||
if (BatteryService.batteryLevel >= 40)
|
||||
return "battery_3_bar"
|
||||
if (BatteryService.batteryLevel >= 25)
|
||||
return "battery_2_bar"
|
||||
return "battery_1_bar"
|
||||
}
|
||||
size: Theme.iconSize - 6
|
||||
color: {
|
||||
if (!BatteryService.batteryAvailable)
|
||||
return Theme.surfaceText
|
||||
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||
return Theme.error
|
||||
|
||||
if (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||
return Theme.primary
|
||||
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
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
|
||||
onPressed: {
|
||||
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
|
||||
width: BatteryService.batteryAvailable ? 70 : 40
|
||||
height: 30
|
||||
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
|
||||
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
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
id: tooltipText
|
||||
DankIcon {
|
||||
name: {
|
||||
if (!BatteryService.batteryAvailable)
|
||||
return "power"
|
||||
|
||||
text: {
|
||||
if (!BatteryService.batteryAvailable) {
|
||||
if (typeof PowerProfiles === "undefined")
|
||||
return "Power Management"
|
||||
if (BatteryService.isCharging) {
|
||||
if (BatteryService.batteryLevel >= 90)
|
||||
return "battery_charging_full"
|
||||
if (BatteryService.batteryLevel >= 80)
|
||||
return "battery_charging_90"
|
||||
if (BatteryService.batteryLevel >= 60)
|
||||
return "battery_charging_80"
|
||||
if (BatteryService.batteryLevel >= 50)
|
||||
return "battery_charging_60"
|
||||
if (BatteryService.batteryLevel >= 30)
|
||||
return "battery_charging_50"
|
||||
if (BatteryService.batteryLevel >= 20)
|
||||
return "battery_charging_30"
|
||||
return "battery_charging_20"
|
||||
}
|
||||
|
||||
switch (PowerProfiles.profile) {
|
||||
case PowerProfile.PowerSaver:
|
||||
return "Power Profile: Power Saver"
|
||||
case PowerProfile.Performance:
|
||||
return "Power Profile: Performance"
|
||||
default:
|
||||
return "Power Profile: Balanced"
|
||||
// Check if plugged in but not charging (like at 80% charge limit)
|
||||
if (BatteryService.isPluggedIn) {
|
||||
if (BatteryService.batteryLevel >= 90)
|
||||
return "battery_charging_full"
|
||||
if (BatteryService.batteryLevel >= 80)
|
||||
return "battery_charging_90"
|
||||
if (BatteryService.batteryLevel >= 60)
|
||||
return "battery_charging_80"
|
||||
if (BatteryService.batteryLevel >= 50)
|
||||
return "battery_charging_60"
|
||||
if (BatteryService.batteryLevel >= 30)
|
||||
return "battery_charging_50"
|
||||
if (BatteryService.batteryLevel >= 20)
|
||||
return "battery_charging_30"
|
||||
return "battery_charging_20"
|
||||
}
|
||||
|
||||
// On battery power
|
||||
if (BatteryService.batteryLevel >= 95)
|
||||
return "battery_full"
|
||||
if (BatteryService.batteryLevel >= 85)
|
||||
return "battery_6_bar"
|
||||
if (BatteryService.batteryLevel >= 70)
|
||||
return "battery_5_bar"
|
||||
if (BatteryService.batteryLevel >= 55)
|
||||
return "battery_4_bar"
|
||||
if (BatteryService.batteryLevel >= 40)
|
||||
return "battery_3_bar"
|
||||
if (BatteryService.batteryLevel >= 25)
|
||||
return "battery_2_bar"
|
||||
return "battery_1_bar"
|
||||
}
|
||||
}
|
||||
let status = BatteryService.batteryStatus
|
||||
let level = BatteryService.batteryLevel + "%"
|
||||
let time = BatteryService.formatTimeRemaining()
|
||||
if (time !== "Unknown")
|
||||
return status + " • " + level + " • " + time
|
||||
else
|
||||
return status + " • " + level
|
||||
size: Theme.iconSize - 6
|
||||
color: {
|
||||
if (!BatteryService.batteryAvailable)
|
||||
return Theme.surfaceText
|
||||
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||
return Theme.error
|
||||
|
||||
if (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||
return Theme.primary
|
||||
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: batteryArea
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
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
|
||||
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
@@ -12,17 +12,18 @@ Rectangle {
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
|
||||
signal clockClicked()
|
||||
signal clockClicked
|
||||
|
||||
width: clockRow.implicitWidth + Theme.spacingS * 2
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
const baseColor = clockMouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
const baseColor = clockMouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||
baseColor.a * Theme.widgetTransparency)
|
||||
}
|
||||
Component.onCompleted: {
|
||||
root.currentDate = systemClock.date;
|
||||
root.currentDate = systemClock.date
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -32,7 +33,10 @@ Rectangle {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: SettingsData.use24HourClock ? Qt.formatTime(root.currentDate, "H:mm") : Qt.formatTime(root.currentDate, "h:mm AP")
|
||||
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
|
||||
@@ -53,7 +57,6 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !SettingsData.clockCompactMode
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SystemClock {
|
||||
@@ -71,13 +74,15 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
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);
|
||||
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();
|
||||
root.clockClicked()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +91,5 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,134 +4,139 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property bool isActive: false
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property bool isActive: false
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
|
||||
signal clicked
|
||||
signal clicked
|
||||
|
||||
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"
|
||||
return NetworkService.wifiSignalIcon
|
||||
}
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "bluetooth"
|
||||
size: Theme.iconSize - 8
|
||||
color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: BluetoothService.available && BluetoothService.enabled
|
||||
}
|
||||
Row {
|
||||
id: controlIndicators
|
||||
|
||||
Rectangle {
|
||||
width: audioIcon.implicitWidth + 4
|
||||
height: audioIcon.implicitHeight + 4
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
id: audioIcon
|
||||
|
||||
name: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0)
|
||||
return "volume_off"
|
||||
else if (AudioService.sink.audio.volume * 100 < 33)
|
||||
return "volume_down"
|
||||
else
|
||||
return "volume_up"
|
||||
}
|
||||
return "volume_up"
|
||||
}
|
||||
size: Theme.iconSize - 8
|
||||
color: audioWheelArea.containsMouse || controlCenterArea.containsMouse
|
||||
|| root.isActive ? Theme.primary : Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
MouseArea {
|
||||
id: audioWheelArea
|
||||
DankIcon {
|
||||
name: {
|
||||
if (NetworkService.networkStatus === "ethernet")
|
||||
return "lan"
|
||||
return NetworkService.wifiSignalIcon
|
||||
}
|
||||
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: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
if (AudioService.sink.audio.muted
|
||||
|| AudioService.sink.audio.volume === 0)
|
||||
return "volume_off"
|
||||
else if (AudioService.sink.audio.volume * 100 < 33)
|
||||
return "volume_down"
|
||||
else
|
||||
return "volume_up"
|
||||
}
|
||||
return "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
|
||||
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
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
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 {
|
||||
name: "mic"
|
||||
size: Theme.iconSize - 8
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: false // TODO: Add mic detection
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: controlCenterArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
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,84 +5,84 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property bool showPercentage: true
|
||||
property bool showIcon: true
|
||||
property var toggleProcessList
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property bool showPercentage: true
|
||||
property bool showIcon: true
|
||||
property var toggleProcessList
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
|
||||
width: 55
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
const baseColor = cpuArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||
baseColor.a * Theme.widgetTransparency)
|
||||
}
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["cpu"])
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["cpu"])
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cpuArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
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)
|
||||
}
|
||||
DgopService.setSortBy("cpu")
|
||||
if (root.toggleProcessList)
|
||||
root.toggleProcessList()
|
||||
width: 55
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
const baseColor = cpuArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||
baseColor.a * Theme.widgetTransparency)
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "memory"
|
||||
size: Theme.iconSize - 8
|
||||
color: {
|
||||
if (DgopService.cpuUsage > 80)
|
||||
return Theme.tempDanger
|
||||
|
||||
if (DgopService.cpuUsage > 60)
|
||||
return Theme.tempWarning
|
||||
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["cpu"])
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["cpu"])
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuUsage === undefined
|
||||
|| DgopService.cpuUsage === null
|
||||
|| DgopService.cpuUsage === 0) {
|
||||
return "--%"
|
||||
MouseArea {
|
||||
id: cpuArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
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)
|
||||
}
|
||||
DgopService.setSortBy("cpu")
|
||||
if (root.toggleProcessList)
|
||||
root.toggleProcessList()
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "memory"
|
||||
size: Theme.iconSize - 8
|
||||
color: {
|
||||
if (DgopService.cpuUsage > 80)
|
||||
return Theme.tempDanger
|
||||
|
||||
if (DgopService.cpuUsage > 60)
|
||||
return Theme.tempWarning
|
||||
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuUsage === undefined
|
||||
|| DgopService.cpuUsage === null
|
||||
|| DgopService.cpuUsage === 0) {
|
||||
return "--%"
|
||||
}
|
||||
return DgopService.cpuUsage.toFixed(0) + "%"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
return DgopService.cpuUsage.toFixed(0) + "%"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,91 +5,91 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property bool showPercentage: true
|
||||
property bool showIcon: true
|
||||
property var toggleProcessList
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property bool showPercentage: true
|
||||
property bool showIcon: true
|
||||
property var toggleProcessList
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
|
||||
width: 55
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
const baseColor = cpuTempArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||
baseColor.a * Theme.widgetTransparency)
|
||||
}
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["cpu"])
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["cpu"])
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cpuTempArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
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)
|
||||
}
|
||||
DgopService.setSortBy("cpu")
|
||||
if (root.toggleProcessList)
|
||||
root.toggleProcessList()
|
||||
width: 55
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
const baseColor = cpuTempArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||
baseColor.a * Theme.widgetTransparency)
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "memory"
|
||||
size: Theme.iconSize - 8
|
||||
color: {
|
||||
if (DgopService.cpuTemperature > 85)
|
||||
return Theme.tempDanger
|
||||
|
||||
if (DgopService.cpuTemperature > 69)
|
||||
return Theme.tempWarning
|
||||
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["cpu"])
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["cpu"])
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuTemperature === undefined
|
||||
|| DgopService.cpuTemperature === null
|
||||
|| DgopService.cpuTemperature < 0) {
|
||||
return "--°"
|
||||
MouseArea {
|
||||
id: cpuTempArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
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)
|
||||
}
|
||||
DgopService.setSortBy("cpu")
|
||||
if (root.toggleProcessList)
|
||||
root.toggleProcessList()
|
||||
}
|
||||
return Math.round(DgopService.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
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "memory"
|
||||
size: Theme.iconSize - 8
|
||||
color: {
|
||||
if (DgopService.cpuTemperature > 85)
|
||||
return Theme.tempDanger
|
||||
|
||||
if (DgopService.cpuTemperature > 69)
|
||||
return Theme.tempWarning
|
||||
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuTemperature === undefined
|
||||
|| DgopService.cpuTemperature === null
|
||||
|| DgopService.cpuTemperature < 0) {
|
||||
return "--°"
|
||||
}
|
||||
return Math.round(DgopService.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,18 @@ Rectangle {
|
||||
readonly property int maxNormalWidth: 456
|
||||
readonly property int maxCompactWidth: 288
|
||||
|
||||
width: compactMode ? Math.min(baseWidth, maxCompactWidth) : Math.min(baseWidth, maxNormalWidth)
|
||||
width: compactMode ? Math.min(baseWidth,
|
||||
maxCompactWidth) : Math.min(baseWidth,
|
||||
maxNormalWidth)
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (!NiriService.focusedWindowTitle)
|
||||
return "transparent";
|
||||
return "transparent"
|
||||
|
||||
const baseColor = mouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
const baseColor = mouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||
baseColor.a * Theme.widgetTransparency)
|
||||
}
|
||||
clip: true
|
||||
visible: NiriService.niriAvailable && NiriService.focusedWindowTitle
|
||||
@@ -37,16 +40,17 @@ Rectangle {
|
||||
|
||||
text: {
|
||||
if (!NiriService.focusedWindowId)
|
||||
return "";
|
||||
return ""
|
||||
|
||||
var window = NiriService.windows.find((w) => {
|
||||
return w.id == NiriService.focusedWindowId;
|
||||
});
|
||||
var window = NiriService.windows.find(w => {
|
||||
return w.id == NiriService.focusedWindowId
|
||||
})
|
||||
if (!window || !window.app_id)
|
||||
return "";
|
||||
return ""
|
||||
|
||||
var desktopEntry = DesktopEntries.byId(window.app_id);
|
||||
return desktopEntry && desktopEntry.name ? desktopEntry.name : window.app_id;
|
||||
var desktopEntry = DesktopEntries.byId(window.app_id)
|
||||
return desktopEntry
|
||||
&& desktopEntry.name ? desktopEntry.name : window.app_id
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
@@ -70,20 +74,24 @@ Rectangle {
|
||||
id: titleText
|
||||
|
||||
text: {
|
||||
var title = NiriService.focusedWindowTitle || "";
|
||||
var appName = appText.text;
|
||||
|
||||
if (!title || !appName) return title;
|
||||
|
||||
var title = NiriService.focusedWindowTitle || ""
|
||||
var appName = appText.text
|
||||
|
||||
if (!title || !appName)
|
||||
return title
|
||||
|
||||
// Remove app name from end of title if it exists there
|
||||
if (title.endsWith(" - " + appName)) {
|
||||
return title.substring(0, title.length - (" - " + appName).length);
|
||||
return title.substring(
|
||||
0, title.length - (" - " + appName).length)
|
||||
}
|
||||
if (title.endsWith(appName)) {
|
||||
return title.substring(0, title.length - appName.length).replace(/ - $/, "");
|
||||
return title.substring(
|
||||
0, title.length - appName.length).replace(
|
||||
/ - $/, "")
|
||||
}
|
||||
|
||||
return title;
|
||||
|
||||
return title
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
@@ -94,7 +102,6 @@ Rectangle {
|
||||
width: Math.min(implicitWidth, compactMode ? 280 : 250)
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -109,7 +116,6 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
@@ -117,7 +123,5 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,182 +5,185 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
id: root
|
||||
|
||||
property bool showPercentage: true
|
||||
property bool showIcon: true
|
||||
property var toggleProcessList
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property var widgetData: null
|
||||
property int selectedGpuIndex: (widgetData && widgetData.selectedGpuIndex
|
||||
!== undefined) ? widgetData.selectedGpuIndex : 0
|
||||
property bool showPercentage: true
|
||||
property bool showIcon: true
|
||||
property var toggleProcessList
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property var widgetData: null
|
||||
property int selectedGpuIndex: (widgetData && widgetData.selectedGpuIndex
|
||||
!== undefined) ? widgetData.selectedGpuIndex : 0
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onWidgetDataChanged() {
|
||||
// Force property re-evaluation by triggering change detection
|
||||
root.selectedGpuIndex = Qt.binding(() => {
|
||||
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: {
|
||||
DgopService.addRef(["gpu"])
|
||||
console.log("GpuTemperature widget - pciId:", widgetData ? widgetData.pciId : "no widgetData", "selectedGpuIndex:", widgetData ? widgetData.selectedGpuIndex : "no widgetData")
|
||||
// Add this widget's PCI ID to the service
|
||||
if (widgetData && widgetData.pciId) {
|
||||
console.log("Adding GPU PCI ID to service:", widgetData.pciId)
|
||||
DgopService.addGpuPciId(widgetData.pciId)
|
||||
} else {
|
||||
console.log("No PCI ID in widget data, starting auto-detection")
|
||||
// No PCI ID saved, auto-detect and save the first GPU
|
||||
autoSaveTimer.running = true
|
||||
}
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["gpu"])
|
||||
// Remove this widget's PCI ID from the service
|
||||
if (widgetData && widgetData.pciId) {
|
||||
DgopService.removeGpuPciId(widgetData.pciId)
|
||||
}
|
||||
}
|
||||
|
||||
property real displayTemp: {
|
||||
if (!DgopService.availableGpus
|
||||
|| DgopService.availableGpus.length === 0)
|
||||
return 0
|
||||
if (selectedGpuIndex >= 0
|
||||
&& selectedGpuIndex < DgopService.availableGpus.length) {
|
||||
return DgopService.availableGpus[selectedGpuIndex].temperature || 0
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: gpuArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
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)
|
||||
}
|
||||
DgopService.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 "--°"
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onWidgetDataChanged() {
|
||||
// Force property re-evaluation by triggering change detection
|
||||
root.selectedGpuIndex = Qt.binding(() => {
|
||||
return (root.widgetData
|
||||
&& root.widgetData.selectedGpuIndex !== undefined) ? root.widgetData.selectedGpuIndex : 0
|
||||
})
|
||||
}
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: autoSaveTimer
|
||||
interval: 100
|
||||
running: false
|
||||
onTriggered: {
|
||||
if (DgopService.availableGpus && DgopService.availableGpus.length > 0) {
|
||||
const firstGpu = DgopService.availableGpus[0]
|
||||
if (firstGpu && firstGpu.pciId) {
|
||||
// Save the first GPU's PCI ID to this widget's settings
|
||||
updateWidgetPciId(firstGpu.pciId)
|
||||
DgopService.addGpuPciId(firstGpu.pciId)
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["gpu"])
|
||||
console.log("GpuTemperature widget - pciId:",
|
||||
widgetData ? widgetData.pciId : "no widgetData",
|
||||
"selectedGpuIndex:",
|
||||
widgetData ? widgetData.selectedGpuIndex : "no widgetData")
|
||||
// Add this widget's PCI ID to the service
|
||||
if (widgetData && widgetData.pciId) {
|
||||
console.log("Adding GPU PCI ID to service:", widgetData.pciId)
|
||||
DgopService.addGpuPciId(widgetData.pciId)
|
||||
} else {
|
||||
console.log("No PCI ID in widget data, starting auto-detection")
|
||||
// No PCI ID saved, auto-detect and save the first GPU
|
||||
autoSaveTimer.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateWidgetPciId(pciId) {
|
||||
// Find and update this widget's pciId in the settings
|
||||
var sections = ["left", "center", "right"]
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
var sectionId = sections[s]
|
||||
var widgets = []
|
||||
if (sectionId === "left")
|
||||
widgets = SettingsData.topBarLeftWidgets.slice()
|
||||
else if (sectionId === "center")
|
||||
widgets = SettingsData.topBarCenterWidgets.slice()
|
||||
else if (sectionId === "right")
|
||||
widgets = SettingsData.topBarRightWidgets.slice()
|
||||
|
||||
for (var i = 0; i < widgets.length; i++) {
|
||||
var widget = widgets[i]
|
||||
if (typeof widget === "object" && widget.id === "gpuTemp"
|
||||
&& (!widget.pciId || widget.pciId === "")) {
|
||||
widgets[i] = {
|
||||
"id": widget.id,
|
||||
"enabled": widget.enabled !== undefined ? widget.enabled : true,
|
||||
"selectedGpuIndex": 0,
|
||||
"pciId": pciId
|
||||
}
|
||||
|
||||
if (sectionId === "left")
|
||||
SettingsData.setTopBarLeftWidgets(widgets)
|
||||
else if (sectionId === "center")
|
||||
SettingsData.setTopBarCenterWidgets(widgets)
|
||||
else if (sectionId === "right")
|
||||
SettingsData.setTopBarRightWidgets(widgets)
|
||||
return
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["gpu"])
|
||||
// Remove this widget's PCI ID from the service
|
||||
if (widgetData && widgetData.pciId) {
|
||||
DgopService.removeGpuPciId(widgetData.pciId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property real displayTemp: {
|
||||
if (!DgopService.availableGpus
|
||||
|| DgopService.availableGpus.length === 0)
|
||||
return 0
|
||||
if (selectedGpuIndex >= 0
|
||||
&& selectedGpuIndex < DgopService.availableGpus.length) {
|
||||
return DgopService.availableGpus[selectedGpuIndex].temperature || 0
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: gpuArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
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)
|
||||
}
|
||||
DgopService.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
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: autoSaveTimer
|
||||
interval: 100
|
||||
running: false
|
||||
onTriggered: {
|
||||
if (DgopService.availableGpus
|
||||
&& DgopService.availableGpus.length > 0) {
|
||||
const firstGpu = DgopService.availableGpus[0]
|
||||
if (firstGpu && firstGpu.pciId) {
|
||||
// Save the first GPU's PCI ID to this widget's settings
|
||||
updateWidgetPciId(firstGpu.pciId)
|
||||
DgopService.addGpuPciId(firstGpu.pciId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateWidgetPciId(pciId) {
|
||||
// Find and update this widget's pciId in the settings
|
||||
var sections = ["left", "center", "right"]
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
var sectionId = sections[s]
|
||||
var widgets = []
|
||||
if (sectionId === "left")
|
||||
widgets = SettingsData.topBarLeftWidgets.slice()
|
||||
else if (sectionId === "center")
|
||||
widgets = SettingsData.topBarCenterWidgets.slice()
|
||||
else if (sectionId === "right")
|
||||
widgets = SettingsData.topBarRightWidgets.slice()
|
||||
|
||||
for (var i = 0; i < widgets.length; i++) {
|
||||
var widget = widgets[i]
|
||||
if (typeof widget === "object" && widget.id === "gpuTemp"
|
||||
&& (!widget.pciId || widget.pciId === "")) {
|
||||
widgets[i] = {
|
||||
"id": widget.id,
|
||||
"enabled": widget.enabled !== undefined ? widget.enabled : true,
|
||||
"selectedGpuIndex": 0,
|
||||
"pciId": pciId
|
||||
}
|
||||
|
||||
if (sectionId === "left")
|
||||
SettingsData.setTopBarLeftWidgets(widgets)
|
||||
else if (sectionId === "center")
|
||||
SettingsData.setTopBarCenterWidgets(widgets)
|
||||
else if (sectionId === "right")
|
||||
SettingsData.setTopBarRightWidgets(widgets)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,46 +7,43 @@ import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
|
||||
|
||||
width: 40
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
const baseColor = mouseArea.containsMouse
|
||||
? Theme.primaryPressed
|
||||
: (IdleInhibitorService.idleInhibited ? Theme.primaryHover : Theme.secondaryHover)
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||
baseColor.a * Theme.widgetTransparency)
|
||||
const baseColor = mouseArea.containsMouse ? Theme.primaryPressed : (IdleInhibitorService.idleInhibited ? Theme.primaryHover : Theme.secondaryHover)
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||
baseColor.a * Theme.widgetTransparency)
|
||||
}
|
||||
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: IdleInhibitorService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||
size: Theme.iconSize - 6
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
|
||||
onClicked: {
|
||||
IdleInhibitorService.toggleIdleInhibit()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user