mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
Compare commits
174 Commits
v0.0.7
...
more-loadi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
983d185de6 | ||
|
|
066d1847e4 | ||
|
|
3155bf24c1 | ||
|
|
92bb5b90aa | ||
|
|
a207ed74ff | ||
|
|
9d224113c4 | ||
|
|
934f4b2210 | ||
|
|
a6a41d4de1 | ||
|
|
185ee20f2d | ||
|
|
778b960130 | ||
|
|
184938d10a | ||
|
|
25565af5f9 | ||
|
|
0bd9a4f860 | ||
|
|
fe64dd1dea | ||
|
|
a4a59fd586 | ||
|
|
7ccd2d9418 | ||
|
|
974dd70a06 | ||
|
|
1690e4f63b | ||
|
|
ec75ef468b | ||
|
|
a57c1e6451 | ||
|
|
bdc79a13a9 | ||
|
|
b893694977 | ||
|
|
9be7d44765 | ||
|
|
3f8d8ca379 | ||
|
|
e50c3cceeb | ||
|
|
14e648911d | ||
|
|
3c42a618d4 | ||
|
|
bed2259944 | ||
|
|
7516d44de9 | ||
|
|
e9a61a4f81 | ||
|
|
7a7d7d053a | ||
|
|
2067e44baf | ||
|
|
8e010478c7 | ||
|
|
ec4f0ff2ed | ||
|
|
c04177e45d | ||
|
|
b9b1737639 | ||
|
|
4468e4c6af | ||
|
|
cf5dec733d | ||
|
|
d9b9da4b3d | ||
|
|
d62ef89bc3 | ||
|
|
1b681e68b9 | ||
|
|
d15ee0c29b | ||
|
|
62b7b30754 | ||
|
|
aa52b586d6 | ||
|
|
ca11735c1d | ||
|
|
78683032aa | ||
|
|
bd5064e567 | ||
|
|
3862f0ced5 | ||
|
|
878c1d9385 | ||
|
|
0a5f635a2d | ||
|
|
6a963ed618 | ||
|
|
35f059b1dc | ||
|
|
b7838e497d | ||
|
|
3edfff388c | ||
|
|
0bfaf2a8f8 | ||
|
|
50f6dfa709 | ||
|
|
897f48d151 | ||
|
|
c43f58e1bf | ||
|
|
de532fdeda | ||
|
|
c21896b0df | ||
|
|
1669be4dd9 | ||
|
|
81550229e8 | ||
|
|
f38ffa1de5 | ||
|
|
66b493fb2b | ||
|
|
ceae254160 | ||
|
|
a3b3e49313 | ||
|
|
8fb17f7fd5 | ||
|
|
c464bb3e47 | ||
|
|
bd72cf3adf | ||
|
|
a734d9b497 | ||
|
|
b18814e086 | ||
|
|
74417a18ea | ||
|
|
227081c5c9 | ||
|
|
c3b3edcae8 | ||
|
|
c8f87085a0 | ||
|
|
118375f004 | ||
|
|
babcc66765 | ||
|
|
64c8e79bf2 | ||
|
|
12e8e72bb2 | ||
|
|
72b79c0f51 | ||
|
|
50e440f212 | ||
|
|
7c1c64fefc | ||
|
|
2e4770f4be | ||
|
|
0b2bf0b83d | ||
|
|
1fbb614aa8 | ||
|
|
55a29d8504 | ||
|
|
ffc13f71b7 | ||
|
|
d4816bd174 | ||
|
|
3cac6e5eea | ||
|
|
50bc3db048 | ||
|
|
2b99e61c65 | ||
|
|
306a0c0e2d | ||
|
|
0ba4ee74b5 | ||
|
|
2b5a5115b7 | ||
|
|
8a7e386adb | ||
|
|
6e6412fffc | ||
|
|
bc540056fc | ||
|
|
0055ddbc8d | ||
|
|
3376dc893d | ||
|
|
4a673fe1b5 | ||
|
|
6639f6ead0 | ||
|
|
2ce32e8bb5 | ||
|
|
4d13a3d909 | ||
|
|
691b6da7a7 | ||
|
|
e6265c2f71 | ||
|
|
de9bb43c26 | ||
|
|
1ae9802866 | ||
|
|
0e887408c5 | ||
|
|
88b7657e34 | ||
|
|
5fcffba73e | ||
|
|
542536455b | ||
|
|
97f0acadb6 | ||
|
|
32f3e579e7 | ||
|
|
bc446dabaf | ||
|
|
d8e2a73e0b | ||
|
|
ae6fd42163 | ||
|
|
cc7363eee1 | ||
|
|
38c4b33d15 | ||
|
|
6c81aa0908 | ||
|
|
102cc3c0b6 | ||
|
|
02300024cf | ||
|
|
344761df00 | ||
|
|
c0edaea716 | ||
|
|
75e660f78e | ||
|
|
be4d9cadba | ||
|
|
fdaac6ac8b | ||
|
|
5c540f826e | ||
|
|
55b3bffccd | ||
|
|
0cb2a96b3e | ||
|
|
9f13ac6963 | ||
|
|
19e148f65c | ||
|
|
7d9334916c | ||
|
|
50dedb55ac | ||
|
|
38f61ceae2 | ||
|
|
fff4b8c817 | ||
|
|
0fe76b7575 | ||
|
|
3c4719d430 | ||
|
|
c04e2ae0ff | ||
|
|
fec921df10 | ||
|
|
5186afce06 | ||
|
|
77ea6a9372 | ||
|
|
afb0435b5c | ||
|
|
903ef0cc72 | ||
|
|
e4f86abda9 | ||
|
|
8ee43de145 | ||
|
|
83b29c5ed9 | ||
|
|
cdf4ca3b3b | ||
|
|
a0192e709e | ||
|
|
59c7246989 | ||
|
|
76ee483b27 | ||
|
|
ba6c7ae28c | ||
|
|
07c1677145 | ||
|
|
3db1a2ccf2 | ||
|
|
6d84553da9 | ||
|
|
77bc129dfc | ||
|
|
2c451b9779 | ||
|
|
3c6bb58088 | ||
|
|
0372c44d98 | ||
|
|
8fc40ab12d | ||
|
|
9e1272029e | ||
|
|
27344a47e2 | ||
|
|
c42681b9b9 | ||
|
|
939f47286e | ||
|
|
c8cb5dc146 | ||
|
|
c3759dc542 | ||
|
|
17b49ad1f9 | ||
|
|
63c54b5611 | ||
|
|
988d5c2fc3 | ||
|
|
7a671b586c | ||
|
|
b36da8eba2 | ||
|
|
5b49e36da4 | ||
|
|
2395274714 | ||
|
|
d0cbe689f8 | ||
|
|
4ae157af35 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -63,6 +63,11 @@ CLAUDE-temp.md
|
||||
niri-colors.generated.kdl
|
||||
ghostty-colors.generated.conf
|
||||
|
||||
# Notepad files (should be in ~/.local/state/DankMaterialShell/)
|
||||
untitled-*.txt
|
||||
file:*
|
||||
notepad-files/
|
||||
|
||||
result
|
||||
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
@@ -96,4 +101,4 @@ go.work.sum
|
||||
|
||||
# Editor/IDE
|
||||
# .idea/
|
||||
# .vscode/
|
||||
# .vscode/
|
||||
@@ -1,11 +1,11 @@
|
||||
pragma Singleton
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
@@ -26,7 +26,7 @@ Singleton {
|
||||
|
||||
property bool hasTriedDefaultSession: false
|
||||
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
|
||||
readonly property string _stateDir: _stateUrl.startsWith("file://") ? _stateUrl.substring(7) : _stateUrl
|
||||
readonly property string _stateDir: Paths.strip(_stateUrl)
|
||||
property int nightModeStartHour: 18
|
||||
property int nightModeStartMinute: 0
|
||||
property int nightModeEndHour: 6
|
||||
@@ -43,13 +43,27 @@ Singleton {
|
||||
property string wallpaperCyclingMode: "interval" // "interval" or "time"
|
||||
property int wallpaperCyclingInterval: 300 // seconds (5 minutes)
|
||||
property string wallpaperCyclingTime: "06:00" // HH:mm format
|
||||
property var monitorCyclingSettings: ({})
|
||||
property string lastBrightnessDevice: ""
|
||||
property string notepadContent: ""
|
||||
property string notepadCurrentFileName: ""
|
||||
property string notepadCurrentFileUrl: ""
|
||||
property string notepadLastSavedContent: ""
|
||||
property var notepadTabs: []
|
||||
property int notepadCurrentTabIndex: 0
|
||||
property string launchPrefix: ""
|
||||
property string wallpaperTransition: "fade"
|
||||
readonly property var availableWallpaperTransitions: ["none", "fade", "wipe", "disc", "stripes", "iris bloom", "pixelate", "portal"]
|
||||
property var includedTransitions: availableWallpaperTransitions.filter(t => t !== "none")
|
||||
|
||||
// Power management settings - AC Power
|
||||
property int acMonitorTimeout: 0 // Never
|
||||
property int acLockTimeout: 0 // Never
|
||||
property int acSuspendTimeout: 0 // Never
|
||||
property int acHibernateTimeout: 0 // Never
|
||||
|
||||
// Power management settings - Battery
|
||||
property int batteryMonitorTimeout: 0 // Never
|
||||
property int batteryLockTimeout: 0 // Never
|
||||
property int batterySuspendTimeout: 0 // Never
|
||||
property int batteryHibernateTimeout: 0 // Never
|
||||
|
||||
property bool lockBeforeSuspend: false
|
||||
|
||||
|
||||
Component.onCompleted: {
|
||||
loadSettings()
|
||||
@@ -103,46 +117,26 @@ Singleton {
|
||||
wallpaperCyclingMode = settings.wallpaperCyclingMode !== undefined ? settings.wallpaperCyclingMode : "interval"
|
||||
wallpaperCyclingInterval = settings.wallpaperCyclingInterval !== undefined ? settings.wallpaperCyclingInterval : 300
|
||||
wallpaperCyclingTime = settings.wallpaperCyclingTime !== undefined ? settings.wallpaperCyclingTime : "06:00"
|
||||
monitorCyclingSettings = settings.monitorCyclingSettings !== undefined ? settings.monitorCyclingSettings : {}
|
||||
lastBrightnessDevice = settings.lastBrightnessDevice !== undefined ? settings.lastBrightnessDevice : ""
|
||||
notepadContent = settings.notepadContent !== undefined ? settings.notepadContent : ""
|
||||
launchPrefix = settings.launchPrefix !== undefined ? settings.launchPrefix : ""
|
||||
wallpaperTransition = settings.wallpaperTransition !== undefined ? settings.wallpaperTransition : "fade"
|
||||
includedTransitions = settings.includedTransitions !== undefined ? settings.includedTransitions : availableWallpaperTransitions.filter(t => t !== "none")
|
||||
|
||||
acMonitorTimeout = settings.acMonitorTimeout !== undefined ? settings.acMonitorTimeout : 0
|
||||
acLockTimeout = settings.acLockTimeout !== undefined ? settings.acLockTimeout : 0
|
||||
acSuspendTimeout = settings.acSuspendTimeout !== undefined ? settings.acSuspendTimeout : 0
|
||||
acHibernateTimeout = settings.acHibernateTimeout !== undefined ? settings.acHibernateTimeout : 0
|
||||
batteryMonitorTimeout = settings.batteryMonitorTimeout !== undefined ? settings.batteryMonitorTimeout : 0
|
||||
batteryLockTimeout = settings.batteryLockTimeout !== undefined ? settings.batteryLockTimeout : 0
|
||||
batterySuspendTimeout = settings.batterySuspendTimeout !== undefined ? settings.batterySuspendTimeout : 0
|
||||
batteryHibernateTimeout = settings.batteryHibernateTimeout !== undefined ? settings.batteryHibernateTimeout : 0
|
||||
lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false
|
||||
|
||||
// Generate system themes but don't override user's theme choice
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
notepadCurrentFileName = settings.notepadCurrentFileName !== undefined ? settings.notepadCurrentFileName : ""
|
||||
notepadCurrentFileUrl = settings.notepadCurrentFileUrl !== undefined ? settings.notepadCurrentFileUrl : ""
|
||||
notepadLastSavedContent = settings.notepadLastSavedContent !== undefined ? settings.notepadLastSavedContent : ""
|
||||
notepadTabs = settings.notepadTabs !== undefined ? settings.notepadTabs : []
|
||||
notepadCurrentTabIndex = settings.notepadCurrentTabIndex !== undefined ? settings.notepadCurrentTabIndex : 0
|
||||
|
||||
// Migrate legacy single notepad to tabs if needed
|
||||
if (notepadTabs.length === 0 && (notepadContent || notepadCurrentFileName)) {
|
||||
notepadTabs = [{
|
||||
id: Date.now(),
|
||||
title: notepadCurrentFileName || "Untitled",
|
||||
content: notepadContent,
|
||||
fileName: notepadCurrentFileName,
|
||||
fileUrl: notepadCurrentFileUrl,
|
||||
lastSavedContent: notepadLastSavedContent,
|
||||
hasUnsavedChanges: false
|
||||
}]
|
||||
notepadCurrentTabIndex = 0
|
||||
}
|
||||
|
||||
// Ensure at least one tab exists
|
||||
if (notepadTabs.length === 0) {
|
||||
notepadTabs = [{
|
||||
id: Date.now(),
|
||||
title: "Untitled",
|
||||
content: "",
|
||||
fileName: "",
|
||||
fileUrl: "",
|
||||
lastSavedContent: "",
|
||||
hasUnsavedChanges: false
|
||||
}]
|
||||
notepadCurrentTabIndex = 0
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -178,13 +172,20 @@ Singleton {
|
||||
"wallpaperCyclingMode": wallpaperCyclingMode,
|
||||
"wallpaperCyclingInterval": wallpaperCyclingInterval,
|
||||
"wallpaperCyclingTime": wallpaperCyclingTime,
|
||||
"monitorCyclingSettings": monitorCyclingSettings,
|
||||
"lastBrightnessDevice": lastBrightnessDevice,
|
||||
"notepadContent": notepadContent,
|
||||
"notepadCurrentFileName": notepadCurrentFileName,
|
||||
"notepadCurrentFileUrl": notepadCurrentFileUrl,
|
||||
"notepadLastSavedContent": notepadLastSavedContent,
|
||||
"notepadTabs": notepadTabs,
|
||||
"notepadCurrentTabIndex": notepadCurrentTabIndex
|
||||
"launchPrefix": launchPrefix,
|
||||
"wallpaperTransition": wallpaperTransition,
|
||||
"includedTransitions": includedTransitions,
|
||||
"acMonitorTimeout": acMonitorTimeout,
|
||||
"acLockTimeout": acLockTimeout,
|
||||
"acSuspendTimeout": acSuspendTimeout,
|
||||
"acHibernateTimeout": acHibernateTimeout,
|
||||
"batteryMonitorTimeout": batteryMonitorTimeout,
|
||||
"batteryLockTimeout": batteryLockTimeout,
|
||||
"batterySuspendTimeout": batterySuspendTimeout,
|
||||
"batteryHibernateTimeout": batteryHibernateTimeout,
|
||||
"lockBeforeSuspend": lockBeforeSuspend
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
@@ -266,9 +267,6 @@ Singleton {
|
||||
saveSettings()
|
||||
|
||||
if (typeof Theme !== "undefined") {
|
||||
if (Theme.currentTheme === Theme.dynamic) {
|
||||
Theme.extractColors()
|
||||
}
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
@@ -278,9 +276,6 @@ Singleton {
|
||||
saveSettings()
|
||||
|
||||
if (typeof Theme !== "undefined") {
|
||||
if (Theme.currentTheme === Theme.dynamic) {
|
||||
Theme.extractColors()
|
||||
}
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
@@ -374,14 +369,57 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function getMonitorCyclingSettings(screenName) {
|
||||
return monitorCyclingSettings[screenName] || {
|
||||
enabled: false,
|
||||
mode: "interval",
|
||||
interval: 300,
|
||||
time: "06:00"
|
||||
}
|
||||
}
|
||||
|
||||
function setMonitorCyclingEnabled(screenName, enabled) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].enabled = enabled
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingMode(screenName, mode) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].mode = mode
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingInterval(screenName, interval) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].interval = interval
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingTime(screenName, time) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].time = time
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setPerMonitorWallpaper(enabled) {
|
||||
perMonitorWallpaper = enabled
|
||||
|
||||
// Disable automatic cycling when per-monitor mode is enabled
|
||||
if (enabled && wallpaperCyclingEnabled) {
|
||||
wallpaperCyclingEnabled = false
|
||||
}
|
||||
|
||||
saveSettings()
|
||||
|
||||
// Refresh dynamic theming when per-monitor mode changes
|
||||
@@ -404,10 +442,6 @@ Singleton {
|
||||
if (typeof Theme !== "undefined" && typeof Quickshell !== "undefined") {
|
||||
var screens = Quickshell.screens
|
||||
if (screens.length > 0 && screenName === screens[0].name) {
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.wallpaperDynamicTheming) {
|
||||
Theme.switchTheme("dynamic")
|
||||
Theme.extractColors()
|
||||
}
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
@@ -425,6 +459,61 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLaunchPrefix(prefix) {
|
||||
launchPrefix = prefix
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperTransition(transition) {
|
||||
wallpaperTransition = transition
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setAcMonitorTimeout(timeout) {
|
||||
acMonitorTimeout = timeout
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setAcLockTimeout(timeout) {
|
||||
acLockTimeout = timeout
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setAcSuspendTimeout(timeout) {
|
||||
acSuspendTimeout = timeout
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setBatteryMonitorTimeout(timeout) {
|
||||
batteryMonitorTimeout = timeout
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setBatteryLockTimeout(timeout) {
|
||||
batteryLockTimeout = timeout
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setBatterySuspendTimeout(timeout) {
|
||||
batterySuspendTimeout = timeout
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setAcHibernateTimeout(timeout) {
|
||||
acHibernateTimeout = timeout
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setBatteryHibernateTimeout(timeout) {
|
||||
batteryHibernateTimeout = timeout
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLockBeforeSuspend(enabled) {
|
||||
lockBeforeSuspend = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: settingsFile
|
||||
|
||||
@@ -594,4 +683,4 @@ Singleton {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import QtCore
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
@@ -14,9 +15,10 @@ Singleton {
|
||||
// Theme settings
|
||||
property string currentThemeName: "blue"
|
||||
property string customThemeFile: ""
|
||||
property string matugenScheme: "scheme-tonal-spot"
|
||||
property real topBarTransparency: 0.75
|
||||
property real topBarWidgetTransparency: 0.85
|
||||
property real popupTransparency: 0.92
|
||||
property real popupTransparency: 1.0
|
||||
property real dockTransparency: 1
|
||||
property bool use24HourClock: true
|
||||
property bool useFahrenheit: false
|
||||
@@ -44,12 +46,23 @@ Singleton {
|
||||
property bool controlCenterShowNetworkIcon: true
|
||||
property bool controlCenterShowBluetoothIcon: true
|
||||
property bool controlCenterShowAudioIcon: true
|
||||
property var controlCenterWidgets: [
|
||||
{"id": "volumeSlider", "enabled": true, "width": 50},
|
||||
{"id": "brightnessSlider", "enabled": true, "width": 50},
|
||||
{"id": "wifi", "enabled": true, "width": 50},
|
||||
{"id": "bluetooth", "enabled": true, "width": 50},
|
||||
{"id": "audioOutput", "enabled": true, "width": 50},
|
||||
{"id": "audioInput", "enabled": true, "width": 50},
|
||||
{"id": "nightMode", "enabled": true, "width": 50},
|
||||
{"id": "darkMode", "enabled": true, "width": 50}
|
||||
]
|
||||
property bool showWorkspaceIndex: false
|
||||
property bool showWorkspacePadding: false
|
||||
property bool showWorkspaceApps: false
|
||||
property int maxWorkspaceIcons: 3
|
||||
property bool workspacesPerMonitor: true
|
||||
property var workspaceNameIcons: ({})
|
||||
property bool waveProgressEnabled: true
|
||||
property bool clockCompactMode: false
|
||||
property bool focusedWindowCompactMode: false
|
||||
property bool runningAppsCompactMode: true
|
||||
@@ -76,16 +89,34 @@ Singleton {
|
||||
property string osLogoColorOverride: ""
|
||||
property real osLogoBrightness: 0.5
|
||||
property real osLogoContrast: 1
|
||||
property bool wallpaperDynamicTheming: true
|
||||
property bool weatherEnabled: true
|
||||
property string fontFamily: "Inter Variable"
|
||||
property string monoFontFamily: "Fira Code"
|
||||
property int fontWeight: Font.Normal
|
||||
property real fontScale: 1.0
|
||||
property bool notepadUseMonospace: true
|
||||
property string notepadFontFamily: ""
|
||||
property real notepadFontSize: 14
|
||||
property bool notepadShowLineNumbers: false
|
||||
property real notepadTransparencyOverride: -1
|
||||
property real notepadLastCustomTransparency: 0.7
|
||||
|
||||
onNotepadUseMonospaceChanged: saveSettings()
|
||||
onNotepadFontFamilyChanged: saveSettings()
|
||||
onNotepadFontSizeChanged: saveSettings()
|
||||
onNotepadShowLineNumbersChanged: saveSettings()
|
||||
onNotepadTransparencyOverrideChanged: {
|
||||
if (notepadTransparencyOverride > 0) {
|
||||
notepadLastCustomTransparency = notepadTransparencyOverride
|
||||
}
|
||||
saveSettings()
|
||||
}
|
||||
onNotepadLastCustomTransparencyChanged: saveSettings()
|
||||
property bool gtkThemingEnabled: false
|
||||
property bool qtThemingEnabled: false
|
||||
property bool showDock: false
|
||||
property bool dockAutoHide: false
|
||||
property bool dockGroupByApp: false
|
||||
property real cornerRadius: 12
|
||||
property bool notificationOverlayEnabled: false
|
||||
property bool topBarAutoHide: false
|
||||
@@ -96,8 +127,11 @@ Singleton {
|
||||
property real topBarInnerPadding: 8
|
||||
property bool topBarSquareCorners: false
|
||||
property bool topBarNoBackground: false
|
||||
property bool topBarGothCornersEnabled: false
|
||||
property bool lockScreenShowPowerActions: true
|
||||
property bool hideBrightnessSlider: false
|
||||
property string widgetBackgroundColor: "sch"
|
||||
property string surfaceBase: "s"
|
||||
property int notificationTimeoutLow: 5000
|
||||
property int notificationTimeoutNormal: 5000
|
||||
property int notificationTimeoutCritical: 0
|
||||
@@ -106,12 +140,14 @@ Singleton {
|
||||
readonly property string defaultMonoFontFamily: "Fira Code"
|
||||
readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
|
||||
readonly property string _configUrl: StandardPaths.writableLocation(StandardPaths.ConfigLocation)
|
||||
readonly property string _configDir: _configUrl.startsWith("file://") ? _configUrl.substring(7) : _configUrl
|
||||
readonly property string _configDir: Paths.strip(_configUrl)
|
||||
|
||||
signal forceTopBarLayoutRefresh
|
||||
signal widgetDataChanged
|
||||
signal workspaceIconsUpdated
|
||||
|
||||
property bool _loading: false
|
||||
|
||||
function getEffectiveTimeFormat() {
|
||||
if (use24HourClock) {
|
||||
return Locale.ShortFormat
|
||||
@@ -136,7 +172,8 @@ Singleton {
|
||||
"enabled": true,
|
||||
"size": 20,
|
||||
"selectedGpuIndex": 0,
|
||||
"pciId": ""
|
||||
"pciId": "",
|
||||
"mountPath": "/"
|
||||
}
|
||||
leftWidgetsModel.append(dummyItem)
|
||||
centerWidgetsModel.append(dummyItem)
|
||||
@@ -148,10 +185,13 @@ Singleton {
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
_loading = true
|
||||
parseSettings(settingsFile.text())
|
||||
_loading = false
|
||||
}
|
||||
|
||||
function parseSettings(content) {
|
||||
_loading = true
|
||||
try {
|
||||
if (content && content.trim()) {
|
||||
var settings = JSON.parse(content)
|
||||
@@ -168,9 +208,10 @@ Singleton {
|
||||
currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "blue"
|
||||
}
|
||||
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : ""
|
||||
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot"
|
||||
topBarTransparency = settings.topBarTransparency !== undefined ? (settings.topBarTransparency > 1 ? settings.topBarTransparency / 100 : settings.topBarTransparency) : 0.75
|
||||
topBarWidgetTransparency = settings.topBarWidgetTransparency !== undefined ? (settings.topBarWidgetTransparency > 1 ? settings.topBarWidgetTransparency / 100 : settings.topBarWidgetTransparency) : 0.85
|
||||
popupTransparency = settings.popupTransparency !== undefined ? (settings.popupTransparency > 1 ? settings.popupTransparency / 100 : settings.popupTransparency) : 0.92
|
||||
popupTransparency = settings.popupTransparency !== undefined ? (settings.popupTransparency > 1 ? settings.popupTransparency / 100 : settings.popupTransparency) : 1.0
|
||||
dockTransparency = settings.dockTransparency !== undefined ? (settings.dockTransparency > 1 ? settings.dockTransparency / 100 : settings.dockTransparency) : 1
|
||||
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true
|
||||
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false
|
||||
@@ -199,12 +240,23 @@ Singleton {
|
||||
controlCenterShowNetworkIcon = settings.controlCenterShowNetworkIcon !== undefined ? settings.controlCenterShowNetworkIcon : true
|
||||
controlCenterShowBluetoothIcon = settings.controlCenterShowBluetoothIcon !== undefined ? settings.controlCenterShowBluetoothIcon : true
|
||||
controlCenterShowAudioIcon = settings.controlCenterShowAudioIcon !== undefined ? settings.controlCenterShowAudioIcon : true
|
||||
controlCenterWidgets = settings.controlCenterWidgets !== undefined ? settings.controlCenterWidgets : [
|
||||
{"id": "volumeSlider", "enabled": true, "width": 50},
|
||||
{"id": "brightnessSlider", "enabled": true, "width": 50},
|
||||
{"id": "wifi", "enabled": true, "width": 50},
|
||||
{"id": "bluetooth", "enabled": true, "width": 50},
|
||||
{"id": "audioOutput", "enabled": true, "width": 50},
|
||||
{"id": "audioInput", "enabled": true, "width": 50},
|
||||
{"id": "nightMode", "enabled": true, "width": 50},
|
||||
{"id": "darkMode", "enabled": true, "width": 50}
|
||||
]
|
||||
showWorkspaceIndex = settings.showWorkspaceIndex !== undefined ? settings.showWorkspaceIndex : false
|
||||
showWorkspacePadding = settings.showWorkspacePadding !== undefined ? settings.showWorkspacePadding : false
|
||||
showWorkspaceApps = settings.showWorkspaceApps !== undefined ? settings.showWorkspaceApps : false
|
||||
maxWorkspaceIcons = settings.maxWorkspaceIcons !== undefined ? settings.maxWorkspaceIcons : 3
|
||||
workspaceNameIcons = settings.workspaceNameIcons !== undefined ? settings.workspaceNameIcons : ({})
|
||||
workspacesPerMonitor = settings.workspacesPerMonitor !== undefined ? settings.workspacesPerMonitor : true
|
||||
waveProgressEnabled = settings.waveProgressEnabled !== undefined ? settings.waveProgressEnabled : true
|
||||
clockCompactMode = settings.clockCompactMode !== undefined ? settings.clockCompactMode : false
|
||||
focusedWindowCompactMode = settings.focusedWindowCompactMode !== undefined ? settings.focusedWindowCompactMode : false
|
||||
runningAppsCompactMode = settings.runningAppsCompactMode !== undefined ? settings.runningAppsCompactMode : true
|
||||
@@ -241,15 +293,21 @@ Singleton {
|
||||
osLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : ""
|
||||
osLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5
|
||||
osLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1
|
||||
wallpaperDynamicTheming = settings.wallpaperDynamicTheming !== undefined ? settings.wallpaperDynamicTheming : true
|
||||
fontFamily = settings.fontFamily !== undefined ? settings.fontFamily : defaultFontFamily
|
||||
monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : defaultMonoFontFamily
|
||||
fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal
|
||||
fontScale = settings.fontScale !== undefined ? settings.fontScale : 1.0
|
||||
notepadUseMonospace = settings.notepadUseMonospace !== undefined ? settings.notepadUseMonospace : true
|
||||
notepadFontFamily = settings.notepadFontFamily !== undefined ? settings.notepadFontFamily : ""
|
||||
notepadFontSize = settings.notepadFontSize !== undefined ? settings.notepadFontSize : 14
|
||||
notepadShowLineNumbers = settings.notepadShowLineNumbers !== undefined ? settings.notepadShowLineNumbers : false
|
||||
notepadTransparencyOverride = settings.notepadTransparencyOverride !== undefined ? settings.notepadTransparencyOverride : -1
|
||||
notepadLastCustomTransparency = settings.notepadLastCustomTransparency !== undefined ? settings.notepadLastCustomTransparency : 0.95
|
||||
gtkThemingEnabled = settings.gtkThemingEnabled !== undefined ? settings.gtkThemingEnabled : false
|
||||
qtThemingEnabled = settings.qtThemingEnabled !== undefined ? settings.qtThemingEnabled : false
|
||||
showDock = settings.showDock !== undefined ? settings.showDock : false
|
||||
dockAutoHide = settings.dockAutoHide !== undefined ? settings.dockAutoHide : false
|
||||
dockGroupByApp = settings.dockGroupByApp !== undefined ? settings.dockGroupByApp : false
|
||||
cornerRadius = settings.cornerRadius !== undefined ? settings.cornerRadius : 12
|
||||
notificationOverlayEnabled = settings.notificationOverlayEnabled !== undefined ? settings.notificationOverlayEnabled : false
|
||||
topBarAutoHide = settings.topBarAutoHide !== undefined ? settings.topBarAutoHide : false
|
||||
@@ -263,8 +321,11 @@ Singleton {
|
||||
topBarInnerPadding = settings.topBarInnerPadding !== undefined ? settings.topBarInnerPadding : 8
|
||||
topBarSquareCorners = settings.topBarSquareCorners !== undefined ? settings.topBarSquareCorners : false
|
||||
topBarNoBackground = settings.topBarNoBackground !== undefined ? settings.topBarNoBackground : false
|
||||
topBarGothCornersEnabled = settings.topBarGothCornersEnabled !== undefined ? settings.topBarGothCornersEnabled : false
|
||||
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true
|
||||
hideBrightnessSlider = settings.hideBrightnessSlider !== undefined ? settings.hideBrightnessSlider : false
|
||||
widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch"
|
||||
surfaceBase = settings.surfaceBase !== undefined ? settings.surfaceBase : "s"
|
||||
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({})
|
||||
applyStoredTheme()
|
||||
detectAvailableIconThemes()
|
||||
@@ -276,13 +337,18 @@ Singleton {
|
||||
}
|
||||
} catch (e) {
|
||||
applyStoredTheme()
|
||||
} finally {
|
||||
_loading = false
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
if (_loading)
|
||||
return
|
||||
settingsFile.setText(JSON.stringify({
|
||||
"currentThemeName": currentThemeName,
|
||||
"customThemeFile": customThemeFile,
|
||||
"matugenScheme": matugenScheme,
|
||||
"topBarTransparency": topBarTransparency,
|
||||
"topBarWidgetTransparency": topBarWidgetTransparency,
|
||||
"popupTransparency": popupTransparency,
|
||||
@@ -314,12 +380,14 @@ Singleton {
|
||||
"controlCenterShowNetworkIcon": controlCenterShowNetworkIcon,
|
||||
"controlCenterShowBluetoothIcon": controlCenterShowBluetoothIcon,
|
||||
"controlCenterShowAudioIcon": controlCenterShowAudioIcon,
|
||||
"controlCenterWidgets": controlCenterWidgets,
|
||||
"showWorkspaceIndex": showWorkspaceIndex,
|
||||
"showWorkspacePadding": showWorkspacePadding,
|
||||
"showWorkspaceApps": showWorkspaceApps,
|
||||
"maxWorkspaceIcons": maxWorkspaceIcons,
|
||||
"workspacesPerMonitor": workspacesPerMonitor,
|
||||
"workspaceNameIcons": workspaceNameIcons,
|
||||
"waveProgressEnabled": waveProgressEnabled,
|
||||
"clockCompactMode": clockCompactMode,
|
||||
"focusedWindowCompactMode": focusedWindowCompactMode,
|
||||
"runningAppsCompactMode": runningAppsCompactMode,
|
||||
@@ -338,15 +406,21 @@ Singleton {
|
||||
"osLogoColorOverride": osLogoColorOverride,
|
||||
"osLogoBrightness": osLogoBrightness,
|
||||
"osLogoContrast": osLogoContrast,
|
||||
"wallpaperDynamicTheming": wallpaperDynamicTheming,
|
||||
"fontFamily": fontFamily,
|
||||
"monoFontFamily": monoFontFamily,
|
||||
"fontWeight": fontWeight,
|
||||
"fontScale": fontScale,
|
||||
"notepadUseMonospace": notepadUseMonospace,
|
||||
"notepadFontFamily": notepadFontFamily,
|
||||
"notepadFontSize": notepadFontSize,
|
||||
"notepadShowLineNumbers": notepadShowLineNumbers,
|
||||
"notepadTransparencyOverride": notepadTransparencyOverride,
|
||||
"notepadLastCustomTransparency": notepadLastCustomTransparency,
|
||||
"gtkThemingEnabled": gtkThemingEnabled,
|
||||
"qtThemingEnabled": qtThemingEnabled,
|
||||
"showDock": showDock,
|
||||
"dockAutoHide": dockAutoHide,
|
||||
"dockGroupByApp": dockGroupByApp,
|
||||
"cornerRadius": cornerRadius,
|
||||
"notificationOverlayEnabled": notificationOverlayEnabled,
|
||||
"topBarAutoHide": topBarAutoHide,
|
||||
@@ -357,8 +431,11 @@ Singleton {
|
||||
"topBarInnerPadding": topBarInnerPadding,
|
||||
"topBarSquareCorners": topBarSquareCorners,
|
||||
"topBarNoBackground": topBarNoBackground,
|
||||
"topBarGothCornersEnabled": topBarGothCornersEnabled,
|
||||
"lockScreenShowPowerActions": lockScreenShowPowerActions,
|
||||
"hideBrightnessSlider": hideBrightnessSlider,
|
||||
"widgetBackgroundColor": widgetBackgroundColor,
|
||||
"surfaceBase": surfaceBase,
|
||||
"notificationTimeoutLow": notificationTimeoutLow,
|
||||
"notificationTimeoutNormal": notificationTimeoutNormal,
|
||||
"notificationTimeoutCritical": notificationTimeoutCritical,
|
||||
@@ -391,6 +468,11 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWaveProgressEnabled(enabled) {
|
||||
waveProgressEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWorkspaceNameIcon(workspaceName, iconData) {
|
||||
var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons))
|
||||
iconMap[workspaceName] = iconData
|
||||
@@ -491,6 +573,19 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMatugenScheme(scheme) {
|
||||
var normalized = scheme || "scheme-tonal-spot"
|
||||
if (matugenScheme === normalized)
|
||||
return
|
||||
|
||||
matugenScheme = normalized
|
||||
saveSettings()
|
||||
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
|
||||
function setTopBarTransparency(transparency) {
|
||||
topBarTransparency = transparency
|
||||
saveSettings()
|
||||
@@ -627,6 +722,10 @@ Singleton {
|
||||
controlCenterShowAudioIcon = enabled
|
||||
saveSettings()
|
||||
}
|
||||
function setControlCenterWidgets(widgets) {
|
||||
controlCenterWidgets = widgets
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarWidgetOrder(order) {
|
||||
topBarWidgetOrder = order
|
||||
@@ -659,6 +758,7 @@ Singleton {
|
||||
var size = typeof order[i] === "string" ? undefined : order[i].size
|
||||
var selectedGpuIndex = typeof order[i] === "string" ? undefined : order[i].selectedGpuIndex
|
||||
var pciId = typeof order[i] === "string" ? undefined : order[i].pciId
|
||||
var mountPath = typeof order[i] === "string" ? undefined : order[i].mountPath
|
||||
var item = {
|
||||
"widgetId": widgetId,
|
||||
"enabled": enabled
|
||||
@@ -669,6 +769,8 @@ Singleton {
|
||||
item.selectedGpuIndex = selectedGpuIndex
|
||||
if (pciId !== undefined)
|
||||
item.pciId = pciId
|
||||
if (mountPath !== undefined)
|
||||
item.mountPath = mountPath
|
||||
|
||||
listModel.append(item)
|
||||
}
|
||||
@@ -773,7 +875,7 @@ Singleton {
|
||||
|
||||
function updateQtIconTheme(themeName) {
|
||||
var qtThemeName = (themeName === "System Default") ? "" : themeName
|
||||
var home = _shq(root._homeUrl.replace("file://", ""))
|
||||
var home = _shq(Paths.strip(root._homeUrl))
|
||||
if (!qtThemeName) {
|
||||
// When "System Default" is selected, don't modify the config files at all
|
||||
// This preserves the user's existing qt6ct configuration
|
||||
@@ -811,11 +913,6 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperDynamicTheming(enabled) {
|
||||
wallpaperDynamicTheming = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setFontFamily(family) {
|
||||
fontFamily = family
|
||||
saveSettings()
|
||||
@@ -862,6 +959,11 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setDockGroupByApp(enabled) {
|
||||
dockGroupByApp = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setCornerRadius(radius) {
|
||||
cornerRadius = radius
|
||||
saveSettings()
|
||||
@@ -932,6 +1034,11 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarGothCornersEnabled(enabled) {
|
||||
topBarGothCornersEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLockScreenShowPowerActions(enabled) {
|
||||
lockScreenShowPowerActions = enabled
|
||||
saveSettings()
|
||||
@@ -942,6 +1049,19 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWidgetBackgroundColor(color) {
|
||||
widgetBackgroundColor = color
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setSurfaceBase(base) {
|
||||
surfaceBase = base
|
||||
saveSettings()
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
|
||||
function setScreenPreferences(prefs) {
|
||||
screenPreferences = prefs
|
||||
saveSettings()
|
||||
@@ -1006,6 +1126,7 @@ Singleton {
|
||||
path: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
watchChanges: true
|
||||
onLoaded: {
|
||||
parseSettings(settingsFile.text())
|
||||
|
||||
@@ -1,41 +1,136 @@
|
||||
// Stock theme definitions for DankMaterialShell
|
||||
// Separated from Theme.qml to keep that file clean
|
||||
|
||||
const CatppuccinMocha = {
|
||||
surface: "#45475a",
|
||||
surfaceText: "#cdd6f4",
|
||||
surfaceVariant: "#45475a",
|
||||
surfaceVariantText: "#a6adc8",
|
||||
background: "#1e1e2e",
|
||||
backgroundText: "#cdd6f4",
|
||||
outline: "#6c7086",
|
||||
surfaceContainer: "#313244",
|
||||
surfaceContainerHigh: "#585b70",
|
||||
surfaceContainerHighest: "#7f849c"
|
||||
}
|
||||
|
||||
const CatppuccinLatte = {
|
||||
surface: "#bcc0cc",
|
||||
surfaceText: "#4c4f69",
|
||||
surfaceVariant: "#bcc0cc",
|
||||
surfaceVariantText: "#6c6f85",
|
||||
background: "#eff1f5",
|
||||
backgroundText: "#4c4f69",
|
||||
outline: "#9ca0b0",
|
||||
surfaceContainer: "#ccd0da",
|
||||
surfaceContainerHigh: "#acb0be",
|
||||
surfaceContainerHighest: "#8c8fa1"
|
||||
}
|
||||
|
||||
const CatppuccinVariants = {
|
||||
"cat-rosewater": {
|
||||
name: "Rosewater",
|
||||
dark: { primary: "#f5e0dc", secondary: "#f2cdcd", primaryText: "#1e1e2e", primaryContainer: "#8b6b5e", surfaceTint: "#f5e0dc" },
|
||||
light: { primary: "#dc8a78", secondary: "#dd7878", primaryText: "#ffffff", primaryContainer: "#f4d2ca", surfaceTint: "#dc8a78" }
|
||||
},
|
||||
"cat-flamingo": {
|
||||
name: "Flamingo",
|
||||
dark: { primary: "#f2cdcd", secondary: "#f5e0dc", primaryText: "#1e1e2e", primaryContainer: "#885d62", surfaceTint: "#f2cdcd" },
|
||||
light: { primary: "#dd7878", secondary: "#dc8a78", primaryText: "#ffffff", primaryContainer: "#f4caca", surfaceTint: "#dd7878" }
|
||||
},
|
||||
"cat-pink": {
|
||||
name: "Pink",
|
||||
dark: { primary: "#f5c2e7", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#8b537a", surfaceTint: "#f5c2e7" },
|
||||
light: { primary: "#ea76cb", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#f7c9e7", surfaceTint: "#ea76cb" }
|
||||
},
|
||||
"cat-mauve": {
|
||||
name: "Mauve",
|
||||
dark: { primary: "#cba6f7", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#61378a", surfaceTint: "#cba6f7" },
|
||||
light: { primary: "#8839ef", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#e4d3ff", surfaceTint: "#8839ef" }
|
||||
},
|
||||
"cat-red": {
|
||||
name: "Red",
|
||||
dark: { primary: "#f38ba8", secondary: "#eba0ac", primaryText: "#1e1e2e", primaryContainer: "#891c3b", surfaceTint: "#f38ba8" },
|
||||
light: { primary: "#d20f39", secondary: "#e64553", primaryText: "#ffffff", primaryContainer: "#f1b8c4", surfaceTint: "#d20f39" }
|
||||
},
|
||||
"cat-maroon": {
|
||||
name: "Maroon",
|
||||
dark: { primary: "#eba0ac", secondary: "#f38ba8", primaryText: "#1e1e2e", primaryContainer: "#81313f", surfaceTint: "#eba0ac" },
|
||||
light: { primary: "#e64553", secondary: "#d20f39", primaryText: "#ffffff", primaryContainer: "#f4c3c8", surfaceTint: "#e64553" }
|
||||
},
|
||||
"cat-peach": {
|
||||
name: "Peach",
|
||||
dark: { primary: "#fab387", secondary: "#f9e2af", primaryText: "#1e1e2e", primaryContainer: "#90441a", surfaceTint: "#fab387" },
|
||||
light: { primary: "#fe640b", secondary: "#df8e1d", primaryText: "#ffffff", primaryContainer: "#ffddcc", surfaceTint: "#fe640b" }
|
||||
},
|
||||
"cat-yellow": {
|
||||
name: "Yellow",
|
||||
dark: { primary: "#f9e2af", secondary: "#a6e3a1", primaryText: "#1e1e2e", primaryContainer: "#8f7342", surfaceTint: "#f9e2af" },
|
||||
light: { primary: "#df8e1d", secondary: "#40a02b", primaryText: "#ffffff", primaryContainer: "#fff3cc", surfaceTint: "#df8e1d" }
|
||||
},
|
||||
"cat-green": {
|
||||
name: "Green",
|
||||
dark: { primary: "#a6e3a1", secondary: "#94e2d5", primaryText: "#1e1e2e", primaryContainer: "#3c7534", surfaceTint: "#a6e3a1" },
|
||||
light: { primary: "#40a02b", secondary: "#179299", primaryText: "#ffffff", primaryContainer: "#d4f5d4", surfaceTint: "#40a02b" }
|
||||
},
|
||||
"cat-teal": {
|
||||
name: "Teal",
|
||||
dark: { primary: "#94e2d5", secondary: "#89dceb", primaryText: "#1e1e2e", primaryContainer: "#2a7468", surfaceTint: "#94e2d5" },
|
||||
light: { primary: "#179299", secondary: "#04a5e5", primaryText: "#ffffff", primaryContainer: "#ccf2f2", surfaceTint: "#179299" }
|
||||
},
|
||||
"cat-sky": {
|
||||
name: "Sky",
|
||||
dark: { primary: "#89dceb", secondary: "#74c7ec", primaryText: "#1e1e2e", primaryContainer: "#196e7e", surfaceTint: "#89dceb" },
|
||||
light: { primary: "#04a5e5", secondary: "#209fb5", primaryText: "#ffffff", primaryContainer: "#ccebff", surfaceTint: "#04a5e5" }
|
||||
},
|
||||
"cat-sapphire": {
|
||||
name: "Sapphire",
|
||||
dark: { primary: "#74c7ec", secondary: "#89b4fa", primaryText: "#1e1e2e", primaryContainer: "#0a597f", surfaceTint: "#74c7ec" },
|
||||
light: { primary: "#209fb5", secondary: "#1e66f5", primaryText: "#ffffff", primaryContainer: "#d0f0f5", surfaceTint: "#209fb5" }
|
||||
},
|
||||
"cat-blue": {
|
||||
name: "Blue",
|
||||
dark: { primary: "#89b4fa", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#19468d", surfaceTint: "#89b4fa" },
|
||||
light: { primary: "#1e66f5", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#ccd9ff", surfaceTint: "#1e66f5" }
|
||||
},
|
||||
"cat-lavender": {
|
||||
name: "Lavender",
|
||||
dark: { primary: "#b4befe", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#4a5091", surfaceTint: "#b4befe" },
|
||||
light: { primary: "#7287fd", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#dde1ff", surfaceTint: "#7287fd" }
|
||||
}
|
||||
}
|
||||
|
||||
function getCatppuccinTheme(variant, isLight = false) {
|
||||
const variantData = CatppuccinVariants[variant]
|
||||
if (!variantData) return null
|
||||
|
||||
const baseColors = isLight ? CatppuccinLatte : CatppuccinMocha
|
||||
const accentColors = isLight ? variantData.light : variantData.dark
|
||||
|
||||
return Object.assign({
|
||||
name: `${variantData.name}${isLight ? ' Light' : ''}`
|
||||
}, baseColors, accentColors)
|
||||
}
|
||||
|
||||
const StockThemes = {
|
||||
DARK: {
|
||||
blue: {
|
||||
name: "Blue",
|
||||
primary: "#42a5f5",
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#1976d2",
|
||||
primaryText: "#000000",
|
||||
primaryContainer: "#0d47a1",
|
||||
secondary: "#8ab4f8",
|
||||
surface: "#1a1c1e",
|
||||
surfaceText: "#e3e8ef",
|
||||
surfaceVariant: "#44464f",
|
||||
surfaceVariantText: "#c4c7c5",
|
||||
surface: "#101418",
|
||||
surfaceText: "#e0e2e8",
|
||||
surfaceVariant: "#42474e",
|
||||
surfaceVariantText: "#c2c7cf",
|
||||
surfaceTint: "#8ab4f8",
|
||||
background: "#1a1c1e",
|
||||
backgroundText: "#e3e8ef",
|
||||
outline: "#8e918f",
|
||||
surfaceContainer: "#1e2023",
|
||||
surfaceContainerHigh: "#292b2f"
|
||||
},
|
||||
deepBlue: {
|
||||
name: "Deep Blue",
|
||||
primary: "#0061a4",
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#004881",
|
||||
secondary: "#42a5f5",
|
||||
surface: "#1a1c1e",
|
||||
surfaceText: "#e3e8ef",
|
||||
surfaceVariant: "#44464f",
|
||||
surfaceVariantText: "#c4c7c5",
|
||||
surfaceTint: "#8ab4f8",
|
||||
background: "#1a1c1e",
|
||||
backgroundText: "#e3e8ef",
|
||||
outline: "#8e918f",
|
||||
surfaceContainer: "#1e2023",
|
||||
surfaceContainerHigh: "#292b2f"
|
||||
background: "#101418",
|
||||
backgroundText: "#e0e2e8",
|
||||
outline: "#8c9199",
|
||||
surfaceContainer: "#1d2024",
|
||||
surfaceContainerHigh: "#272a2f",
|
||||
surfaceContainerHighest: "#32353a"
|
||||
},
|
||||
purple: {
|
||||
name: "Purple",
|
||||
@@ -43,135 +138,165 @@ const StockThemes = {
|
||||
primaryText: "#381E72",
|
||||
primaryContainer: "#4F378B",
|
||||
secondary: "#CCC2DC",
|
||||
surface: "#10121E",
|
||||
surfaceText: "#E6E0E9",
|
||||
surfaceVariant: "#49454F",
|
||||
surfaceVariantText: "#CAC4D0",
|
||||
surface: "#141218",
|
||||
surfaceText: "#e6e0e9",
|
||||
surfaceVariant: "#49454e",
|
||||
surfaceVariantText: "#cac4cf",
|
||||
surfaceTint: "#D0BCFF",
|
||||
background: "#10121E",
|
||||
backgroundText: "#E6E0E9",
|
||||
outline: "#938F99",
|
||||
surfaceContainer: "#1D1B20",
|
||||
surfaceContainerHigh: "#2B2930"
|
||||
background: "#141218",
|
||||
backgroundText: "#e6e0e9",
|
||||
outline: "#948f99",
|
||||
surfaceContainer: "#211f24",
|
||||
surfaceContainerHigh: "#2b292f",
|
||||
surfaceContainerHighest: "#36343a"
|
||||
},
|
||||
green: {
|
||||
name: "Green",
|
||||
primary: "#4caf50",
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#388e3c",
|
||||
primaryText: "#000000",
|
||||
primaryContainer: "#1b5e20",
|
||||
secondary: "#81c995",
|
||||
surface: "#0f1411",
|
||||
surfaceText: "#e1f5e3",
|
||||
surfaceVariant: "#404943",
|
||||
surfaceVariantText: "#c1cbc4",
|
||||
surface: "#10140f",
|
||||
surfaceText: "#e0e4db",
|
||||
surfaceVariant: "#424940",
|
||||
surfaceVariantText: "#c2c9bd",
|
||||
surfaceTint: "#81c995",
|
||||
background: "#0f1411",
|
||||
backgroundText: "#e1f5e3",
|
||||
outline: "#8b938c",
|
||||
surfaceContainer: "#1a1f1b",
|
||||
surfaceContainerHigh: "#252a26"
|
||||
background: "#10140f",
|
||||
backgroundText: "#e0e4db",
|
||||
outline: "#8c9388",
|
||||
surfaceContainer: "#1d211b",
|
||||
surfaceContainerHigh: "#272b25",
|
||||
surfaceContainerHighest: "#323630"
|
||||
},
|
||||
orange: {
|
||||
name: "Orange",
|
||||
primary: "#ff6d00",
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#e65100",
|
||||
primaryText: "#000000",
|
||||
primaryContainer: "#3e2723",
|
||||
secondary: "#ffb74d",
|
||||
surface: "#1c1410",
|
||||
surfaceText: "#f5f1ea",
|
||||
surfaceVariant: "#4a453a",
|
||||
surfaceVariantText: "#cbc5b8",
|
||||
surface: "#1a120e",
|
||||
surfaceText: "#f0dfd8",
|
||||
surfaceVariant: "#52443d",
|
||||
surfaceVariantText: "#d7c2b9",
|
||||
surfaceTint: "#ffb74d",
|
||||
background: "#1c1410",
|
||||
backgroundText: "#f5f1ea",
|
||||
outline: "#958f84",
|
||||
surfaceContainer: "#211e17",
|
||||
surfaceContainerHigh: "#2c291f"
|
||||
background: "#1a120e",
|
||||
backgroundText: "#f0dfd8",
|
||||
outline: "#a08d85",
|
||||
surfaceContainer: "#271e1a",
|
||||
surfaceContainerHigh: "#322824",
|
||||
surfaceContainerHighest: "#3d332e"
|
||||
},
|
||||
red: {
|
||||
name: "Red",
|
||||
primary: "#f44336",
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#d32f2f",
|
||||
primaryText: "#000000",
|
||||
primaryContainer: "#4a0e0e",
|
||||
secondary: "#f28b82",
|
||||
surface: "#1c1011",
|
||||
surfaceText: "#f5e8ea",
|
||||
surfaceVariant: "#4a3f41",
|
||||
surfaceVariantText: "#cbc2c4",
|
||||
surface: "#1a1110",
|
||||
surfaceText: "#f1dedc",
|
||||
surfaceVariant: "#534341",
|
||||
surfaceVariantText: "#d8c2be",
|
||||
surfaceTint: "#f28b82",
|
||||
background: "#1c1011",
|
||||
backgroundText: "#f5e8ea",
|
||||
outline: "#958b8d",
|
||||
surfaceContainer: "#211b1c",
|
||||
surfaceContainerHigh: "#2c2426"
|
||||
background: "#1a1110",
|
||||
backgroundText: "#f1dedc",
|
||||
outline: "#a08c89",
|
||||
surfaceContainer: "#271d1c",
|
||||
surfaceContainerHigh: "#322826",
|
||||
surfaceContainerHighest: "#3d3231"
|
||||
},
|
||||
cyan: {
|
||||
name: "Cyan",
|
||||
primary: "#00bcd4",
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#0097a7",
|
||||
primaryText: "#000000",
|
||||
primaryContainer: "#004d5c",
|
||||
secondary: "#4dd0e1",
|
||||
surface: "#0f1617",
|
||||
surfaceText: "#e8f4f5",
|
||||
surfaceVariant: "#3f474a",
|
||||
surfaceVariantText: "#c2c9cb",
|
||||
surface: "#0e1416",
|
||||
surfaceText: "#dee3e5",
|
||||
surfaceVariant: "#3f484a",
|
||||
surfaceVariantText: "#bfc8ca",
|
||||
surfaceTint: "#4dd0e1",
|
||||
background: "#0f1617",
|
||||
backgroundText: "#e8f4f5",
|
||||
outline: "#8c9194",
|
||||
surfaceContainer: "#1a1f20",
|
||||
surfaceContainerHigh: "#252b2c"
|
||||
background: "#0e1416",
|
||||
backgroundText: "#dee3e5",
|
||||
outline: "#899295",
|
||||
surfaceContainer: "#1b2122",
|
||||
surfaceContainerHigh: "#252b2c",
|
||||
surfaceContainerHighest: "#303637"
|
||||
},
|
||||
pink: {
|
||||
name: "Pink",
|
||||
primary: "#e91e63",
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#c2185b",
|
||||
primaryText: "#000000",
|
||||
primaryContainer: "#4a0e2f",
|
||||
secondary: "#f8bbd9",
|
||||
surface: "#1a1014",
|
||||
surfaceText: "#f3e8ee",
|
||||
surfaceVariant: "#483f45",
|
||||
surfaceVariantText: "#c9c2c7",
|
||||
surface: "#191112",
|
||||
surfaceText: "#f0dee0",
|
||||
surfaceVariant: "#524345",
|
||||
surfaceVariantText: "#d6c2c3",
|
||||
surfaceTint: "#f8bbd9",
|
||||
background: "#1a1014",
|
||||
backgroundText: "#f3e8ee",
|
||||
outline: "#938a90",
|
||||
surfaceContainer: "#1f1b1e",
|
||||
surfaceContainerHigh: "#2a2428"
|
||||
background: "#191112",
|
||||
backgroundText: "#f0dee0",
|
||||
outline: "#9f8c8e",
|
||||
surfaceContainer: "#261d1e",
|
||||
surfaceContainerHigh: "#312829",
|
||||
surfaceContainerHighest: "#3c3233"
|
||||
},
|
||||
amber: {
|
||||
name: "Amber",
|
||||
primary: "#ffc107",
|
||||
primaryText: "#000000",
|
||||
primaryContainer: "#ff8f00",
|
||||
primaryContainer: "#4a3c00",
|
||||
secondary: "#ffd54f",
|
||||
surface: "#1a1710",
|
||||
surfaceText: "#f3f0e8",
|
||||
surfaceVariant: "#49453a",
|
||||
surfaceVariantText: "#cac5b8",
|
||||
surface: "#17130b",
|
||||
surfaceText: "#ebe1d4",
|
||||
surfaceVariant: "#4d4639",
|
||||
surfaceVariantText: "#d0c5b4",
|
||||
surfaceTint: "#ffd54f",
|
||||
background: "#1a1710",
|
||||
backgroundText: "#f3f0e8",
|
||||
outline: "#949084",
|
||||
surfaceContainer: "#1f1e17",
|
||||
surfaceContainerHigh: "#2a281f"
|
||||
background: "#17130b",
|
||||
backgroundText: "#ebe1d4",
|
||||
outline: "#998f80",
|
||||
surfaceContainer: "#231f17",
|
||||
surfaceContainerHigh: "#2e2921",
|
||||
surfaceContainerHighest: "#39342b"
|
||||
},
|
||||
coral: {
|
||||
name: "Coral",
|
||||
primary: "#ffb4ab",
|
||||
primaryText: "#5f1412",
|
||||
primaryText: "#000000",
|
||||
primaryContainer: "#8c1d18",
|
||||
secondary: "#f9dedc",
|
||||
surface: "#1a1110",
|
||||
surfaceText: "#f1e8e7",
|
||||
surfaceVariant: "#4a4142",
|
||||
surfaceVariantText: "#cdc2c1",
|
||||
surfaceText: "#f1dedc",
|
||||
surfaceVariant: "#534341",
|
||||
surfaceVariantText: "#d8c2bf",
|
||||
surfaceTint: "#ffb4ab",
|
||||
background: "#1a1110",
|
||||
backgroundText: "#f1e8e7",
|
||||
outline: "#968b8a",
|
||||
surfaceContainer: "#201a19",
|
||||
surfaceContainerHigh: "#2b2221"
|
||||
backgroundText: "#f1dedc",
|
||||
outline: "#a08c8a",
|
||||
surfaceContainer: "#271d1c",
|
||||
surfaceContainerHigh: "#322826",
|
||||
surfaceContainerHighest: "#3d3231"
|
||||
},
|
||||
monochrome: {
|
||||
name: "Monochrome",
|
||||
primary: "#ffffff",
|
||||
primaryText: "#2b303c",
|
||||
primaryContainer: "#424753",
|
||||
secondary: "#c4c6d0",
|
||||
surface: "#2a2a2a",
|
||||
surfaceText: "#e4e2e3",
|
||||
surfaceVariant: "#474648",
|
||||
surfaceVariantText: "#c8c6c7",
|
||||
surfaceTint: "#c2c6d6",
|
||||
background: "#131315",
|
||||
backgroundText: "#e4e2e3",
|
||||
outline: "#929092",
|
||||
surfaceContainer: "#2a2a2a",
|
||||
surfaceContainerHigh: "#2a2a2b",
|
||||
surfaceContainerHighest: "#353535",
|
||||
error: "#ffb4ab",
|
||||
warning: "#3f4759",
|
||||
info: "#595e6c",
|
||||
matugen_type: "scheme-monochrome"
|
||||
}
|
||||
},
|
||||
LIGHT: {
|
||||
@@ -181,33 +306,17 @@ const StockThemes = {
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#e3f2fd",
|
||||
secondary: "#42a5f5",
|
||||
surface: "#fefefe",
|
||||
surfaceText: "#1a1c1e",
|
||||
surfaceVariant: "#e7e0ec",
|
||||
surfaceVariantText: "#49454f",
|
||||
surface: "#f7f9ff",
|
||||
surfaceText: "#181c20",
|
||||
surfaceVariant: "#dee3eb",
|
||||
surfaceVariantText: "#42474e",
|
||||
surfaceTint: "#1976d2",
|
||||
background: "#fefefe",
|
||||
backgroundText: "#1a1c1e",
|
||||
outline: "#79747e",
|
||||
surfaceContainer: "#f3f3f3",
|
||||
surfaceContainerHigh: "#ececec"
|
||||
},
|
||||
deepBlue: {
|
||||
name: "Deep Blue Light",
|
||||
primary: "#0061a4",
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#cfe5ff",
|
||||
secondary: "#1976d2",
|
||||
surface: "#fefefe",
|
||||
surfaceText: "#1a1c1e",
|
||||
surfaceVariant: "#e7e0ec",
|
||||
surfaceVariantText: "#49454f",
|
||||
surfaceTint: "#0061a4",
|
||||
background: "#fefefe",
|
||||
backgroundText: "#1a1c1e",
|
||||
outline: "#79747e",
|
||||
surfaceContainer: "#f3f3f3",
|
||||
surfaceContainerHigh: "#ececec"
|
||||
background: "#f7f9ff",
|
||||
backgroundText: "#181c20",
|
||||
outline: "#72777f",
|
||||
surfaceContainer: "#eceef4",
|
||||
surfaceContainerHigh: "#e6e8ee",
|
||||
surfaceContainerHighest: "#e0e2e8"
|
||||
},
|
||||
purple: {
|
||||
name: "Purple Light",
|
||||
@@ -215,16 +324,17 @@ const StockThemes = {
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#EADDFF",
|
||||
secondary: "#625B71",
|
||||
surface: "#FFFBFE",
|
||||
surfaceText: "#1C1B1F",
|
||||
surfaceVariant: "#E7E0EC",
|
||||
surfaceVariantText: "#49454F",
|
||||
surface: "#fef7ff",
|
||||
surfaceText: "#1d1b20",
|
||||
surfaceVariant: "#e7e0eb",
|
||||
surfaceVariantText: "#49454e",
|
||||
surfaceTint: "#6750A4",
|
||||
background: "#FFFBFE",
|
||||
backgroundText: "#1C1B1F",
|
||||
outline: "#79747E",
|
||||
surfaceContainer: "#F3EDF7",
|
||||
surfaceContainerHigh: "#ECE6F0"
|
||||
background: "#fef7ff",
|
||||
backgroundText: "#1d1b20",
|
||||
outline: "#7a757f",
|
||||
surfaceContainer: "#f2ecf4",
|
||||
surfaceContainerHigh: "#ece6ee",
|
||||
surfaceContainerHighest: "#e6e0e9"
|
||||
},
|
||||
green: {
|
||||
name: "Green Light",
|
||||
@@ -232,16 +342,17 @@ const StockThemes = {
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#e8f5e8",
|
||||
secondary: "#4caf50",
|
||||
surface: "#fefefe",
|
||||
surfaceText: "#1a1c1e",
|
||||
surfaceVariant: "#e7e0ec",
|
||||
surfaceVariantText: "#49454f",
|
||||
surface: "#f7fbf1",
|
||||
surfaceText: "#191d17",
|
||||
surfaceVariant: "#dee5d8",
|
||||
surfaceVariantText: "#424940",
|
||||
surfaceTint: "#2e7d32",
|
||||
background: "#fefefe",
|
||||
backgroundText: "#1a1c1e",
|
||||
outline: "#79747e",
|
||||
surfaceContainer: "#f3f3f3",
|
||||
surfaceContainerHigh: "#ececec"
|
||||
background: "#f7fbf1",
|
||||
backgroundText: "#191d17",
|
||||
outline: "#72796f",
|
||||
surfaceContainer: "#ecefe6",
|
||||
surfaceContainerHigh: "#e6e9e0",
|
||||
surfaceContainerHighest: "#e0e4db"
|
||||
},
|
||||
orange: {
|
||||
name: "Orange Light",
|
||||
@@ -249,16 +360,17 @@ const StockThemes = {
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#ffecb3",
|
||||
secondary: "#ff9800",
|
||||
surface: "#fefefe",
|
||||
surfaceText: "#1a1c1e",
|
||||
surfaceVariant: "#e7e0ec",
|
||||
surfaceVariantText: "#49454f",
|
||||
surface: "#fff8f6",
|
||||
surfaceText: "#221a16",
|
||||
surfaceVariant: "#f4ded5",
|
||||
surfaceVariantText: "#52443d",
|
||||
surfaceTint: "#e65100",
|
||||
background: "#fefefe",
|
||||
backgroundText: "#1a1c1e",
|
||||
outline: "#79747e",
|
||||
surfaceContainer: "#f3f3f3",
|
||||
surfaceContainerHigh: "#ececec"
|
||||
background: "#fff8f6",
|
||||
backgroundText: "#221a16",
|
||||
outline: "#85736c",
|
||||
surfaceContainer: "#fceae3",
|
||||
surfaceContainerHigh: "#f6e5de",
|
||||
surfaceContainerHighest: "#f0dfd8"
|
||||
},
|
||||
red: {
|
||||
name: "Red Light",
|
||||
@@ -266,16 +378,17 @@ const StockThemes = {
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#ffebee",
|
||||
secondary: "#f44336",
|
||||
surface: "#fefefe",
|
||||
surfaceText: "#1a1c1e",
|
||||
surfaceVariant: "#e7e0ec",
|
||||
surfaceVariantText: "#49454f",
|
||||
surface: "#fff8f7",
|
||||
surfaceText: "#231918",
|
||||
surfaceVariant: "#f5ddda",
|
||||
surfaceVariantText: "#534341",
|
||||
surfaceTint: "#d32f2f",
|
||||
background: "#fefefe",
|
||||
backgroundText: "#1a1c1e",
|
||||
outline: "#79747e",
|
||||
surfaceContainer: "#f3f3f3",
|
||||
surfaceContainerHigh: "#ececec"
|
||||
background: "#fff8f7",
|
||||
backgroundText: "#231918",
|
||||
outline: "#857370",
|
||||
surfaceContainer: "#fceae7",
|
||||
surfaceContainerHigh: "#f7e4e1",
|
||||
surfaceContainerHighest: "#f1dedc"
|
||||
},
|
||||
cyan: {
|
||||
name: "Cyan Light",
|
||||
@@ -283,16 +396,17 @@ const StockThemes = {
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#e0f2f1",
|
||||
secondary: "#00bcd4",
|
||||
surface: "#fefefe",
|
||||
surfaceText: "#1a1c1e",
|
||||
surfaceVariant: "#e7e0ec",
|
||||
surfaceVariantText: "#49454f",
|
||||
surface: "#f5fafc",
|
||||
surfaceText: "#171d1e",
|
||||
surfaceVariant: "#dbe4e6",
|
||||
surfaceVariantText: "#3f484a",
|
||||
surfaceTint: "#0097a7",
|
||||
background: "#fefefe",
|
||||
backgroundText: "#1a1c1e",
|
||||
outline: "#79747e",
|
||||
surfaceContainer: "#f3f3f3",
|
||||
surfaceContainerHigh: "#ececec"
|
||||
background: "#f5fafc",
|
||||
backgroundText: "#171d1e",
|
||||
outline: "#6f797b",
|
||||
surfaceContainer: "#e9eff0",
|
||||
surfaceContainerHigh: "#e3e9eb",
|
||||
surfaceContainerHighest: "#dee3e5"
|
||||
},
|
||||
pink: {
|
||||
name: "Pink Light",
|
||||
@@ -300,16 +414,17 @@ const StockThemes = {
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#fce4ec",
|
||||
secondary: "#e91e63",
|
||||
surface: "#fefefe",
|
||||
surfaceText: "#1a1c1e",
|
||||
surfaceVariant: "#e7e0ec",
|
||||
surfaceVariantText: "#49454f",
|
||||
surface: "#fff8f7",
|
||||
surfaceText: "#22191a",
|
||||
surfaceVariant: "#f3dddf",
|
||||
surfaceVariantText: "#524345",
|
||||
surfaceTint: "#c2185b",
|
||||
background: "#fefefe",
|
||||
backgroundText: "#1a1c1e",
|
||||
outline: "#79747e",
|
||||
surfaceContainer: "#f3f3f3",
|
||||
surfaceContainerHigh: "#ececec"
|
||||
background: "#fff8f7",
|
||||
backgroundText: "#22191a",
|
||||
outline: "#847375",
|
||||
surfaceContainer: "#fbeaeb",
|
||||
surfaceContainerHigh: "#f5e4e5",
|
||||
surfaceContainerHighest: "#f0dee0"
|
||||
},
|
||||
amber: {
|
||||
name: "Amber Light",
|
||||
@@ -317,16 +432,17 @@ const StockThemes = {
|
||||
primaryText: "#000000",
|
||||
primaryContainer: "#fff8e1",
|
||||
secondary: "#ffc107",
|
||||
surface: "#fefefe",
|
||||
surfaceText: "#1a1c1e",
|
||||
surfaceVariant: "#e7e0ec",
|
||||
surfaceVariantText: "#49454f",
|
||||
surface: "#fff8f2",
|
||||
surfaceText: "#1f1b13",
|
||||
surfaceVariant: "#ede1cf",
|
||||
surfaceVariantText: "#4d4639",
|
||||
surfaceTint: "#ff8f00",
|
||||
background: "#fefefe",
|
||||
backgroundText: "#1a1c1e",
|
||||
outline: "#79747e",
|
||||
surfaceContainer: "#f3f3f3",
|
||||
surfaceContainerHigh: "#ececec"
|
||||
background: "#fff8f2",
|
||||
backgroundText: "#1f1b13",
|
||||
outline: "#7f7667",
|
||||
surfaceContainer: "#f6ecdf",
|
||||
surfaceContainerHigh: "#f1e7d9",
|
||||
surfaceContainerHighest: "#ebe1d4"
|
||||
},
|
||||
coral: {
|
||||
name: "Coral Light",
|
||||
@@ -334,23 +450,55 @@ const StockThemes = {
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#ffdad6",
|
||||
secondary: "#ff5449",
|
||||
surface: "#fefefe",
|
||||
surfaceText: "#1a1c1e",
|
||||
surfaceVariant: "#e7e0ec",
|
||||
surfaceVariantText: "#49454f",
|
||||
surface: "#fff8f7",
|
||||
surfaceText: "#231918",
|
||||
surfaceVariant: "#f5ddda",
|
||||
surfaceVariantText: "#534341",
|
||||
surfaceTint: "#8c1d18",
|
||||
background: "#fefefe",
|
||||
backgroundText: "#1a1c1e",
|
||||
outline: "#79747e",
|
||||
surfaceContainer: "#f3f3f3",
|
||||
surfaceContainerHigh: "#ececec"
|
||||
background: "#fff8f7",
|
||||
backgroundText: "#231918",
|
||||
outline: "#857371",
|
||||
surfaceContainer: "#fceae7",
|
||||
surfaceContainerHigh: "#f6e4e2",
|
||||
surfaceContainerHighest: "#f1dedc"
|
||||
},
|
||||
monochrome: {
|
||||
name: "Monochrome Light",
|
||||
primary: "#2b303c",
|
||||
primaryText: "#ffffff",
|
||||
primaryContainer: "#d6d7dc",
|
||||
secondary: "#4a4d56",
|
||||
surface: "#f5f5f6",
|
||||
surfaceText: "#2a2a2a",
|
||||
surfaceVariant: "#e0e0e2",
|
||||
surfaceVariantText: "#424242",
|
||||
surfaceTint: "#5a5f6e",
|
||||
background: "#ffffff",
|
||||
backgroundText: "#1a1a1a",
|
||||
outline: "#757577",
|
||||
surfaceContainer: "#f5f5f6",
|
||||
surfaceContainerHigh: "#eaeaeb",
|
||||
error: "#ba1a1a",
|
||||
warning: "#f9e79f",
|
||||
info: "#5d6475",
|
||||
matugen_type: "scheme-monochrome"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ThemeCategories = {
|
||||
GENERIC: {
|
||||
name: "Generic",
|
||||
variants: ["blue", "purple", "green", "orange", "red", "cyan", "pink", "amber", "coral", "monochrome"]
|
||||
},
|
||||
CATPPUCCIN: {
|
||||
name: "Catppuccin",
|
||||
variants: Object.keys(CatppuccinVariants)
|
||||
}
|
||||
}
|
||||
|
||||
const ThemeNames = {
|
||||
BLUE: "blue",
|
||||
DEEP_BLUE: "deepBlue",
|
||||
PURPLE: "purple",
|
||||
GREEN: "green",
|
||||
ORANGE: "orange",
|
||||
@@ -359,6 +507,7 @@ const ThemeNames = {
|
||||
PINK: "pink",
|
||||
AMBER: "amber",
|
||||
CORAL: "coral",
|
||||
MONOCHROME: "monochrome",
|
||||
DYNAMIC: "dynamic"
|
||||
}
|
||||
|
||||
@@ -366,15 +515,30 @@ function isStockTheme(themeName) {
|
||||
return Object.keys(StockThemes.DARK).includes(themeName)
|
||||
}
|
||||
|
||||
function isCatppuccinVariant(themeName) {
|
||||
return Object.keys(CatppuccinVariants).includes(themeName)
|
||||
}
|
||||
|
||||
function getAvailableThemes(isLight = false) {
|
||||
return isLight ? StockThemes.LIGHT : StockThemes.DARK
|
||||
}
|
||||
|
||||
function getThemeByName(themeName, isLight = false) {
|
||||
if (isCatppuccinVariant(themeName)) {
|
||||
return getCatppuccinTheme(themeName, isLight)
|
||||
}
|
||||
const themes = getAvailableThemes(isLight)
|
||||
return themes[themeName] || themes.blue
|
||||
}
|
||||
|
||||
function getAllThemeNames() {
|
||||
return Object.keys(StockThemes.DARK)
|
||||
}
|
||||
|
||||
function getCatppuccinVariantNames() {
|
||||
return Object.keys(CatppuccinVariants)
|
||||
}
|
||||
|
||||
function getThemeCategories() {
|
||||
return ThemeCategories
|
||||
}
|
||||
472
Common/Theme.qml
472
Common/Theme.qml
@@ -7,29 +7,56 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import "StockThemes.js" as StockThemes
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true"
|
||||
|
||||
readonly property real popupDistance: 4
|
||||
|
||||
property string currentTheme: "blue"
|
||||
property string currentThemeCategory: "generic"
|
||||
property bool isLightMode: false
|
||||
|
||||
readonly property string dynamic: "dynamic"
|
||||
readonly property string custom : "custom"
|
||||
|
||||
readonly property string homeDir: {
|
||||
const url = StandardPaths.writableLocation(StandardPaths.HomeLocation).toString()
|
||||
return url.startsWith("file://") ? url.substring(7) : url
|
||||
}
|
||||
readonly property string configDir: {
|
||||
const url = StandardPaths.writableLocation(StandardPaths.ConfigLocation).toString()
|
||||
return url.startsWith("file://") ? url.substring(7) : url
|
||||
}
|
||||
readonly property string shellDir: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Common/", "")
|
||||
readonly property string homeDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation))
|
||||
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
|
||||
readonly property string shellDir: Paths.strip(Qt.resolvedUrl(".").toString()).replace("/Common/", "")
|
||||
readonly property string wallpaperPath: {
|
||||
if (typeof SessionData === "undefined") return ""
|
||||
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
// Use first monitor's wallpaper for dynamic theming
|
||||
var screens = Quickshell.screens
|
||||
if (screens.length > 0) {
|
||||
var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name)
|
||||
var wallpaperPath = firstMonitorWallpaper || SessionData.wallpaperPath
|
||||
|
||||
if (wallpaperPath && wallpaperPath.startsWith("we:")) {
|
||||
return stateDir + "/we_screenshots/" + wallpaperPath.substring(3) + ".jpg"
|
||||
}
|
||||
|
||||
return wallpaperPath
|
||||
}
|
||||
}
|
||||
|
||||
var wallpaperPath = SessionData.wallpaperPath
|
||||
var screens = Quickshell.screens
|
||||
if (screens.length > 0 && wallpaperPath && wallpaperPath.startsWith("we:")) {
|
||||
return stateDir + "/we_screenshots/" + wallpaperPath.substring(3) + ".jpg"
|
||||
}
|
||||
|
||||
return wallpaperPath
|
||||
}
|
||||
readonly property string rawWallpaperPath: {
|
||||
if (typeof SessionData === "undefined") return ""
|
||||
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
// Use first monitor's wallpaper for dynamic theming
|
||||
var screens = Quickshell.screens
|
||||
@@ -38,7 +65,7 @@ Singleton {
|
||||
return firstMonitorWallpaper || SessionData.wallpaperPath
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return SessionData.wallpaperPath
|
||||
}
|
||||
|
||||
@@ -47,15 +74,10 @@ Singleton {
|
||||
property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false
|
||||
property var workerRunning: false
|
||||
property var matugenColors: ({})
|
||||
property bool extractionRequested: false
|
||||
property int colorUpdateTrigger: 0
|
||||
property var customThemeData: null
|
||||
|
||||
readonly property string stateDir: {
|
||||
const cacheHome = StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()
|
||||
const path = cacheHome.startsWith("file://") ? cacheHome.substring(7) : cacheHome
|
||||
return path + "/dankshell"
|
||||
}
|
||||
readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()) + "/dankshell"
|
||||
|
||||
Component.onCompleted: {
|
||||
Quickshell.execDetached(["mkdir", "-p", stateDir])
|
||||
@@ -99,6 +121,7 @@ Singleton {
|
||||
"outline": getMatugenColor("outline", "#8e918f"),
|
||||
"surfaceContainer": getMatugenColor("surface_container", "#1e2023"),
|
||||
"surfaceContainerHigh": getMatugenColor("surface_container_high", "#292b2f"),
|
||||
"surfaceContainerHighest": getMatugenColor("surface_container_highest", "#343740"),
|
||||
"error": "#F2B8B5",
|
||||
"warning": "#FF9800",
|
||||
"info": "#2196F3",
|
||||
@@ -109,11 +132,36 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var availableMatugenSchemes: [
|
||||
({ "value": "scheme-tonal-spot", "label": "Tonal Spot", "description": "Balanced palette with focused accents (default)." }),
|
||||
({ "value": "scheme-content", "label": "Content", "description": "Derives colors that closely match the underlying image." }),
|
||||
({ "value": "scheme-expressive", "label": "Expressive", "description": "Vibrant palette with playful saturation." }),
|
||||
({ "value": "scheme-fidelity", "label": "Fidelity", "description": "High-fidelity palette that preserves source hues." }),
|
||||
({ "value": "scheme-fruit-salad", "label": "Fruit Salad", "description": "Colorful mix of bright contrasting accents." }),
|
||||
({ "value": "scheme-monochrome", "label": "Monochrome", "description": "Minimal palette built around a single hue." }),
|
||||
({ "value": "scheme-neutral", "label": "Neutral", "description": "Muted palette with subdued, calming tones." }),
|
||||
({ "value": "scheme-rainbow", "label": "Rainbow", "description": "Diverse palette spanning the full spectrum." })
|
||||
]
|
||||
|
||||
function getMatugenScheme(value) {
|
||||
const schemes = availableMatugenSchemes
|
||||
for (let i = 0; i < schemes.length; i++) {
|
||||
if (schemes[i].value === value)
|
||||
return schemes[i]
|
||||
}
|
||||
return schemes[0]
|
||||
}
|
||||
|
||||
property color primary: currentThemeData.primary
|
||||
property color primaryText: currentThemeData.primaryText
|
||||
property color primaryContainer: currentThemeData.primaryContainer
|
||||
property color secondary: currentThemeData.secondary
|
||||
property color surface: currentThemeData.surface
|
||||
property color surface: {
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.surfaceBase === "s") {
|
||||
return currentThemeData.background
|
||||
}
|
||||
return currentThemeData.surface
|
||||
}
|
||||
property color surfaceText: currentThemeData.surfaceText
|
||||
property color surfaceVariant: currentThemeData.surfaceVariant
|
||||
property color surfaceVariantText: currentThemeData.surfaceVariantText
|
||||
@@ -121,8 +169,32 @@ Singleton {
|
||||
property color background: currentThemeData.background
|
||||
property color backgroundText: currentThemeData.backgroundText
|
||||
property color outline: currentThemeData.outline
|
||||
property color surfaceContainer: currentThemeData.surfaceContainer
|
||||
property color surfaceContainerHigh: currentThemeData.surfaceContainerHigh
|
||||
property color outlineVariant: currentThemeData.outlineVariant || Qt.rgba(outline.r, outline.g, outline.b, 0.6)
|
||||
property color surfaceContainer: {
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.surfaceBase === "s") {
|
||||
return currentThemeData.surface
|
||||
}
|
||||
return currentThemeData.surfaceContainer
|
||||
}
|
||||
property color surfaceContainerHigh: {
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.surfaceBase === "s") {
|
||||
return currentThemeData.surfaceContainer
|
||||
}
|
||||
return currentThemeData.surfaceContainerHigh
|
||||
}
|
||||
property color surfaceContainerHighest: {
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.surfaceBase === "s") {
|
||||
return currentThemeData.surfaceContainerHigh
|
||||
}
|
||||
return currentThemeData.surfaceContainerHighest
|
||||
}
|
||||
|
||||
property color onSurface: surfaceText
|
||||
property color onSurfaceVariant: surfaceVariantText
|
||||
property color onPrimary: primaryText
|
||||
property color onSurface_12: Qt.rgba(onSurface.r, onSurface.g, onSurface.b, 0.12)
|
||||
property color onSurface_38: Qt.rgba(onSurface.r, onSurface.g, onSurface.b, 0.38)
|
||||
property color onSurfaceVariant_30: Qt.rgba(onSurfaceVariant.r, onSurfaceVariant.g, onSurfaceVariant.b, 0.30)
|
||||
|
||||
property color error: currentThemeData.error || "#F2B8B5"
|
||||
property color warning: currentThemeData.warning || "#FF9800"
|
||||
@@ -155,6 +227,7 @@ Singleton {
|
||||
property color outlineStrong: Qt.rgba(outline.r, outline.g, outline.b, 0.12)
|
||||
|
||||
property color errorHover: Qt.rgba(error.r, error.g, error.b, 0.12)
|
||||
property color errorPressed: Qt.rgba(error.r, error.g, error.b, 0.16)
|
||||
|
||||
property color shadowMedium: Qt.rgba(0, 0, 0, 0.08)
|
||||
property color shadowStrong: Qt.rgba(0, 0, 0, 0.3)
|
||||
@@ -186,17 +259,31 @@ Singleton {
|
||||
property real widgetTransparency: typeof SettingsData !== "undefined" && SettingsData.topBarWidgetTransparency !== undefined ? SettingsData.topBarWidgetTransparency : 0.85
|
||||
property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 0.92
|
||||
|
||||
function switchTheme(themeName, savePrefs = true) {
|
||||
function screenTransition() {
|
||||
CompositorService.isNiri && NiriService.doScreenTransition()
|
||||
}
|
||||
|
||||
function switchTheme(themeName, savePrefs = true, enableTransition = true) {
|
||||
if (enableTransition) {
|
||||
screenTransition()
|
||||
}
|
||||
if (themeName === dynamic) {
|
||||
currentTheme = dynamic
|
||||
extractColors()
|
||||
} else if (themeName === "custom") {
|
||||
currentTheme = "custom"
|
||||
currentThemeCategory = dynamic
|
||||
} else if (themeName === custom) {
|
||||
currentTheme = custom
|
||||
currentThemeCategory = custom
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.customThemeFile) {
|
||||
loadCustomThemeFromFile(SettingsData.customThemeFile)
|
||||
}
|
||||
} else {
|
||||
currentTheme = themeName
|
||||
// Determine category based on theme name
|
||||
if (StockThemes.isCatppuccinVariant(themeName)) {
|
||||
currentThemeCategory = "catppuccin"
|
||||
} else {
|
||||
currentThemeCategory = "generic"
|
||||
}
|
||||
}
|
||||
if (savePrefs && typeof SettingsData !== "undefined")
|
||||
SettingsData.setTheme(currentTheme)
|
||||
@@ -205,6 +292,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function setLightMode(light, savePrefs = true) {
|
||||
screenTransition()
|
||||
isLightMode = light
|
||||
if (savePrefs && typeof SessionData !== "undefined")
|
||||
SessionData.setLightMode(isLightMode)
|
||||
@@ -217,10 +305,8 @@ Singleton {
|
||||
}
|
||||
|
||||
function forceGenerateSystemThemes() {
|
||||
screenTransition()
|
||||
if (!matugenAvailable) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showWarning("matugen not available - cannot generate system themes")
|
||||
}
|
||||
return
|
||||
}
|
||||
generateSystemThemesFromCurrentTheme()
|
||||
@@ -242,7 +328,33 @@ Singleton {
|
||||
return StockThemes.getThemeByName(themeName, isLightMode)
|
||||
}
|
||||
|
||||
function switchThemeCategory(category, defaultTheme) {
|
||||
currentThemeCategory = category
|
||||
switchTheme(defaultTheme, true, false)
|
||||
}
|
||||
|
||||
function getCatppuccinColor(variantName) {
|
||||
const catColors = {
|
||||
"cat-rosewater": "#f5e0dc", "cat-flamingo": "#f2cdcd", "cat-pink": "#f5c2e7", "cat-mauve": "#cba6f7",
|
||||
"cat-red": "#f38ba8", "cat-maroon": "#eba0ac", "cat-peach": "#fab387", "cat-yellow": "#f9e2af",
|
||||
"cat-green": "#a6e3a1", "cat-teal": "#94e2d5", "cat-sky": "#89dceb", "cat-sapphire": "#74c7ec",
|
||||
"cat-blue": "#89b4fa", "cat-lavender": "#b4befe"
|
||||
}
|
||||
return catColors[variantName] || "#cba6f7"
|
||||
}
|
||||
|
||||
function getCatppuccinVariantName(variantName) {
|
||||
const catNames = {
|
||||
"cat-rosewater": "Rosewater", "cat-flamingo": "Flamingo", "cat-pink": "Pink", "cat-mauve": "Mauve",
|
||||
"cat-red": "Red", "cat-maroon": "Maroon", "cat-peach": "Peach", "cat-yellow": "Yellow",
|
||||
"cat-green": "Green", "cat-teal": "Teal", "cat-sky": "Sky", "cat-sapphire": "Sapphire",
|
||||
"cat-blue": "Blue", "cat-lavender": "Lavender"
|
||||
}
|
||||
return catNames[variantName] || "Unknown"
|
||||
}
|
||||
|
||||
function loadCustomTheme(themeData) {
|
||||
screenTransition()
|
||||
if (themeData.dark || themeData.light) {
|
||||
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
|
||||
const selectedTheme = themeData[colorMode] || themeData.dark || themeData.light
|
||||
@@ -274,8 +386,42 @@ Singleton {
|
||||
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, panelTransparency)
|
||||
}
|
||||
|
||||
function widgetBackground() {
|
||||
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, widgetTransparency)
|
||||
property real notepadTransparency: SettingsData.notepadTransparencyOverride >= 0 ? SettingsData.notepadTransparencyOverride : popupTransparency
|
||||
|
||||
property var widgetBaseBackgroundColor: {
|
||||
const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sch"
|
||||
switch (colorMode) {
|
||||
case "s":
|
||||
return surface
|
||||
case "sc":
|
||||
return surfaceContainer
|
||||
case "sch":
|
||||
return surfaceContainerHigh
|
||||
case "sth":
|
||||
default:
|
||||
return surfaceTextHover
|
||||
}
|
||||
}
|
||||
|
||||
property var widgetBaseHoverColor: {
|
||||
const baseColor = widgetBaseBackgroundColor
|
||||
const factor = 1.2
|
||||
return isLightMode ? Qt.darker(baseColor, factor) : Qt.lighter(baseColor, factor)
|
||||
}
|
||||
|
||||
property var widgetBackground: {
|
||||
const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sch"
|
||||
switch (colorMode) {
|
||||
case "s":
|
||||
return Qt.rgba(surface.r, surface.g, surface.b, widgetTransparency)
|
||||
case "sc":
|
||||
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, widgetTransparency)
|
||||
case "sch":
|
||||
return Qt.rgba(surfaceContainerHigh.r, surfaceContainerHigh.g, surfaceContainerHigh.b, widgetTransparency)
|
||||
case "sth":
|
||||
default:
|
||||
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, widgetTransparency)
|
||||
}
|
||||
}
|
||||
|
||||
function getPopupBackgroundAlpha() {
|
||||
@@ -380,13 +526,6 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function extractColors() {
|
||||
extractionRequested = true
|
||||
if (matugenAvailable)
|
||||
fileChecker.running = true
|
||||
else
|
||||
matugenCheck.running = true
|
||||
}
|
||||
|
||||
function onLightModeChanged() {
|
||||
if (matugenColors && Object.keys(matugenColors).length > 0) {
|
||||
@@ -396,21 +535,25 @@ Singleton {
|
||||
if (currentTheme === "custom" && customThemeFileView.path) {
|
||||
customThemeFileView.reload()
|
||||
}
|
||||
|
||||
generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
|
||||
function setDesiredTheme(kind, value, isLight, iconTheme) {
|
||||
function setDesiredTheme(kind, value, isLight, iconTheme, matugenType) {
|
||||
if (!matugenAvailable) {
|
||||
console.warn("matugen not available - cannot set system theme")
|
||||
console.warn("matugen not available or disabled - cannot set system theme")
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
|
||||
NiriService.suppressNextToast()
|
||||
}
|
||||
|
||||
const desired = {
|
||||
"kind": kind,
|
||||
"value": value,
|
||||
"mode": isLight ? "light" : "dark",
|
||||
"iconTheme": iconTheme || "System Default"
|
||||
"iconTheme": iconTheme || "System Default",
|
||||
"matugenType": matugenType || "scheme-tonal-spot",
|
||||
"surfaceBase": (typeof SettingsData !== "undefined" && SettingsData.surfaceBase) ? SettingsData.surfaceBase : "sc"
|
||||
}
|
||||
|
||||
const json = JSON.stringify(desired)
|
||||
@@ -418,7 +561,15 @@ Singleton {
|
||||
|
||||
Quickshell.execDetached(["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF`])
|
||||
workerRunning = true
|
||||
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, "--run"]
|
||||
if (rawWallpaperPath.startsWith("we:")) {
|
||||
console.log("calling matugen worker")
|
||||
systemThemeGenerator.command = [
|
||||
"sh", "-c",
|
||||
`sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' --run`
|
||||
]
|
||||
} else {
|
||||
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, "--run"]
|
||||
}
|
||||
systemThemeGenerator.running = true
|
||||
}
|
||||
|
||||
@@ -433,35 +584,39 @@ Singleton {
|
||||
if (!wallpaperPath) {
|
||||
return
|
||||
}
|
||||
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
|
||||
if (wallpaperPath.startsWith("#")) {
|
||||
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme)
|
||||
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
|
||||
} else {
|
||||
setDesiredTheme("image", wallpaperPath, isLight, iconTheme)
|
||||
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
|
||||
}
|
||||
} else {
|
||||
let primaryColor
|
||||
let matugenType
|
||||
if (currentTheme === "custom") {
|
||||
if (!customThemeData || !customThemeData.primary) {
|
||||
console.warn("Custom theme data not available for system theme generation")
|
||||
return
|
||||
}
|
||||
primaryColor = customThemeData.primary
|
||||
matugenType = customThemeData.matugen_type
|
||||
} else {
|
||||
primaryColor = currentThemeData.primary
|
||||
matugenType = currentThemeData.matugen_type
|
||||
}
|
||||
|
||||
if (!primaryColor) {
|
||||
console.warn("No primary color available for theme:", currentTheme)
|
||||
return
|
||||
}
|
||||
setDesiredTheme("hex", primaryColor, isLight, iconTheme)
|
||||
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType)
|
||||
}
|
||||
}
|
||||
|
||||
function applyGtkColors() {
|
||||
if (!matugenAvailable) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showError("matugen not available - cannot apply GTK colors")
|
||||
ToastService.showError("matugen not available or disabled - cannot apply GTK colors")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -474,7 +629,7 @@ Singleton {
|
||||
function applyQtColors() {
|
||||
if (!matugenAvailable) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showError("matugen not available - cannot apply Qt colors")
|
||||
ToastService.showError("matugen not available or disabled - cannot apply Qt colors")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -483,77 +638,16 @@ Singleton {
|
||||
qtApplier.running = true
|
||||
}
|
||||
|
||||
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: matugenCheck
|
||||
command: ["which", "matugen"]
|
||||
onExited: code => {
|
||||
matugenAvailable = (code === 0)
|
||||
matugenAvailable = (code === 0) && !envDisableMatugen
|
||||
if (!matugenAvailable) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.wallpaperErrorStatus = "matugen_missing"
|
||||
ToastService.showWarning("matugen not found - dynamic theming disabled")
|
||||
}
|
||||
console.log("matugen not not available in path or disabled via DMS_DISABLE_MATUGEN")
|
||||
return
|
||||
}
|
||||
if (extractionRequested) {
|
||||
fileChecker.running = true
|
||||
}
|
||||
|
||||
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
|
||||
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
|
||||
@@ -561,137 +655,35 @@ Singleton {
|
||||
if (currentTheme === dynamic) {
|
||||
if (wallpaperPath) {
|
||||
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
|
||||
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
|
||||
if (wallpaperPath.startsWith("#")) {
|
||||
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme)
|
||||
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
|
||||
} else {
|
||||
setDesiredTheme("image", wallpaperPath, isLight, iconTheme)
|
||||
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let primaryColor
|
||||
let matugenType
|
||||
if (currentTheme === "custom") {
|
||||
if (customThemeData && customThemeData.primary) {
|
||||
primaryColor = customThemeData.primary
|
||||
matugenType = customThemeData.matugen_type
|
||||
}
|
||||
} else {
|
||||
primaryColor = currentThemeData.primary
|
||||
matugenType = currentThemeData.matugen_type
|
||||
}
|
||||
|
||||
if (primaryColor) {
|
||||
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
|
||||
setDesiredTheme("hex", primaryColor, isLight, iconTheme)
|
||||
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fileChecker
|
||||
command: ["test", "-r", wallpaperPath]
|
||||
onExited: code => {
|
||||
if (code === 0) {
|
||||
matugenProcess.running = true
|
||||
} else if (wallpaperPath.startsWith("#")) {
|
||||
colorMatugenProcess.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: matugenProcess
|
||||
command: ["matugen", "image", wallpaperPath, "--json", "hex"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
id: matugenCollector
|
||||
onStreamFinished: {
|
||||
if (!matugenCollector.text) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Wallpaper Processing Failed: Empty JSON extracted from matugen output.")
|
||||
}
|
||||
return
|
||||
}
|
||||
const extractedJson = extractJsonFromText(matugenCollector.text)
|
||||
if (!extractedJson) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
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.colorUpdateTrigger++
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.clearWallpaperError()
|
||||
}
|
||||
} catch (e) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Wallpaper processing failed (JSON parse error after extraction)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: code => {
|
||||
if (code !== 0) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Matugen command failed with exit code " + code)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: colorMatugenProcess
|
||||
command: ["matugen", "color", "hex", wallpaperPath, "--json", "hex"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
id: colorMatugenCollector
|
||||
onStreamFinished: {
|
||||
if (!colorMatugenCollector.text) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Color Processing Failed: Empty JSON extracted from matugen output.")
|
||||
}
|
||||
return
|
||||
}
|
||||
const extractedJson = extractJsonFromText(colorMatugenCollector.text)
|
||||
if (!extractedJson) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Color Processing Failed: Invalid JSON extracted from matugen output.")
|
||||
}
|
||||
console.log("Raw matugen output:", colorMatugenCollector.text)
|
||||
return
|
||||
}
|
||||
try {
|
||||
root.matugenColors = JSON.parse(extractedJson)
|
||||
root.colorUpdateTrigger++
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.clearWallpaperError()
|
||||
}
|
||||
} catch (e) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Color processing failed (JSON parse error after extraction)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: code => {
|
||||
if (code !== 0) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Matugen color command failed with exit code " + code)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: ensureStateDir
|
||||
@@ -730,7 +722,7 @@ Singleton {
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
|
||||
ToastService.showInfo("GTK colors applied successfully")
|
||||
}
|
||||
} else {
|
||||
@@ -794,6 +786,48 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: dynamicColorsFileView
|
||||
path: stateDir + "/dms-colors.json"
|
||||
watchChanges: currentTheme === dynamic
|
||||
|
||||
function parseAndLoadColors() {
|
||||
try {
|
||||
const colorsText = dynamicColorsFileView.text()
|
||||
if (colorsText) {
|
||||
root.matugenColors = JSON.parse(colorsText)
|
||||
root.colorUpdateTrigger++
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.clearWallpaperError()
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Dynamic colors parse error: " + e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
if (currentTheme === dynamic) {
|
||||
parseAndLoadColors()
|
||||
}
|
||||
}
|
||||
|
||||
onFileChanged: {
|
||||
if (currentTheme === dynamic) {
|
||||
dynamicColorsFileView.reload()
|
||||
}
|
||||
}
|
||||
|
||||
onLoadFailed: function (error) {
|
||||
if (currentTheme === dynamic && typeof ToastService !== "undefined") {
|
||||
ToastService.showError("Failed to read dynamic colors: " + error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "theme"
|
||||
|
||||
|
||||
373
IPC.qml
Normal file
373
IPC.qml
Normal file
@@ -0,0 +1,373 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var powermenu: null
|
||||
property var processlist: null
|
||||
property var controlCenter: null
|
||||
property var dash: null
|
||||
property var notepadVariants: null
|
||||
property var spotlight: null
|
||||
property var clipboard: null
|
||||
property var notifications: null
|
||||
property var settings: null
|
||||
|
||||
function getFocusedScreenName() {
|
||||
if (CompositorService.isHyprland && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor) {
|
||||
return Hyprland.focusedWorkspace.monitor.name
|
||||
}
|
||||
if (CompositorService.isNiri && NiriService.currentOutput) {
|
||||
return NiriService.currentOutput
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function getActiveNotepadInstance() {
|
||||
if (!notepadVariants || notepadVariants.instances.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (notepadVariants.instances.length === 1) {
|
||||
return notepadVariants.instances[0]
|
||||
}
|
||||
|
||||
var focusedScreen = getFocusedScreenName()
|
||||
if (focusedScreen && notepadVariants.instances.length > 0) {
|
||||
for (var i = 0; i < notepadVariants.instances.length; i++) {
|
||||
var slideout = notepadVariants.instances[i]
|
||||
if (slideout.modelData && slideout.modelData.name === focusedScreen) {
|
||||
return slideout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < notepadVariants.instances.length; i++) {
|
||||
var slideout = notepadVariants.instances[i]
|
||||
if (slideout.isVisible) {
|
||||
return slideout
|
||||
}
|
||||
}
|
||||
|
||||
return notepadVariants.instances[0]
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open() {
|
||||
powermenu.active = true
|
||||
if (powermenu.item)
|
||||
powermenu.item.open()
|
||||
return "POWERMENU_OPEN_SUCCESS"
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (powermenu.item) {
|
||||
powermenu.item.close()
|
||||
powermenu.active = false
|
||||
}
|
||||
return "POWERMENU_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
powermenu.active = true
|
||||
if (powermenu.item)
|
||||
powermenu.item.toggle()
|
||||
return "POWERMENU_TOGGLE_SUCCESS"
|
||||
}
|
||||
|
||||
target: "powermenu"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
processlist.active = true
|
||||
if (processlist.item)
|
||||
processlist.item.show()
|
||||
return "PROCESSLIST_OPEN_SUCCESS"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
if (processlist.item) {
|
||||
processlist.item.hide()
|
||||
processlist.active = false
|
||||
}
|
||||
return "PROCESSLIST_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
processlist.active = true
|
||||
if (processlist.item)
|
||||
processlist.item.toggle()
|
||||
return "PROCESSLIST_TOGGLE_SUCCESS"
|
||||
}
|
||||
|
||||
target: "processlist"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
controlCenter.active = true
|
||||
if (controlCenter.item) {
|
||||
controlCenter.item.open()
|
||||
return "CONTROL_CENTER_OPEN_SUCCESS"
|
||||
}
|
||||
return "CONTROL_CENTER_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
if (controlCenter.item) {
|
||||
controlCenter.item.close()
|
||||
controlCenter.active = false
|
||||
return "CONTROL_CENTER_CLOSE_SUCCESS"
|
||||
}
|
||||
return "CONTROL_CENTER_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
controlCenter.active = true
|
||||
if (controlCenter.item) {
|
||||
controlCenter.item.toggle()
|
||||
return "CONTROL_CENTER_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "CONTROL_CENTER_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
target: "control-center"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(tab: string): string {
|
||||
dash.active = true
|
||||
if (dash.item) {
|
||||
switch (tab.toLowerCase()) {
|
||||
case "media":
|
||||
dash.item.currentTabIndex = 1
|
||||
break
|
||||
case "weather":
|
||||
dash.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
|
||||
break
|
||||
default:
|
||||
dash.item.currentTabIndex = 0
|
||||
break
|
||||
}
|
||||
dash.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
|
||||
dash.item.dashVisible = true
|
||||
return "DASH_OPEN_SUCCESS"
|
||||
}
|
||||
return "DASH_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
if (dash.item) {
|
||||
dash.item.dashVisible = false
|
||||
dash.active = false
|
||||
return "DASH_CLOSE_SUCCESS"
|
||||
}
|
||||
return "DASH_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggle(tab: string): string {
|
||||
dash.active = true
|
||||
if (dash.item) {
|
||||
if (dash.item.dashVisible) {
|
||||
dash.item.dashVisible = false
|
||||
} else {
|
||||
switch (tab.toLowerCase()) {
|
||||
case "media":
|
||||
dash.item.currentTabIndex = 1
|
||||
break
|
||||
case "weather":
|
||||
dash.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
|
||||
break
|
||||
default:
|
||||
dash.item.currentTabIndex = 0
|
||||
break
|
||||
}
|
||||
dash.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
|
||||
dash.item.dashVisible = true
|
||||
}
|
||||
return "DASH_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "DASH_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
target: "dash"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
var instance = getActiveNotepadInstance()
|
||||
if (instance) {
|
||||
instance.show()
|
||||
return "NOTEPAD_OPEN_SUCCESS"
|
||||
}
|
||||
return "NOTEPAD_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
var instance = getActiveNotepadInstance()
|
||||
if (instance) {
|
||||
instance.hide()
|
||||
return "NOTEPAD_CLOSE_SUCCESS"
|
||||
}
|
||||
return "NOTEPAD_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
var instance = getActiveNotepadInstance()
|
||||
if (instance) {
|
||||
instance.toggle()
|
||||
return "NOTEPAD_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "NOTEPAD_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
target: "notepad"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
spotlight.active = true
|
||||
if (spotlight.item) {
|
||||
spotlight.item.show()
|
||||
return "SPOTLIGHT_OPEN_SUCCESS"
|
||||
}
|
||||
return "SPOTLIGHT_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
if (spotlight.item) {
|
||||
spotlight.item.hide()
|
||||
spotlight.active = false
|
||||
return "SPOTLIGHT_CLOSE_SUCCESS"
|
||||
}
|
||||
return "SPOTLIGHT_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
spotlight.active = true
|
||||
if (spotlight.item) {
|
||||
spotlight.item.toggle()
|
||||
return "SPOTLIGHT_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "SPOTLIGHT_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
target: "spotlight"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
clipboard.active = true
|
||||
if (clipboard.item) {
|
||||
clipboard.item.show()
|
||||
return "CLIPBOARD_OPEN_SUCCESS"
|
||||
}
|
||||
return "CLIPBOARD_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
if (clipboard.item) {
|
||||
clipboard.item.hide()
|
||||
clipboard.active = false
|
||||
return "CLIPBOARD_CLOSE_SUCCESS"
|
||||
}
|
||||
return "CLIPBOARD_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
clipboard.active = true
|
||||
if (clipboard.item) {
|
||||
clipboard.item.toggle()
|
||||
return "CLIPBOARD_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "CLIPBOARD_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
target: "clipboard"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
notifications.active = true
|
||||
if (notifications.item) {
|
||||
notifications.item.show()
|
||||
return "NOTIFICATION_MODAL_OPEN_SUCCESS"
|
||||
}
|
||||
return "NOTIFICATION_MODAL_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
if (notifications.item) {
|
||||
notifications.item.hide()
|
||||
notifications.active = false
|
||||
return "NOTIFICATION_MODAL_CLOSE_SUCCESS"
|
||||
}
|
||||
return "NOTIFICATION_MODAL_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
notifications.active = true
|
||||
if (notifications.item) {
|
||||
notifications.item.toggle()
|
||||
return "NOTIFICATION_MODAL_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "NOTIFICATION_MODAL_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
target: "notifications"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
settings.active = true
|
||||
if (settings.item) {
|
||||
settings.item.show()
|
||||
return "SETTINGS_OPEN_SUCCESS"
|
||||
}
|
||||
return "SETTINGS_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
if (settings.item) {
|
||||
settings.item.hide()
|
||||
settings.active = false
|
||||
return "SETTINGS_CLOSE_SUCCESS"
|
||||
}
|
||||
return "SETTINGS_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
settings.active = true
|
||||
if (settings.item) {
|
||||
settings.item.toggle()
|
||||
return "SETTINGS_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "SETTINGS_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
target: "settings"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function browse(type: string) {
|
||||
settings.active = true
|
||||
if (settings.item) {
|
||||
if (type === "wallpaper") {
|
||||
settings.item.wallpaperBrowser.allowStacking = false
|
||||
settings.item.wallpaperBrowser.open()
|
||||
} else if (type === "profile") {
|
||||
settings.item.profileBrowser.allowStacking = false
|
||||
settings.item.profileBrowser.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target: "file"
|
||||
}
|
||||
}
|
||||
@@ -77,15 +77,12 @@ Item {
|
||||
width: parent.width
|
||||
height: parent.height - ClipboardConstants.headerHeight - 70
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceLight
|
||||
border.color: Theme.outlineLight
|
||||
border.width: 1
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
DankListView {
|
||||
id: clipboardListView
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
model: filteredModel
|
||||
|
||||
currentIndex: clipboardContent.modal ? clipboardContent.modal.selectedIndex : 0
|
||||
|
||||
@@ -24,17 +24,10 @@ Rectangle {
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (isSelected) {
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2)
|
||||
return Theme.primaryPressed
|
||||
}
|
||||
return mouseArea.containsMouse ? Theme.primaryHover : Theme.primaryBackground
|
||||
return mouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
|
||||
}
|
||||
border.color: {
|
||||
if (isSelected) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5)
|
||||
}
|
||||
return Theme.outlineStrong
|
||||
}
|
||||
border.width: isSelected ? 1.5 : 1
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -187,24 +187,6 @@ DankModal {
|
||||
filteredClipboardModel: filteredClipboardModel
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
clipboardHistoryModal.show()
|
||||
return "CLIPBOARD_OPEN_SUCCESS"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
clipboardHistoryModal.hide()
|
||||
return "CLIPBOARD_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
clipboardHistoryModal.toggle()
|
||||
return "CLIPBOARD_TOGGLE_SUCCESS"
|
||||
}
|
||||
|
||||
target: "clipboard"
|
||||
}
|
||||
|
||||
clipboardContent: Component {
|
||||
ClipboardContent {
|
||||
|
||||
37
Modals/ColorPickerModal.qml
Normal file
37
Modals/ColorPickerModal.qml
Normal file
@@ -0,0 +1,37 @@
|
||||
import QtQuick
|
||||
import Qt.labs.platform
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: colorPickerModal
|
||||
|
||||
signal colorSelected(color selectedColor)
|
||||
|
||||
function show() {
|
||||
colorDialog.open()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
colorDialog.close()
|
||||
}
|
||||
|
||||
function copyColorToClipboard(colorValue) {
|
||||
Quickshell.execDetached(["sh", "-c", `echo "${colorValue}" | wl-copy`])
|
||||
ToastService.showInfo(`Color ${colorValue} copied to clipboard`)
|
||||
console.log("Copied color to clipboard:", colorValue)
|
||||
}
|
||||
|
||||
ColorDialog {
|
||||
id: colorDialog
|
||||
title: "Color Picker - Select and copy color"
|
||||
color: Theme.primary
|
||||
|
||||
onAccepted: {
|
||||
const colorString = color.toString()
|
||||
copyColorToClipboard(colorString)
|
||||
colorSelected(color)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,9 @@ DankModal {
|
||||
property bool selectedFileIsDir: false
|
||||
property bool showOverwriteConfirmation: false
|
||||
property string pendingFilePath: ""
|
||||
property bool weAvailable: false
|
||||
property string wePath: ""
|
||||
property bool weMode: false
|
||||
|
||||
signal fileSelected(string path)
|
||||
|
||||
@@ -131,6 +134,31 @@ DankModal {
|
||||
Component.onCompleted: {
|
||||
currentPath = getLastPath()
|
||||
}
|
||||
|
||||
property var steamPaths: [
|
||||
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.steam/steam/steamapps/workshop/content/431960",
|
||||
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.local/share/Steam/steamapps/workshop/content/431960",
|
||||
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/workshop/content/431960",
|
||||
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/snap/steam/common/.local/share/Steam/steamapps/workshop/content/431960"
|
||||
]
|
||||
property int currentPathIndex: 0
|
||||
|
||||
function discoverWallpaperEngine() {
|
||||
currentPathIndex = 0
|
||||
checkNextPath()
|
||||
}
|
||||
|
||||
function checkNextPath() {
|
||||
if (currentPathIndex >= steamPaths.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const wePath = steamPaths[currentPathIndex]
|
||||
const cleanPath = wePath.replace(/^file:\/\//, '')
|
||||
weDiscoveryProcess.command = ["test", "-d", cleanPath]
|
||||
weDiscoveryProcess.wePath = wePath
|
||||
weDiscoveryProcess.running = true
|
||||
}
|
||||
width: 800
|
||||
height: 600
|
||||
enableShadow: true
|
||||
@@ -148,6 +176,9 @@ DankModal {
|
||||
selectedIndex = -1
|
||||
keyboardNavigationActive = false
|
||||
backButtonFocused = false
|
||||
if (browserType === "wallpaper" && !weAvailable) {
|
||||
discoverWallpaperEngine()
|
||||
}
|
||||
}
|
||||
}
|
||||
onCurrentPathChanged: {
|
||||
@@ -334,6 +365,23 @@ DankModal {
|
||||
executeKeyboardSelection(targetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: weDiscoveryProcess
|
||||
|
||||
property string wePath: ""
|
||||
running: false
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
fileBrowserModal.weAvailable = true
|
||||
fileBrowserModal.wePath = wePath
|
||||
} else {
|
||||
currentPathIndex++
|
||||
checkNextPath()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
@@ -373,6 +421,22 @@ DankModal {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankActionButton {
|
||||
circular: false
|
||||
iconName: "movie"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: weMode ? Theme.primary : Theme.surfaceText
|
||||
visible: weAvailable && browserType === "wallpaper"
|
||||
onClicked: {
|
||||
weMode = !weMode
|
||||
if (weMode) {
|
||||
navigateTo(wePath)
|
||||
} else {
|
||||
navigateTo(getLastPath())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
circular: false
|
||||
iconName: "info"
|
||||
@@ -439,8 +503,8 @@ DankModal {
|
||||
width: parent.width
|
||||
height: parent.height - 80
|
||||
clip: true
|
||||
cellWidth: 150
|
||||
cellHeight: 130
|
||||
cellWidth: weMode ? 255 : 150
|
||||
cellHeight: weMode ? 215 : 130
|
||||
cacheBuffer: 260
|
||||
model: folderModel
|
||||
currentIndex: selectedIndex
|
||||
@@ -466,8 +530,8 @@ DankModal {
|
||||
required property url fileURL
|
||||
required property int index
|
||||
|
||||
width: 140
|
||||
height: 120
|
||||
width: weMode ? 245 : 140
|
||||
height: weMode ? 205 : 120
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (keyboardNavigationActive && delegateRoot.index === selectedIndex)
|
||||
@@ -498,16 +562,33 @@ DankModal {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Item {
|
||||
width: 80
|
||||
height: 60
|
||||
width: weMode ? 225 : 80
|
||||
height: weMode ? 165 : 60
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
CachingImage {
|
||||
anchors.fill: parent
|
||||
source: (!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) ? ("file://" + delegateRoot.filePath) : ""
|
||||
property var weExtensions: [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tga"]
|
||||
property int weExtIndex: 0
|
||||
source: {
|
||||
if (weMode && delegateRoot.fileIsDir) {
|
||||
return "file://" + delegateRoot.filePath + "/preview" + weExtensions[weExtIndex]
|
||||
}
|
||||
return (!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) ? ("file://" + delegateRoot.filePath) : ""
|
||||
}
|
||||
onStatusChanged: {
|
||||
if (weMode && delegateRoot.fileIsDir && status === Image.Error) {
|
||||
if (weExtIndex < weExtensions.length - 1) {
|
||||
weExtIndex++
|
||||
source = "file://" + delegateRoot.filePath + "/preview" + weExtensions[weExtIndex]
|
||||
} else {
|
||||
source = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
visible: !delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)
|
||||
maxCacheSize: 80
|
||||
visible: (!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) || (weMode && delegateRoot.fileIsDir)
|
||||
maxCacheSize: weMode ? 225 : 80
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
@@ -523,7 +604,7 @@ DankModal {
|
||||
name: "folder"
|
||||
size: Theme.iconSizeLarge
|
||||
color: Theme.primary
|
||||
visible: delegateRoot.fileIsDir
|
||||
visible: delegateRoot.fileIsDir && !weMode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,11 +631,15 @@ DankModal {
|
||||
// Update selected file info and index first
|
||||
selectedIndex = delegateRoot.index
|
||||
setSelectedFileData(delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
|
||||
if (delegateRoot.fileIsDir) {
|
||||
if (weMode && delegateRoot.fileIsDir) {
|
||||
var sceneId = delegateRoot.filePath.split("/").pop()
|
||||
fileSelected("we:" + sceneId)
|
||||
fileBrowserModal.close()
|
||||
} else if (delegateRoot.fileIsDir) {
|
||||
navigateTo(delegateRoot.filePath)
|
||||
} else {
|
||||
fileSelected(delegateRoot.filePath)
|
||||
fileBrowserModal.close() // Close modal after file selection
|
||||
fileBrowserModal.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -566,7 +651,11 @@ DankModal {
|
||||
fileBrowserModal.keyboardSelectionRequested = false
|
||||
selectedIndex = delegateRoot.index
|
||||
setSelectedFileData(delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
|
||||
if (delegateRoot.fileIsDir) {
|
||||
if (weMode && delegateRoot.fileIsDir) {
|
||||
var sceneId = delegateRoot.filePath.split("/").pop()
|
||||
fileSelected("we:" + sceneId)
|
||||
fileBrowserModal.close()
|
||||
} else if (delegateRoot.fileIsDir) {
|
||||
navigateTo(delegateRoot.filePath)
|
||||
} else {
|
||||
fileSelected(delegateRoot.filePath)
|
||||
|
||||
@@ -27,6 +27,7 @@ DankModal {
|
||||
modalKeyboardController.updateSelectedIdFromIndex()
|
||||
if (notificationListRef) {
|
||||
notificationListRef.keyboardActive = true
|
||||
notificationListRef.currentIndex = 0
|
||||
}
|
||||
modalKeyboardController.selectionVersion++
|
||||
modalKeyboardController.ensureVisible()
|
||||
@@ -70,24 +71,6 @@ DankModal {
|
||||
onClose: () => notificationModal.hide()
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
notificationModal.show();
|
||||
return "NOTIFICATION_MODAL_OPEN_SUCCESS";
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
notificationModal.hide();
|
||||
return "NOTIFICATION_MODAL_CLOSE_SUCCESS";
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
notificationModal.toggle();
|
||||
return "NOTIFICATION_MODAL_TOGGLE_SUCCESS";
|
||||
}
|
||||
|
||||
target: "notifications"
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
|
||||
@@ -1,45 +1,51 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: root
|
||||
|
||||
property int selectedIndex: 0
|
||||
property int optionCount: 4
|
||||
property int optionCount: SessionService.hibernateSupported ? 5 : 4
|
||||
|
||||
signal powerActionRequested(string action, string title, string message)
|
||||
|
||||
function selectOption() {
|
||||
function selectOption(action) {
|
||||
close();
|
||||
const actions = [{
|
||||
"action": "logout",
|
||||
"title": "Log Out",
|
||||
"message": "Are you sure you want to log out?"
|
||||
}, {
|
||||
"action": "suspend",
|
||||
"title": "Suspend",
|
||||
"message": "Are you sure you want to suspend the system?"
|
||||
}, {
|
||||
"action": "reboot",
|
||||
"title": "Reboot",
|
||||
"message": "Are you sure you want to reboot the system?"
|
||||
}, {
|
||||
"action": "poweroff",
|
||||
"title": "Power Off",
|
||||
"message": "Are you sure you want to power off the system?"
|
||||
}];
|
||||
const selected = actions[selectedIndex];
|
||||
const actions = {
|
||||
"logout": {
|
||||
"title": "Log Out",
|
||||
"message": "Are you sure you want to log out?"
|
||||
},
|
||||
"suspend": {
|
||||
"title": "Suspend",
|
||||
"message": "Are you sure you want to suspend the system?"
|
||||
},
|
||||
"hibernate": {
|
||||
"title": "Hibernate",
|
||||
"message": "Are you sure you want to hibernate the system?"
|
||||
},
|
||||
"reboot": {
|
||||
"title": "Reboot",
|
||||
"message": "Are you sure you want to reboot the system?"
|
||||
},
|
||||
"poweroff": {
|
||||
"title": "Power Off",
|
||||
"message": "Are you sure you want to power off the system?"
|
||||
}
|
||||
}
|
||||
const selected = actions[action]
|
||||
if (selected) {
|
||||
root.powerActionRequested(selected.action, selected.title, selected.message);
|
||||
root.powerActionRequested(action, selected.title, selected.message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
shouldBeVisible: false
|
||||
width: 320
|
||||
height: 300
|
||||
height: contentLoader.item ? contentLoader.item.implicitHeight : 300
|
||||
enableShadow: true
|
||||
onBackgroundClicked: () => {
|
||||
return close();
|
||||
@@ -64,7 +70,12 @@ DankModal {
|
||||
break;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
selectOption();
|
||||
const actions = ["logout", "suspend"];
|
||||
if (SessionService.hibernateSupported) actions.push("hibernate");
|
||||
actions.push("reboot", "poweroff");
|
||||
if (selectedIndex < actions.length) {
|
||||
selectOption(actions[selectedIndex]);
|
||||
}
|
||||
event.accepted = true;
|
||||
break;
|
||||
}
|
||||
@@ -73,8 +84,10 @@ DankModal {
|
||||
content: Component {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
implicitHeight: mainColumn.implicitHeight + Theme.spacingL * 2
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
@@ -157,7 +170,7 @@ DankModal {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
selectedIndex = 0;
|
||||
selectOption();
|
||||
selectOption("logout");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +223,7 @@ DankModal {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
selectedIndex = 1;
|
||||
selectOption();
|
||||
selectOption("suspend");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,15 +235,70 @@ DankModal {
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (selectedIndex === 2) {
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
||||
} else if (rebootArea.containsMouse) {
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08);
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
||||
} else if (hibernateArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
|
||||
} else {
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
|
||||
}
|
||||
}
|
||||
border.color: selectedIndex === 2 ? Theme.warning : "transparent"
|
||||
border.color: selectedIndex === 2 ? Theme.primary : "transparent"
|
||||
border.width: selectedIndex === 2 ? 1 : 0
|
||||
visible: SessionService.hibernateSupported
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "ac_unit"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Hibernate"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: hibernateArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
selectedIndex = 2;
|
||||
selectOption("hibernate");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
const rebootIndex = SessionService.hibernateSupported ? 3 : 2;
|
||||
if (selectedIndex === rebootIndex) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
||||
} else if (rebootArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
|
||||
} else {
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
|
||||
}
|
||||
}
|
||||
border.color: selectedIndex === (SessionService.hibernateSupported ? 3 : 2) ? Theme.primary : "transparent"
|
||||
border.width: selectedIndex === (SessionService.hibernateSupported ? 3 : 2) ? 1 : 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -262,8 +330,8 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
selectedIndex = 2;
|
||||
selectOption();
|
||||
selectedIndex = SessionService.hibernateSupported ? 3 : 2;
|
||||
selectOption("reboot");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,16 +342,17 @@ DankModal {
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (selectedIndex === 3) {
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
|
||||
const powerOffIndex = SessionService.hibernateSupported ? 4 : 3;
|
||||
if (selectedIndex === powerOffIndex) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
||||
} else if (powerOffArea.containsMouse) {
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08);
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
|
||||
} else {
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
|
||||
}
|
||||
}
|
||||
border.color: selectedIndex === 3 ? Theme.error : "transparent"
|
||||
border.width: selectedIndex === 3 ? 1 : 0
|
||||
border.color: selectedIndex === (SessionService.hibernateSupported ? 4 : 3) ? Theme.primary : "transparent"
|
||||
border.width: selectedIndex === (SessionService.hibernateSupported ? 4 : 3) ? 1 : 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -315,8 +384,8 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
selectedIndex = 3;
|
||||
selectOption();
|
||||
selectedIndex = SessionService.hibernateSupported ? 4 : 3;
|
||||
selectOption("poweroff");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,14 +397,6 @@ DankModal {
|
||||
height: Theme.spacingS
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "↑↓ Navigate • Tab Cycle • Enter Select • Esc Close"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
opacity: 0.7
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ DankModal {
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 52
|
||||
color: Theme.surfaceSelected
|
||||
color: Theme.surfaceContainerHigh
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Theme.outlineLight
|
||||
border.width: 1
|
||||
@@ -281,7 +281,7 @@ DankModal {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceLight
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Theme.outlineLight
|
||||
border.width: 1
|
||||
|
||||
|
||||
246
Modals/Settings/PowerSettings.qml
Normal file
246
Modals/Settings/PowerSettings.qml
Normal file
@@ -0,0 +1,246 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: powerTab
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Theme.spacingL
|
||||
clip: true
|
||||
contentHeight: mainColumn.height
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
StyledText {
|
||||
text: "Battery not detected - only AC power settings available"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
visible: !BatteryService.batteryAvailable
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: timeoutSection.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: 0
|
||||
|
||||
Column {
|
||||
id: timeoutSection
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "schedule"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Idle Settings"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Math.max(0, parent.width - parent.children[0].width - parent.children[1].width - powerCategory.width - Theme.spacingM * 3)
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
DankButtonGroup {
|
||||
id: powerCategory
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: BatteryService.batteryAvailable
|
||||
model: ["AC Power", "Battery"]
|
||||
currentIndex: 0
|
||||
selectionMode: "single"
|
||||
checkEnabled: false
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
id: lockDropdown
|
||||
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||
|
||||
width: parent.width
|
||||
text: "Automatically lock after"
|
||||
options: timeoutOptions
|
||||
|
||||
Connections {
|
||||
target: powerCategory
|
||||
function onCurrentIndexChanged() {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acLockTimeout : SessionData.batteryLockTimeout
|
||||
const index = lockDropdown.timeoutValues.indexOf(currentTimeout)
|
||||
lockDropdown.currentValue = index >= 0 ? lockDropdown.timeoutOptions[index] : "Never"
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acLockTimeout : SessionData.batteryLockTimeout
|
||||
const index = timeoutValues.indexOf(currentTimeout)
|
||||
currentValue = index >= 0 ? timeoutOptions[index] : "Never"
|
||||
}
|
||||
|
||||
onValueChanged: value => {
|
||||
const index = timeoutOptions.indexOf(value)
|
||||
if (index >= 0) {
|
||||
const timeout = timeoutValues[index]
|
||||
if (powerCategory.currentIndex === 0) {
|
||||
SessionData.setAcLockTimeout(timeout)
|
||||
} else {
|
||||
SessionData.setBatteryLockTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
id: monitorDropdown
|
||||
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||
|
||||
width: parent.width
|
||||
text: "Turn off monitors after"
|
||||
options: timeoutOptions
|
||||
|
||||
Connections {
|
||||
target: powerCategory
|
||||
function onCurrentIndexChanged() {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acMonitorTimeout : SessionData.batteryMonitorTimeout
|
||||
const index = monitorDropdown.timeoutValues.indexOf(currentTimeout)
|
||||
monitorDropdown.currentValue = index >= 0 ? monitorDropdown.timeoutOptions[index] : "Never"
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acMonitorTimeout : SessionData.batteryMonitorTimeout
|
||||
const index = timeoutValues.indexOf(currentTimeout)
|
||||
currentValue = index >= 0 ? timeoutOptions[index] : "Never"
|
||||
}
|
||||
|
||||
onValueChanged: value => {
|
||||
const index = timeoutOptions.indexOf(value)
|
||||
if (index >= 0) {
|
||||
const timeout = timeoutValues[index]
|
||||
if (powerCategory.currentIndex === 0) {
|
||||
SessionData.setAcMonitorTimeout(timeout)
|
||||
} else {
|
||||
SessionData.setBatteryMonitorTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
id: suspendDropdown
|
||||
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||
|
||||
width: parent.width
|
||||
text: "Suspend system after"
|
||||
options: timeoutOptions
|
||||
|
||||
Connections {
|
||||
target: powerCategory
|
||||
function onCurrentIndexChanged() {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acSuspendTimeout : SessionData.batterySuspendTimeout
|
||||
const index = suspendDropdown.timeoutValues.indexOf(currentTimeout)
|
||||
suspendDropdown.currentValue = index >= 0 ? suspendDropdown.timeoutOptions[index] : "Never"
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acSuspendTimeout : SessionData.batterySuspendTimeout
|
||||
const index = timeoutValues.indexOf(currentTimeout)
|
||||
currentValue = index >= 0 ? timeoutOptions[index] : "Never"
|
||||
}
|
||||
|
||||
onValueChanged: value => {
|
||||
const index = timeoutOptions.indexOf(value)
|
||||
if (index >= 0) {
|
||||
const timeout = timeoutValues[index]
|
||||
if (powerCategory.currentIndex === 0) {
|
||||
SessionData.setAcSuspendTimeout(timeout)
|
||||
} else {
|
||||
SessionData.setBatterySuspendTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
id: hibernateDropdown
|
||||
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||
|
||||
width: parent.width
|
||||
text: "Hibernate system after"
|
||||
options: timeoutOptions
|
||||
visible: SessionService.hibernateSupported
|
||||
|
||||
Connections {
|
||||
target: powerCategory
|
||||
function onCurrentIndexChanged() {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acHibernateTimeout : SessionData.batteryHibernateTimeout
|
||||
const index = hibernateDropdown.timeoutValues.indexOf(currentTimeout)
|
||||
hibernateDropdown.currentValue = index >= 0 ? hibernateDropdown.timeoutOptions[index] : "Never"
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acHibernateTimeout : SessionData.batteryHibernateTimeout
|
||||
const index = timeoutValues.indexOf(currentTimeout)
|
||||
currentValue = index >= 0 ? timeoutOptions[index] : "Never"
|
||||
}
|
||||
|
||||
onValueChanged: value => {
|
||||
const index = timeoutOptions.indexOf(value)
|
||||
if (index >= 0) {
|
||||
const timeout = timeoutValues[index]
|
||||
if (powerCategory.currentIndex === 0) {
|
||||
SessionData.setAcHibernateTimeout(timeout)
|
||||
} else {
|
||||
SessionData.setBatteryHibernateTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: "Lock before suspend"
|
||||
description: "Automatically lock the screen when the system prepares to suspend"
|
||||
checked: SessionData.lockBeforeSuspend
|
||||
onToggled: checked => SessionData.setLockBeforeSuspend(checked)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Idle monitoring not supported - requires newer Quickshell version"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.error
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: !IdleService.idleMonitorAvailable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,25 +25,15 @@ Rectangle {
|
||||
Item {
|
||||
id: profileImageContainer
|
||||
|
||||
property bool hasImage: profileImageSource.status === Image.Ready
|
||||
|
||||
width: 80
|
||||
height: 80
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
DankCircularImage {
|
||||
id: profileImage
|
||||
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: "transparent"
|
||||
border.color: Theme.primary
|
||||
border.width: 1
|
||||
visible: parent.hasImage
|
||||
}
|
||||
|
||||
Image {
|
||||
id: profileImageSource
|
||||
|
||||
source: {
|
||||
imageSource: {
|
||||
if (PortalService.profileImage === "") {
|
||||
return "";
|
||||
}
|
||||
@@ -52,55 +42,7 @@ Rectangle {
|
||||
}
|
||||
return PortalService.profileImage;
|
||||
}
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
mipmap: true
|
||||
cache: true
|
||||
visible: false
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 5
|
||||
source: profileImageSource
|
||||
maskEnabled: true
|
||||
maskSource: profileCircularMask
|
||||
visible: profileImageContainer.hasImage
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
|
||||
Item {
|
||||
id: profileCircularMask
|
||||
|
||||
width: 70
|
||||
height: 70
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: "black"
|
||||
antialiasing: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: Theme.primary
|
||||
visible: !parent.hasImage
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "person"
|
||||
size: Theme.iconSizeLarge
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
fallbackIcon: "person"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -147,7 +89,7 @@ Rectangle {
|
||||
height: 28
|
||||
radius: 14
|
||||
color: Qt.rgba(255, 255, 255, 0.9)
|
||||
visible: profileImageContainer.hasImage
|
||||
visible: profileImage.hasImage
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -115,14 +115,14 @@ Item {
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: recentAppsLoader
|
||||
id: launcherLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 7
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: RecentAppsTab {
|
||||
sourceComponent: LauncherTab {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -141,13 +141,26 @@ Item {
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: aboutLoader
|
||||
id: powerLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 9
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: PowerSettings {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: aboutLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 10
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: AboutTab {
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ DankModal {
|
||||
id: settingsModal
|
||||
|
||||
property Component settingsContent
|
||||
property alias profileBrowser: profileBrowser
|
||||
|
||||
signal closingModal()
|
||||
|
||||
@@ -40,38 +41,6 @@ DankModal {
|
||||
}
|
||||
content: settingsContent
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
settingsModal.show();
|
||||
return "SETTINGS_OPEN_SUCCESS";
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
settingsModal.hide();
|
||||
return "SETTINGS_CLOSE_SUCCESS";
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
settingsModal.toggle();
|
||||
return "SETTINGS_TOGGLE_SUCCESS";
|
||||
}
|
||||
|
||||
target: "settings"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function browse(type: string) {
|
||||
if (type === "wallpaper") {
|
||||
wallpaperBrowser.allowStacking = false;
|
||||
wallpaperBrowser.open();
|
||||
} else if (type === "profile") {
|
||||
profileBrowser.allowStacking = false;
|
||||
profileBrowser.open();
|
||||
}
|
||||
}
|
||||
|
||||
target: "file"
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: profileBrowser
|
||||
|
||||
@@ -30,11 +30,14 @@ Rectangle {
|
||||
"text": "Displays",
|
||||
"icon": "monitor"
|
||||
}, {
|
||||
"text": "Recent Apps",
|
||||
"icon": "history"
|
||||
"text": "Launcher",
|
||||
"icon": "apps"
|
||||
}, {
|
||||
"text": "Theme & Colors",
|
||||
"icon": "palette"
|
||||
}, {
|
||||
"text": "Power",
|
||||
"icon": "power_settings_new"
|
||||
}, {
|
||||
"text": "About",
|
||||
"icon": "info"
|
||||
@@ -80,7 +83,7 @@ Rectangle {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 44
|
||||
radius: Theme.cornerRadius
|
||||
color: isActive ? Theme.surfaceContainerHigh : tabMouseArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
color: isActive ? Theme.primaryContainer : tabMouseArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -91,14 +94,14 @@ Rectangle {
|
||||
DankIcon {
|
||||
name: modelData.icon || ""
|
||||
size: Theme.iconSize - 2
|
||||
color: parent.parent.isActive ? Theme.primary : Theme.surfaceText
|
||||
color: parent.parent.isActive ? Theme.surfaceText : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.text || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: parent.parent.isActive ? Theme.primary : Theme.surfaceText
|
||||
color: parent.parent.isActive ? Theme.surfaceText : Theme.surfaceText
|
||||
font.weight: parent.parent.isActive ? Font.Medium : Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
@@ -59,23 +59,21 @@ Item {
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: categorySelector.height + Theme.spacingM * 2
|
||||
height: categorySelector.height + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceVariantAlpha
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
color: "transparent"
|
||||
visible: appLauncher.categories.length > 1 || appLauncher.model.count > 0
|
||||
|
||||
CategorySelector {
|
||||
id: categorySelector
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
categories: appLauncher.categories
|
||||
selectedCategory: appLauncher.selectedCategory
|
||||
compact: false
|
||||
@@ -88,14 +86,15 @@ Item {
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
leftPadding: Theme.spacingS
|
||||
|
||||
DankTextField {
|
||||
id: searchField
|
||||
|
||||
width: parent.width - 80 - Theme.spacingM
|
||||
width: parent.width - 80 - Theme.spacingL
|
||||
height: 56
|
||||
cornerRadius: Theme.cornerRadius
|
||||
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7)
|
||||
backgroundColor: Theme.surfaceContainerHigh
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
focusedBorderColor: Theme.primary
|
||||
leftIconName: "search"
|
||||
@@ -141,8 +140,6 @@ Item {
|
||||
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.width: 1
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
@@ -168,8 +165,6 @@ Item {
|
||||
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.width: 1
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -82,24 +82,6 @@ DankModal {
|
||||
target: ModalManager
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
spotlightModal.show()
|
||||
return "SPOTLIGHT_OPEN_SUCCESS"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
spotlightModal.hide()
|
||||
return "SPOTLIGHT_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
spotlightModal.toggle()
|
||||
return "SPOTLIGHT_TOGGLE_SUCCESS"
|
||||
}
|
||||
|
||||
target: "spotlight"
|
||||
}
|
||||
|
||||
spotlightContent: Component {
|
||||
SpotlightContent {
|
||||
|
||||
@@ -13,9 +13,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height - y
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceLight
|
||||
border.color: Theme.outlineLight
|
||||
border.width: 1
|
||||
color: "transparent"
|
||||
|
||||
DankListView {
|
||||
id: resultsList
|
||||
@@ -75,9 +73,7 @@ Rectangle {
|
||||
width: ListView.view.width
|
||||
height: resultsList.itemHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: ListView.isCurrentItem ? Theme.primaryPressed : listMouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03)
|
||||
border.color: ListView.isCurrentItem ? Theme.primarySelected : Theme.outlineMedium
|
||||
border.width: ListView.isCurrentItem ? 2 : 1
|
||||
color: ListView.isCurrentItem ? Theme.primaryPressed : listMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
@@ -136,6 +132,7 @@ Rectangle {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
visible: resultsList.showDescription && model.comment && model.comment.length > 0
|
||||
}
|
||||
}
|
||||
@@ -237,9 +234,7 @@ Rectangle {
|
||||
width: resultsGrid.cellWidth - resultsGrid.cellPadding
|
||||
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.width: resultsGrid.currentIndex === index ? 2 : 1
|
||||
color: resultsGrid.currentIndex === index ? Theme.primaryPressed : gridMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -33,7 +33,7 @@ DankPopout {
|
||||
popupWidth: 520
|
||||
popupHeight: 600
|
||||
triggerX: Theme.spacingL
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingXS
|
||||
triggerY: Math.max(26 + SettingsData.topBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.topBarInnerPadding)) + SettingsData.topBarSpacing + SettingsData.topBarBottomGap - 2 + Theme.popupDistance
|
||||
triggerWidth: 40
|
||||
positioning: "center"
|
||||
screen: triggerScreen
|
||||
@@ -95,7 +95,7 @@ DankPopout {
|
||||
color: "transparent"
|
||||
radius: parent.radius + Math.abs(modelData.margin)
|
||||
border.color: modelData.color
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
z: modelData.z
|
||||
}
|
||||
}
|
||||
@@ -136,15 +136,16 @@ DankPopout {
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.spacingL * 2
|
||||
height: parent.height - Theme.spacingL * 2
|
||||
x: Theme.spacingL
|
||||
y: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: parent.height - Theme.spacingS * 2
|
||||
x: Theme.spacingS
|
||||
y: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 40
|
||||
leftPadding: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -170,7 +171,8 @@ DankPopout {
|
||||
DankTextField {
|
||||
id: searchField
|
||||
|
||||
width: parent.width
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
height: 52
|
||||
cornerRadius: Theme.cornerRadius
|
||||
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7)
|
||||
@@ -231,6 +233,8 @@ DankPopout {
|
||||
height: 40
|
||||
spacing: Theme.spacingM
|
||||
visible: searchField.text.length === 0
|
||||
leftPadding: Theme.spacingS
|
||||
topPadding: Theme.spacingXS
|
||||
|
||||
Item {
|
||||
width: 200
|
||||
@@ -238,6 +242,9 @@ DankPopout {
|
||||
|
||||
DankDropdown {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
text: ""
|
||||
currentValue: appLauncher.selectedCategory
|
||||
options: appLauncher.categories
|
||||
@@ -249,7 +256,7 @@ DankPopout {
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - 300
|
||||
width: parent.width - 310
|
||||
height: 1
|
||||
}
|
||||
|
||||
@@ -286,15 +293,13 @@ DankPopout {
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: {
|
||||
let usedHeight = 40 + Theme.spacingL
|
||||
usedHeight += 52 + Theme.spacingL
|
||||
usedHeight += (searchField.text.length === 0 ? 40 + Theme.spacingL : 0)
|
||||
let usedHeight = 40 + Theme.spacingS
|
||||
usedHeight += 52 + Theme.spacingS
|
||||
usedHeight += (searchField.text.length === 0 ? 40 : 0)
|
||||
return parent.height - usedHeight
|
||||
}
|
||||
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
|
||||
color: "transparent"
|
||||
|
||||
DankListView {
|
||||
id: appList
|
||||
@@ -323,7 +328,9 @@ DankPopout {
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
visible: appLauncher.viewMode === "list"
|
||||
model: appLauncher.model
|
||||
currentIndex: appLauncher.selectedIndex
|
||||
@@ -353,9 +360,7 @@ DankPopout {
|
||||
width: ListView.view.width
|
||||
height: appList.itemHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: ListView.isCurrentItem ? Theme.primaryPressed : listMouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03)
|
||||
border.color: ListView.isCurrentItem ? Theme.primarySelected : Theme.outlineMedium
|
||||
border.width: ListView.isCurrentItem ? 2 : 1
|
||||
color: ListView.isCurrentItem ? Theme.primaryPressed : listMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
@@ -371,6 +376,7 @@ DankPopout {
|
||||
id: listIconImg
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingXS
|
||||
source: Quickshell.iconPath(model.icon, true)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
@@ -379,10 +385,13 @@ DankPopout {
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingM
|
||||
visible: !listIconImg.visible
|
||||
color: Theme.surfaceLight
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
border.color: Theme.primarySelected
|
||||
|
||||
StyledText {
|
||||
@@ -415,6 +424,7 @@ DankPopout {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
visible: appList.showDescription && model.comment && model.comment.length > 0
|
||||
}
|
||||
}
|
||||
@@ -424,6 +434,9 @@ DankPopout {
|
||||
id: listMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingM
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
@@ -483,7 +496,9 @@ DankPopout {
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
visible: appLauncher.viewMode === "grid"
|
||||
model: appLauncher.model
|
||||
clip: true
|
||||
@@ -515,9 +530,7 @@ DankPopout {
|
||||
width: appGrid.cellWidth - appGrid.cellPadding
|
||||
height: appGrid.cellHeight - appGrid.cellPadding
|
||||
radius: Theme.cornerRadius
|
||||
color: appGrid.currentIndex === index ? Theme.primaryPressed : gridMouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03)
|
||||
border.color: appGrid.currentIndex === index ? Theme.primarySelected : Theme.outlineMedium
|
||||
border.width: appGrid.currentIndex === index ? 2 : 1
|
||||
color: appGrid.currentIndex === index ? Theme.primaryPressed : gridMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
@@ -534,6 +547,9 @@ DankPopout {
|
||||
id: gridIconImg
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
source: Quickshell.iconPath(model.icon, true)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
@@ -542,10 +558,13 @@ DankPopout {
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
visible: !gridIconImg.visible
|
||||
color: Theme.surfaceLight
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
border.color: Theme.primarySelected
|
||||
|
||||
StyledText {
|
||||
@@ -576,6 +595,9 @@ DankPopout {
|
||||
id: gridMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
@@ -652,7 +674,7 @@ DankPopout {
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
z: 1000
|
||||
opacity: menuVisible ? 1 : 0
|
||||
scale: menuVisible ? 1 : 0.85
|
||||
|
||||
@@ -17,6 +17,7 @@ Item {
|
||||
property bool debounceSearch: true
|
||||
property int debounceInterval: 50
|
||||
property bool keyboardNavigationActive: false
|
||||
property bool suppressUpdatesWhileLaunching: false
|
||||
readonly property var categories: {
|
||||
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
|
||||
const result = ["All"]
|
||||
@@ -32,6 +33,10 @@ Item {
|
||||
signal viewModeSelected(string mode)
|
||||
|
||||
function updateFilteredModel() {
|
||||
if (suppressUpdatesWhileLaunching) {
|
||||
suppressUpdatesWhileLaunching = false
|
||||
return
|
||||
}
|
||||
filteredModel.clear()
|
||||
selectedIndex = 0
|
||||
keyboardNavigationActive = false
|
||||
@@ -125,7 +130,8 @@ Item {
|
||||
if (!appData) {
|
||||
return
|
||||
}
|
||||
appData.desktopEntry.execute()
|
||||
suppressUpdatesWhileLaunching = true
|
||||
SessionService.launchDesktopEntry(appData.desktopEntry)
|
||||
appLaunched(appData)
|
||||
AppUsageHistoryData.addAppUsage(appData.desktopEntry)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ Item {
|
||||
readonly property int maxCompactItems: 8
|
||||
readonly property int itemHeight: 36
|
||||
readonly property color selectedBorderColor: "transparent"
|
||||
readonly property color unselectedBorderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
readonly property color unselectedBorderColor: "transparent"
|
||||
|
||||
function handleCategoryClick(category) {
|
||||
selectedCategory = category
|
||||
@@ -42,8 +42,7 @@ Item {
|
||||
height: root.itemHeight
|
||||
width: root.getButtonWidth(itemCount, parent.width)
|
||||
radius: Theme.cornerRadius
|
||||
color: selectedCategory === modelData ? Theme.primary : "transparent"
|
||||
border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
|
||||
color: selectedCategory === modelData ? Theme.primary : Theme.surfaceContainerHigh
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
@@ -82,7 +81,7 @@ Item {
|
||||
height: root.itemHeight
|
||||
width: root.getButtonWidth(itemCount, parent.width)
|
||||
radius: Theme.cornerRadius
|
||||
color: selectedCategory === modelData ? Theme.primary : "transparent"
|
||||
color: selectedCategory === modelData ? Theme.primary : Theme.surfaceContainerHigh
|
||||
border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
|
||||
|
||||
StyledText {
|
||||
@@ -118,7 +117,7 @@ Item {
|
||||
height: root.itemHeight
|
||||
width: root.getButtonWidth(itemCount, parent.width)
|
||||
radius: Theme.cornerRadius
|
||||
color: selectedCategory === modelData ? Theme.primary : "transparent"
|
||||
color: selectedCategory === modelData ? Theme.primary : Theme.surfaceContainerHigh
|
||||
border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
|
||||
|
||||
StyledText {
|
||||
|
||||
120
Modules/ControlCenter/Components/ActionTile.qml
Normal file
120
Modules/ControlCenter/Components/ActionTile.qml
Normal file
@@ -0,0 +1,120 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string iconName: ""
|
||||
property string text: ""
|
||||
property string secondaryText: ""
|
||||
property bool isActive: false
|
||||
property bool enabled: true
|
||||
property int widgetIndex: 0
|
||||
property var widgetData: null
|
||||
property bool editMode: false
|
||||
|
||||
signal clicked()
|
||||
|
||||
width: parent ? parent.width : 200
|
||||
height: 60
|
||||
radius: {
|
||||
if (Theme.cornerRadius === 0) return 0
|
||||
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4
|
||||
}
|
||||
|
||||
readonly property color _tileBgActive: Theme.primary
|
||||
readonly property color _tileBgInactive:
|
||||
Theme.surfaceContainerHigh
|
||||
readonly property color _tileRingActive:
|
||||
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||
|
||||
color: isActive ? _tileBgActive : _tileBgInactive
|
||||
border.color: isActive ? _tileRingActive : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: isActive ? 1 : 1
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
function hoverTint(base) {
|
||||
const factor = 1.2
|
||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: mouseArea.containsMouse ? hoverTint(root.color) : "transparent"
|
||||
opacity: mouseArea.containsMouse ? 0.08 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingL + 2
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: root.iconName
|
||||
size: Theme.iconSize
|
||||
color: isActive ? Theme.primaryContainer : Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - Theme.iconSize - parent.spacing
|
||||
height: parent.height
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
Typography {
|
||||
width: parent.width
|
||||
text: root.text
|
||||
style: Typography.Style.Body
|
||||
color: isActive ? Theme.primaryContainer : Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
Typography {
|
||||
width: parent.width
|
||||
text: root.secondaryText
|
||||
style: Typography.Style.Caption
|
||||
color: isActive ? Theme.primaryContainer : Theme.surfaceVariantText
|
||||
visible: text.length > 0
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: root.enabled
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
86
Modules/ControlCenter/Components/DetailHost.qml
Normal file
86
Modules/ControlCenter/Components/DetailHost.qml
Normal file
@@ -0,0 +1,86 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.ControlCenter.Details
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string expandedSection: ""
|
||||
property var expandedWidgetData: null
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
height: 250
|
||||
y: Theme.spacingS
|
||||
active: parent.height > 0
|
||||
property string sectionKey: root.expandedSection
|
||||
sourceComponent: {
|
||||
switch (root.expandedSection) {
|
||||
case "network":
|
||||
case "wifi": return networkDetailComponent
|
||||
case "bluetooth": return bluetoothDetailComponent
|
||||
case "audioOutput": return audioOutputDetailComponent
|
||||
case "audioInput": return audioInputDetailComponent
|
||||
case "battery": return batteryDetailComponent
|
||||
default:
|
||||
if (root.expandedSection.startsWith("diskUsage_")) {
|
||||
return diskUsageDetailComponent
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
onSectionKeyChanged: {
|
||||
active = false
|
||||
active = true
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: networkDetailComponent
|
||||
NetworkDetail {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: bluetoothDetailComponent
|
||||
BluetoothDetail {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: audioOutputDetailComponent
|
||||
AudioOutputDetail {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: audioInputDetailComponent
|
||||
AudioInputDetail {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: batteryDetailComponent
|
||||
BatteryDetail {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: diskUsageDetailComponent
|
||||
DiskUsageDetail {
|
||||
currentMountPath: root.expandedWidgetData?.mountPath || "/"
|
||||
instanceId: root.expandedWidgetData?.instanceId || ""
|
||||
|
||||
|
||||
onMountPathChanged: (newMountPath) => {
|
||||
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
const newWidgets = widgets.map(w => {
|
||||
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
|
||||
const updatedWidget = Object.assign({}, w)
|
||||
updatedWidget.mountPath = newMountPath
|
||||
return updatedWidget
|
||||
}
|
||||
return w
|
||||
})
|
||||
SettingsData.setControlCenterWidgets(newWidgets)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Modules/ControlCenter/Components/DragDropDetailHost.qml
Normal file
91
Modules/ControlCenter/Components/DragDropDetailHost.qml
Normal file
@@ -0,0 +1,91 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.ControlCenter.Details
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string expandedSection: ""
|
||||
property var expandedWidgetData: null
|
||||
|
||||
height: active ? 250 : 0
|
||||
visible: active
|
||||
|
||||
readonly property bool active: expandedSection !== ""
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Theme.spacingS
|
||||
sourceComponent: {
|
||||
if (!root.active) return null
|
||||
|
||||
if (expandedSection.startsWith("diskUsage_")) {
|
||||
return diskUsageDetailComponent
|
||||
}
|
||||
|
||||
switch (expandedSection) {
|
||||
case "wifi": return networkDetailComponent
|
||||
case "bluetooth": return bluetoothDetailComponent
|
||||
case "audioOutput": return audioOutputDetailComponent
|
||||
case "audioInput": return audioInputDetailComponent
|
||||
case "battery": return batteryDetailComponent
|
||||
default: return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: networkDetailComponent
|
||||
NetworkDetail {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: bluetoothDetailComponent
|
||||
BluetoothDetail {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: audioOutputDetailComponent
|
||||
AudioOutputDetail {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: audioInputDetailComponent
|
||||
AudioInputDetail {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: batteryDetailComponent
|
||||
BatteryDetail {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: diskUsageDetailComponent
|
||||
DiskUsageDetail {
|
||||
currentMountPath: root.expandedWidgetData?.mountPath || "/"
|
||||
instanceId: root.expandedWidgetData?.instanceId || ""
|
||||
|
||||
onMountPathChanged: (newMountPath) => {
|
||||
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
const newWidgets = widgets.map(w => {
|
||||
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
|
||||
const updatedWidget = Object.assign({}, w)
|
||||
updatedWidget.mountPath = newMountPath
|
||||
return updatedWidget
|
||||
}
|
||||
return w
|
||||
})
|
||||
SettingsData.setControlCenterWidgets(newWidgets)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
672
Modules/ControlCenter/Components/DragDropGrid.qml
Normal file
672
Modules/ControlCenter/Components/DragDropGrid.qml
Normal file
@@ -0,0 +1,672 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
import qs.Modules.ControlCenter.Components
|
||||
import "../utils/layout.js" as LayoutUtils
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property bool editMode: false
|
||||
property string expandedSection: ""
|
||||
property int expandedWidgetIndex: -1
|
||||
property var model: null
|
||||
property var expandedWidgetData: null
|
||||
|
||||
signal expandClicked(var widgetData, int globalIndex)
|
||||
signal removeWidget(int index)
|
||||
signal moveWidget(int fromIndex, int toIndex)
|
||||
signal toggleWidgetSize(int index)
|
||||
|
||||
spacing: editMode ? Theme.spacingL : Theme.spacingS
|
||||
|
||||
property var currentRowWidgets: []
|
||||
property real currentRowWidth: 0
|
||||
property int expandedRowIndex: -1
|
||||
|
||||
function calculateRowsAndWidgets() {
|
||||
return LayoutUtils.calculateRowsAndWidgets(root, expandedSection, expandedWidgetIndex)
|
||||
}
|
||||
|
||||
property var layoutResult: {
|
||||
const dummy = [expandedSection, expandedWidgetIndex, model?.controlCenterWidgets]
|
||||
return calculateRowsAndWidgets()
|
||||
}
|
||||
|
||||
onLayoutResultChanged: {
|
||||
expandedRowIndex = layoutResult.expandedRowIndex
|
||||
}
|
||||
|
||||
function moveToTop(item) {
|
||||
const children = root.children
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
if (children[i] === item)
|
||||
continue
|
||||
if (children[i].z)
|
||||
children[i].z = Math.min(children[i].z, 999)
|
||||
}
|
||||
item.z = 1000
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.layoutResult.rows
|
||||
|
||||
Column {
|
||||
width: root.width
|
||||
spacing: 0
|
||||
property int rowIndex: index
|
||||
property var rowWidgets: modelData
|
||||
property bool isSliderOnlyRow: {
|
||||
const widgets = rowWidgets || []
|
||||
if (widgets.length === 0) return false
|
||||
return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider")
|
||||
}
|
||||
topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0
|
||||
bottomPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0
|
||||
|
||||
Flow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: rowWidgets || []
|
||||
|
||||
DragDropWidgetWrapper {
|
||||
widgetData: modelData
|
||||
property int globalWidgetIndex: {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
for (var i = 0; i < widgets.length; i++) {
|
||||
if (widgets[i].id === modelData.id) {
|
||||
if (modelData.id === "diskUsage") {
|
||||
if (widgets[i].instanceId === modelData.instanceId) {
|
||||
return i
|
||||
}
|
||||
} else {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
property int widgetWidth: modelData.width || 50
|
||||
width: {
|
||||
const baseWidth = root.width
|
||||
const spacing = Theme.spacingS
|
||||
if (widgetWidth <= 25) {
|
||||
return (baseWidth - spacing * 3) / 4
|
||||
} else if (widgetWidth <= 50) {
|
||||
return (baseWidth - spacing) / 2
|
||||
} else if (widgetWidth <= 75) {
|
||||
return (baseWidth - spacing * 2) * 0.75
|
||||
} else {
|
||||
return baseWidth
|
||||
}
|
||||
}
|
||||
height: isSliderOnlyRow ? 48 : 60
|
||||
|
||||
editMode: root.editMode
|
||||
widgetIndex: globalWidgetIndex
|
||||
gridCellWidth: width
|
||||
gridCellHeight: height
|
||||
gridColumns: 4
|
||||
gridLayout: root
|
||||
isSlider: {
|
||||
const id = modelData.id || ""
|
||||
return id === "volumeSlider" || id === "brightnessSlider" || id === "inputVolumeSlider"
|
||||
}
|
||||
|
||||
widgetComponent: {
|
||||
const id = modelData.id || ""
|
||||
if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
|
||||
return compoundPillComponent
|
||||
} else if (id === "volumeSlider") {
|
||||
return audioSliderComponent
|
||||
} else if (id === "brightnessSlider") {
|
||||
return brightnessSliderComponent
|
||||
} else if (id === "inputVolumeSlider") {
|
||||
return inputAudioSliderComponent
|
||||
} else if (id === "battery") {
|
||||
return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent
|
||||
} else if (id === "diskUsage") {
|
||||
return diskUsagePillComponent
|
||||
} else {
|
||||
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent
|
||||
}
|
||||
}
|
||||
|
||||
onWidgetMoved: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
onRemoveWidget: index => root.removeWidget(index)
|
||||
onToggleWidgetSize: index => root.toggleWidgetSize(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DetailHost {
|
||||
width: parent.width
|
||||
height: active ? (250 + Theme.spacingS) : 0
|
||||
property bool active: {
|
||||
if (root.expandedSection === "") return false
|
||||
|
||||
if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) {
|
||||
const expandedInstanceId = root.expandedWidgetData.instanceId
|
||||
return rowWidgets.some(w => w.id === "diskUsage" && w.instanceId === expandedInstanceId)
|
||||
}
|
||||
|
||||
return rowIndex === root.expandedRowIndex
|
||||
}
|
||||
visible: active
|
||||
expandedSection: root.expandedSection
|
||||
expandedWidgetData: root.expandedWidgetData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: compoundPillComponent
|
||||
CompoundPill {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||
width: parent.width
|
||||
height: 60
|
||||
iconName: {
|
||||
switch (widgetData.id || "") {
|
||||
case "wifi":
|
||||
{
|
||||
if (NetworkService.wifiToggling)
|
||||
return "sync"
|
||||
if (NetworkService.networkStatus === "ethernet")
|
||||
return "settings_ethernet"
|
||||
if (NetworkService.networkStatus === "wifi")
|
||||
return NetworkService.wifiSignalIcon
|
||||
if (NetworkService.wifiEnabled)
|
||||
return "wifi_off"
|
||||
return "wifi_off"
|
||||
}
|
||||
case "bluetooth":
|
||||
{
|
||||
if (!BluetoothService.available)
|
||||
return "bluetooth_disabled"
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled)
|
||||
return "bluetooth_disabled"
|
||||
const primaryDevice = (() => {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
|
||||
return null
|
||||
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
|
||||
for (let device of devices) {
|
||||
if (device && device.connected)
|
||||
return device
|
||||
}
|
||||
return null
|
||||
})()
|
||||
if (primaryDevice)
|
||||
return BluetoothService.getDeviceIcon(primaryDevice)
|
||||
return "bluetooth"
|
||||
}
|
||||
case "audioOutput":
|
||||
{
|
||||
if (!AudioService.sink)
|
||||
return "volume_off"
|
||||
let volume = AudioService.sink.audio.volume
|
||||
let muted = AudioService.sink.audio.muted
|
||||
if (muted || volume === 0.0)
|
||||
return "volume_off"
|
||||
if (volume <= 0.33)
|
||||
return "volume_down"
|
||||
if (volume <= 0.66)
|
||||
return "volume_up"
|
||||
return "volume_up"
|
||||
}
|
||||
case "audioInput":
|
||||
{
|
||||
if (!AudioService.source)
|
||||
return "mic_off"
|
||||
let muted = AudioService.source.audio.muted
|
||||
return muted ? "mic_off" : "mic"
|
||||
}
|
||||
default:
|
||||
return widgetDef?.icon || "help"
|
||||
}
|
||||
}
|
||||
primaryText: {
|
||||
switch (widgetData.id || "") {
|
||||
case "wifi":
|
||||
{
|
||||
if (NetworkService.wifiToggling)
|
||||
return NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
|
||||
if (NetworkService.networkStatus === "ethernet")
|
||||
return "Ethernet"
|
||||
if (NetworkService.networkStatus === "wifi" && NetworkService.currentWifiSSID)
|
||||
return NetworkService.currentWifiSSID
|
||||
if (NetworkService.wifiEnabled)
|
||||
return "Not connected"
|
||||
return "WiFi off"
|
||||
}
|
||||
case "bluetooth":
|
||||
{
|
||||
if (!BluetoothService.available)
|
||||
return "Bluetooth"
|
||||
if (!BluetoothService.adapter)
|
||||
return "No adapter"
|
||||
if (!BluetoothService.adapter.enabled)
|
||||
return "Disabled"
|
||||
return "Enabled"
|
||||
}
|
||||
case "audioOutput":
|
||||
return AudioService.sink?.description || "No output device"
|
||||
case "audioInput":
|
||||
return AudioService.source?.description || "No input device"
|
||||
default:
|
||||
return widgetDef?.text || "Unknown"
|
||||
}
|
||||
}
|
||||
secondaryText: {
|
||||
switch (widgetData.id || "") {
|
||||
case "wifi":
|
||||
{
|
||||
if (NetworkService.wifiToggling)
|
||||
return "Please wait..."
|
||||
if (NetworkService.networkStatus === "ethernet")
|
||||
return "Connected"
|
||||
if (NetworkService.networkStatus === "wifi")
|
||||
return NetworkService.wifiSignalStrength > 0 ? NetworkService.wifiSignalStrength + "%" : "Connected"
|
||||
if (NetworkService.wifiEnabled)
|
||||
return "Select network"
|
||||
return ""
|
||||
}
|
||||
case "bluetooth":
|
||||
{
|
||||
if (!BluetoothService.available)
|
||||
return "No adapters"
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled)
|
||||
return "Off"
|
||||
const primaryDevice = (() => {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
|
||||
return null
|
||||
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
|
||||
for (let device of devices) {
|
||||
if (device && device.connected)
|
||||
return device
|
||||
}
|
||||
return null
|
||||
})()
|
||||
if (primaryDevice)
|
||||
return primaryDevice.name || primaryDevice.alias || primaryDevice.deviceName || "Connected Device"
|
||||
return "No devices"
|
||||
}
|
||||
case "audioOutput":
|
||||
{
|
||||
if (!AudioService.sink)
|
||||
return "Select device"
|
||||
if (AudioService.sink.audio.muted)
|
||||
return "Muted"
|
||||
return Math.round(AudioService.sink.audio.volume * 100) + "%"
|
||||
}
|
||||
case "audioInput":
|
||||
{
|
||||
if (!AudioService.source)
|
||||
return "Select device"
|
||||
if (AudioService.source.audio.muted)
|
||||
return "Muted"
|
||||
return Math.round(AudioService.source.audio.volume * 100) + "%"
|
||||
}
|
||||
default:
|
||||
return widgetDef?.description || ""
|
||||
}
|
||||
}
|
||||
isActive: {
|
||||
switch (widgetData.id || "") {
|
||||
case "wifi":
|
||||
{
|
||||
if (NetworkService.wifiToggling)
|
||||
return false
|
||||
if (NetworkService.networkStatus === "ethernet")
|
||||
return true
|
||||
if (NetworkService.networkStatus === "wifi")
|
||||
return true
|
||||
return NetworkService.wifiEnabled
|
||||
}
|
||||
case "bluetooth":
|
||||
return !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled)
|
||||
case "audioOutput":
|
||||
return !!(AudioService.sink && !AudioService.sink.audio.muted)
|
||||
case "audioInput":
|
||||
return !!(AudioService.source && !AudioService.source.audio.muted)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
enabled: widgetDef?.enabled ?? true
|
||||
onToggled: {
|
||||
if (root.editMode) return
|
||||
switch (widgetData.id || "") {
|
||||
case "wifi":
|
||||
{
|
||||
if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) {
|
||||
NetworkService.toggleWifiRadio()
|
||||
}
|
||||
break
|
||||
}
|
||||
case "bluetooth":
|
||||
{
|
||||
if (BluetoothService.available && BluetoothService.adapter) {
|
||||
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
|
||||
}
|
||||
break
|
||||
}
|
||||
case "audioOutput":
|
||||
{
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = !AudioService.sink.audio.muted
|
||||
}
|
||||
break
|
||||
}
|
||||
case "audioInput":
|
||||
{
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = !AudioService.source.audio.muted
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
onExpandClicked: {
|
||||
if (root.editMode) return
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
onWheelEvent: function (wheelEvent) {
|
||||
if (root.editMode) return
|
||||
const id = widgetData.id || ""
|
||||
if (id === "audioOutput") {
|
||||
if (!AudioService.sink || !AudioService.sink.audio)
|
||||
return
|
||||
let delta = wheelEvent.angleDelta.y
|
||||
let currentVolume = AudioService.sink.audio.volume * 100
|
||||
let newVolume
|
||||
if (delta > 0)
|
||||
newVolume = Math.min(100, currentVolume + 5)
|
||||
else
|
||||
newVolume = Math.max(0, currentVolume - 5)
|
||||
AudioService.sink.audio.muted = false
|
||||
AudioService.sink.audio.volume = newVolume / 100
|
||||
wheelEvent.accepted = true
|
||||
} else if (id === "audioInput") {
|
||||
if (!AudioService.source || !AudioService.source.audio)
|
||||
return
|
||||
let delta = wheelEvent.angleDelta.y
|
||||
let currentVolume = AudioService.source.audio.volume * 100
|
||||
let newVolume
|
||||
if (delta > 0)
|
||||
newVolume = Math.min(100, currentVolume + 5)
|
||||
else
|
||||
newVolume = Math.max(0, currentVolume - 5)
|
||||
AudioService.source.audio.muted = false
|
||||
AudioService.source.audio.volume = newVolume / 100
|
||||
wheelEvent.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: audioSliderComponent
|
||||
Item {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 16
|
||||
|
||||
AudioSliderRow {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 14
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: brightnessSliderComponent
|
||||
Item {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 16
|
||||
|
||||
BrightnessSliderRow {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 14
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: inputAudioSliderComponent
|
||||
Item {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 16
|
||||
|
||||
InputAudioSliderRow {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 14
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: batteryPillComponent
|
||||
BatteryPill {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 60
|
||||
|
||||
onExpandClicked: {
|
||||
if (!root.editMode) {
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: smallBatteryComponent
|
||||
SmallBatteryButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 48
|
||||
|
||||
onClicked: {
|
||||
if (!root.editMode) {
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: toggleButtonComponent
|
||||
ToggleButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 60
|
||||
|
||||
iconName: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode":
|
||||
return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
|
||||
case "darkMode":
|
||||
return "contrast"
|
||||
case "doNotDisturb":
|
||||
return SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off"
|
||||
case "idleInhibitor":
|
||||
return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||
default:
|
||||
return "help"
|
||||
}
|
||||
}
|
||||
|
||||
text: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode":
|
||||
return "Night Mode"
|
||||
case "darkMode":
|
||||
return SessionData.isLightMode ? "Light Mode" : "Dark Mode"
|
||||
case "doNotDisturb":
|
||||
return "Do Not Disturb"
|
||||
case "idleInhibitor":
|
||||
return SessionService.idleInhibited ? "Keeping Awake" : "Keep Awake"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
|
||||
|
||||
isActive: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode":
|
||||
return DisplayService.nightModeEnabled || false
|
||||
case "darkMode":
|
||||
return !SessionData.isLightMode
|
||||
case "doNotDisturb":
|
||||
return SessionData.doNotDisturb || false
|
||||
case "idleInhibitor":
|
||||
return SessionService.idleInhibited || false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
enabled: !root.editMode
|
||||
|
||||
onClicked: {
|
||||
if (root.editMode)
|
||||
return
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode":
|
||||
{
|
||||
if (DisplayService.automationAvailable)
|
||||
DisplayService.toggleNightMode()
|
||||
break
|
||||
}
|
||||
case "darkMode":
|
||||
{
|
||||
Theme.toggleLightMode()
|
||||
break
|
||||
}
|
||||
case "doNotDisturb":
|
||||
{
|
||||
SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
||||
break
|
||||
}
|
||||
case "idleInhibitor":
|
||||
{
|
||||
SessionService.toggleIdleInhibit()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: smallToggleComponent
|
||||
SmallToggleButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 48
|
||||
|
||||
iconName: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode":
|
||||
return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
|
||||
case "darkMode":
|
||||
return "contrast"
|
||||
case "doNotDisturb":
|
||||
return SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off"
|
||||
case "idleInhibitor":
|
||||
return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||
default:
|
||||
return "help"
|
||||
}
|
||||
}
|
||||
|
||||
iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
|
||||
|
||||
isActive: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode":
|
||||
return DisplayService.nightModeEnabled || false
|
||||
case "darkMode":
|
||||
return !SessionData.isLightMode
|
||||
case "doNotDisturb":
|
||||
return SessionData.doNotDisturb || false
|
||||
case "idleInhibitor":
|
||||
return SessionService.idleInhibited || false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
enabled: !root.editMode
|
||||
|
||||
onClicked: {
|
||||
if (root.editMode)
|
||||
return
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode":
|
||||
{
|
||||
if (DisplayService.automationAvailable)
|
||||
DisplayService.toggleNightMode()
|
||||
break
|
||||
}
|
||||
case "darkMode":
|
||||
{
|
||||
Theme.toggleLightMode()
|
||||
break
|
||||
}
|
||||
case "doNotDisturb":
|
||||
{
|
||||
SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
||||
break
|
||||
}
|
||||
case "idleInhibitor":
|
||||
{
|
||||
SessionService.toggleIdleInhibit()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: diskUsagePillComponent
|
||||
DiskUsagePill {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 60
|
||||
|
||||
mountPath: widgetData.mountPath || "/"
|
||||
instanceId: widgetData.instanceId || ""
|
||||
|
||||
onExpandClicked: {
|
||||
if (!root.editMode) {
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
289
Modules/ControlCenter/Components/DragDropWidgetWrapper.qml
Normal file
289
Modules/ControlCenter/Components/DragDropWidgetWrapper.qml
Normal file
@@ -0,0 +1,289 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool editMode: false
|
||||
property var widgetData: null
|
||||
property int widgetIndex: -1
|
||||
property bool isSlider: false
|
||||
property Component widgetComponent: null
|
||||
property real gridCellWidth: 100
|
||||
property real gridCellHeight: 60
|
||||
property int gridColumns: 4
|
||||
property var gridLayout: null
|
||||
|
||||
z: dragArea.drag.active ? 10000 : 1
|
||||
|
||||
signal widgetMoved(int fromIndex, int toIndex)
|
||||
signal removeWidget(int index)
|
||||
signal toggleWidgetSize(int index)
|
||||
|
||||
width: {
|
||||
const widgetWidth = widgetData?.width || 50
|
||||
if (widgetWidth <= 25) return gridCellWidth
|
||||
else if (widgetWidth <= 50) return gridCellWidth * 2
|
||||
else if (widgetWidth <= 75) return gridCellWidth * 3
|
||||
else return gridCellWidth * 4
|
||||
}
|
||||
height: isSlider ? 16 : gridCellHeight
|
||||
|
||||
Rectangle {
|
||||
id: dragIndicator
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: Theme.primary
|
||||
border.width: dragArea.drag.active ? 2 : 0
|
||||
radius: Theme.cornerRadius
|
||||
opacity: dragArea.drag.active ? 0.8 : 1.0
|
||||
z: dragArea.drag.active ? 10000 : 1
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation { duration: 150 }
|
||||
}
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 150 }
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: widgetLoader
|
||||
anchors.fill: parent
|
||||
sourceComponent: widgetComponent
|
||||
property var widgetData: root.widgetData
|
||||
property int widgetIndex: root.widgetIndex
|
||||
property int globalWidgetIndex: root.widgetIndex
|
||||
property int widgetWidth: root.widgetData?.width || 50
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: editModeBlocker
|
||||
anchors.fill: parent
|
||||
enabled: root.editMode
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onPressed: function(mouse) { mouse.accepted = true }
|
||||
onWheel: function(wheel) { wheel.accepted = true }
|
||||
z: 100
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dragArea
|
||||
anchors.fill: parent
|
||||
enabled: editMode
|
||||
cursorShape: editMode ? Qt.OpenHandCursor : Qt.PointingHandCursor
|
||||
drag.target: editMode ? root : null
|
||||
drag.axis: Drag.XAndYAxis
|
||||
drag.smoothed: true
|
||||
|
||||
onPressed: function(mouse) {
|
||||
if (editMode) {
|
||||
cursorShape = Qt.ClosedHandCursor
|
||||
if (root.gridLayout && root.gridLayout.moveToTop) {
|
||||
root.gridLayout.moveToTop(root)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: function(mouse) {
|
||||
if (editMode) {
|
||||
cursorShape = Qt.OpenHandCursor
|
||||
root.snapToGrid()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Drag.active: dragArea.drag.active
|
||||
Drag.hotSpot.x: width / 2
|
||||
Drag.hotSpot.y: height / 2
|
||||
|
||||
function swapIndices(i, j) {
|
||||
if (i === j) return;
|
||||
const arr = SettingsData.controlCenterWidgets;
|
||||
if (!arr || i < 0 || j < 0 || i >= arr.length || j >= arr.length) return;
|
||||
|
||||
const copy = arr.slice();
|
||||
const tmp = copy[i];
|
||||
copy[i] = copy[j];
|
||||
copy[j] = tmp;
|
||||
|
||||
SettingsData.setControlCenterWidgets(copy);
|
||||
}
|
||||
|
||||
function snapToGrid() {
|
||||
if (!editMode || !gridLayout) return
|
||||
|
||||
const globalPos = root.mapToItem(gridLayout, 0, 0)
|
||||
const cellWidth = gridLayout.width / gridColumns
|
||||
const cellHeight = gridCellHeight + Theme.spacingS
|
||||
|
||||
const centerX = globalPos.x + (root.width / 2)
|
||||
const centerY = globalPos.y + (root.height / 2)
|
||||
|
||||
let targetCol = Math.max(0, Math.floor(centerX / cellWidth))
|
||||
let targetRow = Math.max(0, Math.floor(centerY / cellHeight))
|
||||
|
||||
targetCol = Math.min(targetCol, gridColumns - 1)
|
||||
|
||||
const newIndex = findBestInsertionIndex(targetRow, targetCol)
|
||||
|
||||
if (newIndex !== widgetIndex && newIndex >= 0 && newIndex < (SettingsData.controlCenterWidgets?.length || 0)) {
|
||||
swapIndices(widgetIndex, newIndex)
|
||||
}
|
||||
}
|
||||
|
||||
function findBestInsertionIndex(targetRow, targetCol) {
|
||||
const widgets = SettingsData.controlCenterWidgets || [];
|
||||
const n = widgets.length;
|
||||
if (!n || widgetIndex < 0 || widgetIndex >= n) return -1;
|
||||
|
||||
function spanFor(width) {
|
||||
const w = width ?? 50;
|
||||
if (w <= 25) return 1;
|
||||
if (w <= 50) return 2;
|
||||
if (w <= 75) return 3;
|
||||
return 4;
|
||||
}
|
||||
|
||||
const cols = gridColumns || 4;
|
||||
|
||||
let row = 0, col = 0;
|
||||
let draggedOrigKey = null;
|
||||
|
||||
const pos = [];
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const span = Math.min(spanFor(widgets[i].width), cols);
|
||||
|
||||
if (col + span > cols) {
|
||||
row++;
|
||||
col = 0;
|
||||
}
|
||||
|
||||
const startCol = col;
|
||||
const centerKey = row * cols + (startCol + (span - 1) / 2);
|
||||
|
||||
if (i === widgetIndex) {
|
||||
draggedOrigKey = centerKey;
|
||||
} else {
|
||||
pos.push({ index: i, row, startCol, span, centerKey });
|
||||
}
|
||||
|
||||
col += span;
|
||||
if (col >= cols) {
|
||||
row++;
|
||||
col = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos.length === 0) return -1;
|
||||
|
||||
const centerColCoord = targetCol + 0.5;
|
||||
const targetKey = targetRow * cols + centerColCoord;
|
||||
|
||||
for (let k = 0; k < pos.length; k++) {
|
||||
const p = pos[k];
|
||||
if (p.row === targetRow && centerColCoord >= p.startCol && centerColCoord < (p.startCol + p.span)) {
|
||||
return p.index;
|
||||
}
|
||||
}
|
||||
|
||||
let lo = 0, hi = pos.length - 1;
|
||||
if (targetKey <= pos[0].centerKey) return pos[0].index;
|
||||
if (targetKey >= pos[hi].centerKey) return pos[hi].index;
|
||||
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
const mk = pos[mid].centerKey;
|
||||
if (targetKey < mk) hi = mid - 1;
|
||||
else if (targetKey > mk) lo = mid + 1;
|
||||
else return pos[mid].index;
|
||||
}
|
||||
const movingUp = (draggedOrigKey != null) ? (targetKey < draggedOrigKey) : false;
|
||||
return (movingUp ? pos[lo].index : pos[hi].index);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 16
|
||||
height: 16
|
||||
radius: 8
|
||||
color: Theme.error
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: -4
|
||||
visible: editMode
|
||||
z: 10
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "close"
|
||||
size: 12
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: removeWidget(widgetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
SizeControls {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: -6
|
||||
visible: editMode
|
||||
z: 10
|
||||
currentSize: root.widgetData?.width || 50
|
||||
isSlider: root.isSlider
|
||||
widgetIndex: root.widgetIndex
|
||||
onSizeChanged: (newSize) => {
|
||||
var widgets = SettingsData.controlCenterWidgets.slice()
|
||||
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
|
||||
widgets[widgetIndex].width = newSize
|
||||
SettingsData.setControlCenterWidgets(widgets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dragHandle
|
||||
width: 16
|
||||
height: 12
|
||||
radius: 2
|
||||
color: Theme.primary
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.margins: 4
|
||||
visible: editMode
|
||||
z: 15
|
||||
opacity: dragArea.drag.active ? 1.0 : 0.7
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "drag_indicator"
|
||||
size: 10
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 150 }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: editMode ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius
|
||||
border.color: "transparent"
|
||||
border.width: 0
|
||||
z: -1
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
236
Modules/ControlCenter/Components/EditControls.qml
Normal file
236
Modules/ControlCenter/Components/EditControls.qml
Normal file
@@ -0,0 +1,236 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
property var availableWidgets: []
|
||||
|
||||
signal addWidget(string widgetId)
|
||||
signal resetToDefault()
|
||||
signal clearAll()
|
||||
|
||||
height: 48
|
||||
spacing: Theme.spacingS
|
||||
|
||||
onAddWidget: addWidgetPopup.close()
|
||||
|
||||
Popup {
|
||||
id: addWidgetPopup
|
||||
anchors.centerIn: parent
|
||||
width: 400
|
||||
height: 300
|
||||
modal: true
|
||||
focus: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surfaceContainer
|
||||
border.color: Theme.primarySelected
|
||||
border.width: 0
|
||||
radius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "add_circle"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: "Add Widget"
|
||||
style: Typography.Style.Subtitle
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankListView {
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.topMargin: Theme.spacingM
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: Theme.spacingS
|
||||
model: root.availableWidgets
|
||||
|
||||
delegate: Rectangle {
|
||||
width: 400 - Theme.spacingL * 2
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: widgetMouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: modelData.icon
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
width: 400 - Theme.spacingL * 2 - Theme.iconSize - Theme.spacingM * 3 - Theme.iconSize
|
||||
|
||||
Typography {
|
||||
text: modelData.text
|
||||
style: Typography.Style.Body
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: modelData.description
|
||||
style: Typography.Style.Caption
|
||||
color: Theme.outline
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "add"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: widgetMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.addWidget(modelData.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingS * 2) / 3
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
border.color: Theme.primary
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "add"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: "Add Widget"
|
||||
style: Typography.Style.Button
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: addWidgetPopup.open()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingS * 2) / 3
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
|
||||
border.color: Theme.warning
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "settings_backup_restore"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.warning
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: "Defaults"
|
||||
style: Typography.Style.Button
|
||||
color: Theme.warning
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.resetToDefault()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingS * 2) / 3
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||
border.color: Theme.error
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "clear_all"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.error
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: "Reset"
|
||||
style: Typography.Style.Button
|
||||
color: Theme.error
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.clearAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
115
Modules/ControlCenter/Components/HeaderPane.qml
Normal file
115
Modules/ControlCenter/Components/HeaderPane.qml
Normal file
@@ -0,0 +1,115 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool powerOptionsExpanded: false
|
||||
property bool editMode: false
|
||||
|
||||
signal powerActionRequested(string action, string title, string message)
|
||||
signal lockRequested()
|
||||
signal editModeToggled()
|
||||
|
||||
implicitHeight: 70
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankCircularImage {
|
||||
id: avatarContainer
|
||||
|
||||
width: 60
|
||||
height: 60
|
||||
imageSource: {
|
||||
if (PortalService.profileImage === "")
|
||||
return ""
|
||||
|
||||
if (PortalService.profileImage.startsWith("/"))
|
||||
return "file://" + PortalService.profileImage
|
||||
|
||||
return PortalService.profileImage
|
||||
}
|
||||
fallbackIcon: "person"
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
Typography {
|
||||
text: UserInfoService.fullName
|
||||
|| UserInfoService.username || "User"
|
||||
style: Typography.Style.Subtitle
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: (UserInfoService.uptime || "Unknown")
|
||||
style: Typography.Style.Caption
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: actionButtonsRow
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: Theme.spacingXS
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 36
|
||||
iconName: "lock"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
backgroundColor: "transparent"
|
||||
onClicked: {
|
||||
root.lockRequested()
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 36
|
||||
iconName: root.powerOptionsExpanded ? "expand_less" : "power_settings_new"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: root.powerOptionsExpanded ? Theme.primary : Theme.surfaceText
|
||||
backgroundColor: "transparent"
|
||||
onClicked: {
|
||||
root.powerOptionsExpanded = !root.powerOptionsExpanded
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 36
|
||||
iconName: "settings"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
backgroundColor: "transparent"
|
||||
onClicked: {
|
||||
settingsModal.show()
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 36
|
||||
iconName: editMode ? "done" : "edit"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: editMode ? Theme.primary : Theme.surfaceText
|
||||
backgroundColor: "transparent"
|
||||
onClicked: root.editModeToggled()
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Modules/ControlCenter/Components/PowerButton.qml
Normal file
52
Modules/ControlCenter/Components/PowerButton.qml
Normal file
@@ -0,0 +1,52 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string iconName: ""
|
||||
property string text: ""
|
||||
|
||||
signal pressed()
|
||||
|
||||
height: 34
|
||||
radius: Theme.cornerRadius
|
||||
color: mouseArea.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.5)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: root.iconName
|
||||
size: Theme.fontSizeSmall
|
||||
color: mouseArea.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: root.text
|
||||
style: Typography.Style.Button
|
||||
color: mouseArea.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: root.pressed()
|
||||
}
|
||||
}
|
||||
70
Modules/ControlCenter/Components/PowerOptionsPane.qml
Normal file
70
Modules/ControlCenter/Components/PowerOptionsPane.qml
Normal file
@@ -0,0 +1,70 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool expanded: false
|
||||
|
||||
signal powerActionRequested(string action, string title, string message)
|
||||
|
||||
implicitHeight: expanded ? 60 : 0
|
||||
height: implicitHeight
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.08)
|
||||
border.width: root.expanded ? 1 : 0
|
||||
opacity: root.expanded ? 1 : 0
|
||||
clip: true
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: SessionService.hibernateSupported ? Theme.spacingS : Theme.spacingL
|
||||
visible: root.expanded
|
||||
|
||||
PowerButton {
|
||||
width: SessionService.hibernateSupported ? 85 : 100
|
||||
iconName: "logout"
|
||||
text: "Logout"
|
||||
onPressed: root.powerActionRequested("logout", "Logout", "Are you sure you want to logout?")
|
||||
}
|
||||
|
||||
PowerButton {
|
||||
width: SessionService.hibernateSupported ? 85 : 100
|
||||
iconName: "restart_alt"
|
||||
text: "Restart"
|
||||
onPressed: root.powerActionRequested("reboot", "Restart", "Are you sure you want to restart?")
|
||||
}
|
||||
|
||||
PowerButton {
|
||||
width: SessionService.hibernateSupported ? 85 : 100
|
||||
iconName: "bedtime"
|
||||
text: "Suspend"
|
||||
onPressed: root.powerActionRequested("suspend", "Suspend", "Are you sure you want to suspend?")
|
||||
}
|
||||
|
||||
PowerButton {
|
||||
width: SessionService.hibernateSupported ? 85 : 100
|
||||
iconName: "ac_unit"
|
||||
text: "Hibernate"
|
||||
visible: SessionService.hibernateSupported
|
||||
onPressed: root.powerActionRequested("hibernate", "Hibernate", "Are you sure you want to hibernate?")
|
||||
}
|
||||
|
||||
PowerButton {
|
||||
width: SessionService.hibernateSupported ? 85 : 100
|
||||
iconName: "power_settings_new"
|
||||
text: "Shutdown"
|
||||
onPressed: root.powerActionRequested("poweroff", "Shutdown", "Are you sure you want to shutdown?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Modules/ControlCenter/Components/SizeControls.qml
Normal file
52
Modules/ControlCenter/Components/SizeControls.qml
Normal file
@@ -0,0 +1,52 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
property int currentSize: 50
|
||||
property bool isSlider: false
|
||||
property int widgetIndex: -1
|
||||
|
||||
signal sizeChanged(int newSize)
|
||||
|
||||
readonly property var availableSizes: isSlider ? [50, 100] : [25, 50, 75, 100]
|
||||
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: root.availableSizes
|
||||
|
||||
Rectangle {
|
||||
width: 16
|
||||
height: 16
|
||||
radius: 3
|
||||
color: modelData === root.currentSize ? Theme.primary : Theme.surfaceContainer
|
||||
border.color: modelData === root.currentSize ? Theme.primary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.toString()
|
||||
font.pixelSize: 8
|
||||
font.weight: Font.Medium
|
||||
color: modelData === root.currentSize ? Theme.primaryContainer : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.currentSize = modelData
|
||||
root.sizeChanged(modelData)
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Modules/ControlCenter/Components/Typography.qml
Normal file
46
Modules/ControlCenter/Components/Typography.qml
Normal file
@@ -0,0 +1,46 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
StyledText {
|
||||
id: root
|
||||
|
||||
enum Style {
|
||||
Title,
|
||||
Subtitle,
|
||||
Body,
|
||||
Caption,
|
||||
Button
|
||||
}
|
||||
|
||||
property int style: Typography.Style.Body
|
||||
|
||||
font.pixelSize: {
|
||||
switch (style) {
|
||||
case Typography.Style.Title: return Theme.fontSizeXLarge
|
||||
case Typography.Style.Subtitle: return Theme.fontSizeLarge
|
||||
case Typography.Style.Body: return Theme.fontSizeMedium
|
||||
case Typography.Style.Caption: return Theme.fontSizeSmall
|
||||
case Typography.Style.Button: return Theme.fontSizeSmall
|
||||
default: return Theme.fontSizeMedium
|
||||
}
|
||||
}
|
||||
|
||||
font.weight: {
|
||||
switch (style) {
|
||||
case Typography.Style.Title: return Font.Bold
|
||||
case Typography.Style.Subtitle: return Font.Medium
|
||||
case Typography.Style.Body: return Font.Normal
|
||||
case Typography.Style.Caption: return Font.Normal
|
||||
case Typography.Style.Button: return Font.Medium
|
||||
default: return Font.Normal
|
||||
}
|
||||
}
|
||||
|
||||
color: {
|
||||
switch (style) {
|
||||
case Typography.Style.Caption: return Theme.surfaceVariantText
|
||||
default: return Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,12 @@ import qs.Common
|
||||
import qs.Modules.ControlCenter
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
import qs.Modules.ControlCenter.Details
|
||||
import qs.Modules.ControlCenter.Details 1.0 as Details
|
||||
import qs.Modules.TopBar
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Components
|
||||
import qs.Modules.ControlCenter.Models
|
||||
import "./utils/state.js" as StateUtils
|
||||
|
||||
DankPopout {
|
||||
id: root
|
||||
@@ -21,39 +24,49 @@ DankPopout {
|
||||
property bool powerOptionsExpanded: 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
|
||||
}
|
||||
|
||||
function openWithSection(section) {
|
||||
if (shouldBeVisible) {
|
||||
close()
|
||||
} else {
|
||||
expandedSection = section
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSection(section) {
|
||||
if (expandedSection === section) {
|
||||
expandedSection = ""
|
||||
} else {
|
||||
expandedSection = section
|
||||
}
|
||||
}
|
||||
property bool editMode: false
|
||||
property int expandedWidgetIndex: -1
|
||||
property var expandedWidgetData: null
|
||||
|
||||
signal powerActionRequested(string action, string title, string message)
|
||||
signal lockRequested
|
||||
|
||||
function collapseAll() {
|
||||
expandedSection = ""
|
||||
expandedWidgetIndex = -1
|
||||
expandedWidgetData = null
|
||||
}
|
||||
|
||||
onEditModeChanged: {
|
||||
if (editMode) {
|
||||
collapseAll()
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
collapseAll()
|
||||
}
|
||||
}
|
||||
|
||||
readonly property color _containerBg: Theme.surfaceContainerHigh
|
||||
|
||||
function setTriggerPosition(x, y, width, section, screen) {
|
||||
StateUtils.setTriggerPosition(root, x, y, width, section, screen)
|
||||
}
|
||||
|
||||
function openWithSection(section) {
|
||||
StateUtils.openWithSection(root, section)
|
||||
}
|
||||
|
||||
function toggleSection(section) {
|
||||
StateUtils.toggleSection(root, section)
|
||||
}
|
||||
|
||||
popupWidth: 550
|
||||
popupHeight: Math.min((triggerScreen?.height ?? 1080) - 100, contentLoader.item && contentLoader.item.implicitHeight > 0 ? contentLoader.item.implicitHeight + 20 : 400)
|
||||
triggerX: (triggerScreen?.width ?? 1920) - 600 - Theme.spacingL
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingXS
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.popupDistance
|
||||
triggerWidth: 80
|
||||
positioning: "center"
|
||||
screen: triggerScreen
|
||||
@@ -70,20 +83,24 @@ DankPopout {
|
||||
} else {
|
||||
Qt.callLater(() => {
|
||||
NetworkService.autoRefreshEnabled = false
|
||||
if (BluetoothService.adapter
|
||||
&& BluetoothService.adapter.discovering)
|
||||
if (BluetoothService.adapter && BluetoothService.adapter.discovering)
|
||||
BluetoothService.adapter.discovering = false
|
||||
editMode = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
WidgetModel {
|
||||
id: widgetModel
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Rectangle {
|
||||
id: controlContent
|
||||
|
||||
|
||||
implicitHeight: mainColumn.implicitHeight + Theme.spacingM
|
||||
property alias bluetoothCodecSelector: bluetoothCodecSelector
|
||||
|
||||
|
||||
color: {
|
||||
const transparency = Theme.popupTransparency || 0.92
|
||||
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1)
|
||||
@@ -92,7 +109,7 @@ DankPopout {
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
antialiasing: true
|
||||
smooth: true
|
||||
|
||||
@@ -103,614 +120,67 @@ DankPopout {
|
||||
y: Theme.spacingL
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
HeaderPane {
|
||||
id: headerPane
|
||||
width: parent.width
|
||||
height: 90
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r,
|
||||
Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b,
|
||||
Theme.getContentBackgroundAlpha() * 0.4)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Item {
|
||||
id: avatarContainer
|
||||
|
||||
property bool hasImage: profileImageLoader.status === Image.Ready
|
||||
|
||||
width: 64
|
||||
height: 64
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: "transparent"
|
||||
border.color: Theme.primary
|
||||
border.width: 1
|
||||
visible: parent.hasImage
|
||||
}
|
||||
|
||||
Image {
|
||||
id: profileImageLoader
|
||||
|
||||
source: {
|
||||
if (PortalService.profileImage === "")
|
||||
return ""
|
||||
|
||||
if (PortalService.profileImage.startsWith(
|
||||
"/"))
|
||||
return "file://" + PortalService.profileImage
|
||||
|
||||
return PortalService.profileImage
|
||||
}
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
mipmap: true
|
||||
cache: true
|
||||
visible: false
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 5
|
||||
source: profileImageLoader
|
||||
maskEnabled: true
|
||||
maskSource: circularMask
|
||||
visible: avatarContainer.hasImage
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
|
||||
Item {
|
||||
id: circularMask
|
||||
|
||||
width: 64 - 10
|
||||
height: 64 - 10
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: "black"
|
||||
antialiasing: true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: Theme.primary
|
||||
visible: !parent.hasImage
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "person"
|
||||
size: Theme.iconSize + 8
|
||||
color: Theme.primaryText
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "warning"
|
||||
size: Theme.iconSize + 8
|
||||
color: Theme.primaryText
|
||||
visible: PortalService.profileImage !== ""
|
||||
&& profileImageLoader.status === Image.Error
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: UserInfoService.fullName
|
||||
|| UserInfoService.username || "User"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: (UserInfoService.uptime
|
||||
|| "Unknown")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
font.weight: Font.Normal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 40
|
||||
iconName: "lock"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.surfaceText
|
||||
backgroundColor: Qt.rgba(
|
||||
Theme.surfaceVariant.r,
|
||||
Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b,
|
||||
0.5)
|
||||
onClicked: {
|
||||
root.close()
|
||||
root.lockRequested()
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 40
|
||||
iconName: root.powerOptionsExpanded ? "expand_less" : "power_settings_new"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.surfaceText
|
||||
backgroundColor: Qt.rgba(
|
||||
Theme.surfaceVariant.r,
|
||||
Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b,
|
||||
0.5)
|
||||
onClicked: {
|
||||
root.powerOptionsExpanded = !root.powerOptionsExpanded
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 40
|
||||
iconName: "settings"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.surfaceText
|
||||
backgroundColor: Qt.rgba(
|
||||
Theme.surfaceVariant.r,
|
||||
Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b,
|
||||
0.5)
|
||||
onClicked: {
|
||||
root.close()
|
||||
settingsModal.show()
|
||||
}
|
||||
}
|
||||
powerOptionsExpanded: root.powerOptionsExpanded
|
||||
editMode: root.editMode
|
||||
onPowerOptionsExpandedChanged: root.powerOptionsExpanded = powerOptionsExpanded
|
||||
onEditModeToggled: root.editMode = !root.editMode
|
||||
onPowerActionRequested: (action, title, message) => root.powerActionRequested(action, title, message)
|
||||
onLockRequested: {
|
||||
root.close()
|
||||
root.lockRequested()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
PowerOptionsPane {
|
||||
id: powerOptionsPane
|
||||
width: parent.width
|
||||
implicitHeight: root.powerOptionsExpanded ? 60 : 0
|
||||
height: implicitHeight
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r,
|
||||
Theme.surfaceVariant.g,
|
||||
Theme.surfaceVariant.b,
|
||||
Theme.getContentBackgroundAlpha() * 0.4)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.08)
|
||||
border.width: root.powerOptionsExpanded ? 1 : 0
|
||||
opacity: root.powerOptionsExpanded ? 1 : 0
|
||||
clip: true
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingL
|
||||
visible: root.powerOptionsExpanded
|
||||
|
||||
Rectangle {
|
||||
width: 100
|
||||
height: 34
|
||||
radius: Theme.cornerRadius
|
||||
color: logoutButton.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.5)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "logout"
|
||||
size: Theme.fontSizeSmall
|
||||
color: logoutButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Logout"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: logoutButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: logoutButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
root.powerOptionsExpanded = false
|
||||
root.close()
|
||||
root.powerActionRequested(
|
||||
"logout", "Logout",
|
||||
"Are you sure you want to logout?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100
|
||||
height: 34
|
||||
radius: Theme.cornerRadius
|
||||
color: rebootButton.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.5)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "restart_alt"
|
||||
size: Theme.fontSizeSmall
|
||||
color: rebootButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Restart"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: rebootButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: rebootButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
root.powerOptionsExpanded = false
|
||||
root.close()
|
||||
root.powerActionRequested(
|
||||
"reboot", "Restart",
|
||||
"Are you sure you want to restart?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100
|
||||
height: 34
|
||||
radius: Theme.cornerRadius
|
||||
color: suspendButton.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.5)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "bedtime"
|
||||
size: Theme.fontSizeSmall
|
||||
color: suspendButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Suspend"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: suspendButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: suspendButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
root.powerOptionsExpanded = false
|
||||
root.close()
|
||||
root.powerActionRequested(
|
||||
"suspend", "Suspend",
|
||||
"Are you sure you want to suspend?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100
|
||||
height: 34
|
||||
radius: Theme.cornerRadius
|
||||
color: shutdownButton.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.5)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "power_settings_new"
|
||||
size: Theme.fontSizeSmall
|
||||
color: shutdownButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Shutdown"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: shutdownButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: shutdownButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
root.powerOptionsExpanded = false
|
||||
root.close()
|
||||
root.powerActionRequested(
|
||||
"poweroff", "Shutdown",
|
||||
"Are you sure you want to shutdown?")
|
||||
}
|
||||
}
|
||||
}
|
||||
expanded: root.powerOptionsExpanded
|
||||
onPowerActionRequested: (action, title, message) => {
|
||||
root.powerOptionsExpanded = false
|
||||
root.close()
|
||||
root.powerActionRequested(action, title, message)
|
||||
}
|
||||
}
|
||||
|
||||
DragDropGrid {
|
||||
id: widgetGrid
|
||||
width: parent.width
|
||||
editMode: root.editMode
|
||||
expandedSection: root.expandedSection
|
||||
expandedWidgetIndex: root.expandedWidgetIndex
|
||||
expandedWidgetData: root.expandedWidgetData
|
||||
model: widgetModel
|
||||
onExpandClicked: (widgetData, globalIndex) => {
|
||||
root.expandedWidgetIndex = globalIndex
|
||||
root.expandedWidgetData = widgetData
|
||||
if (widgetData.id === "diskUsage") {
|
||||
root.toggleSection("diskUsage_" + (widgetData.instanceId || "default"))
|
||||
} else {
|
||||
root.toggleSection(widgetData.id)
|
||||
}
|
||||
}
|
||||
onRemoveWidget: (index) => widgetModel.removeWidget(index)
|
||||
onMoveWidget: (fromIndex, toIndex) => widgetModel.moveWidget(fromIndex, toIndex)
|
||||
onToggleWidgetSize: (index) => widgetModel.toggleWidgetSize(index)
|
||||
}
|
||||
|
||||
Item {
|
||||
EditControls {
|
||||
width: parent.width
|
||||
height: audioSliderRow.implicitHeight
|
||||
|
||||
Row {
|
||||
id: audioSliderRow
|
||||
x: -Theme.spacingS
|
||||
width: parent.width + Theme.spacingS * 2
|
||||
spacing: Theme.spacingM
|
||||
|
||||
AudioSliderRow {
|
||||
width: SettingsData.hideBrightnessSlider ? parent.width - Theme.spacingM : (parent.width - Theme.spacingM) / 2
|
||||
}
|
||||
|
||||
Item {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: parent.height
|
||||
visible: !SettingsData.hideBrightnessSlider
|
||||
|
||||
BrightnessSliderRow {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
x: -Theme.spacingS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
NetworkPill {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
expanded: root.expandedSection === "network"
|
||||
onClicked: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return
|
||||
}
|
||||
if (NetworkService.networkStatus === "ethernet") {
|
||||
if (NetworkService.ethernetConnected && !NetworkService.wifiEnabled) {
|
||||
NetworkService.toggleWifiRadio()
|
||||
return
|
||||
}
|
||||
root.toggleSection("network")
|
||||
return
|
||||
}
|
||||
if (NetworkService.networkStatus === "wifi") {
|
||||
if (NetworkService.ethernetConnected) {
|
||||
NetworkService.toggleWifiRadio()
|
||||
return
|
||||
}
|
||||
NetworkService.disconnectWifi()
|
||||
return
|
||||
}
|
||||
if (!NetworkService.wifiEnabled) {
|
||||
NetworkService.toggleWifiRadio()
|
||||
return
|
||||
}
|
||||
if (NetworkService.wifiEnabled && NetworkService.networkStatus === "disconnected") {
|
||||
root.toggleSection("network")
|
||||
}
|
||||
}
|
||||
onExpandClicked: root.toggleSection("network")
|
||||
}
|
||||
|
||||
BluetoothPill {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
expanded: root.expandedSection === "bluetooth"
|
||||
onClicked: {
|
||||
if (BluetoothService.adapter)
|
||||
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
|
||||
}
|
||||
onExpandClicked: root.toggleSection("bluetooth")
|
||||
visible: BluetoothService.available
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
active: root.expandedSection === "network" || root.expandedSection === "bluetooth"
|
||||
visible: active
|
||||
sourceComponent: DetailView {
|
||||
width: parent.width
|
||||
isVisible: true
|
||||
title: {
|
||||
switch (root.expandedSection) {
|
||||
case "network": return "Network Settings"
|
||||
case "bluetooth": return "Bluetooth Settings"
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
content: {
|
||||
switch (root.expandedSection) {
|
||||
case "network": return networkDetailComponent
|
||||
case "bluetooth": return bluetoothDetailComponent
|
||||
default: return null
|
||||
}
|
||||
}
|
||||
contentHeight: 250
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
AudioOutputPill {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
expanded: root.expandedSection === "audio_output"
|
||||
onClicked: {
|
||||
if (AudioService.sink) {
|
||||
AudioService.sink.audio.muted = !AudioService.sink.audio.muted
|
||||
}
|
||||
}
|
||||
onExpandClicked: root.toggleSection("audio_output")
|
||||
}
|
||||
|
||||
AudioInputPill {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
expanded: root.expandedSection === "audio_input"
|
||||
onClicked: {
|
||||
if (AudioService.source) {
|
||||
AudioService.source.audio.muted = !AudioService.source.audio.muted
|
||||
}
|
||||
}
|
||||
onExpandClicked: root.toggleSection("audio_input")
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
active: root.expandedSection === "audio_output" || root.expandedSection === "audio_input"
|
||||
visible: active
|
||||
sourceComponent: DetailView {
|
||||
width: parent.width
|
||||
isVisible: true
|
||||
title: {
|
||||
switch (root.expandedSection) {
|
||||
case "audio_output": return "Audio Output"
|
||||
case "audio_input": return "Audio Input"
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
content: {
|
||||
switch (root.expandedSection) {
|
||||
case "audio_output": return audioOutputDetailComponent
|
||||
case "audio_input": return audioInputDetailComponent
|
||||
default: return null
|
||||
}
|
||||
}
|
||||
contentHeight: 250
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
ToggleButton {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
iconName: DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
|
||||
text: "Night Mode"
|
||||
secondaryText: SessionData.nightModeAutoEnabled ? "Auto" : (DisplayService.nightModeEnabled ? "On" : "Off")
|
||||
isActive: DisplayService.nightModeEnabled
|
||||
enabled: DisplayService.automationAvailable
|
||||
onClicked: DisplayService.toggleNightMode()
|
||||
|
||||
DankIcon {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
name: "schedule"
|
||||
size: 12
|
||||
color: Theme.primary
|
||||
visible: SessionData.nightModeAutoEnabled
|
||||
opacity: 0.8
|
||||
}
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
iconName: SessionData.isLightMode ? "light_mode" : "palette"
|
||||
text: "Theme"
|
||||
secondaryText: SessionData.isLightMode ? "Light" : "Dark"
|
||||
isActive: true
|
||||
onClicked: Theme.toggleLightMode()
|
||||
visible: editMode
|
||||
availableWidgets: {
|
||||
const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id)
|
||||
return widgetModel.baseWidgetDefinitions.filter(w => w.allowMultiple || !existingIds.includes(w.id))
|
||||
}
|
||||
onAddWidget: (widgetId) => widgetModel.addWidget(widgetId)
|
||||
onResetToDefault: () => widgetModel.resetToDefault()
|
||||
onClearAll: () => widgetModel.clearAll()
|
||||
}
|
||||
}
|
||||
|
||||
Details.BluetoothCodecSelector {
|
||||
|
||||
BluetoothCodecSelector {
|
||||
id: bluetoothCodecSelector
|
||||
anchors.fill: parent
|
||||
z: 10000
|
||||
@@ -747,4 +217,9 @@ DankPopout {
|
||||
id: audioInputDetailComponent
|
||||
AudioInputDetail {}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: batteryDetailComponent
|
||||
BatteryDetail {}
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,16 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
implicitHeight: headerRow.height + volumeSlider.height + audioContent.height + Theme.spacingM
|
||||
property bool hasInputVolumeSliderInCC: {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
return widgets.some(widget => widget.id === "inputVolumeSlider")
|
||||
}
|
||||
|
||||
implicitHeight: headerRow.height + (hasInputVolumeSliderInCC ? 0 : volumeSlider.height) + audioContent.height + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
@@ -33,7 +38,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
Row {
|
||||
id: volumeSlider
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -42,34 +47,60 @@ Rectangle {
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
height: 35
|
||||
value: AudioService.source && AudioService.source.audio ? Math.round(AudioService.source.audio.volume * 100) : 0
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
leftIcon: {
|
||||
if (!AudioService.source || !AudioService.source.audio) return "mic_off"
|
||||
let muted = AudioService.source.audio.muted
|
||||
return muted ? "mic_off" : "mic"
|
||||
}
|
||||
rightIcon: "volume_up"
|
||||
enabled: AudioService.source && AudioService.source.audio
|
||||
unit: "%"
|
||||
showValue: true
|
||||
visible: AudioService.source && AudioService.source.audio
|
||||
|
||||
onSliderValueChanged: function(newValue) {
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.volume = newValue / 100
|
||||
spacing: 0
|
||||
visible: !hasInputVolumeSliderInCC
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
height: Theme.iconSize + Theme.spacingS * 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
|
||||
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
MouseArea {
|
||||
id: iconArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = !AudioService.source.audio.muted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!AudioService.source || !AudioService.source.audio) return "mic_off"
|
||||
let muted = AudioService.source.audio.muted
|
||||
return muted ? "mic_off" : "mic"
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: AudioService.source && AudioService.source.audio && !AudioService.source.audio.muted && AudioService.source.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.left: parent.left
|
||||
|
||||
DankSlider {
|
||||
readonly property real actualVolumePercent: AudioService.source && AudioService.source.audio ? Math.round(AudioService.source.audio.volume * 100) : 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
height: parent.height
|
||||
onClicked: {
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: AudioService.source && AudioService.source.audio
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
value: AudioService.source && AudioService.source.audio ? Math.min(100, Math.round(AudioService.source.audio.volume * 100)) : 0
|
||||
showValue: true
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceVariant
|
||||
|
||||
onSliderValueChanged: function(newValue) {
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = !AudioService.source.audio.muted
|
||||
AudioService.source.audio.volume = newValue / 100
|
||||
if (newValue > 0 && AudioService.source.audio.muted) {
|
||||
AudioService.source.audio.muted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,12 +108,12 @@ Rectangle {
|
||||
|
||||
DankFlickable {
|
||||
id: audioContent
|
||||
anchors.top: volumeSlider.bottom
|
||||
anchors.top: hasInputVolumeSliderInCC ? headerRow.bottom : volumeSlider.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.topMargin: hasInputVolumeSliderInCC ? Theme.spacingM : Theme.spacingS
|
||||
contentHeight: audioColumn.height
|
||||
clip: true
|
||||
|
||||
@@ -103,9 +134,9 @@ Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceMouseArea.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, index % 2 === 0 ? 0.3 : 0.2)
|
||||
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
|
||||
border.color: modelData === AudioService.source ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: modelData === AudioService.source ? 2 : 1
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -163,14 +194,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,16 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
implicitHeight: headerRow.height + audioContent.height + Theme.spacingM
|
||||
property bool hasVolumeSliderInCC: {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
return widgets.some(widget => widget.id === "volumeSlider")
|
||||
}
|
||||
|
||||
implicitHeight: headerRow.height + (!hasVolumeSliderInCC ? volumeSlider.height : 0) + audioContent.height + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
@@ -32,15 +37,88 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Row {
|
||||
id: volumeSlider
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
height: 35
|
||||
spacing: 0
|
||||
visible: !hasVolumeSliderInCC
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
height: Theme.iconSize + Theme.spacingS * 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
|
||||
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
MouseArea {
|
||||
id: iconArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = !AudioService.sink.audio.muted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!AudioService.sink || !AudioService.sink.audio) return "volume_off"
|
||||
let muted = AudioService.sink.audio.muted
|
||||
let volume = AudioService.sink.audio.volume
|
||||
if (muted || volume === 0.0) return "volume_off"
|
||||
if (volume <= 0.33) return "volume_down"
|
||||
if (volume <= 0.66) return "volume_up"
|
||||
return "volume_up"
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: AudioService.sink && AudioService.sink.audio && !AudioService.sink.audio.muted && AudioService.sink.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
readonly property real actualVolumePercent: AudioService.sink && AudioService.sink.audio ? Math.round(AudioService.sink.audio.volume * 100) : 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: AudioService.sink && AudioService.sink.audio
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
value: AudioService.sink && AudioService.sink.audio ? Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) : 0
|
||||
showValue: true
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceVariant
|
||||
|
||||
onSliderValueChanged: function(newValue) {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.volume = newValue / 100
|
||||
if (newValue > 0 && AudioService.sink.audio.muted) {
|
||||
AudioService.sink.audio.muted = false
|
||||
}
|
||||
AudioService.volumeChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
id: audioContent
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.top: volumeSlider.visible ? volumeSlider.bottom : headerRow.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
anchors.topMargin: volumeSlider.visible ? Theme.spacingS : Theme.spacingM
|
||||
contentHeight: audioColumn.height
|
||||
clip: true
|
||||
|
||||
@@ -61,9 +139,9 @@ Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceMouseArea.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, index % 2 === 0 ? 0.3 : 0.2)
|
||||
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
|
||||
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: modelData === AudioService.sink ? 2 : 1
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -123,14 +201,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
258
Modules/ControlCenter/Details/BatteryDetail.qml
Normal file
258
Modules/ControlCenter/Details/BatteryDetail.qml
Normal file
@@ -0,0 +1,258 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
function isActiveProfile(profile) {
|
||||
if (typeof PowerProfiles === "undefined") {
|
||||
return false
|
||||
}
|
||||
return PowerProfiles.profile === profile
|
||||
}
|
||||
|
||||
function setProfile(profile) {
|
||||
if (typeof PowerProfiles === "undefined") {
|
||||
ToastService.showError("power-profiles-daemon not available")
|
||||
return
|
||||
}
|
||||
PowerProfiles.profile = profile
|
||||
if (PowerProfiles.profile !== profile) {
|
||||
ToastService.showError("Failed to set power profile")
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
width: parent.width - Theme.spacingL * 2
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
width: parent.width
|
||||
height: 48
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: BatteryService.getBatteryIcon()
|
||||
size: Theme.iconSizeLarge
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||
return Theme.error
|
||||
if (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||
return Theme.primary
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - Theme.iconSizeLarge - Theme.spacingM
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : "Power"
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error
|
||||
}
|
||||
if (BatteryService.isCharging) {
|
||||
return Theme.primary
|
||||
}
|
||||
return Theme.surfaceText
|
||||
}
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryAvailable ? BatteryService.batteryStatus : "Management"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error
|
||||
}
|
||||
if (BatteryService.isCharging) {
|
||||
return Theme.primary
|
||||
}
|
||||
return Theme.surfaceText
|
||||
}
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!BatteryService.batteryAvailable) return "Power profile management available"
|
||||
const time = BatteryService.formatTimeRemaining()
|
||||
if (time !== "Unknown") {
|
||||
return BatteryService.isCharging ? `Time until full: ${time}` : `Time remaining: ${time}`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
visible: text.length > 0
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: BatteryService.batteryAvailable
|
||||
|
||||
StyledRect {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 64
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHighest
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Health"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryHealth
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: {
|
||||
if (BatteryService.batteryHealth === "N/A") {
|
||||
return Theme.surfaceText
|
||||
}
|
||||
const healthNum = parseInt(BatteryService.batteryHealth)
|
||||
return healthNum < 80 ? Theme.error : Theme.surfaceText
|
||||
}
|
||||
font.weight: Font.Bold
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 64
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHighest
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Capacity"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryCapacity > 0 ? `${BatteryService.batteryCapacity.toFixed(1)} Wh` : "Unknown"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Bold
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankButtonGroup {
|
||||
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
||||
property int currentProfileIndex: {
|
||||
if (typeof PowerProfiles === "undefined") return 1
|
||||
return profileModel.findIndex(profile => isActiveProfile(profile))
|
||||
}
|
||||
|
||||
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
|
||||
currentIndex: currentProfileIndex
|
||||
selectionMode: "single"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected) return
|
||||
setProfile(profileModel[index])
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: degradationContent.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||
border.color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3)
|
||||
border.width: 0
|
||||
visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None
|
||||
|
||||
Column {
|
||||
id: degradationContent
|
||||
width: parent.width - Theme.spacingL * 2
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "warning"
|
||||
size: Theme.iconSize
|
||||
color: Theme.error
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: "Power Profile Degradation"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.error
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: (typeof PowerProfiles !== "undefined") ? PerformanceDegradationReason.toString(PowerProfiles.degradationReason) : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.8)
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,7 @@ Item {
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
opacity: modalVisible ? 1 : 0
|
||||
scale: modalVisible ? 1 : 0.9
|
||||
|
||||
@@ -206,14 +206,14 @@ Item {
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (modelData.name === currentCodec)
|
||||
return Theme.surfaceContainerHigh;
|
||||
return Theme.surfaceContainerHighest;
|
||||
else if (codecMouseArea.containsMouse)
|
||||
return Theme.surfaceHover;
|
||||
else
|
||||
return "transparent";
|
||||
}
|
||||
border.color: "transparent"
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -272,12 +272,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
implicitHeight: BluetoothService.adapter && BluetoothService.adapter.enabled ? headerRow.height + bluetoothContent.height + Theme.spacingM : headerRow.height
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
property var bluetoothCodecModalRef: null
|
||||
|
||||
@@ -58,11 +58,11 @@ Rectangle {
|
||||
radius: 18
|
||||
color: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
return Theme.surfaceContainerHigh
|
||||
return scanMouseArea.containsMouse ? Theme.surfaceContainerHigh : "transparent"
|
||||
}
|
||||
border.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
|
||||
Row {
|
||||
@@ -96,13 +96,6 @@ Rectangle {
|
||||
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +152,7 @@ Rectangle {
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
|
||||
if (deviceMouseArea.containsMouse)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, index % 2 === 0 ? 0.3 : 0.2)
|
||||
return Theme.surfaceContainerHighest
|
||||
}
|
||||
border.color: {
|
||||
if (modelData.state === BluetoothDeviceState.Connecting)
|
||||
@@ -168,7 +161,7 @@ Rectangle {
|
||||
return Theme.primary
|
||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
border.width: (modelData.connected || modelData.state === BluetoothDeviceState.Connecting) ? 2 : 1
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -284,14 +277,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,9 +332,9 @@ Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: availableMouseArea.containsMouse && !isBusy ? 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.15)
|
||||
color: availableMouseArea.containsMouse && !isBusy ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
opacity: canConnect ? 1 : 0.6
|
||||
|
||||
Row {
|
||||
@@ -427,9 +412,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +440,7 @@ Rectangle {
|
||||
background: Rectangle {
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
|
||||
166
Modules/ControlCenter/Details/DiskUsageDetail.qml
Normal file
166
Modules/ControlCenter/Details/DiskUsageDetail.qml
Normal file
@@ -0,0 +1,166 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string currentMountPath: "/"
|
||||
property string instanceId: ""
|
||||
|
||||
signal mountPathChanged(string newMountPath)
|
||||
|
||||
implicitHeight: diskContent.height + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["diskmounts"])
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["diskmounts"])
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
id: diskContent
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
contentHeight: diskColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: diskColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 100
|
||||
visible: !DgopService.dgopAvailable || !DgopService.diskMounts || DgopService.diskMounts.length === 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: DgopService.dgopAvailable ? "storage" : "error"
|
||||
size: 32
|
||||
color: DgopService.dgopAvailable ? Theme.primary : Theme.error
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: DgopService.dgopAvailable ? "No disk data available" : "dgop not available"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: DgopService.diskMounts || []
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHighest
|
||||
border.color: modelData.mount === currentMountPath ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: modelData.mount === currentMountPath ? 2 : 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
DankIcon {
|
||||
name: "storage"
|
||||
size: Theme.iconSize
|
||||
color: {
|
||||
const percentStr = modelData.percent?.replace("%", "") || "0"
|
||||
const percent = parseFloat(percentStr) || 0
|
||||
if (percent > 90) return Theme.error
|
||||
if (percent > 75) return Theme.warning
|
||||
return modelData.mount === currentMountPath ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const percentStr = modelData.percent?.replace("%", "") || "0"
|
||||
const percent = parseFloat(percentStr) || 0
|
||||
return percent.toFixed(0) + "%"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - 50 - Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: modelData.mount === "/" ? "Root Filesystem" : modelData.mount
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData.mount === currentMountPath ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.mount
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
visible: modelData.mount !== "/"
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `${modelData.used || "?"} / ${modelData.size || "?"}`
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
currentMountPath = modelData.mount
|
||||
mountPathChanged(modelData.mount)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,9 @@ Rectangle {
|
||||
return headerRow.height + wifiOffContent.height + Theme.spacingM
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
Component.onCompleted: {
|
||||
NetworkService.addRef()
|
||||
@@ -27,20 +27,11 @@ Rectangle {
|
||||
NetworkService.scanWifi()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component.onDestruction: {
|
||||
NetworkService.removeRef()
|
||||
}
|
||||
|
||||
property var wifiPasswordModalRef: {
|
||||
wifiPasswordModalLoader.active = true
|
||||
return wifiPasswordModalLoader.item
|
||||
}
|
||||
property var networkInfoModalRef: {
|
||||
networkInfoModalLoader.active = true
|
||||
return networkInfoModalLoader.item
|
||||
}
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.left: parent.left
|
||||
@@ -65,74 +56,19 @@ Rectangle {
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
Row {
|
||||
DankButtonGroup {
|
||||
id: preferenceControls
|
||||
spacing: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.ethernetConnected && NetworkService.wifiConnected
|
||||
|
||||
Rectangle {
|
||||
property bool isActive: NetworkService.userPreference === "ethernet"
|
||||
|
||||
width: 90
|
||||
height: 36
|
||||
radius: 18
|
||||
color: isActive ? Theme.surfaceContainerHigh : ethernetMouseArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: "Ethernet"
|
||||
color: parent.isActive ? Theme.primary : Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: parent.isActive ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: ethernetMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: NetworkService.setNetworkPreference("ethernet")
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
property bool isActive: NetworkService.userPreference === "wifi"
|
||||
|
||||
width: 70
|
||||
height: 36
|
||||
radius: 18
|
||||
color: isActive ? Theme.surfaceContainerHigh : wifiMouseArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: "WiFi"
|
||||
color: parent.isActive ? Theme.primary : Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: parent.isActive ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wifiMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: NetworkService.setNetworkPreference("wifi")
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
property int currentPreferenceIndex: NetworkService.userPreference === "ethernet" ? 0 : 1
|
||||
|
||||
model: ["Ethernet", "WiFi"]
|
||||
currentIndex: currentPreferenceIndex
|
||||
selectionMode: "single"
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected) return
|
||||
NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,7 +149,7 @@ Rectangle {
|
||||
height: 36
|
||||
radius: 18
|
||||
color: enableWifiButton.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.width: 1
|
||||
border.width: 0
|
||||
border.color: Theme.primary
|
||||
|
||||
StyledText {
|
||||
@@ -232,12 +168,6 @@ Rectangle {
|
||||
onClicked: NetworkService.toggleWifiRadio()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,9 +227,9 @@ Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: networkMouseArea.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, index % 2 === 0 ? 0.3 : 0.2)
|
||||
color: networkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
|
||||
border.color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: modelData.ssid === NetworkService.currentWifiSSID ? 2 : 1
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -310,11 +240,9 @@ Rectangle {
|
||||
DankIcon {
|
||||
name: {
|
||||
let strength = modelData.signal || 0
|
||||
if (strength >= 70) return "signal_wifi_4_bar"
|
||||
if (strength >= 50) return "network_wifi_3_bar"
|
||||
if (strength >= 25) return "network_wifi_2_bar"
|
||||
if (strength >= 10) return "network_wifi_1_bar"
|
||||
return "signal_wifi_bad"
|
||||
if (strength >= 50) return "wifi"
|
||||
if (strength >= 25) return "wifi_2_bar"
|
||||
return "wifi_1_bar"
|
||||
}
|
||||
size: Theme.iconSize - 4
|
||||
color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Theme.surfaceText
|
||||
@@ -389,9 +317,7 @@ Rectangle {
|
||||
onClicked: function(event) {
|
||||
if (modelData.ssid !== NetworkService.currentWifiSSID) {
|
||||
if (modelData.secured && !modelData.saved) {
|
||||
if (wifiPasswordModalRef) {
|
||||
wifiPasswordModalRef.show(modelData.ssid)
|
||||
}
|
||||
wifiPasswordModal.show(modelData.ssid)
|
||||
} else {
|
||||
NetworkService.connectToWifi(modelData.ssid)
|
||||
}
|
||||
@@ -400,13 +326,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,7 +345,7 @@ Rectangle {
|
||||
background: Rectangle {
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
@@ -452,9 +371,7 @@ Rectangle {
|
||||
NetworkService.disconnectWifi()
|
||||
} else {
|
||||
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
|
||||
if (wifiPasswordModalRef) {
|
||||
wifiPasswordModalRef.show(networkContextMenu.currentSSID)
|
||||
}
|
||||
wifiPasswordModal.show(networkContextMenu.currentSSID)
|
||||
} else {
|
||||
NetworkService.connectToWifi(networkContextMenu.currentSSID)
|
||||
}
|
||||
@@ -480,10 +397,8 @@ Rectangle {
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
if (networkInfoModalRef) {
|
||||
let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID)
|
||||
networkInfoModalRef.showNetworkInfo(networkContextMenu.currentSSID, networkData)
|
||||
}
|
||||
let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID)
|
||||
networkInfoModal.showNetworkInfo(networkContextMenu.currentSSID, networkData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,22 +426,12 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: wifiPasswordModalLoader
|
||||
active: false
|
||||
|
||||
WifiPasswordModal {
|
||||
id: wifiPasswordModal
|
||||
}
|
||||
WifiPasswordModal {
|
||||
id: wifiPasswordModal
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: networkInfoModalLoader
|
||||
active: false
|
||||
|
||||
NetworkInfoModal {
|
||||
id: networkInfoModal
|
||||
}
|
||||
|
||||
NetworkInfoModal {
|
||||
id: networkInfoModal
|
||||
}
|
||||
|
||||
|
||||
|
||||
149
Modules/ControlCenter/Models/WidgetModel.qml
Normal file
149
Modules/ControlCenter/Models/WidgetModel.qml
Normal file
@@ -0,0 +1,149 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import "../utils/widgets.js" as WidgetUtils
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
readonly property var baseWidgetDefinitions: [
|
||||
{
|
||||
"id": "nightMode",
|
||||
"text": "Night Mode",
|
||||
"description": "Blue light filter",
|
||||
"icon": "nightlight",
|
||||
"type": "toggle",
|
||||
"enabled": DisplayService.automationAvailable,
|
||||
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
|
||||
},
|
||||
{
|
||||
"id": "darkMode",
|
||||
"text": "Dark Mode",
|
||||
"description": "System theme toggle",
|
||||
"icon": "contrast",
|
||||
"type": "toggle",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "doNotDisturb",
|
||||
"text": "Do Not Disturb",
|
||||
"description": "Block notifications",
|
||||
"icon": "do_not_disturb_on",
|
||||
"type": "toggle",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "idleInhibitor",
|
||||
"text": "Keep Awake",
|
||||
"description": "Prevent screen timeout",
|
||||
"icon": "motion_sensor_active",
|
||||
"type": "toggle",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "wifi",
|
||||
"text": "Network",
|
||||
"description": "Wi-Fi and Ethernet connection",
|
||||
"icon": "wifi",
|
||||
"type": "connection",
|
||||
"enabled": NetworkService.wifiAvailable,
|
||||
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
|
||||
},
|
||||
{
|
||||
"id": "bluetooth",
|
||||
"text": "Bluetooth",
|
||||
"description": "Device connections",
|
||||
"icon": "bluetooth",
|
||||
"type": "connection",
|
||||
"enabled": BluetoothService.available,
|
||||
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined
|
||||
},
|
||||
{
|
||||
"id": "audioOutput",
|
||||
"text": "Audio Output",
|
||||
"description": "Speaker settings",
|
||||
"icon": "volume_up",
|
||||
"type": "connection",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "audioInput",
|
||||
"text": "Audio Input",
|
||||
"description": "Microphone settings",
|
||||
"icon": "mic",
|
||||
"type": "connection",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "volumeSlider",
|
||||
"text": "Volume Slider",
|
||||
"description": "Audio volume control",
|
||||
"icon": "volume_up",
|
||||
"type": "slider",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "brightnessSlider",
|
||||
"text": "Brightness Slider",
|
||||
"description": "Display brightness control",
|
||||
"icon": "brightness_6",
|
||||
"type": "slider",
|
||||
"enabled": DisplayService.brightnessAvailable,
|
||||
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined
|
||||
},
|
||||
{
|
||||
"id": "inputVolumeSlider",
|
||||
"text": "Input Volume Slider",
|
||||
"description": "Microphone volume control",
|
||||
"icon": "mic",
|
||||
"type": "slider",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "battery",
|
||||
"text": "Battery",
|
||||
"description": "Battery and power management",
|
||||
"icon": "battery_std",
|
||||
"type": "action",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "diskUsage",
|
||||
"text": "Disk Usage",
|
||||
"description": "Filesystem usage monitoring",
|
||||
"icon": "storage",
|
||||
"type": "action",
|
||||
"enabled": DgopService.dgopAvailable,
|
||||
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
|
||||
"allowMultiple": true
|
||||
}
|
||||
]
|
||||
|
||||
function getWidgetForId(widgetId) {
|
||||
return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId)
|
||||
}
|
||||
|
||||
function addWidget(widgetId) {
|
||||
WidgetUtils.addWidget(widgetId)
|
||||
}
|
||||
|
||||
function removeWidget(index) {
|
||||
WidgetUtils.removeWidget(index)
|
||||
}
|
||||
|
||||
function toggleWidgetSize(index) {
|
||||
WidgetUtils.toggleWidgetSize(index)
|
||||
}
|
||||
|
||||
function moveWidget(fromIndex, toIndex) {
|
||||
WidgetUtils.moveWidget(fromIndex, toIndex)
|
||||
}
|
||||
|
||||
function resetToDefault() {
|
||||
WidgetUtils.resetToDefault()
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
WidgetUtils.clearAll()
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ PanelWindow {
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
opacity: powerMenuVisible ? 1 : 0
|
||||
scale: powerMenuVisible ? 1 : 0.85
|
||||
|
||||
|
||||
@@ -7,17 +7,17 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
|
||||
BasePill {
|
||||
CompoundPill {
|
||||
id: root
|
||||
|
||||
property var defaultSource: AudioService.source
|
||||
|
||||
iconName: {
|
||||
if (!defaultSource) return "mic_off"
|
||||
|
||||
|
||||
let volume = defaultSource.audio.volume
|
||||
let muted = defaultSource.audio.muted
|
||||
|
||||
|
||||
if (muted || volume === 0.0) return "mic_off"
|
||||
return "mic"
|
||||
}
|
||||
@@ -41,6 +41,12 @@ BasePill {
|
||||
return Math.round(defaultSource.audio.volume * 100) + "%"
|
||||
}
|
||||
|
||||
onToggled: {
|
||||
if (defaultSource && defaultSource.audio) {
|
||||
defaultSource.audio.muted = !defaultSource.audio.muted
|
||||
}
|
||||
}
|
||||
|
||||
onWheelEvent: function (wheelEvent) {
|
||||
if (!defaultSource || !defaultSource.audio) return
|
||||
let delta = wheelEvent.angleDelta.y
|
||||
|
||||
@@ -7,17 +7,17 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
|
||||
BasePill {
|
||||
CompoundPill {
|
||||
id: root
|
||||
|
||||
property var defaultSink: AudioService.sink
|
||||
|
||||
iconName: {
|
||||
if (!defaultSink) return "volume_off"
|
||||
|
||||
|
||||
let volume = defaultSink.audio.volume
|
||||
let muted = defaultSink.audio.muted
|
||||
|
||||
|
||||
if (muted || volume === 0.0) return "volume_off"
|
||||
if (volume <= 0.33) return "volume_down"
|
||||
if (volume <= 0.66) return "volume_up"
|
||||
@@ -43,6 +43,12 @@ BasePill {
|
||||
return Math.round(defaultSink.audio.volume * 100) + "%"
|
||||
}
|
||||
|
||||
onToggled: {
|
||||
if (defaultSink && defaultSink.audio) {
|
||||
defaultSink.audio.muted = !defaultSink.audio.muted
|
||||
}
|
||||
}
|
||||
|
||||
onWheelEvent: function (wheelEvent) {
|
||||
if (!defaultSink || !defaultSink.audio) return
|
||||
let delta = wheelEvent.angleDelta.y
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
|
||||
SimpleSlider {
|
||||
id: root
|
||||
|
||||
property var defaultSink: AudioService.sink
|
||||
|
||||
iconName: {
|
||||
if (!defaultSink) return "volume_off"
|
||||
|
||||
let volume = defaultSink.audio.volume
|
||||
let muted = defaultSink.audio.muted
|
||||
|
||||
if (muted || volume === 0.0) return "volume_off"
|
||||
if (volume <= 0.33) return "volume_down"
|
||||
if (volume <= 0.66) return "volume_up"
|
||||
return "volume_up"
|
||||
}
|
||||
|
||||
iconColor: defaultSink && !defaultSink.audio.muted && defaultSink.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||
|
||||
enabled: defaultSink !== null
|
||||
allowIconClick: defaultSink !== null
|
||||
|
||||
value: defaultSink ? defaultSink.audio.volume : 0.0
|
||||
maximumValue: 1.0
|
||||
minimumValue: 0.0
|
||||
|
||||
onSliderValueChanged: function(newValue) {
|
||||
if (defaultSink) {
|
||||
defaultSink.audio.volume = newValue
|
||||
if (newValue > 0 && defaultSink.audio.muted) {
|
||||
defaultSink.audio.muted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onIconClicked: function() {
|
||||
if (defaultSink) {
|
||||
defaultSink.audio.muted = !defaultSink.audio.muted
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,17 +10,18 @@ Row {
|
||||
id: root
|
||||
|
||||
property var defaultSink: AudioService.sink
|
||||
property color sliderTrackColor: "transparent"
|
||||
|
||||
height: 40
|
||||
spacing: Theme.spacingS
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
height: Theme.iconSize + Theme.spacingS * 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 // Make it circular
|
||||
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
|
||||
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
@@ -42,10 +43,10 @@ Row {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!defaultSink) return "volume_off"
|
||||
|
||||
|
||||
let volume = defaultSink.audio.volume
|
||||
let muted = defaultSink.audio.muted
|
||||
|
||||
|
||||
if (muted || volume === 0.0) return "volume_off"
|
||||
if (volume <= 0.33) return "volume_down"
|
||||
if (volume <= 0.66) return "volume_up"
|
||||
@@ -57,12 +58,19 @@ Row {
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
readonly property real actualVolumePercent: defaultSink ? Math.round(defaultSink.audio.volume * 100) : 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2) - Theme.spacingM
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: defaultSink !== null
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
value: defaultSink ? Math.round(defaultSink.audio.volume * 100) : 0
|
||||
value: defaultSink ? Math.min(100, Math.round(defaultSink.audio.volume * 100)) : 0
|
||||
showValue: true
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh
|
||||
onSliderValueChanged: function(newValue) {
|
||||
if (defaultSink) {
|
||||
defaultSink.audio.volume = newValue / 100.0
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string iconName: ""
|
||||
property color iconColor: Theme.surfaceText
|
||||
property string primaryText: ""
|
||||
property string secondaryText: ""
|
||||
property bool expanded: false
|
||||
property bool isActive: false
|
||||
|
||||
signal clicked()
|
||||
signal expandClicked()
|
||||
signal wheelEvent(var wheelEvent)
|
||||
|
||||
width: parent ? parent.width : 200
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
Rectangle {
|
||||
id: mainArea
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width - expandArea.width
|
||||
topLeftRadius: Theme.cornerRadius
|
||||
bottomLeftRadius: Theme.cornerRadius
|
||||
topRightRadius: 0
|
||||
bottomRightRadius: 0
|
||||
color: mainAreaMouse.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
"transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: root.iconName
|
||||
size: Theme.iconSize
|
||||
color: root.isActive ? Theme.primary : root.iconColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - Theme.iconSize - Theme.spacingS
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.primaryText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.secondaryText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
visible: text.length > 0
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mainAreaMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.clicked()
|
||||
onWheel: function (wheelEvent) {
|
||||
root.wheelEvent(wheelEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: expandArea
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
width: Theme.iconSize + Theme.spacingM * 2
|
||||
topLeftRadius: 0
|
||||
bottomLeftRadius: 0
|
||||
topRightRadius: Theme.cornerRadius
|
||||
bottomRightRadius: Theme.cornerRadius
|
||||
color: expandAreaMouse.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
"transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
width: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: expandIcon
|
||||
anchors.centerIn: parent
|
||||
name: expanded ? "expand_less" : "expand_more"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: expandAreaMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.expandClicked()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Modules/ControlCenter/Widgets/BatteryPill.qml
Normal file
48
Modules/ControlCenter/Widgets/BatteryPill.qml
Normal file
@@ -0,0 +1,48 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
|
||||
CompoundPill {
|
||||
id: root
|
||||
|
||||
iconName: BatteryService.getBatteryIcon()
|
||||
|
||||
isActive: BatteryService.batteryAvailable && (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||
|
||||
primaryText: {
|
||||
if (!BatteryService.batteryAvailable) {
|
||||
return "No battery"
|
||||
}
|
||||
return "Battery"
|
||||
}
|
||||
|
||||
secondaryText: {
|
||||
if (!BatteryService.batteryAvailable) {
|
||||
return "Not available"
|
||||
}
|
||||
if (BatteryService.isCharging) {
|
||||
return `${BatteryService.batteryLevel}% • Charging`
|
||||
}
|
||||
if (BatteryService.isPluggedIn) {
|
||||
return `${BatteryService.batteryLevel}% • Plugged in`
|
||||
}
|
||||
return `${BatteryService.batteryLevel}%`
|
||||
}
|
||||
|
||||
iconColor: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error
|
||||
}
|
||||
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
|
||||
return Theme.primary
|
||||
}
|
||||
return Theme.surfaceText
|
||||
}
|
||||
|
||||
onToggled: {
|
||||
expandClicked()
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,14 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
|
||||
BasePill {
|
||||
CompoundPill {
|
||||
id: root
|
||||
|
||||
property var primaryDevice: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.devices) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
|
||||
for (let device of devices) {
|
||||
if (device && device.connected) {
|
||||
@@ -30,17 +30,15 @@ BasePill {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
|
||||
return "bluetooth_disabled"
|
||||
}
|
||||
if (primaryDevice) {
|
||||
return BluetoothService.getDeviceIcon(primaryDevice)
|
||||
}
|
||||
return "bluetooth"
|
||||
}
|
||||
|
||||
isActive: !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled)
|
||||
showExpandArea: BluetoothService.available
|
||||
|
||||
primaryText: {
|
||||
if (!BluetoothService.available) {
|
||||
return "Bluetooth unavailable"
|
||||
return "Bluetooth"
|
||||
}
|
||||
if (!BluetoothService.adapter) {
|
||||
return "No adapter"
|
||||
@@ -53,7 +51,7 @@ BasePill {
|
||||
|
||||
secondaryText: {
|
||||
if (!BluetoothService.available) {
|
||||
return "Hardware not found"
|
||||
return "No adapters"
|
||||
}
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
|
||||
return "Off"
|
||||
@@ -63,4 +61,10 @@ BasePill {
|
||||
}
|
||||
return "No devices"
|
||||
}
|
||||
|
||||
onToggled: {
|
||||
if (BluetoothService.available && BluetoothService.adapter) {
|
||||
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
|
||||
SimpleSlider {
|
||||
id: root
|
||||
|
||||
iconName: {
|
||||
if (!DisplayService.brightnessAvailable) return "brightness_low"
|
||||
|
||||
let brightness = DisplayService.brightnessLevel
|
||||
if (brightness <= 33) return "brightness_low"
|
||||
if (brightness <= 66) return "brightness_medium"
|
||||
return "brightness_high"
|
||||
}
|
||||
|
||||
iconColor: DisplayService.brightnessAvailable && DisplayService.brightnessLevel > 0 ? Theme.primary : Theme.surfaceText
|
||||
|
||||
enabled: DisplayService.brightnessAvailable
|
||||
|
||||
value: DisplayService.brightnessLevel
|
||||
maximumValue: 100.0
|
||||
minimumValue: 0.0
|
||||
|
||||
onSliderValueChanged: function(newValue) {
|
||||
if (DisplayService.brightnessAvailable) {
|
||||
DisplayService.brightnessLevel = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ Row {
|
||||
id: root
|
||||
|
||||
height: 40
|
||||
spacing: Theme.spacingS
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
@@ -17,9 +17,9 @@ Row {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
|
||||
color: iconArea.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.12)
|
||||
: "transparent"
|
||||
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
@@ -29,7 +29,7 @@ Row {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
|
||||
onClicked: function(event) {
|
||||
if (DisplayService.devices.length > 1) {
|
||||
if (deviceMenu.visible) {
|
||||
@@ -40,26 +40,26 @@ Row {
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!DisplayService.brightnessAvailable) return "brightness_low"
|
||||
|
||||
let brightness = DisplayService.brightnessLevel
|
||||
if (brightness <= 33) return "brightness_low"
|
||||
if (brightness <= 66) return "brightness_medium"
|
||||
return "brightness_high"
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!DisplayService.brightnessAvailable) return "brightness_low"
|
||||
|
||||
let brightness = DisplayService.brightnessLevel
|
||||
if (brightness <= 33) return "brightness_low"
|
||||
if (brightness <= 66) return "brightness_medium"
|
||||
return "brightness_high"
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: DisplayService.brightnessAvailable && DisplayService.brightnessLevel > 0 ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: DisplayService.brightnessAvailable && DisplayService.brightnessLevel > 0 ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2) - Theme.spacingM
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: DisplayService.brightnessAvailable
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
@@ -79,6 +79,8 @@ Row {
|
||||
DisplayService.setBrightness(newValue)
|
||||
}
|
||||
}
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
trackColor: Theme.surfaceContainerHigh
|
||||
}
|
||||
|
||||
Menu {
|
||||
@@ -89,7 +91,7 @@ Row {
|
||||
background: Rectangle {
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ Rectangle {
|
||||
width: parent ? parent.width : 200
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
Row {
|
||||
|
||||
170
Modules/ControlCenter/Widgets/CompoundPill.qml
Normal file
170
Modules/ControlCenter/Widgets/CompoundPill.qml
Normal file
@@ -0,0 +1,170 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string iconName: ""
|
||||
property color iconColor: Theme.surfaceText
|
||||
property string primaryText: ""
|
||||
property string secondaryText: ""
|
||||
property bool expanded: false
|
||||
property bool isActive: false
|
||||
property bool showExpandArea: true
|
||||
|
||||
signal toggled()
|
||||
signal expandClicked()
|
||||
signal wheelEvent(var wheelEvent)
|
||||
|
||||
width: parent ? parent.width : 220
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
function hoverTint(base) {
|
||||
const factor = 1.2
|
||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
|
||||
}
|
||||
|
||||
readonly property color _containerBg: Theme.surfaceContainerHigh
|
||||
|
||||
color: {
|
||||
const baseColor = bodyMouse.containsMouse ? Theme.widgetBaseHoverColor : _containerBg
|
||||
return baseColor
|
||||
}
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.10)
|
||||
border.width: 0
|
||||
antialiasing: true
|
||||
|
||||
readonly property color _labelPrimary: Theme.surfaceText
|
||||
readonly property color _labelSecondary: Theme.surfaceVariantText
|
||||
readonly property color _tileBgActive: Theme.primary
|
||||
readonly property color _tileBgInactive: {
|
||||
const transparency = Theme.popupTransparency || 0.92
|
||||
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1)
|
||||
return Qt.rgba(surface.r, surface.g, surface.b, transparency)
|
||||
}
|
||||
readonly property color _tileRingActive:
|
||||
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||
readonly property color _tileRingInactive:
|
||||
Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.18)
|
||||
readonly property color _tileIconActive: Theme.primaryContainer
|
||||
readonly property color _tileIconInactive: Theme.primary
|
||||
|
||||
property int _padH: Theme.spacingS
|
||||
property int _tileSize: 48
|
||||
property int _tileRadius: Theme.cornerRadius
|
||||
|
||||
Rectangle {
|
||||
id: rightHoverOverlay
|
||||
anchors.fill: parent
|
||||
radius: root.radius
|
||||
z: 0
|
||||
visible: false
|
||||
color: hoverTint(_containerBg)
|
||||
opacity: 0.08
|
||||
antialiasing: true
|
||||
Behavior on opacity { NumberAnimation { duration: Theme.shortDuration } }
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: _padH
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
id: iconTile
|
||||
z: 1
|
||||
width: _tileSize
|
||||
height: _tileSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: _tileRadius
|
||||
color: isActive ? _tileBgActive : _tileBgInactive
|
||||
border.color: isActive ? _tileRingActive : "transparent"
|
||||
border.width: isActive ? 1 : 0
|
||||
antialiasing: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: _tileRadius
|
||||
color: hoverTint(iconTile.color)
|
||||
opacity: tileMouse.pressed ? 0.3 : (tileMouse.containsMouse ? 0.2 : 0.0)
|
||||
visible: opacity > 0
|
||||
antialiasing: true
|
||||
Behavior on opacity { NumberAnimation { duration: Theme.shortDuration } }
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: iconName
|
||||
size: Theme.iconSize
|
||||
color: isActive ? _tileIconActive : _tileIconInactive
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: tileMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.toggled()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: body
|
||||
width: row.width - iconTile.width - row.spacing
|
||||
height: row.height
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.primaryText
|
||||
color: _labelPrimary
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.secondaryText
|
||||
color: _labelSecondary
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
visible: text.length > 0
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: bodyMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: { rightHoverOverlay.visible = true; rightHoverOverlay.opacity = 0.08 }
|
||||
onExited: { rightHoverOverlay.opacity = 0.0; rightHoverOverlay.visible = false }
|
||||
onPressed: rightHoverOverlay.opacity = 0.16
|
||||
onReleased: rightHoverOverlay.opacity = containsMouse ? 0.08 : 0.0
|
||||
onClicked: root.expandClicked()
|
||||
onWheel: function (ev) {
|
||||
root.wheelEvent(ev)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
focus: true
|
||||
Keys.onPressed: function (ev) {
|
||||
if (ev.key === Qt.Key_Space || ev.key === Qt.Key_Return) { root.toggled(); ev.accepted = true }
|
||||
else if (ev.key === Qt.Key_Right) { root.expandClicked(); ev.accepted = true }
|
||||
}
|
||||
}
|
||||
78
Modules/ControlCenter/Widgets/DiskUsagePill.qml
Normal file
78
Modules/ControlCenter/Widgets/DiskUsagePill.qml
Normal file
@@ -0,0 +1,78 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
|
||||
CompoundPill {
|
||||
id: root
|
||||
|
||||
property string mountPath: "/"
|
||||
property string instanceId: ""
|
||||
|
||||
iconName: "storage"
|
||||
|
||||
property var selectedMount: {
|
||||
if (!DgopService.diskMounts || DgopService.diskMounts.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const targetMount = DgopService.diskMounts.find(mount => mount.mount === mountPath)
|
||||
return targetMount || DgopService.diskMounts.find(mount => mount.mount === "/") || DgopService.diskMounts[0]
|
||||
}
|
||||
|
||||
property real usagePercent: {
|
||||
if (!selectedMount || !selectedMount.percent) {
|
||||
return 0
|
||||
}
|
||||
const percentStr = selectedMount.percent.replace("%", "")
|
||||
return parseFloat(percentStr) || 0
|
||||
}
|
||||
|
||||
isActive: DgopService.dgopAvailable && selectedMount !== null
|
||||
|
||||
primaryText: {
|
||||
if (!DgopService.dgopAvailable) {
|
||||
return "Disk Usage"
|
||||
}
|
||||
if (!selectedMount) {
|
||||
return "No disk data"
|
||||
}
|
||||
return selectedMount.mount
|
||||
}
|
||||
|
||||
secondaryText: {
|
||||
if (!DgopService.dgopAvailable) {
|
||||
return "dgop not available"
|
||||
}
|
||||
if (!selectedMount) {
|
||||
return "No disk data available"
|
||||
}
|
||||
return `${selectedMount.used} / ${selectedMount.size} (${usagePercent.toFixed(0)}%)`
|
||||
}
|
||||
|
||||
iconColor: {
|
||||
if (!DgopService.dgopAvailable || !selectedMount) {
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
}
|
||||
if (usagePercent > 90) {
|
||||
return Theme.error
|
||||
}
|
||||
if (usagePercent > 75) {
|
||||
return Theme.warning
|
||||
}
|
||||
return Theme.surfaceText
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["diskmounts"])
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["diskmounts"])
|
||||
}
|
||||
|
||||
onToggled: {
|
||||
expandClicked()
|
||||
}
|
||||
}
|
||||
81
Modules/ControlCenter/Widgets/InputAudioSliderRow.qml
Normal file
81
Modules/ControlCenter/Widgets/InputAudioSliderRow.qml
Normal file
@@ -0,0 +1,81 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
property var defaultSource: AudioService.source
|
||||
property color sliderTrackColor: "transparent"
|
||||
|
||||
height: 40
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
height: Theme.iconSize + Theme.spacingS * 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
|
||||
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: iconArea
|
||||
anchors.fill: parent
|
||||
visible: defaultSource !== null
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (defaultSource) {
|
||||
defaultSource.audio.muted = !defaultSource.audio.muted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!defaultSource) return "mic_off"
|
||||
|
||||
let volume = defaultSource.audio.volume
|
||||
let muted = defaultSource.audio.muted
|
||||
|
||||
if (muted || volume === 0.0) return "mic_off"
|
||||
return "mic"
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: defaultSource && !defaultSource.audio.muted && defaultSource.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
readonly property real actualVolumePercent: defaultSource ? Math.round(defaultSource.audio.volume * 100) : 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: defaultSource !== null
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
value: defaultSource ? Math.min(100, Math.round(defaultSource.audio.volume * 100)) : 0
|
||||
showValue: true
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh
|
||||
onSliderValueChanged: function(newValue) {
|
||||
if (defaultSource) {
|
||||
defaultSource.audio.volume = newValue / 100.0
|
||||
if (newValue > 0 && defaultSource.audio.muted) {
|
||||
defaultSource.audio.muted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
|
||||
BasePill {
|
||||
CompoundPill {
|
||||
id: root
|
||||
|
||||
isActive: {
|
||||
@@ -21,7 +21,7 @@ BasePill {
|
||||
}
|
||||
return NetworkService.wifiEnabled
|
||||
}
|
||||
|
||||
|
||||
iconName: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return "sync"
|
||||
@@ -33,7 +33,7 @@ BasePill {
|
||||
return NetworkService.wifiSignalIcon
|
||||
}
|
||||
if (NetworkService.wifiEnabled) {
|
||||
return "signal_wifi_off"
|
||||
return "wifi_off"
|
||||
}
|
||||
return "wifi_off"
|
||||
}
|
||||
@@ -67,6 +67,12 @@ BasePill {
|
||||
if (NetworkService.wifiEnabled) {
|
||||
return "Select network"
|
||||
}
|
||||
return "Tap to enable"
|
||||
return ""
|
||||
}
|
||||
|
||||
onToggled: {
|
||||
if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) {
|
||||
NetworkService.toggleWifiRadio()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
property string iconName: ""
|
||||
property color iconColor: Theme.surfaceText
|
||||
property real value: 0.0
|
||||
property real maximumValue: 1.0
|
||||
property real minimumValue: 0.0
|
||||
property bool enabled: true
|
||||
property bool allowIconClick: false
|
||||
|
||||
signal sliderValueChanged(real value)
|
||||
signal iconClicked()
|
||||
|
||||
height: 60
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: root.iconName
|
||||
size: Theme.iconSize
|
||||
color: root.iconColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
visible: root.allowIconClick
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.iconClicked()
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: {
|
||||
if (parent.width <= 0) return 80
|
||||
return Math.max(80, Math.min(400, parent.width - Theme.iconSize - Theme.spacingM))
|
||||
}
|
||||
enabled: root.enabled
|
||||
minimum: Math.round(root.minimumValue * 100)
|
||||
maximum: Math.round(root.maximumValue * 100)
|
||||
value: Math.round(root.value * 100)
|
||||
onSliderValueChanged: function(newValue) { root.sliderValueChanged(newValue / 100.0) }
|
||||
}
|
||||
}
|
||||
107
Modules/ControlCenter/Widgets/SmallBatteryButton.qml
Normal file
107
Modules/ControlCenter/Widgets/SmallBatteryButton.qml
Normal file
@@ -0,0 +1,107 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool isActive: BatteryService.batteryAvailable && (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||
property bool enabled: BatteryService.batteryAvailable
|
||||
|
||||
signal clicked()
|
||||
|
||||
width: parent ? ((parent.width - parent.spacing * 3) / 4) : 48
|
||||
height: 48
|
||||
radius: {
|
||||
if (Theme.cornerRadius === 0) return 0
|
||||
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4
|
||||
}
|
||||
|
||||
function hoverTint(base) {
|
||||
const factor = 1.2
|
||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
|
||||
}
|
||||
|
||||
readonly property color _tileBgActive: Theme.primary
|
||||
readonly property color _tileBgInactive: Theme.surfaceContainerHigh
|
||||
readonly property color _tileRingActive:
|
||||
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||
readonly property color _tileIconActive: Theme.primaryContainer
|
||||
readonly property color _tileIconInactive: Theme.primary
|
||||
|
||||
color: {
|
||||
if (isActive) return _tileBgActive
|
||||
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : _tileBgInactive
|
||||
return baseColor
|
||||
}
|
||||
border.color: isActive ? _tileRingActive : "transparent"
|
||||
border.width: isActive ? 1 : 0
|
||||
antialiasing: true
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: hoverTint(root.color)
|
||||
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
||||
visible: opacity > 0
|
||||
antialiasing: true
|
||||
Behavior on opacity { NumberAnimation { duration: Theme.shortDuration } }
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
|
||||
DankIcon {
|
||||
name: BatteryService.getBatteryIcon()
|
||||
size: parent.parent.width * 0.25
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error
|
||||
}
|
||||
return isActive ? _tileIconActive : _tileIconInactive
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : ""
|
||||
font.pixelSize: parent.parent.width * 0.15
|
||||
font.weight: Font.Medium
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error
|
||||
}
|
||||
return isActive ? _tileIconActive : _tileIconInactive
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: BatteryService.batteryAvailable
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: root.enabled
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
85
Modules/ControlCenter/Widgets/SmallToggleButton.qml
Normal file
85
Modules/ControlCenter/Widgets/SmallToggleButton.qml
Normal file
@@ -0,0 +1,85 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string iconName: ""
|
||||
property bool isActive: false
|
||||
property bool enabled: true
|
||||
property real iconRotation: 0
|
||||
|
||||
signal clicked()
|
||||
|
||||
width: parent ? ((parent.width - parent.spacing * 3) / 4) : 48
|
||||
height: 48
|
||||
radius: {
|
||||
if (Theme.cornerRadius === 0) return 0
|
||||
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4
|
||||
}
|
||||
|
||||
function hoverTint(base) {
|
||||
const factor = 1.2
|
||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
|
||||
}
|
||||
|
||||
readonly property color _tileBgActive: Theme.primary
|
||||
readonly property color _tileBgInactive: Theme.surfaceContainerHigh
|
||||
readonly property color _tileRingActive:
|
||||
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||
readonly property color _tileIconActive: Theme.primaryContainer
|
||||
readonly property color _tileIconInactive: Theme.primary
|
||||
|
||||
color: {
|
||||
if (isActive) return _tileBgActive
|
||||
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : _tileBgInactive
|
||||
return baseColor
|
||||
}
|
||||
border.color: isActive ? _tileRingActive : "transparent"
|
||||
border.width: isActive ? 1 : 0
|
||||
antialiasing: true
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: hoverTint(root.color)
|
||||
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
||||
visible: opacity > 0
|
||||
antialiasing: true
|
||||
Behavior on opacity { NumberAnimation { duration: Theme.shortDuration } }
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: iconName
|
||||
size: Theme.iconSize
|
||||
color: isActive ? _tileIconActive : _tileIconInactive
|
||||
rotation: iconRotation
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: root.enabled
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
@@ -12,67 +11,92 @@ Rectangle {
|
||||
property bool isActive: false
|
||||
property bool enabled: true
|
||||
property string secondaryText: ""
|
||||
property real iconRotation: 0
|
||||
|
||||
signal clicked()
|
||||
|
||||
width: parent ? parent.width : 200
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
radius: {
|
||||
if (Theme.cornerRadius === 0) return 0
|
||||
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4
|
||||
}
|
||||
|
||||
readonly property color _tileBgActive: Theme.primary
|
||||
readonly property color _tileBgInactive: Theme.surfaceContainerHigh
|
||||
readonly property color _tileRingActive:
|
||||
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||
|
||||
color: {
|
||||
if (isActive) return _tileBgActive
|
||||
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : _tileBgInactive
|
||||
return baseColor
|
||||
}
|
||||
border.color: isActive ? _tileRingActive : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
function hoverTint(base) {
|
||||
const factor = 1.2
|
||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
|
||||
}
|
||||
|
||||
readonly property color _containerBg: Theme.surfaceContainerHigh
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: mouseArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
"transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
color: mouseArea.containsMouse ? hoverTint(_containerBg) : "transparent"
|
||||
opacity: mouseArea.containsMouse ? 0.08 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingL + 2
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: root.iconName
|
||||
size: Theme.iconSize
|
||||
color: root.isActive ? Theme.primary : Theme.surfaceText
|
||||
color: isActive ? Theme.primaryContainer : Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
rotation: root.iconRotation
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - Theme.iconSize - Theme.spacingS
|
||||
spacing: 2
|
||||
Item {
|
||||
width: parent.width - Theme.iconSize - parent.spacing
|
||||
height: parent.height
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.text
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.secondaryText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
visible: text.length > 0
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.text
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: isActive ? Theme.primaryContainer : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.secondaryText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: isActive ? Theme.primaryContainer : Theme.surfaceVariantText
|
||||
visible: text.length > 0
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,4 +116,11 @@ Rectangle {
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Modules/ControlCenter/utils/layout.js
Normal file
45
Modules/ControlCenter/utils/layout.js
Normal file
@@ -0,0 +1,45 @@
|
||||
function calculateRowsAndWidgets(controlCenterColumn, expandedSection, expandedWidgetIndex) {
|
||||
var rows = []
|
||||
var currentRow = []
|
||||
var currentWidth = 0
|
||||
var expandedRow = -1
|
||||
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
const baseWidth = controlCenterColumn.width
|
||||
const spacing = Theme.spacingS
|
||||
|
||||
for (var i = 0; i < widgets.length; i++) {
|
||||
const widget = widgets[i]
|
||||
const widgetWidth = widget.width || 50
|
||||
|
||||
var itemWidth
|
||||
if (widgetWidth <= 25) {
|
||||
itemWidth = (baseWidth - spacing * 3) / 4
|
||||
} else if (widgetWidth <= 50) {
|
||||
itemWidth = (baseWidth - spacing) / 2
|
||||
} else if (widgetWidth <= 75) {
|
||||
itemWidth = (baseWidth - spacing * 2) * 0.75
|
||||
} else {
|
||||
itemWidth = baseWidth
|
||||
}
|
||||
|
||||
if (currentRow.length > 0 && (currentWidth + spacing + itemWidth > baseWidth)) {
|
||||
rows.push([...currentRow])
|
||||
currentRow = [widget]
|
||||
currentWidth = itemWidth
|
||||
} else {
|
||||
currentRow.push(widget)
|
||||
currentWidth += (currentRow.length > 1 ? spacing : 0) + itemWidth
|
||||
}
|
||||
|
||||
if (expandedWidgetIndex === i) {
|
||||
expandedRow = rows.length
|
||||
}
|
||||
}
|
||||
|
||||
if (currentRow.length > 0) {
|
||||
rows.push(currentRow)
|
||||
}
|
||||
|
||||
return { rows: rows, expandedRowIndex: expandedRow }
|
||||
}
|
||||
25
Modules/ControlCenter/utils/state.js
Normal file
25
Modules/ControlCenter/utils/state.js
Normal file
@@ -0,0 +1,25 @@
|
||||
function setTriggerPosition(root, x, y, width, section, screen) {
|
||||
root.triggerX = x
|
||||
root.triggerY = y
|
||||
root.triggerWidth = width
|
||||
root.triggerSection = section
|
||||
root.triggerScreen = screen
|
||||
}
|
||||
|
||||
function openWithSection(root, section) {
|
||||
if (root.shouldBeVisible) {
|
||||
root.close()
|
||||
} else {
|
||||
root.expandedSection = section
|
||||
root.open()
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSection(root, section) {
|
||||
if (root.expandedSection === section) {
|
||||
root.expandedSection = ""
|
||||
root.expandedWidgetIndex = -1
|
||||
} else {
|
||||
root.expandedSection = section
|
||||
}
|
||||
}
|
||||
85
Modules/ControlCenter/utils/widgets.js
Normal file
85
Modules/ControlCenter/utils/widgets.js
Normal file
@@ -0,0 +1,85 @@
|
||||
function getWidgetForId(baseWidgetDefinitions, widgetId) {
|
||||
return baseWidgetDefinitions.find(w => w.id === widgetId)
|
||||
}
|
||||
|
||||
function addWidget(widgetId) {
|
||||
var widgets = SettingsData.controlCenterWidgets.slice()
|
||||
var widget = {
|
||||
"id": widgetId,
|
||||
"enabled": true,
|
||||
"width": 50
|
||||
}
|
||||
|
||||
if (widgetId === "diskUsage") {
|
||||
widget.instanceId = generateUniqueId()
|
||||
widget.mountPath = "/"
|
||||
}
|
||||
|
||||
widgets.push(widget)
|
||||
SettingsData.setControlCenterWidgets(widgets)
|
||||
}
|
||||
|
||||
function generateUniqueId() {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substr(2)
|
||||
}
|
||||
|
||||
function removeWidget(index) {
|
||||
var widgets = SettingsData.controlCenterWidgets.slice()
|
||||
if (index >= 0 && index < widgets.length) {
|
||||
widgets.splice(index, 1)
|
||||
SettingsData.setControlCenterWidgets(widgets)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleWidgetSize(index) {
|
||||
var widgets = SettingsData.controlCenterWidgets.slice()
|
||||
if (index >= 0 && index < widgets.length) {
|
||||
const currentWidth = widgets[index].width || 50
|
||||
const id = widgets[index].id || ""
|
||||
|
||||
if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
|
||||
widgets[index].width = currentWidth <= 50 ? 100 : 50
|
||||
} else {
|
||||
if (currentWidth <= 25) {
|
||||
widgets[index].width = 50
|
||||
} else if (currentWidth <= 50) {
|
||||
widgets[index].width = 100
|
||||
} else {
|
||||
widgets[index].width = 25
|
||||
}
|
||||
}
|
||||
|
||||
SettingsData.setControlCenterWidgets(widgets)
|
||||
}
|
||||
}
|
||||
|
||||
function reorderWidgets(newOrder) {
|
||||
SettingsData.setControlCenterWidgets(newOrder)
|
||||
}
|
||||
|
||||
function moveWidget(fromIndex, toIndex) {
|
||||
let widgets = [...(SettingsData.controlCenterWidgets || [])]
|
||||
if (fromIndex >= 0 && fromIndex < widgets.length && toIndex >= 0 && toIndex < widgets.length) {
|
||||
const movedWidget = widgets.splice(fromIndex, 1)[0]
|
||||
widgets.splice(toIndex, 0, movedWidget)
|
||||
SettingsData.setControlCenterWidgets(widgets)
|
||||
}
|
||||
}
|
||||
|
||||
function resetToDefault() {
|
||||
const defaultWidgets = [
|
||||
{"id": "volumeSlider", "enabled": true, "width": 50},
|
||||
{"id": "brightnessSlider", "enabled": true, "width": 50},
|
||||
{"id": "wifi", "enabled": true, "width": 50},
|
||||
{"id": "bluetooth", "enabled": true, "width": 50},
|
||||
{"id": "audioOutput", "enabled": true, "width": 50},
|
||||
{"id": "audioInput", "enabled": true, "width": 50},
|
||||
{"id": "nightMode", "enabled": true, "width": 50},
|
||||
{"id": "darkMode", "enabled": true, "width": 50}
|
||||
]
|
||||
SettingsData.setControlCenterWidgets(defaultWidgets)
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
SettingsData.setControlCenterWidgets([])
|
||||
}
|
||||
@@ -34,13 +34,13 @@ DankPopout {
|
||||
popupWidth: 700
|
||||
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
|
||||
triggerX: Screen.width - 620 - Theme.spacingL
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingS
|
||||
triggerY: Math.max(26 + SettingsData.topBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.topBarInnerPadding)) + SettingsData.topBarSpacing + SettingsData.topBarBottomGap - 2 + Theme.popupDistance
|
||||
triggerWidth: 80
|
||||
positioning: "center"
|
||||
screen: triggerScreen
|
||||
shouldBeVisible: dashVisible
|
||||
visible: shouldBeVisible
|
||||
|
||||
|
||||
onDashVisibleChanged: {
|
||||
if (dashVisible) {
|
||||
open()
|
||||
@@ -172,6 +172,18 @@ DankPopout {
|
||||
|
||||
OverviewTab {
|
||||
id: overviewTab
|
||||
|
||||
onSwitchToWeatherTab: {
|
||||
if (SettingsData.weatherEnabled) {
|
||||
tabBar.currentIndex = 2
|
||||
tabBar.tabClicked(2)
|
||||
}
|
||||
}
|
||||
|
||||
onSwitchToMediaTab: {
|
||||
tabBar.currentIndex = 1
|
||||
tabBar.tabClicked(1)
|
||||
}
|
||||
}
|
||||
|
||||
MediaPlayerTab {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -8,10 +9,30 @@ Rectangle {
|
||||
id: root
|
||||
|
||||
property bool showEventDetails: false
|
||||
property date selectedDate: new Date()
|
||||
property date selectedDate: systemClock.date
|
||||
property var selectedDateEvents: []
|
||||
property bool hasEvents: selectedDateEvents && selectedDateEvents.length > 0
|
||||
|
||||
function weekStartJs() {
|
||||
return Qt.locale().firstDayOfWeek % 7
|
||||
}
|
||||
|
||||
function startOfWeek(dateObj) {
|
||||
const d = new Date(dateObj)
|
||||
const jsDow = d.getDay()
|
||||
const diff = (jsDow - weekStartJs() + 7) % 7
|
||||
d.setDate(d.getDate() - diff)
|
||||
return d
|
||||
}
|
||||
|
||||
function endOfWeek(dateObj) {
|
||||
const d = new Date(dateObj)
|
||||
const jsDow = d.getDay()
|
||||
const add = (weekStartJs() + 6 - jsDow + 7) % 7
|
||||
d.setDate(d.getDate() + add)
|
||||
return d
|
||||
}
|
||||
|
||||
function updateSelectedDateEvents() {
|
||||
if (CalendarService && CalendarService.khalAvailable) {
|
||||
const events = CalendarService.getEventsForDate(selectedDate)
|
||||
@@ -26,14 +47,16 @@ Rectangle {
|
||||
return
|
||||
}
|
||||
|
||||
const firstDay = new Date(calendarGrid.displayDate.getFullYear(), calendarGrid.displayDate.getMonth(), 1)
|
||||
const dayOfWeek = firstDay.getDay()
|
||||
const startDate = new Date(firstDay)
|
||||
startDate.setDate(startDate.getDate() - dayOfWeek - 7)
|
||||
const firstOfMonth = new Date(calendarGrid.displayDate.getFullYear(),
|
||||
calendarGrid.displayDate.getMonth(), 1)
|
||||
const lastOfMonth = new Date(calendarGrid.displayDate.getFullYear(),
|
||||
calendarGrid.displayDate.getMonth() + 1, 0)
|
||||
|
||||
const lastDay = new Date(calendarGrid.displayDate.getFullYear(), calendarGrid.displayDate.getMonth() + 1, 0)
|
||||
const endDate = new Date(lastDay)
|
||||
endDate.setDate(endDate.getDate() + (6 - lastDay.getDay()) + 7)
|
||||
const startDate = startOfWeek(firstOfMonth)
|
||||
startDate.setDate(startDate.getDate() - 7)
|
||||
|
||||
const endDate = endOfWeek(lastOfMonth)
|
||||
endDate.setDate(endDate.getDate() + 7)
|
||||
|
||||
CalendarService.loadEvents(startDate, endDate)
|
||||
}
|
||||
@@ -61,7 +84,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.1)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
|
||||
border.width: 1
|
||||
|
||||
@@ -194,10 +217,11 @@ Rectangle {
|
||||
Repeater {
|
||||
model: {
|
||||
const days = []
|
||||
const locale = Qt.locale()
|
||||
for (var i = 0; i < 7; i++) {
|
||||
const date = new Date(2024, 0, 7 + i)
|
||||
days.push(locale.dayName(i, Locale.ShortFormat))
|
||||
const loc = Qt.locale()
|
||||
const qtFirst = loc.firstDayOfWeek
|
||||
for (let i = 0; i < 7; ++i) {
|
||||
const qtDay = ((qtFirst - 1 + i) % 7) + 1
|
||||
days.push(loc.dayName(qtDay, Locale.ShortFormat))
|
||||
}
|
||||
return days
|
||||
}
|
||||
@@ -222,14 +246,12 @@ Rectangle {
|
||||
id: calendarGrid
|
||||
visible: !showEventDetails
|
||||
|
||||
property date displayDate: new Date()
|
||||
property date selectedDate: new Date()
|
||||
property date displayDate: systemClock.date
|
||||
property date selectedDate: systemClock.date
|
||||
|
||||
readonly property date firstDay: {
|
||||
const date = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1)
|
||||
const dayOfWeek = date.getDay()
|
||||
date.setDate(date.getDate() - dayOfWeek)
|
||||
return date
|
||||
const firstOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1)
|
||||
return startOfWeek(firstOfMonth)
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
@@ -323,7 +345,7 @@ Rectangle {
|
||||
} 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.04)
|
||||
return Theme.surfaceContainerHigh
|
||||
}
|
||||
border.color: {
|
||||
if (modelData.url && eventMouseArea.containsMouse) {
|
||||
@@ -420,4 +442,9 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SystemClock {
|
||||
id: systemClock
|
||||
precision: SystemClock.Hours
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ Rectangle {
|
||||
property int pad: Theme.spacingM
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
|
||||
@@ -79,12 +79,7 @@ Card {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0) {
|
||||
return systemClock?.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat)
|
||||
}
|
||||
return systemClock?.date?.toLocaleDateString(Qt.locale(), "MMM d")
|
||||
}
|
||||
text: systemClock?.date?.toLocaleDateString(Qt.locale(), "MMM dd")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
@@ -8,6 +8,9 @@ import qs.Widgets
|
||||
|
||||
Card {
|
||||
id: root
|
||||
clip: false
|
||||
|
||||
signal clicked()
|
||||
|
||||
property MprisPlayer activePlayer: MprisController.activePlayer
|
||||
property real currentPosition: activePlayer?.positionSupported ? activePlayer.position : 0
|
||||
@@ -15,7 +18,8 @@ Card {
|
||||
|
||||
readonly property real ratio: {
|
||||
if (!activePlayer || activePlayer.length <= 0) return 0
|
||||
const calculatedRatio = displayPosition / activePlayer.length
|
||||
const pos = displayPosition % Math.max(1, activePlayer.length)
|
||||
const calculatedRatio = pos / activePlayer.length
|
||||
return Math.max(0, Math.min(1, calculatedRatio))
|
||||
}
|
||||
|
||||
@@ -29,11 +33,13 @@ Card {
|
||||
|
||||
Timer {
|
||||
interval: 300
|
||||
running: activePlayer?.playbackState === MprisPlaybackState.Playing && !progressMouseArea.isSeeking
|
||||
running: activePlayer?.playbackState === MprisPlaybackState.Playing && !isSeeking
|
||||
repeat: true
|
||||
onTriggered: activePlayer?.positionSupported && activePlayer.positionChanged()
|
||||
}
|
||||
|
||||
property bool isSeeking: false
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
@@ -61,195 +67,18 @@ Card {
|
||||
visible: activePlayer
|
||||
|
||||
Item {
|
||||
width: 110
|
||||
height: 80
|
||||
width: 140
|
||||
height: 110
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
clip: false
|
||||
|
||||
Loader {
|
||||
active: activePlayer?.playbackState === MprisPlaybackState.Playing
|
||||
sourceComponent: Component {
|
||||
Ref {
|
||||
service: CavaService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shape {
|
||||
id: morphingBlob
|
||||
width: 120
|
||||
height: 120
|
||||
DankAlbumArt {
|
||||
width: 110
|
||||
height: 80
|
||||
anchors.centerIn: parent
|
||||
visible: activePlayer?.playbackState === MprisPlaybackState.Playing
|
||||
asynchronous: false
|
||||
antialiasing: true
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
layer.samples: 4
|
||||
|
||||
|
||||
readonly property real centerX: width / 2
|
||||
readonly property real centerY: height / 2
|
||||
readonly property real baseRadius: 40
|
||||
readonly property int segments: 24
|
||||
|
||||
property var audioLevels: {
|
||||
if (!CavaService.cavaAvailable || CavaService.values.length === 0) {
|
||||
return [0.5, 0.3, 0.7, 0.4, 0.6, 0.5]
|
||||
}
|
||||
return CavaService.values
|
||||
}
|
||||
|
||||
property var smoothedLevels: [0.5, 0.3, 0.7, 0.4, 0.6, 0.5]
|
||||
property var cubics: []
|
||||
|
||||
|
||||
onAudioLevelsChanged: updatePath()
|
||||
|
||||
Timer {
|
||||
running: morphingBlob.visible
|
||||
interval: 16
|
||||
repeat: true
|
||||
onTriggered: morphingBlob.updatePath()
|
||||
}
|
||||
|
||||
Component {
|
||||
id: cubicSegment
|
||||
PathCubic {}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
shapePath.pathElements.push(Qt.createQmlObject(
|
||||
'import QtQuick; import QtQuick.Shapes; PathMove {}', shapePath
|
||||
))
|
||||
|
||||
for (let i = 0; i < segments; i++) {
|
||||
const seg = cubicSegment.createObject(shapePath)
|
||||
shapePath.pathElements.push(seg)
|
||||
cubics.push(seg)
|
||||
}
|
||||
|
||||
updatePath()
|
||||
}
|
||||
|
||||
function expSmooth(prev, next, alpha) {
|
||||
return prev + alpha * (next - prev)
|
||||
}
|
||||
|
||||
function updatePath() {
|
||||
if (cubics.length === 0) return
|
||||
|
||||
for (let i = 0; i < Math.min(smoothedLevels.length, audioLevels.length); i++) {
|
||||
smoothedLevels[i] = expSmooth(smoothedLevels[i], audioLevels[i], 0.2)
|
||||
}
|
||||
|
||||
const points = []
|
||||
for (let i = 0; i < segments; i++) {
|
||||
const angle = (i / segments) * 2 * Math.PI
|
||||
const audioIndex = i % Math.min(smoothedLevels.length, 6)
|
||||
const audioLevel = Math.max(0.1, Math.min(1.5, (smoothedLevels[audioIndex] || 0) / 50))
|
||||
|
||||
const radius = baseRadius * (1.0 + audioLevel * 0.3)
|
||||
const x = centerX + Math.cos(angle) * radius
|
||||
const y = centerY + Math.sin(angle) * radius
|
||||
points.push({x: x, y: y})
|
||||
}
|
||||
|
||||
const startMove = shapePath.pathElements[0]
|
||||
startMove.x = points[0].x
|
||||
startMove.y = points[0].y
|
||||
|
||||
const tension = 0.5
|
||||
for (let i = 0; i < segments; i++) {
|
||||
const p0 = points[(i - 1 + segments) % segments]
|
||||
const p1 = points[i]
|
||||
const p2 = points[(i + 1) % segments]
|
||||
const p3 = points[(i + 2) % segments]
|
||||
|
||||
const c1x = p1.x + (p2.x - p0.x) * tension / 3
|
||||
const c1y = p1.y + (p2.y - p0.y) * tension / 3
|
||||
const c2x = p2.x - (p3.x - p1.x) * tension / 3
|
||||
const c2y = p2.y - (p3.y - p1.y) * tension / 3
|
||||
|
||||
const seg = cubics[i]
|
||||
seg.control1X = c1x
|
||||
seg.control1Y = c1y
|
||||
seg.control2X = c2x
|
||||
seg.control2Y = c2y
|
||||
seg.x = p2.x
|
||||
seg.y = p2.y
|
||||
}
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
id: shapePath
|
||||
fillColor: Theme.primary
|
||||
strokeColor: "transparent"
|
||||
strokeWidth: 0
|
||||
joinStyle: ShapePath.RoundJoin
|
||||
fillRule: ShapePath.WindingFill
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 72
|
||||
height: 72
|
||||
radius: 36
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
border.color: Theme.surfaceContainer
|
||||
border.width: 1
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
z: 1
|
||||
|
||||
Image {
|
||||
id: albumArt
|
||||
source: activePlayer?.trackArtUrl || ""
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
mipmap: true
|
||||
cache: true
|
||||
asynchronous: true
|
||||
visible: false
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
source: albumArt
|
||||
maskEnabled: true
|
||||
maskSource: circularMask
|
||||
visible: albumArt.status === Image.Ready
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
|
||||
Item {
|
||||
id: circularMask
|
||||
width: 68
|
||||
height: 68
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: "black"
|
||||
antialiasing: true
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "album"
|
||||
size: 20
|
||||
color: Theme.surfaceVariantText
|
||||
visible: albumArt.status !== Image.Ready
|
||||
}
|
||||
activePlayer: root.activePlayer
|
||||
albumSize: 76
|
||||
animationScale: 1.05
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,104 +109,13 @@ Card {
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: progressSlider
|
||||
width: parent.width
|
||||
DankSeekbar {
|
||||
width: parent.width + 4
|
||||
height: 20
|
||||
visible: activePlayer?.length > 0
|
||||
|
||||
property real value: ratio
|
||||
property real lineWidth: 2.5
|
||||
property color trackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.40)
|
||||
property color fillColor: Theme.primary
|
||||
property color playheadColor: Theme.primary
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: progressSlider.lineWidth
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: progressSlider.trackColor
|
||||
radius: height / 2
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Math.max(0, Math.min(parent.width, parent.width * progressSlider.value))
|
||||
height: progressSlider.lineWidth
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: progressSlider.fillColor
|
||||
radius: height / 2
|
||||
Behavior on width { NumberAnimation { duration: 80 } }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: playhead
|
||||
width: 2.5
|
||||
height: Math.max(progressSlider.lineWidth + 8, 12)
|
||||
radius: width / 2
|
||||
color: progressSlider.playheadColor
|
||||
x: Math.max(0, Math.min(progressSlider.width, progressSlider.width * progressSlider.value)) - width / 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
z: 3
|
||||
Behavior on x { NumberAnimation { duration: 80 } }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: progressMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: activePlayer ? (activePlayer.canSeek && activePlayer.length > 0) : false
|
||||
|
||||
property bool isSeeking: false
|
||||
property real pendingSeekPosition: -1
|
||||
|
||||
Timer {
|
||||
id: seekDebounceTimer
|
||||
interval: 150
|
||||
onTriggered: {
|
||||
if (progressMouseArea.pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
||||
const clamped = Math.min(progressMouseArea.pendingSeekPosition, activePlayer.length * 0.99)
|
||||
activePlayer.position = clamped
|
||||
progressMouseArea.pendingSeekPosition = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPressed: (mouse) => {
|
||||
isSeeking = true
|
||||
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||
const r = Math.max(0, Math.min(1, mouse.x / progressSlider.width))
|
||||
pendingSeekPosition = r * activePlayer.length
|
||||
displayPosition = pendingSeekPosition
|
||||
seekDebounceTimer.restart()
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
isSeeking = false
|
||||
seekDebounceTimer.stop()
|
||||
if (pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
||||
const clamped = Math.min(pendingSeekPosition, activePlayer.length * 0.99)
|
||||
activePlayer.position = clamped
|
||||
pendingSeekPosition = -1
|
||||
}
|
||||
displayPosition = Qt.binding(() => currentPosition)
|
||||
}
|
||||
onPositionChanged: (mouse) => {
|
||||
if (pressed && isSeeking && activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||
const r = Math.max(0, Math.min(1, mouse.x / progressSlider.width))
|
||||
pendingSeekPosition = r * activePlayer.length
|
||||
displayPosition = pendingSeekPosition
|
||||
seekDebounceTimer.restart()
|
||||
}
|
||||
}
|
||||
onClicked: (mouse) => {
|
||||
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||
const r = Math.max(0, Math.min(1, mouse.x / progressSlider.width))
|
||||
activePlayer.position = r * activePlayer.length
|
||||
}
|
||||
}
|
||||
}
|
||||
x: -2
|
||||
activePlayer: root.activePlayer
|
||||
isSeeking: root.isSeeking
|
||||
onIsSeekingChanged: root.isSeeking = isSeeking
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -393,7 +131,7 @@ Card {
|
||||
height: 28
|
||||
radius: 14
|
||||
anchors.verticalCenter: playPauseButton.verticalCenter
|
||||
color: prevArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12) : "transparent"
|
||||
color: prevArea.containsMouse ? Theme.surfaceContainerHigh : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
@@ -445,7 +183,7 @@ Card {
|
||||
height: 28
|
||||
radius: 14
|
||||
anchors.verticalCenter: playPauseButton.verticalCenter
|
||||
color: nextArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12) : "transparent"
|
||||
color: nextArea.containsMouse ? Theme.surfaceContainerHigh : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
@@ -465,4 +203,16 @@ Card {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 123
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.clicked()
|
||||
visible: activePlayer
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,8 @@ Card {
|
||||
|
||||
property real availableWidth: parent.parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3 - 16 - Theme.spacingS
|
||||
property real longTextWidth: {
|
||||
const testMetrics = Qt.createQmlObject('import QtQuick; TextMetrics { font.pixelSize: ' + Theme.fontSizeSmall + ' }', uptimeText)
|
||||
const fontSize = Math.round(Theme.fontSizeSmall || 12)
|
||||
const testMetrics = Qt.createQmlObject('import QtQuick; TextMetrics { font.pixelSize: ' + fontSize + ' }', uptimeText)
|
||||
testMetrics.text = UserInfoService.uptime || "up 1 hour, 23 minutes"
|
||||
const result = testMetrics.width
|
||||
testMetrics.destroy()
|
||||
|
||||
@@ -8,6 +8,8 @@ import qs.Widgets
|
||||
Card {
|
||||
id: root
|
||||
|
||||
signal clicked()
|
||||
|
||||
Component.onCompleted: WeatherService.addRef()
|
||||
Component.onDestruction: WeatherService.removeRef()
|
||||
|
||||
@@ -79,4 +81,11 @@ Card {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,9 @@ Item {
|
||||
implicitWidth: 700
|
||||
implicitHeight: 410
|
||||
|
||||
signal switchToWeatherTab()
|
||||
signal switchToMediaTab()
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
// Clock - top left (narrower and shorter)
|
||||
@@ -29,6 +32,8 @@ Item {
|
||||
width: SettingsData.weatherEnabled ? parent.width * 0.3 : 0
|
||||
height: 100
|
||||
visible: SettingsData.weatherEnabled
|
||||
|
||||
onClicked: root.switchToWeatherTab()
|
||||
}
|
||||
|
||||
// UserInfo - top middle-right (extend when weather disabled)
|
||||
@@ -61,6 +66,8 @@ Item {
|
||||
y: 100 + Theme.spacingM
|
||||
width: parent.width * 0.2
|
||||
height: 300
|
||||
|
||||
onClicked: root.switchToMediaTab()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,7 +231,7 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
color: Theme.surfaceContainerHigh
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
@@ -278,7 +278,7 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
color: Theme.surfaceContainerHigh
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
@@ -325,7 +325,7 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
color: Theme.surfaceContainerHigh
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
@@ -372,7 +372,7 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
color: Theme.surfaceContainerHigh
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
@@ -419,7 +419,7 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
color: Theme.surfaceContainerHigh
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
@@ -466,7 +466,7 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
color: Theme.surfaceContainerHigh
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
@@ -554,7 +554,7 @@ Item {
|
||||
return null
|
||||
}
|
||||
|
||||
color: isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.1)
|
||||
color: isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : Theme.surfaceContainerHigh
|
||||
border.color: isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent"
|
||||
border.width: isToday ? 1 : 0
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ PanelWindow {
|
||||
property var contextMenu
|
||||
property bool autoHide: SettingsData.dockAutoHide
|
||||
property real backgroundTransparency: SettingsData.dockTransparency
|
||||
property bool groupByApp: SettingsData.dockGroupByApp
|
||||
|
||||
property bool contextMenuOpen: (contextMenu && contextMenu.visible && contextMenu.screen === modelData)
|
||||
property bool windowIsFullscreen: {
|
||||
@@ -135,6 +136,7 @@ PanelWindow {
|
||||
anchors.bottomMargin: 4
|
||||
|
||||
contextMenu: dock.contextMenu
|
||||
groupByApp: dock.groupByApp
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
@@ -25,24 +26,39 @@ Item {
|
||||
property bool isHovered: mouseArea.containsMouse && !dragging
|
||||
property bool showTooltip: mouseArea.containsMouse && !dragging
|
||||
property bool isWindowFocused: {
|
||||
if (!appData || appData.type !== "window") {
|
||||
if (!appData) {
|
||||
return false
|
||||
}
|
||||
const toplevel = getToplevelObject()
|
||||
if (!toplevel) {
|
||||
return false
|
||||
|
||||
if (appData.type === "window") {
|
||||
const toplevel = getToplevelObject()
|
||||
if (!toplevel) {
|
||||
return false
|
||||
}
|
||||
return toplevel.activated
|
||||
} else if (appData.type === "grouped") {
|
||||
// For grouped apps, check if any window is focused
|
||||
const allToplevels = ToplevelManager.toplevels.values
|
||||
for (let i = 0; i < allToplevels.length; i++) {
|
||||
const toplevel = allToplevels[i]
|
||||
if (toplevel.appId === appData.appId && toplevel.activated) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return toplevel.activated
|
||||
|
||||
return false
|
||||
}
|
||||
property string tooltipText: {
|
||||
if (!appData) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if (appData.type === "window" && showWindowTitle) {
|
||||
if ((appData.type === "window" && showWindowTitle) || (appData.type === "grouped" && appData.windowTitle)) {
|
||||
const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
|
||||
const appName = desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId
|
||||
return appName + (windowTitle ? " • " + windowTitle : "")
|
||||
const title = appData.type === "window" ? windowTitle : appData.windowTitle
|
||||
return appName + (title ? " • " + title : "")
|
||||
}
|
||||
|
||||
if (!appData.appId) {
|
||||
@@ -57,7 +73,7 @@ Item {
|
||||
height: 40
|
||||
|
||||
function getToplevelObject() {
|
||||
if (!appData || appData.type !== "window") {
|
||||
if (!appData) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -66,23 +82,47 @@ Item {
|
||||
return null
|
||||
}
|
||||
|
||||
if (appData.uniqueId) {
|
||||
for (var i = 0; i < sortedToplevels.length; i++) {
|
||||
const toplevel = sortedToplevels[i]
|
||||
const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i
|
||||
if (checkId === appData.uniqueId) {
|
||||
return toplevel
|
||||
if (appData.type === "window") {
|
||||
if (appData.uniqueId) {
|
||||
for (var i = 0; i < sortedToplevels.length; i++) {
|
||||
const toplevel = sortedToplevels[i]
|
||||
const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i
|
||||
if (checkId === appData.uniqueId) {
|
||||
return toplevel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (appData.windowId !== undefined && appData.windowId !== null && appData.windowId >= 0) {
|
||||
if (appData.windowId < sortedToplevels.length) {
|
||||
return sortedToplevels[appData.windowId]
|
||||
}
|
||||
}
|
||||
} else if (appData.type === "grouped") {
|
||||
if (appData.windowId !== undefined && appData.windowId !== null && appData.windowId >= 0) {
|
||||
if (appData.windowId < sortedToplevels.length) {
|
||||
return sortedToplevels[appData.windowId]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (appData.windowId !== undefined && appData.windowId !== null && appData.windowId >= 0) {
|
||||
if (appData.windowId < sortedToplevels.length) {
|
||||
return sortedToplevels[appData.windowId]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function getGroupedToplevels() {
|
||||
if (!appData || appData.type !== "grouped") {
|
||||
return []
|
||||
}
|
||||
|
||||
return null
|
||||
const toplevels = []
|
||||
const allToplevels = ToplevelManager.toplevels.values
|
||||
for (let i = 0; i < allToplevels.length; i++) {
|
||||
const toplevel = allToplevels[i]
|
||||
if (toplevel.appId === appData.appId) {
|
||||
toplevels.push(toplevel)
|
||||
}
|
||||
}
|
||||
return toplevels
|
||||
}
|
||||
onIsHoveredChanged: {
|
||||
if (isHovered) {
|
||||
@@ -225,13 +265,43 @@ Item {
|
||||
"comment": desktopEntry.comment || ""
|
||||
})
|
||||
}
|
||||
desktopEntry.execute()
|
||||
SessionService.launchDesktopEntry(desktopEntry)
|
||||
}
|
||||
} else if (appData.type === "window") {
|
||||
const toplevel = getToplevelObject()
|
||||
if (toplevel) {
|
||||
toplevel.activate()
|
||||
}
|
||||
} else if (appData.type === "grouped") {
|
||||
if (appData.windowCount === 0) {
|
||||
if (appData && appData.appId) {
|
||||
const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
|
||||
if (desktopEntry) {
|
||||
AppUsageHistoryData.addAppUsage({
|
||||
"id": appData.appId,
|
||||
"name": desktopEntry.name || appData.appId,
|
||||
"icon": desktopEntry.icon || "",
|
||||
"exec": desktopEntry.exec || "",
|
||||
"comment": desktopEntry.comment || ""
|
||||
})
|
||||
}
|
||||
SessionService.launchDesktopEntry(desktopEntry)
|
||||
}
|
||||
} else if (appData.windowCount === 1) {
|
||||
// For single window, activate directly
|
||||
const toplevel = getToplevelObject()
|
||||
if (toplevel) {
|
||||
console.log("Activating grouped app window:", appData.windowTitle)
|
||||
toplevel.activate()
|
||||
} else {
|
||||
console.warn("No toplevel found for grouped app")
|
||||
}
|
||||
} else {
|
||||
// For multiple windows, show context menu (hide pin option for left-click)
|
||||
if (contextMenu) {
|
||||
contextMenu.showForButton(root, appData, 65, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
if (appData && appData.appId) {
|
||||
@@ -245,11 +315,14 @@ Item {
|
||||
"comment": desktopEntry.comment || ""
|
||||
})
|
||||
}
|
||||
desktopEntry.execute()
|
||||
SessionService.launchDesktopEntry(desktopEntry)
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (contextMenu) {
|
||||
contextMenu.showForButton(root, appData, 40)
|
||||
if (contextMenu && appData) {
|
||||
console.log("Right-clicked on app:", appData.appId, "type:", appData.type, "windowCount:", appData.windowCount || 0)
|
||||
contextMenu.showForButton(root, appData, 40, false)
|
||||
} else {
|
||||
console.warn("No context menu or appData available")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,31 +395,54 @@ Item {
|
||||
}
|
||||
|
||||
// Indicator for running/focused state
|
||||
Rectangle {
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: -2
|
||||
width: 8
|
||||
height: 2
|
||||
radius: 1
|
||||
visible: appData && (appData.isRunning || appData.type === "window")
|
||||
color: {
|
||||
if (!appData) {
|
||||
return "transparent"
|
||||
spacing: 2
|
||||
visible: appData && (appData.isRunning || appData.type === "window" || (appData.type === "grouped" && appData.windowCount > 0))
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
if (!appData) return 0
|
||||
if (appData.type === "grouped") {
|
||||
return Math.min(appData.windowCount, 4)
|
||||
} else if (appData.type === "window" || appData.isRunning) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
if (isWindowFocused) {
|
||||
return Theme.primary
|
||||
}
|
||||
Rectangle {
|
||||
width: appData && appData.type === "grouped" && appData.windowCount > 1 ? 4 : 8
|
||||
height: 2
|
||||
radius: 1
|
||||
color: {
|
||||
if (!appData) {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
if (appData.isRunning || appData.type === "window") {
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
}
|
||||
if (appData.type !== "grouped" || appData.windowCount === 1) {
|
||||
if (isWindowFocused) {
|
||||
return Theme.primary
|
||||
}
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
}
|
||||
|
||||
return "transparent"
|
||||
if (appData.type === "grouped" && appData.windowCount > 1) {
|
||||
const groupToplevels = getGroupedToplevels()
|
||||
if (index < groupToplevels.length && groupToplevels[index].activated) {
|
||||
return Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
transform: Translate {
|
||||
id: translateY
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ Item {
|
||||
property var contextMenu: null
|
||||
property bool requestDockShow: false
|
||||
property int pinnedAppCount: 0
|
||||
property bool groupByApp: false
|
||||
|
||||
implicitWidth: row.width
|
||||
implicitHeight: row.height
|
||||
@@ -50,53 +51,134 @@ Item {
|
||||
|
||||
const items = []
|
||||
const pinnedApps = [...(SessionData.pinnedApps || [])]
|
||||
|
||||
pinnedApps.forEach(appId => {
|
||||
items.push({
|
||||
"type": "pinned",
|
||||
"appId": appId,
|
||||
"windowId": -1,
|
||||
"windowTitle": "",
|
||||
"workspaceId": -1,
|
||||
"isPinned": true,
|
||||
"isRunning": false
|
||||
})
|
||||
})
|
||||
|
||||
root.pinnedAppCount = pinnedApps.length
|
||||
|
||||
const sortedToplevels = CompositorService.sortedToplevels
|
||||
|
||||
if (pinnedApps.length > 0 && sortedToplevels.length > 0) {
|
||||
items.push({
|
||||
"type": "separator",
|
||||
"appId": "__SEPARATOR__",
|
||||
"windowId": -1,
|
||||
"windowTitle": "",
|
||||
"workspaceId": -1,
|
||||
"isPinned": false,
|
||||
"isRunning": false,
|
||||
"isFocused": false
|
||||
})
|
||||
if (root.groupByApp) {
|
||||
// Group windows by appId
|
||||
const appGroups = new Map()
|
||||
|
||||
// Add pinned apps first (even if they have no windows)
|
||||
pinnedApps.forEach(appId => {
|
||||
appGroups.set(appId, {
|
||||
appId: appId,
|
||||
isPinned: true,
|
||||
windows: []
|
||||
})
|
||||
})
|
||||
|
||||
// Group all running windows by appId
|
||||
sortedToplevels.forEach((toplevel, index) => {
|
||||
const appId = toplevel.appId || "unknown"
|
||||
if (!appGroups.has(appId)) {
|
||||
appGroups.set(appId, {
|
||||
appId: appId,
|
||||
isPinned: false,
|
||||
windows: []
|
||||
})
|
||||
}
|
||||
const title = toplevel.title || "(Unnamed)"
|
||||
const truncatedTitle = title.length > 50 ? title.substring(0, 47) + "..." : title
|
||||
const uniqueId = toplevel.title + "|" + (toplevel.appId || "") + "|" + index
|
||||
|
||||
appGroups.get(appId).windows.push({
|
||||
windowId: index,
|
||||
windowTitle: truncatedTitle,
|
||||
uniqueId: uniqueId
|
||||
})
|
||||
})
|
||||
|
||||
// Sort groups: pinned first, then unpinned
|
||||
const pinnedGroups = []
|
||||
const unpinnedGroups = []
|
||||
|
||||
Array.from(appGroups.entries()).forEach(([appId, group]) => {
|
||||
// For grouped apps, just show the first window info but track all windows
|
||||
const firstWindow = group.windows.length > 0 ? group.windows[0] : null
|
||||
|
||||
const item = {
|
||||
"type": "grouped",
|
||||
"appId": appId,
|
||||
"windowId": firstWindow ? firstWindow.windowId : -1,
|
||||
"windowTitle": firstWindow ? firstWindow.windowTitle : "",
|
||||
"workspaceId": -1,
|
||||
"isPinned": group.isPinned,
|
||||
"isRunning": group.windows.length > 0,
|
||||
"windowCount": group.windows.length,
|
||||
"uniqueId": firstWindow ? firstWindow.uniqueId : "",
|
||||
"allWindows": group.windows
|
||||
}
|
||||
|
||||
if (group.isPinned) {
|
||||
pinnedGroups.push(item)
|
||||
} else {
|
||||
unpinnedGroups.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
// Add items in order
|
||||
pinnedGroups.forEach(item => items.push(item))
|
||||
|
||||
// Add separator if needed
|
||||
if (pinnedGroups.length > 0 && unpinnedGroups.length > 0) {
|
||||
items.push({
|
||||
"type": "separator",
|
||||
"appId": "__SEPARATOR__",
|
||||
"windowId": -1,
|
||||
"windowTitle": "",
|
||||
"workspaceId": -1,
|
||||
"isPinned": false,
|
||||
"isRunning": false
|
||||
})
|
||||
}
|
||||
|
||||
unpinnedGroups.forEach(item => items.push(item))
|
||||
root.pinnedAppCount = pinnedGroups.length
|
||||
} else {
|
||||
pinnedApps.forEach(appId => {
|
||||
items.push({
|
||||
"type": "pinned",
|
||||
"appId": appId,
|
||||
"windowId": -1,
|
||||
"windowTitle": "",
|
||||
"workspaceId": -1,
|
||||
"isPinned": true,
|
||||
"isRunning": false
|
||||
})
|
||||
})
|
||||
|
||||
root.pinnedAppCount = pinnedApps.length
|
||||
|
||||
if (pinnedApps.length > 0 && sortedToplevels.length > 0) {
|
||||
items.push({
|
||||
"type": "separator",
|
||||
"appId": "__SEPARATOR__",
|
||||
"windowId": -1,
|
||||
"windowTitle": "",
|
||||
"workspaceId": -1,
|
||||
"isPinned": false,
|
||||
"isRunning": false,
|
||||
"isFocused": false
|
||||
})
|
||||
}
|
||||
|
||||
sortedToplevels.forEach((toplevel, index) => {
|
||||
const title = toplevel.title || "(Unnamed)"
|
||||
const truncatedTitle = title.length > 50 ? title.substring(0, 47) + "..." : title
|
||||
const uniqueId = toplevel.title + "|" + (toplevel.appId || "") + "|" + index
|
||||
|
||||
items.push({
|
||||
"type": "window",
|
||||
"appId": toplevel.appId,
|
||||
"windowId": index,
|
||||
"windowTitle": truncatedTitle,
|
||||
"workspaceId": -1,
|
||||
"isPinned": false,
|
||||
"isRunning": true,
|
||||
"uniqueId": uniqueId
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
sortedToplevels.forEach((toplevel, index) => {
|
||||
const title = toplevel.title || "(Unnamed)"
|
||||
const truncatedTitle = title.length > 50 ? title.substring(0, 47) + "..." : title
|
||||
const uniqueId = toplevel.title + "|" + (toplevel.appId || "") + "|" + index
|
||||
|
||||
items.push({
|
||||
"type": "window",
|
||||
"appId": toplevel.appId,
|
||||
"windowId": index,
|
||||
"windowTitle": truncatedTitle,
|
||||
"workspaceId": -1,
|
||||
"isPinned": false,
|
||||
"isRunning": true,
|
||||
"uniqueId": uniqueId
|
||||
})
|
||||
})
|
||||
|
||||
items.forEach(item => append(item))
|
||||
}
|
||||
}
|
||||
@@ -131,7 +213,7 @@ Item {
|
||||
index: model.index
|
||||
|
||||
// Override tooltip for windows to show window title
|
||||
showWindowTitle: model.type === "window"
|
||||
showWindowTitle: model.type === "window" || model.type === "grouped"
|
||||
windowTitle: model.windowTitle || ""
|
||||
}
|
||||
}
|
||||
@@ -151,4 +233,8 @@ Item {
|
||||
dockModel.updateModel()
|
||||
}
|
||||
}
|
||||
|
||||
onGroupByAppChanged: {
|
||||
dockModel.updateModel()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,13 @@ PanelWindow {
|
||||
property var anchorItem: null
|
||||
property real dockVisibleHeight: 40
|
||||
property int margin: 10
|
||||
property bool hidePin: false
|
||||
|
||||
function showForButton(button, data, dockHeight) {
|
||||
function showForButton(button, data, dockHeight, hidePinOption) {
|
||||
anchorItem = button
|
||||
appData = data
|
||||
dockVisibleHeight = dockHeight || 40
|
||||
hidePin = hidePinOption || false
|
||||
|
||||
const dockWindow = button.Window.window
|
||||
if (dockWindow) {
|
||||
@@ -144,7 +146,102 @@ PanelWindow {
|
||||
anchors.topMargin: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
// Window list for grouped apps
|
||||
Repeater {
|
||||
model: {
|
||||
if (!root.appData || root.appData.type !== "grouped") return []
|
||||
|
||||
const toplevels = []
|
||||
const allToplevels = ToplevelManager.toplevels.values
|
||||
for (let i = 0; i < allToplevels.length; i++) {
|
||||
const toplevel = allToplevels[i]
|
||||
if (toplevel.appId === root.appData.appId) {
|
||||
toplevels.push(toplevel)
|
||||
}
|
||||
}
|
||||
return toplevels
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.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: closeButton.left
|
||||
anchors.rightMargin: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: (modelData && modelData.title) ? modelData.title : "(Unnamed)"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: closeButton
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
color: closeMouseArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.2) : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "close"
|
||||
size: 12
|
||||
color: closeMouseArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData && modelData.close) {
|
||||
modelData.close()
|
||||
}
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: windowArea
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 24
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData && modelData.activate) {
|
||||
modelData.activate()
|
||||
}
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: {
|
||||
if (!root.appData) return false
|
||||
if (root.appData.type !== "grouped") return false
|
||||
return root.appData.windowCount > 0
|
||||
}
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: !root.hidePin
|
||||
width: parent.width
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
@@ -198,7 +295,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: root.appData && root.appData.type === "window"
|
||||
visible: root.appData && (root.appData.type === "window" || (root.appData.type === "grouped" && root.appData.windowCount > 0))
|
||||
width: parent.width
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
@@ -210,7 +307,12 @@ PanelWindow {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Close Window"
|
||||
text: {
|
||||
if (root.appData && root.appData.type === "grouped") {
|
||||
return "Close All Windows"
|
||||
}
|
||||
return "Close Window"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
@@ -224,8 +326,26 @@ PanelWindow {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.appData && root.appData.toplevelObject) {
|
||||
root.appData.toplevelObject.close()
|
||||
const sortedToplevels = CompositorService.sortedToplevels
|
||||
if (root.appData && root.appData.type === "window") {
|
||||
// Find and close the specific window
|
||||
for (var i = 0; i < sortedToplevels.length; i++) {
|
||||
const toplevel = sortedToplevels[i]
|
||||
const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i
|
||||
if (checkId === root.appData.uniqueId) {
|
||||
toplevel.close()
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if (root.appData && root.appData.type === "grouped") {
|
||||
// Close all windows for this app
|
||||
const allToplevels = ToplevelManager.toplevels.values
|
||||
for (let i = 0; i < allToplevels.length; i++) {
|
||||
const toplevel = allToplevels[i]
|
||||
if (toplevel.appId === root.appData.appId) {
|
||||
toplevel.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
root.close()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: Theme.widgetBackground()
|
||||
color: Theme.widgetBackground
|
||||
|
||||
property double rowSpacing: 0.01 * width // horizontal spacing between keyboard
|
||||
property double columnSpacing: 0.02 * height // vertical spacing between keyboard
|
||||
|
||||
@@ -18,6 +18,18 @@ Item {
|
||||
getSessionPath.running = true
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
lockStateMonitor.running = false
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: IdleService
|
||||
function onLockRequested() {
|
||||
console.log("Lock: Received lock request from IdleService")
|
||||
activate()
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getSessionPath
|
||||
command: ["gdbus", "call", "--system", "--dest", "org.freedesktop.login1", "--object-path", "/org/freedesktop/login1", "--method", "org.freedesktop.login1.Manager.GetSession", sid]
|
||||
@@ -53,7 +65,6 @@ Item {
|
||||
onStreamFinished: {
|
||||
if (text.includes("true")) {
|
||||
console.log("Session is locked on startup, activating lock screen")
|
||||
LockScreenService.resetState()
|
||||
loader.activeAsync = true
|
||||
}
|
||||
}
|
||||
@@ -68,27 +79,39 @@ Item {
|
||||
|
||||
Process {
|
||||
id: lockStateMonitor
|
||||
command: root.sessionPath ? ["gdbus", "monitor", "--system", "--dest", "org.freedesktop.login1", "--object-path", root.sessionPath] : []
|
||||
command: root.sessionPath ? ["gdbus", "monitor", "--system", "--dest", "org.freedesktop.login1"] : []
|
||||
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()
|
||||
if (line.includes(root.sessionPath)) {
|
||||
if (line.includes("org.freedesktop.login1.Session.Lock")) {
|
||||
console.log("login1: Lock signal received -> show lock")
|
||||
loader.activeAsync = true
|
||||
return
|
||||
}
|
||||
if (line.includes("org.freedesktop.login1.Session.Unlock")) {
|
||||
console.log("login1: Unlock signal received -> hide lock")
|
||||
loader.active = false
|
||||
return
|
||||
}
|
||||
if (line.includes("LockedHint") && line.includes("true")) {
|
||||
console.log("login1: LockedHint=true -> show lock")
|
||||
loader.activeAsync = true
|
||||
return
|
||||
}
|
||||
if (line.includes("LockedHint") && line.includes("false")) {
|
||||
console.log("login1: LockedHint=false -> hide lock")
|
||||
loader.active = false
|
||||
return
|
||||
}
|
||||
}
|
||||
if (line.includes("PrepareForSleep") &&
|
||||
line.includes("true") &&
|
||||
SessionData.lockBeforeSuspend) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +160,6 @@ Item {
|
||||
|
||||
function lock() {
|
||||
console.log("Lock screen requested via IPC")
|
||||
LockScreenService.resetState()
|
||||
loader.activeAsync = true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
@@ -5,6 +6,7 @@ import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Pam
|
||||
import Quickshell.Services.Mpris
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -15,6 +17,9 @@ Item {
|
||||
property string passwordBuffer: ""
|
||||
property bool demoMode: false
|
||||
property string screenName: ""
|
||||
property bool unlocking: false
|
||||
property string pamState: ""
|
||||
property string randomFact: ""
|
||||
|
||||
signal unlockRequested
|
||||
|
||||
@@ -39,9 +44,15 @@ Item {
|
||||
powerDialogVisible = false
|
||||
}
|
||||
|
||||
property var facts: ["A photon takes 100,000 to 200,000 years bouncing through the Sun's dense core, then races to Earth in just 8 minutes 20 seconds.", "A teaspoon of neutron star matter would weigh a billion metric tons here on Earth.", "Right now, 100 trillion solar neutrinos are passing through your body every second.", "The Sun converts 4 million metric tons of matter into pure energy every second—enough to power Earth for 500,000 years.", "The universe still glows with leftover heat from the Big Bang—just 2.7 degrees above absolute zero.", "There's a nebula out there that's actually colder than empty space itself.", "We've detected black holes crashing together by measuring spacetime stretch by less than 1/10,000th the width of a proton.", "Fast radio bursts can release more energy in 5 milliseconds than our Sun produces in 3 days.", "Our galaxy might be crawling with billions of rogue planets drifting alone in the dark.", "Distant galaxies can move away from us faster than light because space itself is stretching.", "The edge of what we can see is 46.5 billion light-years away, even though the universe is only 13.8 billion years old.", "The universe is mostly invisible: 5% regular matter, 27% dark matter, 68% dark energy.", "A day on Venus lasts longer than its entire year around the Sun.", "On Mercury, the time between sunrises is 176 Earth days long.", "In about 4.5 billion years, our galaxy will smash into Andromeda.", "Most of the gold in your jewelry was forged when neutron stars collided somewhere in space.", "PSR J1748-2446ad, the fastest spinning star, rotates 716 times per second—its equator moves at 24% the speed of light.", "Cosmic rays create particles that shouldn't make it to Earth's surface, but time dilation lets them sneak through.", "Jupiter's magnetic field is so huge that if we could see it, it would look bigger than the Moon in our sky.", "Interstellar space is so empty it's like a cube 32 kilometers wide containing just a single grain of sand.", "Voyager 1 is 24 billion kilometers away but won't leave the Sun's gravitational influence for another 30,000 years.", "Counting to a billion at one number per second would take over 31 years.", "Space is so vast, even speeding at light-speed, you'd never return past the cosmic horizon.", "Astronauts on the ISS age about 0.01 seconds less each year than people on Earth.", "Sagittarius B2, a dust cloud near our galaxy's center, contains ethyl formate—the compound that gives raspberries their flavor and rum its smell.", "Beyond 16 billion light-years, the cosmic event horizon marks where space expands too fast for light to ever reach us again.", "Even at light-speed, you'd never catch up to most galaxies—space expands faster.", "Only around 5% of galaxies are ever reachable—even at light-speed.", "If the Sun vanished, we'd still orbit it for 8 minutes before drifting away.", "If a planet 65 million light-years away looked at Earth now, it'd see dinosaurs.", "Our oldest radio signals will reach the Milky Way's center in 26,000 years.", "Every atom in your body heavier than hydrogen was forged in the nuclear furnace of a dying star.", "The Moon moves 3.8 centimeters farther from Earth every year.", "The universe creates 275 million new stars every single day.", "Jupiter's Great Red Spot is a storm twice the size of Earth that has been raging for at least 350 years.", "If you watched someone fall into a black hole, they'd appear frozen at the event horizon forever—time effectively stops from your perspective.", "The Boötes Supervoid is a cosmic desert 1.8 billion light-years across with 60% fewer galaxies than it should have."]
|
||||
|
||||
function pickRandomFact() {
|
||||
randomFact = facts[Math.floor(Math.random() * facts.length)]
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (demoMode) {
|
||||
LockScreenService.pickRandomFact()
|
||||
pickRandomFact()
|
||||
}
|
||||
|
||||
WeatherService.addRef()
|
||||
@@ -49,7 +60,7 @@ Item {
|
||||
}
|
||||
onDemoModeChanged: {
|
||||
if (demoMode) {
|
||||
LockScreenService.pickRandomFact()
|
||||
pickRandomFact()
|
||||
}
|
||||
}
|
||||
Component.onDestruction: {
|
||||
@@ -75,6 +86,12 @@ Item {
|
||||
anchors.fill: parent
|
||||
source: {
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName)
|
||||
if (screenName && currentWallpaper && currentWallpaper.startsWith("we:")) {
|
||||
const cacheHome = StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()
|
||||
const baseDir = Paths.strip(cacheHome)
|
||||
const screenshotPath = baseDir + "/dankshell/we_screenshots" + "/" + currentWallpaper.substring(3) + ".jpg"
|
||||
return screenshotPath
|
||||
}
|
||||
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : ""
|
||||
}
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
@@ -295,8 +312,9 @@ Item {
|
||||
return margin
|
||||
}
|
||||
opacity: 0
|
||||
focus: !demoMode
|
||||
focus: true
|
||||
enabled: !demoMode
|
||||
activeFocusOnTab: !demoMode
|
||||
echoMode: parent.showPassword ? TextInput.Normal : TextInput.Password
|
||||
onTextChanged: {
|
||||
if (!demoMode) {
|
||||
@@ -304,7 +322,7 @@ Item {
|
||||
}
|
||||
}
|
||||
onAccepted: {
|
||||
if (!demoMode && root.passwordBuffer.length > 0 && !pam.active) {
|
||||
if (!demoMode && !pam.active) {
|
||||
console.log("Enter pressed, starting PAM authentication")
|
||||
pam.start()
|
||||
}
|
||||
@@ -321,12 +339,36 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: focusTimer
|
||||
Component.onCompleted: {
|
||||
if (!demoMode) {
|
||||
forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
interval: 100
|
||||
running: !demoMode
|
||||
onTriggered: passwordField.forceActiveFocus()
|
||||
onVisibleChanged: {
|
||||
if (visible && !demoMode) {
|
||||
forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (!activeFocus && !demoMode && visible && passwordField) {
|
||||
Qt.callLater(() => {
|
||||
if (passwordField && passwordField.forceActiveFocus) {
|
||||
passwordField.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onEnabledChanged: {
|
||||
if (enabled && !demoMode && visible && passwordField) {
|
||||
Qt.callLater(() => {
|
||||
if (passwordField && passwordField.forceActiveFocus) {
|
||||
passwordField.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,7 +390,7 @@ Item {
|
||||
if (demoMode) {
|
||||
return ""
|
||||
}
|
||||
if (LockScreenService.unlocking) {
|
||||
if (root.unlocking) {
|
||||
return "Unlocking..."
|
||||
}
|
||||
if (pam.active) {
|
||||
@@ -356,7 +398,7 @@ Item {
|
||||
}
|
||||
return "Password..."
|
||||
}
|
||||
color: LockScreenService.unlocking ? Theme.primary : (pam.active ? Theme.primary : Theme.outline)
|
||||
color: root.unlocking ? Theme.primary : (pam.active ? Theme.primary : Theme.outline)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
opacity: (demoMode || root.passwordBuffer.length === 0) ? 1 : 0
|
||||
|
||||
@@ -411,7 +453,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: parent.showPassword ? "visibility_off" : "visibility"
|
||||
buttonSize: 32
|
||||
visible: !demoMode && root.passwordBuffer.length > 0 && !pam.active && !LockScreenService.unlocking
|
||||
visible: !demoMode && root.passwordBuffer.length > 0 && !pam.active && !root.unlocking
|
||||
enabled: visible
|
||||
onClicked: parent.showPassword = !parent.showPassword
|
||||
}
|
||||
@@ -423,7 +465,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "keyboard"
|
||||
buttonSize: 32
|
||||
visible: !demoMode && !pam.active && !LockScreenService.unlocking
|
||||
visible: !demoMode && !pam.active && !root.unlocking
|
||||
enabled: visible
|
||||
onClicked: {
|
||||
if (keyboardController.isKeyboardActive) {
|
||||
@@ -444,17 +486,17 @@ Item {
|
||||
height: 24
|
||||
radius: 12
|
||||
color: "transparent"
|
||||
visible: !demoMode && (pam.active || LockScreenService.unlocking)
|
||||
visible: !demoMode && (pam.active || root.unlocking)
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "check_circle"
|
||||
size: 20
|
||||
color: Theme.primary
|
||||
visible: LockScreenService.unlocking
|
||||
visible: root.unlocking
|
||||
|
||||
SequentialAnimation on scale {
|
||||
running: LockScreenService.unlocking
|
||||
running: root.unlocking
|
||||
|
||||
NumberAnimation {
|
||||
from: 0
|
||||
@@ -476,7 +518,7 @@ Item {
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: pam.active && !LockScreenService.unlocking
|
||||
visible: pam.active && !root.unlocking
|
||||
|
||||
Rectangle {
|
||||
width: 20
|
||||
@@ -506,7 +548,7 @@ Item {
|
||||
}
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: pam.active && !LockScreenService.unlocking
|
||||
running: pam.active && !root.unlocking
|
||||
loops: Animation.Infinite
|
||||
duration: Anims.durLong
|
||||
from: 0
|
||||
@@ -524,7 +566,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "keyboard_return"
|
||||
buttonSize: 36
|
||||
visible: (demoMode || (root.passwordBuffer.length > 0 && !pam.active && !LockScreenService.unlocking))
|
||||
visible: (demoMode || (!pam.active && !root.unlocking))
|
||||
enabled: !demoMode
|
||||
onClicked: {
|
||||
if (!demoMode) {
|
||||
@@ -552,15 +594,15 @@ Item {
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: LockScreenService.pamState ? 20 : 0
|
||||
Layout.preferredHeight: root.pamState ? 20 : 0
|
||||
text: {
|
||||
if (LockScreenService.pamState === "error") {
|
||||
if (root.pamState === "error") {
|
||||
return "Authentication error - try again"
|
||||
}
|
||||
if (LockScreenService.pamState === "max") {
|
||||
if (root.pamState === "max") {
|
||||
return "Too many attempts - locked out"
|
||||
}
|
||||
if (LockScreenService.pamState === "fail") {
|
||||
if (root.pamState === "fail") {
|
||||
return "Incorrect password - try again"
|
||||
}
|
||||
return ""
|
||||
@@ -568,8 +610,8 @@ Item {
|
||||
color: Theme.error
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: LockScreenService.pamState !== ""
|
||||
opacity: LockScreenService.pamState !== "" ? 1 : 0
|
||||
visible: root.pamState !== ""
|
||||
opacity: root.pamState !== "" ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
@@ -604,6 +646,178 @@ Item {
|
||||
anchors.margins: Theme.spacingXL
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
visible: MprisController.activePlayer
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Item {
|
||||
width: 20
|
||||
height: Theme.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Loader {
|
||||
active: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing
|
||||
|
||||
sourceComponent: Component {
|
||||
Ref {
|
||||
service: CavaService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
running: !CavaService.cavaAvailable && MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing
|
||||
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 (MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing && 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: "white"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standardDecel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const player = MprisController.activePlayer
|
||||
if (!player?.trackTitle) return ""
|
||||
const title = player.trackTitle
|
||||
const artist = player.trackArtist || ""
|
||||
return artist ? title + " • " + artist : title
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: "white"
|
||||
opacity: 0.9
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
width: Math.min(implicitWidth, 400)
|
||||
wrapMode: Text.NoWrap
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: prevArea.containsMouse ? Qt.rgba(255, 255, 255, 0.2) : "transparent"
|
||||
visible: MprisController.activePlayer
|
||||
opacity: (MprisController.activePlayer?.canGoPrevious ?? false) ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_previous"
|
||||
size: 12
|
||||
color: "white"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: prevArea
|
||||
anchors.fill: parent
|
||||
enabled: MprisController.activePlayer?.canGoPrevious ?? false
|
||||
hoverEnabled: enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: MprisController.activePlayer?.previous()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? Qt.rgba(255, 255, 255, 0.9) : Qt.rgba(255, 255, 255, 0.2)
|
||||
visible: MprisController.activePlayer
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
||||
size: 14
|
||||
color: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? "black" : "white"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: MprisController.activePlayer
|
||||
hoverEnabled: enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: MprisController.activePlayer?.togglePlaying()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: nextArea.containsMouse ? Qt.rgba(255, 255, 255, 0.2) : "transparent"
|
||||
visible: MprisController.activePlayer
|
||||
opacity: (MprisController.activePlayer?.canGoNext ?? false) ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_next"
|
||||
size: 12
|
||||
color: "white"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nextArea
|
||||
anchors.fill: parent
|
||||
enabled: MprisController.activePlayer?.canGoNext ?? false
|
||||
hoverEnabled: enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: MprisController.activePlayer?.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: 24
|
||||
color: Qt.rgba(255, 255, 255, 0.2)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: MprisController.activePlayer && WeatherService.weather.available
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 6
|
||||
visible: WeatherService.weather.available
|
||||
@@ -850,13 +1064,13 @@ Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.margins: Theme.spacingL
|
||||
width: Math.min(parent.width - Theme.spacingXL * 2, implicitWidth)
|
||||
text: LockScreenService.randomFact
|
||||
text: root.randomFact
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: "white"
|
||||
opacity: 0.8
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.NoWrap
|
||||
visible: LockScreenService.randomFact !== ""
|
||||
visible: root.randomFact !== ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -889,7 +1103,7 @@ Item {
|
||||
console.log("PAM authentication completed with result:", res)
|
||||
if (res === PamResult.Success) {
|
||||
console.log("Authentication successful, unlocking")
|
||||
LockScreenService.setUnlocking(true)
|
||||
root.unlocking = true
|
||||
passwordField.text = ""
|
||||
root.passwordBuffer = ""
|
||||
root.unlockRequested()
|
||||
@@ -897,11 +1111,11 @@ Item {
|
||||
}
|
||||
console.log("Authentication failed:", res)
|
||||
if (res === PamResult.Error)
|
||||
LockScreenService.setPamState("error")
|
||||
root.pamState = "error"
|
||||
else if (res === PamResult.MaxTries)
|
||||
LockScreenService.setPamState("max")
|
||||
root.pamState = "max"
|
||||
else if (res === PamResult.Failed)
|
||||
LockScreenService.setPamState("fail")
|
||||
root.pamState = "fail"
|
||||
placeholderDelay.restart()
|
||||
}
|
||||
}
|
||||
@@ -910,7 +1124,7 @@ Item {
|
||||
id: placeholderDelay
|
||||
|
||||
interval: 4000
|
||||
onTriggered: LockScreenService.setPamState("")
|
||||
onTriggered: root.pamState = ""
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
505
Modules/Notepad/Notepad.qml
Normal file
505
Modules/Notepad/Notepad.qml
Normal file
@@ -0,0 +1,505 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtCore
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Modals.FileBrowser
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool fileDialogOpen: false
|
||||
property string currentFileName: ""
|
||||
property url currentFileUrl
|
||||
property bool confirmationDialogOpen: false
|
||||
property string pendingAction: ""
|
||||
property url pendingFileUrl
|
||||
property string lastSavedFileContent: ""
|
||||
property var currentTab: NotepadStorageService.tabs.length > NotepadStorageService.currentTabIndex ? NotepadStorageService.tabs[NotepadStorageService.currentTabIndex] : null
|
||||
property bool showSettingsMenu: false
|
||||
property string pendingSaveContent: ""
|
||||
|
||||
signal hideRequested()
|
||||
|
||||
function hasUnsavedChanges() {
|
||||
return textEditor.hasUnsavedChanges()
|
||||
}
|
||||
|
||||
function hasUnsavedTemporaryContent() {
|
||||
return hasUnsavedChanges()
|
||||
}
|
||||
|
||||
function createNewTab() {
|
||||
performCreateNewTab()
|
||||
}
|
||||
|
||||
function performCreateNewTab() {
|
||||
NotepadStorageService.createNewTab()
|
||||
textEditor.text = ""
|
||||
textEditor.lastSavedContent = ""
|
||||
textEditor.contentLoaded = true
|
||||
textEditor.textArea.forceActiveFocus()
|
||||
}
|
||||
|
||||
function closeTab(tabIndex) {
|
||||
if (tabIndex === NotepadStorageService.currentTabIndex && hasUnsavedChanges()) {
|
||||
root.pendingAction = "close_tab_" + tabIndex
|
||||
root.confirmationDialogOpen = true
|
||||
confirmationDialog.open()
|
||||
} else {
|
||||
performCloseTab(tabIndex)
|
||||
}
|
||||
}
|
||||
|
||||
function performCloseTab(tabIndex) {
|
||||
NotepadStorageService.closeTab(tabIndex)
|
||||
Qt.callLater(() => {
|
||||
textEditor.loadCurrentTabContent()
|
||||
})
|
||||
}
|
||||
|
||||
function switchToTab(tabIndex) {
|
||||
if (tabIndex < 0 || tabIndex >= NotepadStorageService.tabs.length) return
|
||||
|
||||
if (textEditor.contentLoaded) {
|
||||
textEditor.autoSaveToSession()
|
||||
}
|
||||
|
||||
NotepadStorageService.switchToTab(tabIndex)
|
||||
Qt.callLater(() => {
|
||||
textEditor.loadCurrentTabContent()
|
||||
if (currentTab) {
|
||||
root.currentFileName = currentTab.fileName || ""
|
||||
root.currentFileUrl = currentTab.fileUrl || ""
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function saveToFile(fileUrl) {
|
||||
if (!currentTab) return
|
||||
|
||||
var content = textEditor.text
|
||||
var filePath = fileUrl.toString().replace(/^file:\/\//, '')
|
||||
|
||||
saveFileView.path = ""
|
||||
pendingSaveContent = content
|
||||
saveFileView.path = filePath
|
||||
|
||||
Qt.callLater(() => {
|
||||
saveFileView.setText(pendingSaveContent)
|
||||
})
|
||||
}
|
||||
|
||||
function loadFromFile(fileUrl) {
|
||||
if (hasUnsavedTemporaryContent()) {
|
||||
root.pendingFileUrl = fileUrl
|
||||
root.pendingAction = "load_file"
|
||||
root.confirmationDialogOpen = true
|
||||
confirmationDialog.open()
|
||||
} else {
|
||||
performLoadFromFile(fileUrl)
|
||||
}
|
||||
}
|
||||
|
||||
function performLoadFromFile(fileUrl) {
|
||||
const filePath = fileUrl.toString().replace(/^file:\/\//, '')
|
||||
const fileName = filePath.split('/').pop()
|
||||
|
||||
loadFileView.path = ""
|
||||
loadFileView.path = filePath
|
||||
|
||||
if (loadFileView.waitForJob()) {
|
||||
Qt.callLater(() => {
|
||||
var content = loadFileView.text()
|
||||
if (currentTab && content !== undefined && content !== null) {
|
||||
textEditor.text = content
|
||||
textEditor.lastSavedContent = content
|
||||
textEditor.contentLoaded = true
|
||||
root.lastSavedFileContent = content
|
||||
|
||||
NotepadStorageService.updateTabMetadata(NotepadStorageService.currentTabIndex, {
|
||||
title: fileName,
|
||||
filePath: filePath,
|
||||
isTemporary: false
|
||||
})
|
||||
|
||||
root.currentFileName = fileName
|
||||
root.currentFileUrl = fileUrl
|
||||
textEditor.saveCurrentTabContent()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
NotepadTabs {
|
||||
id: tabBar
|
||||
width: parent.width
|
||||
contentLoaded: textEditor.contentLoaded
|
||||
|
||||
onTabSwitched: (tabIndex) => {
|
||||
switchToTab(tabIndex)
|
||||
}
|
||||
|
||||
onTabClosed: (tabIndex) => {
|
||||
closeTab(tabIndex)
|
||||
}
|
||||
|
||||
onNewTabRequested: {
|
||||
createNewTab()
|
||||
}
|
||||
}
|
||||
|
||||
NotepadTextEditor {
|
||||
id: textEditor
|
||||
width: parent.width
|
||||
height: parent.height - tabBar.height - Theme.spacingM * 2
|
||||
|
||||
onSaveRequested: {
|
||||
if (currentTab && !currentTab.isTemporary && currentTab.filePath) {
|
||||
var fileUrl = "file://" + currentTab.filePath
|
||||
saveToFile(fileUrl)
|
||||
} else {
|
||||
root.fileDialogOpen = true
|
||||
saveBrowser.open()
|
||||
}
|
||||
}
|
||||
|
||||
onOpenRequested: {
|
||||
if (hasUnsavedChanges()) {
|
||||
root.pendingAction = "open"
|
||||
root.confirmationDialogOpen = true
|
||||
confirmationDialog.open()
|
||||
} else {
|
||||
root.fileDialogOpen = true
|
||||
loadBrowser.open()
|
||||
}
|
||||
}
|
||||
|
||||
onNewRequested: {
|
||||
if (hasUnsavedChanges()) {
|
||||
root.pendingAction = "new"
|
||||
root.confirmationDialogOpen = true
|
||||
confirmationDialog.open()
|
||||
} else {
|
||||
createNewTab()
|
||||
}
|
||||
}
|
||||
|
||||
onEscapePressed: {
|
||||
root.hideRequested()
|
||||
}
|
||||
|
||||
onSettingsRequested: {
|
||||
showSettingsMenu = !showSettingsMenu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NotepadSettings {
|
||||
id: notepadSettings
|
||||
anchors.fill: parent
|
||||
isVisible: showSettingsMenu
|
||||
onSettingsRequested: showSettingsMenu = !showSettingsMenu
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: saveFileView
|
||||
blockWrites: true
|
||||
preload: false
|
||||
atomicWrites: true
|
||||
printErrors: true
|
||||
|
||||
onSaved: {
|
||||
if (currentTab && saveFileView.path && pendingSaveContent) {
|
||||
NotepadStorageService.updateTabMetadata(NotepadStorageService.currentTabIndex, {
|
||||
hasUnsavedChanges: false,
|
||||
lastSavedContent: pendingSaveContent
|
||||
})
|
||||
root.lastSavedFileContent = pendingSaveContent
|
||||
pendingSaveContent = ""
|
||||
}
|
||||
}
|
||||
|
||||
onSaveFailed: (error) => {
|
||||
pendingSaveContent = ""
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: loadFileView
|
||||
blockLoading: true
|
||||
preload: true
|
||||
atomicWrites: true
|
||||
printErrors: true
|
||||
|
||||
onLoadFailed: (error) => {
|
||||
}
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: saveBrowser
|
||||
|
||||
browserTitle: qsTr("Save Notepad File")
|
||||
browserIcon: "save"
|
||||
browserType: "notepad_save"
|
||||
fileExtensions: ["*.txt", "*.md", "*.*"]
|
||||
allowStacking: true
|
||||
saveMode: true
|
||||
defaultFileName: {
|
||||
if (currentTab && currentTab.title && currentTab.title !== "Untitled") {
|
||||
return currentTab.title
|
||||
} else if (currentTab && !currentTab.isTemporary && currentTab.filePath) {
|
||||
return currentTab.filePath.split('/').pop()
|
||||
} else {
|
||||
return "note.txt"
|
||||
}
|
||||
}
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
|
||||
onFileSelected: (path) => {
|
||||
root.fileDialogOpen = false
|
||||
const cleanPath = path.toString().replace(/^file:\/\//, '')
|
||||
const fileName = cleanPath.split('/').pop()
|
||||
const fileUrl = "file://" + cleanPath
|
||||
|
||||
root.currentFileName = fileName
|
||||
root.currentFileUrl = fileUrl
|
||||
|
||||
if (currentTab) {
|
||||
NotepadStorageService.saveTabAs(
|
||||
NotepadStorageService.currentTabIndex,
|
||||
cleanPath
|
||||
)
|
||||
}
|
||||
|
||||
saveToFile(fileUrl)
|
||||
|
||||
if (root.pendingAction === "new") {
|
||||
Qt.callLater(() => {
|
||||
createNewTab()
|
||||
})
|
||||
} else if (root.pendingAction === "open") {
|
||||
Qt.callLater(() => {
|
||||
root.fileDialogOpen = true
|
||||
loadBrowser.open()
|
||||
})
|
||||
} else if (root.pendingAction.startsWith("close_tab_")) {
|
||||
Qt.callLater(() => {
|
||||
var tabIndex = parseInt(root.pendingAction.split("_")[2])
|
||||
performCloseTab(tabIndex)
|
||||
})
|
||||
}
|
||||
root.pendingAction = ""
|
||||
|
||||
close()
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
root.fileDialogOpen = false
|
||||
}
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: loadBrowser
|
||||
|
||||
browserTitle: qsTr("Open Notepad File")
|
||||
browserIcon: "folder_open"
|
||||
browserType: "notepad_load"
|
||||
fileExtensions: ["*.txt", "*.md", "*.*"]
|
||||
allowStacking: true
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
|
||||
onFileSelected: (path) => {
|
||||
root.fileDialogOpen = false
|
||||
const cleanPath = path.toString().replace(/^file:\/\//, '')
|
||||
const fileName = cleanPath.split('/').pop()
|
||||
const fileUrl = "file://" + cleanPath
|
||||
|
||||
root.currentFileName = fileName
|
||||
root.currentFileUrl = fileUrl
|
||||
|
||||
loadFromFile(fileUrl)
|
||||
close()
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
root.fileDialogOpen = false
|
||||
}
|
||||
}
|
||||
|
||||
DankModal {
|
||||
id: confirmationDialog
|
||||
|
||||
width: 400
|
||||
height: 180
|
||||
shouldBeVisible: false
|
||||
allowStacking: true
|
||||
|
||||
onBackgroundClicked: {
|
||||
close()
|
||||
root.confirmationDialogOpen = false
|
||||
}
|
||||
|
||||
content: Component {
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
confirmationDialog.close()
|
||||
root.confirmationDialogOpen = false
|
||||
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: qsTr("Unsaved Changes")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.pendingAction === "new" ?
|
||||
qsTr("You have unsaved changes. Save before creating a new file?") :
|
||||
root.pendingAction.startsWith("close_tab_") ?
|
||||
qsTr("You have unsaved changes. Save before closing this tab?") :
|
||||
root.pendingAction === "load_file" || root.pendingAction === "open" ?
|
||||
qsTr("You have unsaved changes. Save before opening a file?") :
|
||||
qsTr("You have unsaved changes. Save before continuing?")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
confirmationDialog.close()
|
||||
root.confirmationDialogOpen = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: Math.max(80, discardText.contentWidth + Theme.spacingM * 2)
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: discardArea.containsMouse ? Theme.surfaceTextHover : "transparent"
|
||||
border.color: Theme.surfaceVariantAlpha
|
||||
border.width: 1
|
||||
|
||||
StyledText {
|
||||
id: discardText
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("Don't Save")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: discardArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
confirmationDialog.close()
|
||||
root.confirmationDialogOpen = false
|
||||
if (root.pendingAction === "new") {
|
||||
createNewTab()
|
||||
} else if (root.pendingAction === "open") {
|
||||
root.fileDialogOpen = true
|
||||
loadBrowser.open()
|
||||
} else if (root.pendingAction === "load_file") {
|
||||
performLoadFromFile(root.pendingFileUrl)
|
||||
} else if (root.pendingAction.startsWith("close_tab_")) {
|
||||
var tabIndex = parseInt(root.pendingAction.split("_")[2])
|
||||
performCloseTab(tabIndex)
|
||||
}
|
||||
root.pendingAction = ""
|
||||
root.pendingFileUrl = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Math.max(70, saveAsText.contentWidth + Theme.spacingM * 2)
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: saveAsArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||
|
||||
StyledText {
|
||||
id: saveAsText
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("Save")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.background
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: saveAsArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
confirmationDialog.close()
|
||||
root.confirmationDialogOpen = false
|
||||
root.fileDialogOpen = true
|
||||
saveBrowser.open()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
327
Modules/Notepad/NotepadSettings.qml
Normal file
327
Modules/Notepad/NotepadSettings.qml
Normal file
@@ -0,0 +1,327 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool isVisible: false
|
||||
property var cachedFontFamilies: []
|
||||
property var cachedMonoFamilies: []
|
||||
property bool fontsEnumerated: false
|
||||
|
||||
signal settingsRequested()
|
||||
|
||||
function enumerateFonts() {
|
||||
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]
|
||||
if (fontName.startsWith("."))
|
||||
continue
|
||||
|
||||
if (fontName === SettingsData.defaultFontFamily)
|
||||
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()
|
||||
if (!seenFamilies.has(rootName) && rootName !== "") {
|
||||
seenFamilies.add(rootName)
|
||||
rootFamilies.push(rootName)
|
||||
}
|
||||
}
|
||||
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]
|
||||
if (fontName2.startsWith("."))
|
||||
continue
|
||||
|
||||
if (fontName2 === SettingsData.defaultMonoFontFamily)
|
||||
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()
|
||||
if (!seenMonoFamilies.has(rootName2) && rootName2 !== "") {
|
||||
seenMonoFamilies.add(rootName2)
|
||||
monoFamilies.push(rootName2)
|
||||
}
|
||||
}
|
||||
}
|
||||
cachedMonoFamilies = monoFonts.concat(monoFamilies.sort())
|
||||
fontsEnumerated = true
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!fontsEnumerated) {
|
||||
enumerateFonts()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
visible: root.isVisible
|
||||
onClicked: root.settingsRequested()
|
||||
z: 50
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: settingsMenu
|
||||
visible: root.isVisible
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 360
|
||||
height: settingsColumn.implicitHeight + Theme.spacingXL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, Theme.notepadTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
z: 100
|
||||
|
||||
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: settingsColumn
|
||||
width: parent.width - Theme.spacingXL * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Theme.spacingXL
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 36
|
||||
color: "transparent"
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Notepad Font Settings"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -Theme.spacingM
|
||||
width: parent.width + Theme.spacingM
|
||||
text: "Use Monospace Font"
|
||||
description: "Toggle fonts"
|
||||
checked: SettingsData.notepadUseMonospace
|
||||
onToggled: checked => {
|
||||
SettingsData.notepadUseMonospace = checked
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -Theme.spacingM
|
||||
width: parent.width + Theme.spacingM
|
||||
text: "Show Line Numbers"
|
||||
description: "Display line numbers in editor"
|
||||
checked: SettingsData.notepadShowLineNumbers
|
||||
onToggled: checked => {
|
||||
SettingsData.notepadShowLineNumbers = checked
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: visible ? (fontDropdown.height + Theme.spacingS) : 0
|
||||
color: "transparent"
|
||||
visible: !SettingsData.notepadUseMonospace
|
||||
|
||||
DankDropdown {
|
||||
id: fontDropdown
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -Theme.spacingM
|
||||
width: parent.width + Theme.spacingM
|
||||
text: "Font Family"
|
||||
options: cachedFontFamilies
|
||||
currentValue: {
|
||||
if (!SettingsData.notepadFontFamily || SettingsData.notepadFontFamily === "")
|
||||
return "Default (Global)"
|
||||
else
|
||||
return SettingsData.notepadFontFamily
|
||||
}
|
||||
enableFuzzySearch: true
|
||||
onValueChanged: value => {
|
||||
if (value && (value.startsWith("Default") || value === "Default (Global)")) {
|
||||
SettingsData.notepadFontFamily = ""
|
||||
} else {
|
||||
SettingsData.notepadFontFamily = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: fontSizeRow.height + Theme.spacingS
|
||||
color: "transparent"
|
||||
|
||||
Row {
|
||||
id: fontSizeRow
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Column {
|
||||
width: parent.width - fontSizeControls.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Font Size"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: SettingsData.notepadFontSize + "px"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: fontSizeControls
|
||||
spacing: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 32
|
||||
iconName: "remove"
|
||||
iconSize: Theme.iconSizeSmall
|
||||
enabled: SettingsData.notepadFontSize > 8
|
||||
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
var newSize = Math.max(8, SettingsData.notepadFontSize - 1)
|
||||
SettingsData.notepadFontSize = newSize
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 60
|
||||
height: 32
|
||||
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 {
|
||||
anchors.centerIn: parent
|
||||
text: SettingsData.notepadFontSize + "px"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 32
|
||||
iconName: "add"
|
||||
iconSize: Theme.iconSizeSmall
|
||||
enabled: SettingsData.notepadFontSize < 48
|
||||
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
var newSize = Math.min(48, SettingsData.notepadFontSize + 1)
|
||||
SettingsData.notepadFontSize = newSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: transparencySliderColumn.height + Theme.spacingS
|
||||
color: "transparent"
|
||||
|
||||
Column {
|
||||
id: transparencySliderColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankToggle {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -Theme.spacingM
|
||||
width: parent.width + Theme.spacingM
|
||||
text: "Custom Transparency"
|
||||
description: "Override global transparency for Notepad"
|
||||
checked: SettingsData.notepadTransparencyOverride >= 0
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
SettingsData.notepadTransparencyOverride = SettingsData.notepadLastCustomTransparency
|
||||
} else {
|
||||
SettingsData.notepadTransparencyOverride = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -Theme.spacingM
|
||||
width: parent.width + Theme.spacingM
|
||||
height: 24
|
||||
visible: SettingsData.notepadTransparencyOverride >= 0
|
||||
value: Math.round((SettingsData.notepadTransparencyOverride >= 0 ? SettingsData.notepadTransparencyOverride : SettingsData.popupTransparency) * 100)
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
unit: ""
|
||||
showValue: true
|
||||
wheelEnabled: false
|
||||
onSliderValueChanged: newValue => {
|
||||
if (SettingsData.notepadTransparencyOverride >= 0) {
|
||||
SettingsData.notepadTransparencyOverride = newValue / 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: SettingsData.notepadUseMonospace ?
|
||||
"Using global monospace font from Settings → Personalization" :
|
||||
"Global fonts can be configured in Settings → Personalization"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
wrapMode: Text.WordWrap
|
||||
opacity: 0.8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
153
Modules/Notepad/NotepadTabs.qml
Normal file
153
Modules/Notepad/NotepadTabs.qml
Normal file
@@ -0,0 +1,153 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property var currentTab: NotepadStorageService.tabs.length > NotepadStorageService.currentTabIndex ? NotepadStorageService.tabs[NotepadStorageService.currentTabIndex] : null
|
||||
property bool contentLoaded: false
|
||||
|
||||
signal tabSwitched(int tabIndex)
|
||||
signal tabClosed(int tabIndex)
|
||||
signal newTabRequested()
|
||||
|
||||
function hasUnsavedChangesForTab(tab) {
|
||||
if (!tab) return false
|
||||
|
||||
if (tab.id === currentTab?.id) {
|
||||
return root.parent?.hasUnsavedChanges ? root.parent.hasUnsavedChanges() : false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 36
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
ScrollView {
|
||||
width: parent.width - newTabButton.width - Theme.spacingXS
|
||||
height: parent.height
|
||||
clip: true
|
||||
|
||||
ScrollBar.horizontal.visible: false
|
||||
ScrollBar.vertical.visible: false
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: NotepadStorageService.tabs
|
||||
|
||||
delegate: Rectangle {
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
readonly property bool isActive: NotepadStorageService.currentTabIndex === index
|
||||
readonly property bool isHovered: tabMouseArea.containsMouse && !closeMouseArea.containsMouse
|
||||
readonly property real calculatedWidth: {
|
||||
const textWidth = tabText.paintedWidth || 100
|
||||
const closeButtonWidth = NotepadStorageService.tabs.length > 1 ? 20 : 0
|
||||
const spacing = Theme.spacingXS
|
||||
const padding = Theme.spacingM * 2
|
||||
return Math.max(120, Math.min(200, textWidth + closeButtonWidth + spacing + padding))
|
||||
}
|
||||
|
||||
width: calculatedWidth
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: isActive ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
|
||||
border.width: isActive ? 0 : 1
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
MouseArea {
|
||||
id: tabMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
onClicked: root.tabSwitched(index)
|
||||
}
|
||||
|
||||
Row {
|
||||
id: tabContent
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
id: tabText
|
||||
text: {
|
||||
var prefix = ""
|
||||
if (hasUnsavedChangesForTab(modelData)) {
|
||||
prefix = "● "
|
||||
}
|
||||
return prefix + (modelData.title || "Untitled")
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: isActive ? Theme.primary : Theme.surfaceText
|
||||
font.weight: isActive ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideMiddle
|
||||
maximumLineCount: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: tabCloseButton
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
color: closeMouseArea.containsMouse ? Theme.surfaceTextHover : "transparent"
|
||||
visible: NotepadStorageService.tabs.length > 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "close"
|
||||
size: 14
|
||||
color: Theme.surfaceTextMedium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 100
|
||||
|
||||
onClicked: {
|
||||
root.tabClosed(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: newTabButton
|
||||
width: 32
|
||||
height: 32
|
||||
iconName: "add"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: root.newTabRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
376
Modules/Notepad/NotepadTextEditor.qml
Normal file
376
Modules/Notepad/NotepadTextEditor.qml
Normal file
@@ -0,0 +1,376 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property alias text: textArea.text
|
||||
property alias textArea: textArea
|
||||
property bool contentLoaded: false
|
||||
property string lastSavedContent: ""
|
||||
property var currentTab: NotepadStorageService.tabs.length > NotepadStorageService.currentTabIndex ? NotepadStorageService.tabs[NotepadStorageService.currentTabIndex] : null
|
||||
|
||||
signal saveRequested()
|
||||
signal openRequested()
|
||||
signal newRequested()
|
||||
signal escapePressed()
|
||||
signal contentChanged()
|
||||
signal settingsRequested()
|
||||
|
||||
function hasUnsavedChanges() {
|
||||
if (!currentTab || !contentLoaded) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (currentTab.isTemporary) {
|
||||
return textArea.text.length > 0
|
||||
}
|
||||
return textArea.text !== lastSavedContent
|
||||
}
|
||||
|
||||
function loadCurrentTabContent() {
|
||||
if (!currentTab) return
|
||||
|
||||
contentLoaded = false
|
||||
NotepadStorageService.loadTabContent(
|
||||
NotepadStorageService.currentTabIndex,
|
||||
(content) => {
|
||||
lastSavedContent = content
|
||||
textArea.text = content
|
||||
contentLoaded = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function saveCurrentTabContent() {
|
||||
if (!currentTab || !contentLoaded) return
|
||||
|
||||
NotepadStorageService.saveTabContent(
|
||||
NotepadStorageService.currentTabIndex,
|
||||
textArea.text
|
||||
)
|
||||
lastSavedContent = textArea.text
|
||||
}
|
||||
|
||||
function autoSaveToSession() {
|
||||
if (!currentTab || !contentLoaded) return
|
||||
saveCurrentTabContent()
|
||||
}
|
||||
|
||||
function setTextDocumentLineHeight() {
|
||||
return
|
||||
}
|
||||
|
||||
property string lastTextForLineModel: ""
|
||||
property var lineModel: []
|
||||
|
||||
function updateLineModel() {
|
||||
if (!SettingsData.notepadShowLineNumbers) {
|
||||
lineModel = []
|
||||
lastTextForLineModel = ""
|
||||
return
|
||||
}
|
||||
|
||||
if (textArea.text !== lastTextForLineModel || lineModel.length === 0) {
|
||||
lastTextForLineModel = textArea.text
|
||||
lineModel = textArea.text.split('\n')
|
||||
}
|
||||
}
|
||||
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: parent.height - bottomControls.height - Theme.spacingM
|
||||
color: Qt.rgba(Theme.surface.r, Theme.surface.g, Theme.surface.b, Theme.notepadTransparency)
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
DankFlickable {
|
||||
id: flickable
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
clip: true
|
||||
contentWidth: width - 11
|
||||
|
||||
Rectangle {
|
||||
id: lineNumberArea
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: SettingsData.notepadShowLineNumbers ? Math.max(30, 32 + Theme.spacingXS) : 0
|
||||
height: textArea.contentHeight + textArea.topPadding + textArea.bottomPadding
|
||||
color: "transparent"
|
||||
visible: SettingsData.notepadShowLineNumbers
|
||||
|
||||
ListView {
|
||||
id: lineNumberList
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: textArea.topPadding
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2
|
||||
width: 32
|
||||
height: textArea.contentHeight
|
||||
model: SettingsData.notepadShowLineNumbers ? root.lineModel : []
|
||||
interactive: false
|
||||
spacing: 0
|
||||
|
||||
delegate: Item {
|
||||
id: lineDelegate
|
||||
required property int index
|
||||
required property string modelData
|
||||
width: 32
|
||||
height: measuringText.contentHeight
|
||||
|
||||
Text {
|
||||
id: measuringText
|
||||
width: textArea.width - textArea.leftPadding - textArea.rightPadding
|
||||
text: modelData || " "
|
||||
font: textArea.font
|
||||
wrapMode: Text.Wrap
|
||||
visible: false
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 4
|
||||
anchors.top: parent.top
|
||||
text: index + 1
|
||||
font.family: textArea.font.family
|
||||
font.pixelSize: textArea.font.pixelSize
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextArea.flickable: TextArea {
|
||||
id: textArea
|
||||
placeholderText: qsTr("Start typing your notes here...")
|
||||
font.family: SettingsData.notepadUseMonospace ? SettingsData.monoFontFamily : (SettingsData.notepadFontFamily || SettingsData.fontFamily)
|
||||
font.pixelSize: SettingsData.notepadFontSize * SettingsData.fontScale
|
||||
font.letterSpacing: 0
|
||||
color: Theme.surfaceText
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
wrapMode: TextArea.Wrap
|
||||
focus: true
|
||||
activeFocusOnTab: true
|
||||
textFormat: TextEdit.PlainText
|
||||
inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
|
||||
persistentSelection: true
|
||||
tabStopDistance: 40
|
||||
leftPadding: (SettingsData.notepadShowLineNumbers ? lineNumberArea.width + Theme.spacingXS : Theme.spacingM)
|
||||
topPadding: Theme.spacingM
|
||||
rightPadding: Theme.spacingM
|
||||
bottomPadding: Theme.spacingM
|
||||
|
||||
Component.onCompleted: {
|
||||
loadCurrentTabContent()
|
||||
setTextDocumentLineHeight()
|
||||
root.updateLineModel()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NotepadStorageService
|
||||
function onCurrentTabIndexChanged() {
|
||||
loadCurrentTabContent()
|
||||
}
|
||||
function onTabsChanged() {
|
||||
if (NotepadStorageService.tabs.length > 0 && !contentLoaded) {
|
||||
loadCurrentTabContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onNotepadShowLineNumbersChanged() {
|
||||
root.updateLineModel()
|
||||
}
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
if (contentLoaded && text !== lastSavedContent) {
|
||||
autoSaveTimer.restart()
|
||||
}
|
||||
root.contentChanged()
|
||||
root.updateLineModel()
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: (event) => {
|
||||
root.escapePressed()
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switch (event.key) {
|
||||
case Qt.Key_S:
|
||||
event.accepted = true
|
||||
root.saveRequested()
|
||||
break
|
||||
case Qt.Key_O:
|
||||
event.accepted = true
|
||||
root.openRequested()
|
||||
break
|
||||
case Qt.Key_N:
|
||||
event.accepted = true
|
||||
root.newRequested()
|
||||
break
|
||||
case Qt.Key_A:
|
||||
event.accepted = true
|
||||
selectAll()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: bottomControls
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 32
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
DankActionButton {
|
||||
iconName: "save"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.primary
|
||||
enabled: currentTab && (hasUnsavedChanges() || textArea.text.length > 0)
|
||||
onClicked: root.saveRequested()
|
||||
}
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Save")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
DankActionButton {
|
||||
iconName: "folder_open"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.secondary
|
||||
onClicked: root.openRequested()
|
||||
}
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Open")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
DankActionButton {
|
||||
iconName: "note_add"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: root.newRequested()
|
||||
}
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("New")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "more_horiz"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: root.settingsRequested()
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
StyledText {
|
||||
text: textArea.text.length > 0 ? qsTr("%1 characters").arg(textArea.text.length) : qsTr("Empty")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Lines: %1").arg(textArea.lineCount)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
visible: textArea.text.length > 0
|
||||
opacity: 1.0
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (autoSaveTimer.running) {
|
||||
return qsTr("Auto-saving...")
|
||||
}
|
||||
|
||||
if (hasUnsavedChanges()) {
|
||||
if (currentTab && currentTab.isTemporary) {
|
||||
return qsTr("Unsaved note...")
|
||||
} else {
|
||||
return qsTr("Unsaved changes")
|
||||
}
|
||||
} else {
|
||||
return qsTr("Saved")
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
if (autoSaveTimer.running) {
|
||||
return Theme.primary
|
||||
}
|
||||
|
||||
if (hasUnsavedChanges()) {
|
||||
return Theme.warning
|
||||
} else {
|
||||
return Theme.success
|
||||
}
|
||||
}
|
||||
opacity: textArea.text.length > 0 ? 1.0 : 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: autoSaveTimer
|
||||
interval: 2000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
autoSaveToSession()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,975 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtCore
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Modals.FileBrowser
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
property bool isVisible: false
|
||||
property bool fileDialogOpen: false
|
||||
property string currentFileName: ""
|
||||
property bool hasUnsavedChanges: false
|
||||
property url currentFileUrl
|
||||
property var targetScreen: null
|
||||
property var modelData: null
|
||||
property bool confirmationDialogOpen: false
|
||||
property string pendingAction: ""
|
||||
property string lastSavedFileContent: ""
|
||||
property bool expandedWidth: false
|
||||
property var currentTab: SessionData.notepadTabs.length > SessionData.notepadCurrentTabIndex ? SessionData.notepadTabs[SessionData.notepadCurrentTabIndex] : null
|
||||
property int nextTabId: Date.now()
|
||||
|
||||
function hasFileChanges() {
|
||||
if (!currentTab) return false
|
||||
return currentTab.content !== currentTab.lastSavedContent
|
||||
}
|
||||
|
||||
function getCurrentTabData() {
|
||||
return currentTab || {
|
||||
id: 0,
|
||||
title: "Untitled",
|
||||
content: "",
|
||||
fileName: "",
|
||||
fileUrl: "",
|
||||
lastSavedContent: "",
|
||||
hasUnsavedChanges: false
|
||||
}
|
||||
}
|
||||
|
||||
function updateCurrentTab(properties, saveImmediately = false) {
|
||||
if (!currentTab) return
|
||||
|
||||
var tabs = [...SessionData.notepadTabs]
|
||||
var tabIndex = SessionData.notepadCurrentTabIndex
|
||||
|
||||
if (tabIndex >= 0 && tabIndex < tabs.length) {
|
||||
var updatedTab = Object.assign({}, tabs[tabIndex])
|
||||
Object.assign(updatedTab, properties)
|
||||
tabs[tabIndex] = updatedTab
|
||||
SessionData.notepadTabs = tabs
|
||||
|
||||
if (saveImmediately) {
|
||||
SessionData.saveSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createNewTab() {
|
||||
var newTab = {
|
||||
id: ++nextTabId,
|
||||
title: "Untitled",
|
||||
content: "",
|
||||
fileName: "",
|
||||
fileUrl: "",
|
||||
lastSavedContent: "",
|
||||
hasUnsavedChanges: false
|
||||
}
|
||||
|
||||
var tabs = [...SessionData.notepadTabs]
|
||||
tabs.push(newTab)
|
||||
SessionData.notepadTabs = tabs
|
||||
SessionData.notepadCurrentTabIndex = tabs.length - 1
|
||||
|
||||
textArea.text = ""
|
||||
textArea.forceActiveFocus()
|
||||
|
||||
deferredSaveTimer.restart()
|
||||
}
|
||||
|
||||
function closeTab(tabIndex) {
|
||||
var tabToClose = SessionData.notepadTabs[tabIndex]
|
||||
var hasChanges = tabToClose && tabToClose.content !== tabToClose.lastSavedContent
|
||||
|
||||
if (hasChanges) {
|
||||
root.pendingAction = "close_tab_" + tabIndex
|
||||
root.confirmationDialogOpen = true
|
||||
confirmationDialog.open()
|
||||
} else {
|
||||
performCloseTab(tabIndex)
|
||||
}
|
||||
}
|
||||
|
||||
function performCloseTab(tabIndex) {
|
||||
var tabs = [...SessionData.notepadTabs]
|
||||
|
||||
if (tabs.length <= 1) {
|
||||
tabs[0] = {
|
||||
id: ++nextTabId,
|
||||
title: "Untitled",
|
||||
content: "",
|
||||
fileName: "",
|
||||
fileUrl: "",
|
||||
lastSavedContent: "",
|
||||
hasUnsavedChanges: false
|
||||
}
|
||||
SessionData.notepadCurrentTabIndex = 0
|
||||
} else {
|
||||
tabs.splice(tabIndex, 1)
|
||||
if (SessionData.notepadCurrentTabIndex >= tabs.length) {
|
||||
SessionData.notepadCurrentTabIndex = tabs.length - 1
|
||||
} else if (SessionData.notepadCurrentTabIndex > tabIndex) {
|
||||
SessionData.notepadCurrentTabIndex -= 1
|
||||
}
|
||||
}
|
||||
|
||||
SessionData.notepadTabs = tabs
|
||||
|
||||
Qt.callLater(() => {
|
||||
if (currentTab) {
|
||||
textArea.text = currentTab.content
|
||||
}
|
||||
})
|
||||
|
||||
deferredSaveTimer.restart()
|
||||
}
|
||||
|
||||
function switchToTab(tabIndex) {
|
||||
if (tabIndex < 0 || tabIndex >= SessionData.notepadTabs.length) return
|
||||
|
||||
SessionData.notepadCurrentTabIndex = tabIndex
|
||||
|
||||
Qt.callLater(() => {
|
||||
if (currentTab) {
|
||||
textArea.text = currentTab.content
|
||||
root.currentFileName = currentTab.fileName
|
||||
root.currentFileUrl = currentTab.fileUrl
|
||||
root.lastSavedFileContent = currentTab.lastSavedContent
|
||||
}
|
||||
})
|
||||
|
||||
deferredSaveTimer.restart()
|
||||
}
|
||||
|
||||
function show() {
|
||||
visible = true
|
||||
isVisible = true
|
||||
textArea.forceActiveFocus()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
isVisible = false
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (isVisible) {
|
||||
hide()
|
||||
} else {
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
visible: isVisible
|
||||
screen: modelData
|
||||
|
||||
anchors.top: true
|
||||
anchors.bottom: true
|
||||
anchors.right: true
|
||||
|
||||
implicitWidth: 960
|
||||
implicitHeight: modelData ? modelData.height : 800
|
||||
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Top
|
||||
WlrLayershell.exclusiveZone: 0
|
||||
WlrLayershell.keyboardFocus: isVisible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||
|
||||
StyledRect {
|
||||
id: contentRect
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: expandedWidth ? 960 : 480
|
||||
color: Theme.surfaceContainer
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
opacity: isVisible ? SettingsData.popupTransparency : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 700
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
transform: Translate {
|
||||
id: slideTransform
|
||||
x: isVisible ? 0 : contentRect.width
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
id: slideAnimation
|
||||
duration: 450
|
||||
easing.type: Easing.OutCubic
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running && !isVisible) {
|
||||
root.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 250
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// Header
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
// Title row
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 32
|
||||
|
||||
Column {
|
||||
width: parent.width - buttonRow.width
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Notepad")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankActionButton {
|
||||
id: expandButton
|
||||
iconName: root.expandedWidth ? "unfold_less" : "unfold_more"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: root.expandedWidth = !root.expandedWidth
|
||||
|
||||
transform: Rotation {
|
||||
angle: 90
|
||||
origin.x: expandButton.width / 2
|
||||
origin.y: expandButton.height / 2
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: closeButton
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: root.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tab bar
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 36
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
ScrollView {
|
||||
width: parent.width - newTabButton.width - Theme.spacingXS
|
||||
height: parent.height
|
||||
clip: true
|
||||
|
||||
ScrollBar.horizontal.visible: false
|
||||
ScrollBar.vertical.visible: false
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: SessionData.notepadTabs
|
||||
|
||||
delegate: Rectangle {
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
readonly property bool isActive: SessionData.notepadCurrentTabIndex === index
|
||||
readonly property bool tabHasChanges: modelData.content !== modelData.lastSavedContent
|
||||
readonly property bool isHovered: tabMouseArea.containsMouse && !closeMouseArea.containsMouse
|
||||
readonly property real calculatedWidth: {
|
||||
const textWidth = tabText.paintedWidth || 100
|
||||
const closeButtonWidth = SessionData.notepadTabs.length > 1 ? 20 : 0
|
||||
const spacing = Theme.spacingXS
|
||||
const padding = Theme.spacingM * 2
|
||||
return Math.max(120, Math.min(200, textWidth + closeButtonWidth + spacing + padding))
|
||||
}
|
||||
|
||||
width: calculatedWidth
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: isActive ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
|
||||
border.width: isActive ? 0 : 1
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
MouseArea {
|
||||
id: tabMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
onClicked: switchToTab(index)
|
||||
}
|
||||
|
||||
Row {
|
||||
id: tabContent
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
id: tabText
|
||||
text: (tabHasChanges ? "● " : "") + (modelData.title || "Untitled")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: isActive ? Theme.primary : Theme.surfaceText
|
||||
font.weight: isActive ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideMiddle
|
||||
maximumLineCount: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: tabCloseButton
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
color: closeMouseArea.containsMouse ? Theme.surfaceTextHover : "transparent"
|
||||
visible: SessionData.notepadTabs.length > 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "close"
|
||||
size: 14
|
||||
color: Theme.surfaceTextMedium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 100
|
||||
|
||||
onClicked: {
|
||||
closeTab(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: newTabButton
|
||||
width: 32
|
||||
height: 32
|
||||
iconName: "add"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: createNewTab()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Text area
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: parent.height - 180
|
||||
color: Theme.surface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
clip: true
|
||||
|
||||
TextArea {
|
||||
id: textArea
|
||||
placeholderText: qsTr("Start typing your notes here...")
|
||||
font.family: SettingsData.monoFontFamily
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
wrapMode: TextArea.Wrap
|
||||
focus: root.isVisible
|
||||
activeFocusOnTab: true
|
||||
textFormat: TextEdit.PlainText
|
||||
persistentSelection: true
|
||||
tabStopDistance: 40
|
||||
leftPadding: Theme.spacingM
|
||||
topPadding: Theme.spacingM
|
||||
rightPadding: Theme.spacingM
|
||||
bottomPadding: Theme.spacingM
|
||||
|
||||
Component.onCompleted: {
|
||||
if (currentTab) {
|
||||
text = currentTab.content
|
||||
root.currentFileName = currentTab.fileName
|
||||
root.currentFileUrl = currentTab.fileUrl
|
||||
root.lastSavedFileContent = currentTab.lastSavedContent
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onCurrentTabChanged() {
|
||||
if (currentTab && textArea.text !== currentTab.content) {
|
||||
textArea.text = currentTab.content
|
||||
root.currentFileName = currentTab.fileName
|
||||
root.currentFileUrl = currentTab.fileUrl
|
||||
root.lastSavedFileContent = currentTab.lastSavedContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
if (currentTab && text !== currentTab.content) {
|
||||
updateCurrentTab({
|
||||
content: text,
|
||||
hasUnsavedChanges: true
|
||||
})
|
||||
autoSaveTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: (event) => {
|
||||
root.hide()
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switch (event.key) {
|
||||
case Qt.Key_S:
|
||||
event.accepted = true
|
||||
if (currentTab && currentTab.fileUrl) {
|
||||
saveToFile(currentTab.fileUrl)
|
||||
} else {
|
||||
root.fileDialogOpen = true
|
||||
saveBrowser.open()
|
||||
}
|
||||
break
|
||||
case Qt.Key_O:
|
||||
event.accepted = true
|
||||
if (hasFileChanges()) {
|
||||
root.pendingAction = "open"
|
||||
root.confirmationDialogOpen = true
|
||||
confirmationDialog.open()
|
||||
} else {
|
||||
root.fileDialogOpen = true
|
||||
loadBrowser.open()
|
||||
}
|
||||
break
|
||||
case Qt.Key_N:
|
||||
event.accepted = true
|
||||
if (hasFileChanges()) {
|
||||
root.pendingAction = "new"
|
||||
root.confirmationDialogOpen = true
|
||||
confirmationDialog.open()
|
||||
} else {
|
||||
createNewTab()
|
||||
}
|
||||
break
|
||||
case Qt.Key_A:
|
||||
event.accepted = true
|
||||
selectAll()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom controls
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
DankActionButton {
|
||||
iconName: "save"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.primary
|
||||
enabled: currentTab && (hasFileChanges() || currentTab.content.length > 0)
|
||||
onClicked: {
|
||||
root.fileDialogOpen = true
|
||||
saveBrowser.open()
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Save")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
DankActionButton {
|
||||
iconName: "folder_open"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.secondary
|
||||
onClicked: {
|
||||
if (hasFileChanges()) {
|
||||
root.pendingAction = "open"
|
||||
root.confirmationDialogOpen = true
|
||||
confirmationDialog.open()
|
||||
} else {
|
||||
root.fileDialogOpen = true
|
||||
loadBrowser.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Open")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
DankActionButton {
|
||||
iconName: "note_add"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: createNewTab()
|
||||
}
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("New")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
StyledText {
|
||||
text: currentTab && currentTab.content.length > 0 ? qsTr("%1 characters").arg(currentTab.content.length) : qsTr("Empty")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Lines: %1").arg(textArea.lineCount)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
visible: currentTab && currentTab.content.length > 0
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: autoSaveTimer.running ? qsTr("Auto-saving...") : (hasFileChanges() ? qsTr("Unsaved changes") : qsTr("Auto-saved"))
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: hasFileChanges() ? Theme.warning : (autoSaveTimer.running ? Theme.primary : Theme.surfaceTextMedium)
|
||||
opacity: currentTab && currentTab.content.length > 0 ? 1 : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: autoSaveTimer
|
||||
interval: 2000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (currentTab) {
|
||||
updateCurrentTab({
|
||||
hasUnsavedChanges: false
|
||||
}, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: deferredSaveTimer
|
||||
interval: 500
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
SessionData.saveSettings()
|
||||
}
|
||||
}
|
||||
|
||||
property string pendingSaveContent: ""
|
||||
|
||||
function saveToFile(fileUrl) {
|
||||
if (!currentTab) return
|
||||
|
||||
const content = currentTab.content
|
||||
const filePath = fileUrl.toString().replace(/^file:\/\//, '')
|
||||
|
||||
saveFileView.path = ""
|
||||
pendingSaveContent = content
|
||||
saveFileView.path = filePath
|
||||
|
||||
// Use Qt.callLater to ensure path is set before calling setText
|
||||
Qt.callLater(() => {
|
||||
saveFileView.setText(pendingSaveContent)
|
||||
})
|
||||
} function loadFromFile(fileUrl) {
|
||||
const filePath = fileUrl.toString().replace(/^file:\/\//, '')
|
||||
|
||||
loadFileView.path = ""
|
||||
loadFileView.path = filePath
|
||||
|
||||
// Wait for the file to be loaded before reading
|
||||
if (loadFileView.waitForJob()) {
|
||||
Qt.callLater(() => {
|
||||
const content = loadFileView.text()
|
||||
if (currentTab && content !== undefined && content !== null) {
|
||||
updateCurrentTab({
|
||||
content: content,
|
||||
hasUnsavedChanges: false,
|
||||
lastSavedContent: content
|
||||
}, true)
|
||||
textArea.text = content
|
||||
root.lastSavedFileContent = content
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.warn("Notepad: Failed to load file - waitForJob returned false")
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: saveFileView
|
||||
blockWrites: true
|
||||
preload: false
|
||||
atomicWrites: true
|
||||
printErrors: true
|
||||
|
||||
onSaved: {
|
||||
if (currentTab && saveFileView.path && pendingSaveContent) {
|
||||
updateCurrentTab({
|
||||
hasUnsavedChanges: false,
|
||||
lastSavedContent: pendingSaveContent
|
||||
}, true)
|
||||
root.lastSavedFileContent = pendingSaveContent
|
||||
pendingSaveContent = ""
|
||||
}
|
||||
}
|
||||
|
||||
onSaveFailed: (error) => {
|
||||
console.warn("Notepad: Failed to save file:", error, "Path:", saveFileView.path)
|
||||
pendingSaveContent = ""
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: loadFileView
|
||||
blockLoading: true
|
||||
preload: true
|
||||
atomicWrites: true
|
||||
printErrors: true
|
||||
|
||||
onLoadFailed: (error) => {
|
||||
console.warn("Notepad: Failed to load file:", error)
|
||||
}
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: saveBrowser
|
||||
|
||||
browserTitle: qsTr("Save Notepad File")
|
||||
browserIcon: "save"
|
||||
browserType: "notepad_save"
|
||||
fileExtensions: ["*.txt", "*.md", "*.*"]
|
||||
allowStacking: true
|
||||
saveMode: true
|
||||
defaultFileName: (currentTab && currentTab.fileName) || "note.txt"
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
|
||||
onFileSelected: (path) => {
|
||||
root.fileDialogOpen = false
|
||||
const cleanPath = path.toString().replace(/^file:\/\//, '')
|
||||
const fileName = cleanPath.split('/').pop()
|
||||
const fileUrl = "file://" + cleanPath
|
||||
|
||||
root.currentFileName = fileName
|
||||
root.currentFileUrl = fileUrl
|
||||
|
||||
if (currentTab) {
|
||||
updateCurrentTab({
|
||||
title: fileName,
|
||||
fileName: fileName,
|
||||
fileUrl: fileUrl
|
||||
})
|
||||
}
|
||||
|
||||
saveToFile(fileUrl)
|
||||
|
||||
if (root.pendingAction === "new") {
|
||||
Qt.callLater(() => {
|
||||
createNewTab()
|
||||
})
|
||||
} else if (root.pendingAction === "open") {
|
||||
Qt.callLater(() => {
|
||||
root.fileDialogOpen = true
|
||||
loadBrowser.open()
|
||||
})
|
||||
} else if (root.pendingAction.startsWith("close_tab_")) {
|
||||
Qt.callLater(() => {
|
||||
var tabIndex = parseInt(root.pendingAction.split("_")[2])
|
||||
performCloseTab(tabIndex)
|
||||
})
|
||||
}
|
||||
root.pendingAction = ""
|
||||
|
||||
close()
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
root.fileDialogOpen = false
|
||||
}
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: loadBrowser
|
||||
|
||||
browserTitle: qsTr("Open Notepad File")
|
||||
browserIcon: "folder_open"
|
||||
browserType: "notepad_load"
|
||||
fileExtensions: ["*.txt", "*.md", "*.*"]
|
||||
allowStacking: true
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
|
||||
onFileSelected: (path) => {
|
||||
root.fileDialogOpen = false
|
||||
const cleanPath = path.toString().replace(/^file:\/\//, '')
|
||||
const fileName = cleanPath.split('/').pop()
|
||||
const fileUrl = "file://" + cleanPath
|
||||
|
||||
root.currentFileName = fileName
|
||||
root.currentFileUrl = fileUrl
|
||||
|
||||
if (currentTab) {
|
||||
updateCurrentTab({
|
||||
title: fileName,
|
||||
fileName: fileName,
|
||||
fileUrl: fileUrl
|
||||
})
|
||||
}
|
||||
|
||||
loadFromFile(fileUrl)
|
||||
close()
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
root.fileDialogOpen = false
|
||||
}
|
||||
}
|
||||
|
||||
DankModal {
|
||||
id: confirmationDialog
|
||||
|
||||
width: 400
|
||||
height: 180
|
||||
shouldBeVisible: false
|
||||
allowStacking: true
|
||||
|
||||
onBackgroundClicked: {
|
||||
close()
|
||||
root.confirmationDialogOpen = false
|
||||
}
|
||||
|
||||
content: Component {
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
confirmationDialog.close()
|
||||
root.confirmationDialogOpen = false
|
||||
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: qsTr("Unsaved Changes")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.pendingAction === "new" ?
|
||||
qsTr("You have unsaved changes. Save before creating a new file?") :
|
||||
root.pendingAction.startsWith("close_tab_") ?
|
||||
qsTr("You have unsaved changes. Save before closing this tab?") :
|
||||
qsTr("You have unsaved changes. Save before opening a file?")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
confirmationDialog.close()
|
||||
root.confirmationDialogOpen = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: Math.max(80, discardText.contentWidth + Theme.spacingM * 2)
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: discardArea.containsMouse ? Theme.surfaceTextHover : "transparent"
|
||||
border.color: Theme.surfaceVariantAlpha
|
||||
border.width: 1
|
||||
|
||||
StyledText {
|
||||
id: discardText
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("Don't Save")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: discardArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
confirmationDialog.close()
|
||||
root.confirmationDialogOpen = false
|
||||
if (root.pendingAction === "new") {
|
||||
createNewTab()
|
||||
} else if (root.pendingAction === "open") {
|
||||
root.fileDialogOpen = true
|
||||
loadBrowser.open()
|
||||
} else if (root.pendingAction.startsWith("close_tab_")) {
|
||||
var tabIndex = parseInt(root.pendingAction.split("_")[2])
|
||||
performCloseTab(tabIndex)
|
||||
}
|
||||
root.pendingAction = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Math.max(70, saveAsText.contentWidth + Theme.spacingM * 2)
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: saveAsArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||
|
||||
StyledText {
|
||||
id: saveAsText
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("Save")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.background
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: saveAsArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
confirmationDialog.close()
|
||||
root.confirmationDialogOpen = false
|
||||
root.fileDialogOpen = true
|
||||
saveBrowser.open()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Services.Notifications
|
||||
@@ -33,12 +34,6 @@ Rectangle {
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
@@ -49,12 +44,12 @@ Rectangle {
|
||||
|
||||
color: {
|
||||
if (isGroupSelected && keyboardNavigationActive) {
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2)
|
||||
return Theme.primaryPressed
|
||||
}
|
||||
if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12)
|
||||
return Theme.primaryHoverLight
|
||||
}
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.1)
|
||||
return Theme.surfaceContainerHigh
|
||||
}
|
||||
border.color: {
|
||||
if (isGroupSelected && keyboardNavigationActive) {
|
||||
@@ -115,47 +110,46 @@ Rectangle {
|
||||
height: 92
|
||||
visible: !expanded
|
||||
|
||||
Rectangle {
|
||||
DankCircularImage {
|
||||
id: iconContainer
|
||||
readonly property bool hasNotificationImage: notificationGroup?.latestNotification?.image && notificationGroup.latestNotification.image !== ""
|
||||
|
||||
width: 55
|
||||
height: 55
|
||||
radius: 27.5
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
|
||||
border.color: "transparent"
|
||||
border.width: 0
|
||||
width: 63
|
||||
height: 63
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 18
|
||||
anchors.topMargin: 14
|
||||
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
source: {
|
||||
if (parent.hasNotificationImage)
|
||||
return notificationGroup.latestNotification.cleanImage
|
||||
if (notificationGroup?.latestNotification?.appIcon) {
|
||||
const appIcon = notificationGroup.latestNotification.appIcon
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
||||
return appIcon
|
||||
return Quickshell.iconPath(appIcon, true)
|
||||
}
|
||||
return ""
|
||||
imageSource: {
|
||||
if (hasNotificationImage)
|
||||
return notificationGroup.latestNotification.cleanImage
|
||||
if (notificationGroup?.latestNotification?.appIcon) {
|
||||
const appIcon = notificationGroup.latestNotification.appIcon
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
||||
return appIcon
|
||||
return Quickshell.iconPath(appIcon, true)
|
||||
}
|
||||
visible: status === Image.Ready
|
||||
return ""
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
visible: !parent.hasNotificationImage && (!notificationGroup?.latestNotification?.appIcon || notificationGroup.latestNotification.appIcon === "")
|
||||
text: {
|
||||
const appName = notificationGroup?.appName || "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
}
|
||||
font.pixelSize: 20
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primaryText
|
||||
hasImage: hasNotificationImage
|
||||
fallbackIcon: notificationGroup?.latestNotification?.appIcon || "notifications"
|
||||
fallbackText: {
|
||||
if (hasNotificationImage || (notificationGroup?.latestNotification?.appIcon && notificationGroup.latestNotification.appIcon !== ""))
|
||||
return ""
|
||||
const appName = notificationGroup?.appName || "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -2
|
||||
radius: width / 2
|
||||
color: "transparent"
|
||||
border.color: root.color
|
||||
border.width: 5
|
||||
visible: parent.hasImage
|
||||
antialiasing: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -350,16 +344,10 @@ Rectangle {
|
||||
return baseHeight
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: isSelected ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.25) : "transparent"
|
||||
color: isSelected ? Theme.primaryPressed : Theme.surfaceContainerHigh
|
||||
border.color: isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
|
||||
border.width: isSelected ? 1 : 1
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
@@ -377,50 +365,46 @@ Rectangle {
|
||||
anchors.margins: 12
|
||||
anchors.bottomMargin: 8
|
||||
|
||||
Rectangle {
|
||||
DankCircularImage {
|
||||
id: messageIcon
|
||||
|
||||
readonly property bool hasNotificationImage: modelData?.image && modelData.image !== ""
|
||||
|
||||
width: 32
|
||||
height: 32
|
||||
radius: 16
|
||||
width: 48
|
||||
height: 48
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 32
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
|
||||
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
|
||||
border.width: 1
|
||||
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
source: {
|
||||
if (parent.hasNotificationImage)
|
||||
return modelData.cleanImage
|
||||
imageSource: {
|
||||
if (hasNotificationImage)
|
||||
return modelData.cleanImage
|
||||
|
||||
if (modelData?.appIcon) {
|
||||
const appIcon = modelData.appIcon
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
||||
return appIcon
|
||||
if (modelData?.appIcon) {
|
||||
const appIcon = modelData.appIcon
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
||||
return appIcon
|
||||
|
||||
return Quickshell.iconPath(appIcon, true)
|
||||
}
|
||||
return ""
|
||||
return Quickshell.iconPath(appIcon, true)
|
||||
}
|
||||
visible: status === Image.Ready
|
||||
return ""
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
visible: !parent.hasNotificationImage && (!modelData?.appIcon || modelData.appIcon === "")
|
||||
text: {
|
||||
fallbackIcon: {
|
||||
if (modelData?.appIcon && !hasNotificationImage) {
|
||||
const appIcon = modelData.appIcon
|
||||
if (!appIcon.startsWith("file://") && !appIcon.startsWith("http://") && !appIcon.startsWith("https://"))
|
||||
return appIcon
|
||||
}
|
||||
return "notifications"
|
||||
}
|
||||
|
||||
fallbackText: {
|
||||
if (!hasNotificationImage && (!modelData?.appIcon || modelData.appIcon === "")) {
|
||||
const appName = modelData?.appName || "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
}
|
||||
font.pixelSize: 12
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primaryText
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ DankPopout {
|
||||
popupWidth: 400
|
||||
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 400
|
||||
triggerX: Screen.width - 400 - Theme.spacingL
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingXS
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.popupDistance
|
||||
triggerWidth: 40
|
||||
positioning: "center"
|
||||
screen: triggerScreen
|
||||
@@ -117,7 +117,7 @@ DankPopout {
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
focus: true
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
@@ -106,9 +106,7 @@ Item {
|
||||
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
|
||||
color: clearArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
@@ -139,19 +137,6 @@ Item {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
@@ -11,6 +12,8 @@ import qs.Widgets
|
||||
PanelWindow {
|
||||
id: win
|
||||
|
||||
WlrLayershell.namespace: "quickshell:notification"
|
||||
|
||||
required property var notificationData
|
||||
required property string notificationId
|
||||
readonly property bool hasValidData: notificationData && notificationData.notification
|
||||
@@ -131,7 +134,7 @@ PanelWindow {
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.popupBackground()
|
||||
border.color: notificationData && notificationData.urgency === NotificationUrgency.Critical ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: notificationData && notificationData.urgency === NotificationUrgency.Critical ? 2 : 1
|
||||
border.width: notificationData && notificationData.urgency === NotificationUrgency.Critical ? 2 : 0
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
@@ -206,55 +209,40 @@ PanelWindow {
|
||||
anchors.rightMargin: 56
|
||||
height: 98
|
||||
|
||||
Rectangle {
|
||||
DankCircularImage {
|
||||
id: iconContainer
|
||||
|
||||
readonly property bool hasNotificationImage: notificationData && notificationData.image && notificationData.image !== ""
|
||||
|
||||
width: 55
|
||||
height: 55
|
||||
radius: 27.5
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
|
||||
border.color: "transparent"
|
||||
border.width: 0
|
||||
width: 63
|
||||
height: 63
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
IconImage {
|
||||
id: iconImage
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
asynchronous: true
|
||||
source: {
|
||||
if (!notificationData)
|
||||
return ""
|
||||
|
||||
if (parent.hasNotificationImage)
|
||||
return notificationData.cleanImage || ""
|
||||
|
||||
if (notificationData.appIcon) {
|
||||
const appIcon = notificationData.appIcon
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
||||
return appIcon
|
||||
|
||||
return Quickshell.iconPath(appIcon, true)
|
||||
}
|
||||
imageSource: {
|
||||
if (!notificationData)
|
||||
return ""
|
||||
|
||||
if (hasNotificationImage)
|
||||
return notificationData.cleanImage || ""
|
||||
|
||||
if (notificationData.appIcon) {
|
||||
const appIcon = notificationData.appIcon
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
||||
return appIcon
|
||||
|
||||
return Quickshell.iconPath(appIcon, true)
|
||||
}
|
||||
visible: status === Image.Ready
|
||||
return ""
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
visible: !parent.hasNotificationImage && (!notificationData || !notificationData.appIcon || notificationData.appIcon === "")
|
||||
text: {
|
||||
const appName = notificationData && notificationData.appName ? notificationData.appName : "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
}
|
||||
font.pixelSize: 20
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primaryText
|
||||
hasImage: hasNotificationImage
|
||||
fallbackIcon: notificationData?.appIcon || "notifications"
|
||||
fallbackText: {
|
||||
if (hasNotificationImage || (notificationData?.appIcon && notificationData.appIcon !== ""))
|
||||
return ""
|
||||
const appName = notificationData?.appName || "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ DankOSD {
|
||||
enabled: DisplayService.brightnessAvailable
|
||||
showValue: true
|
||||
unit: "%"
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
|
||||
Component.onCompleted: {
|
||||
if (DisplayService.brightnessAvailable) {
|
||||
|
||||
@@ -58,7 +58,6 @@ DankOSD {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
AudioService.toggleMute()
|
||||
resetHideTimer()
|
||||
}
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse || volumeSlider.containsMouse)
|
||||
@@ -69,6 +68,9 @@ DankOSD {
|
||||
DankSlider {
|
||||
id: volumeSlider
|
||||
|
||||
readonly property real actualVolumePercent: AudioService.sink && AudioService.sink.audio ? Math.round(AudioService.sink.audio.volume * 100) : 0
|
||||
readonly property real displayPercent: actualVolumePercent
|
||||
|
||||
width: parent.width - Theme.iconSize - parent.gap * 3
|
||||
height: 40
|
||||
x: parent.gap * 2 + Theme.iconSize
|
||||
@@ -78,17 +80,18 @@ DankOSD {
|
||||
enabled: AudioService.sink && AudioService.sink.audio
|
||||
showValue: true
|
||||
unit: "%"
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
valueOverride: displayPercent
|
||||
|
||||
Component.onCompleted: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
value = Math.round(AudioService.sink.audio.volume * 100)
|
||||
value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100))
|
||||
}
|
||||
}
|
||||
|
||||
onSliderValueChanged: newValue => {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.volume = newValue / 100
|
||||
resetHideTimer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +104,7 @@ DankOSD {
|
||||
|
||||
function onVolumeChanged() {
|
||||
if (volumeSlider && !volumeSlider.pressed) {
|
||||
volumeSlider.value = Math.round(AudioService.sink.audio.volume * 100)
|
||||
volumeSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,7 +116,7 @@ DankOSD {
|
||||
if (AudioService.sink && AudioService.sink.audio && contentLoader.item) {
|
||||
const slider = contentLoader.item.children[0].children[1]
|
||||
if (slider) {
|
||||
slider.value = Math.round(AudioService.sink.audio.volume * 100)
|
||||
slider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ Column {
|
||||
width: parent.width
|
||||
height: 200
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.04)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
|
||||
border.width: 1
|
||||
|
||||
@@ -180,7 +180,7 @@ Column {
|
||||
width: parent.width
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.04)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
|
||||
border.width: 1
|
||||
|
||||
@@ -350,7 +350,7 @@ Column {
|
||||
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)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
|
||||
border.width: 1
|
||||
|
||||
@@ -416,7 +416,7 @@ Column {
|
||||
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)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
|
||||
border.width: 1
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ DankPopout {
|
||||
popupWidth: 600
|
||||
popupHeight: 600
|
||||
triggerX: Screen.width - 600 - Theme.spacingL
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingXS
|
||||
triggerY: Math.max(26 + SettingsData.topBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.topBarInnerPadding)) + SettingsData.topBarSpacing + SettingsData.topBarBottomGap - 2 + Theme.popupDistance
|
||||
triggerWidth: 55
|
||||
positioning: "center"
|
||||
screen: triggerScreen
|
||||
@@ -62,7 +62,7 @@ DankPopout {
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
clip: true
|
||||
antialiasing: true
|
||||
smooth: true
|
||||
@@ -100,9 +100,9 @@ DankPopout {
|
||||
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)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
SystemOverview {
|
||||
id: systemOverview
|
||||
@@ -117,9 +117,9 @@ DankPopout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.1)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
ProcessListView {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -24,7 +24,7 @@ DankFlickable {
|
||||
width: parent.width
|
||||
height: systemInfoColumn.implicitHeight + 2 * Theme.spacingL
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.6)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
@@ -386,7 +386,7 @@ DankFlickable {
|
||||
width: parent.width
|
||||
height: storageColumn.implicitHeight + 2 * Theme.spacingL
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.6)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
|
||||
@@ -28,11 +28,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)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: asciiSection
|
||||
@@ -225,11 +224,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)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: projectSection
|
||||
@@ -259,7 +257,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `DankMaterialShell is a modern desktop inspired by <a href="https://m3.material.io/" style="text-decoration:none; color:${Theme.primary};">MUI 3</a>.
|
||||
text: `DankMaterialShell is a modern desktop with a <a href="https://m3.material.io/" style="text-decoration:none; color:${Theme.primary};">material</a>-ish design.
|
||||
<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.
|
||||
`
|
||||
textFormat: Text.RichText
|
||||
@@ -285,11 +283,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)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: techSection
|
||||
@@ -510,7 +507,7 @@ Item {
|
||||
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
x: hoveredButton ? hoveredButton.mapToItem(aboutTab, hoveredButton.width / 2, 0).x - width / 2 : 0
|
||||
|
||||
@@ -80,9 +80,9 @@ Item {
|
||||
width: parent.width
|
||||
height: screensInfoSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: screensInfoSection
|
||||
@@ -146,7 +146,7 @@ Item {
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
id: screenRow
|
||||
@@ -222,9 +222,9 @@ Item {
|
||||
width: parent.width
|
||||
height: componentSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 1
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: componentSection
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user