mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
Initial qmlformat
This commit is contained in:
23
CLAUDE.md
23
CLAUDE.md
@@ -25,6 +25,10 @@ quickshell -p shell.qml
|
||||
|
||||
# Or use the shorthand
|
||||
qs -p .
|
||||
|
||||
# Code formatting and linting
|
||||
qmlformat -i **/*.qml # Format all QML files in place
|
||||
qmllint **/*.qml # Lint all QML files for syntax errors
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
@@ -119,13 +123,19 @@ shell.qml # Main entry point (minimal orchestration)
|
||||
- `id` should be the first property
|
||||
- Properties before signal handlers before child components
|
||||
- Prefer property bindings over imperative code
|
||||
- **IMPORTANT**: Be very conservative with comments - add comments only when absolutely necessary for understanding complex logic
|
||||
|
||||
2. **Naming Conventions**:
|
||||
- **Services**: Use `Singleton` type with `id: root`
|
||||
- **Components**: Use descriptive names (e.g., `CustomSlider`, `TopBar`)
|
||||
- **Properties**: camelCase for properties, PascalCase for types
|
||||
|
||||
3. **Component Structure**:
|
||||
3. **Null-Safe Operations**:
|
||||
- **Do NOT use** `?.` operator (not supported by qmlformat)
|
||||
- **Use** `object && object.property` instead of `object?.property`
|
||||
- **Example**: `activePlayer && activePlayer.trackTitle` instead of `activePlayer?.trackTitle`
|
||||
|
||||
4. **Component Structure**:
|
||||
```qml
|
||||
// For regular components
|
||||
Item {
|
||||
@@ -230,11 +240,12 @@ The shell uses Quickshell's `Variants` pattern for multi-monitor support:
|
||||
|
||||
When modifying the shell:
|
||||
1. **Test changes**: `qs -p .` (automatic reload on file changes)
|
||||
2. **Performance**: Ensure animations remain smooth (60 FPS target)
|
||||
3. **Theming**: Use `Theme.propertyName` for Material Design 3 consistency
|
||||
4. **Wayland compatibility**: Test on Wayland session
|
||||
5. **Multi-monitor**: Verify behavior with multiple displays
|
||||
6. **Feature detection**: Test on systems with/without required tools
|
||||
2. **Code quality**: Run `qmlformat -i **/*.qml` and `qmllint **/*.qml` to ensure proper formatting and syntax
|
||||
3. **Performance**: Ensure animations remain smooth (60 FPS target)
|
||||
4. **Theming**: Use `Theme.propertyName` for Material Design 3 consistency
|
||||
5. **Wayland compatibility**: Test on Wayland session
|
||||
6. **Multi-monitor**: Verify behavior with multiple displays
|
||||
7. **Feature detection**: Test on systems with/without required tools
|
||||
|
||||
### Adding New Widgets
|
||||
|
||||
|
||||
@@ -1,180 +1,181 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Qt.labs.platform
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Qt.labs.platform
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
// This dependency forces re-evaluation when colorUpdateTrigger changes
|
||||
// Just check if matugen is available
|
||||
|
||||
id: root
|
||||
|
||||
/* ──────────────── basic state ──────────────── */
|
||||
signal colorsUpdated()
|
||||
|
||||
readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
|
||||
readonly property string homeDir: _homeUrl.startsWith("file://")
|
||||
? _homeUrl.substring(7)
|
||||
: _homeUrl
|
||||
readonly property string homeDir: _homeUrl.startsWith("file://") ? _homeUrl.substring(7) : _homeUrl
|
||||
readonly property string wallpaperPath: homeDir + "/quickshell/current_wallpaper"
|
||||
readonly property string notifyPath: homeDir + "/quickshell/wallpaper_changed"
|
||||
|
||||
property bool matugenAvailable: false
|
||||
property string matugenJson: ""
|
||||
property var matugenColors: ({})
|
||||
property var matugenColors: ({
|
||||
})
|
||||
property bool extractionRequested: false
|
||||
property int colorUpdateTrigger: 0 // Force property re-evaluation
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("Colors.qml → home =", homeDir)
|
||||
// Don't automatically run color extraction - only when requested
|
||||
matugenCheck.running = true // Just check if matugen is available
|
||||
|
||||
// Connect to Theme light mode changes to update colors
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.isLightModeChanged.connect(root.onLightModeChanged)
|
||||
}
|
||||
}
|
||||
|
||||
function onLightModeChanged() {
|
||||
// Force color properties to update when light mode changes
|
||||
if (matugenColors && Object.keys(matugenColors).length > 0) {
|
||||
console.log("Light mode changed - updating dynamic colors")
|
||||
colorUpdateTrigger++ // This will trigger re-evaluation of all color properties
|
||||
colorsUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
/* ──────────────── availability checks ──────────────── */
|
||||
Process {
|
||||
id: matugenCheck
|
||||
command: ["which", "matugen"]
|
||||
onExited: (code) => {
|
||||
matugenAvailable = (code === 0)
|
||||
console.log("Matugen in PATH:", matugenAvailable)
|
||||
|
||||
if (!matugenAvailable) {
|
||||
console.warn("Matugen missing → dynamic theme disabled")
|
||||
ToastService.wallpaperErrorStatus = "matugen_missing"
|
||||
ToastService.showWarning("matugen not found - dynamic theming disabled")
|
||||
return
|
||||
}
|
||||
|
||||
// If extraction was requested, continue the process
|
||||
if (extractionRequested) {
|
||||
console.log("Continuing with color extraction")
|
||||
fileChecker.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fileChecker // exists & readable?
|
||||
command: ["test", "-r", wallpaperPath]
|
||||
onExited: (code) => {
|
||||
if (code === 0) {
|
||||
matugenProcess.running = true
|
||||
} else {
|
||||
console.error("code", code)
|
||||
console.error("Wallpaper not found:", wallpaperPath)
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Wallpaper processing failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ──────────────── matugen invocation ──────────────── */
|
||||
Process {
|
||||
id: matugenProcess
|
||||
command: ["matugen", "-v", "image", wallpaperPath, "--json", "hex"]
|
||||
|
||||
/* ── grab stdout as a stream ── */
|
||||
stdout: StdioCollector {
|
||||
id: matugenCollector
|
||||
onStreamFinished: {
|
||||
const out = matugenCollector.text
|
||||
if (!out.length) {
|
||||
console.error("matugen produced zero bytes\nstderr:", matugenProcess.stderr)
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Wallpaper processing failed")
|
||||
return
|
||||
}
|
||||
try {
|
||||
root.matugenJson = out
|
||||
root.matugenColors = JSON.parse(out)
|
||||
root.colorsUpdated()
|
||||
ToastService.clearWallpaperError()
|
||||
ToastService.showInfo("Dynamic theme colors updated")
|
||||
} catch (e) {
|
||||
console.error("JSON parse failed:", e)
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Wallpaper processing failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* grab stderr too, so we can print it above */
|
||||
stderr: StdioCollector { id: matugenErr }
|
||||
}
|
||||
|
||||
/* ──────────────── wallpaper change monitor ──────────────── */
|
||||
// ──────────────── wallpaper change monitor ────────────────
|
||||
property string lastWallpaperTimestamp: ""
|
||||
|
||||
|
||||
/* ──────────────── public helper ──────────────── */
|
||||
function extractColors() {
|
||||
console.log("Colors.extractColors() called, matugenAvailable:", matugenAvailable)
|
||||
extractionRequested = true
|
||||
if (matugenAvailable)
|
||||
fileChecker.running = true
|
||||
else
|
||||
matugenCheck.running = true
|
||||
}
|
||||
|
||||
function getMatugenColor(path, fallback) {
|
||||
// Include colorUpdateTrigger in the function to make properties reactive to changes
|
||||
colorUpdateTrigger // This dependency forces re-evaluation when colorUpdateTrigger changes
|
||||
|
||||
// Use light or dark colors based on Theme.isLightMode
|
||||
const colorMode = (typeof Theme !== "undefined" && Theme.isLightMode) ? "light" : "dark"
|
||||
let cur = matugenColors?.colors?.[colorMode]
|
||||
for (const part of path.split(".")) {
|
||||
if (!cur || typeof cur !== "object" || !(part in cur))
|
||||
return fallback
|
||||
cur = cur[part]
|
||||
}
|
||||
return cur || fallback
|
||||
}
|
||||
|
||||
/* ──────────────── color properties (MD3) ──────────────── */
|
||||
// ──────────────── color properties (MD3) ────────────────
|
||||
property color primary: getMatugenColor("primary", "#42a5f5")
|
||||
property color secondary: getMatugenColor("secondary", "#8ab4f8")
|
||||
property color tertiary: getMatugenColor("tertiary", "#bb86fc")
|
||||
property color tertiaryContainer: getMatugenColor("tertiary_container", "#3700b3")
|
||||
property color error: getMatugenColor("error", "#cf6679")
|
||||
property color inversePrimary: getMatugenColor("inverse_primary", "#6200ea")
|
||||
|
||||
/* backgrounds */
|
||||
// backgrounds
|
||||
property color bg: getMatugenColor("background", "#1a1c1e")
|
||||
property color surface: getMatugenColor("surface", "#1a1c1e")
|
||||
property color surfaceContainer: getMatugenColor("surface_container", "#1e2023")
|
||||
property color surfaceContainerHigh: getMatugenColor("surface_container_high", "#292b2f")
|
||||
property color surfaceVariant: getMatugenColor("surface_variant", "#44464f")
|
||||
|
||||
/* text */
|
||||
// text
|
||||
property color surfaceText: getMatugenColor("on_background", "#e3e8ef")
|
||||
property color primaryText: getMatugenColor("on_primary", "#ffffff")
|
||||
property color surfaceVariantText: getMatugenColor("on_surface_variant", "#c4c7c5")
|
||||
|
||||
/* containers & misc */
|
||||
// containers & misc
|
||||
property color primaryContainer: getMatugenColor("primary_container", "#1976d2")
|
||||
property color surfaceTint: getMatugenColor("surface_tint", "#8ab4f8")
|
||||
property color outline: getMatugenColor("outline", "#8e918f")
|
||||
|
||||
/* legacy aliases */
|
||||
// legacy aliases
|
||||
property color accentHi: primary
|
||||
property color accentLo: secondary
|
||||
|
||||
function isColorDark(c) {
|
||||
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5
|
||||
// ──────────────── basic state ────────────────
|
||||
signal colorsUpdated()
|
||||
|
||||
function onLightModeChanged() {
|
||||
// Force color properties to update when light mode changes
|
||||
if (matugenColors && Object.keys(matugenColors).length > 0) {
|
||||
console.log("Light mode changed - updating dynamic colors");
|
||||
colorUpdateTrigger++; // This will trigger re-evaluation of all color properties
|
||||
colorsUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────── public helper ────────────────
|
||||
function extractColors() {
|
||||
console.log("Colors.extractColors() called, matugenAvailable:", matugenAvailable);
|
||||
extractionRequested = true;
|
||||
if (matugenAvailable)
|
||||
fileChecker.running = true;
|
||||
else
|
||||
matugenCheck.running = true;
|
||||
}
|
||||
|
||||
function getMatugenColor(path, fallback) {
|
||||
// Include colorUpdateTrigger in the function to make properties reactive to changes
|
||||
colorUpdateTrigger;
|
||||
// Use light or dark colors based on Theme.isLightMode
|
||||
const colorMode = (typeof Theme !== "undefined" && Theme.isLightMode) ? "light" : "dark";
|
||||
let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode];
|
||||
for (const part of path.split(".")) {
|
||||
if (!cur || typeof cur !== "object" || !(part in cur))
|
||||
return fallback;
|
||||
|
||||
cur = cur[part];
|
||||
}
|
||||
return cur || fallback;
|
||||
}
|
||||
|
||||
function isColorDark(c) {
|
||||
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("Colors.qml → home =", homeDir);
|
||||
// Don't automatically run color extraction - only when requested
|
||||
matugenCheck.running = true;
|
||||
// Connect to Theme light mode changes to update colors
|
||||
if (typeof Theme !== "undefined")
|
||||
Theme.isLightModeChanged.connect(root.onLightModeChanged);
|
||||
|
||||
}
|
||||
|
||||
// ──────────────── availability checks ────────────────
|
||||
Process {
|
||||
id: matugenCheck
|
||||
|
||||
command: ["which", "matugen"]
|
||||
onExited: (code) => {
|
||||
matugenAvailable = (code === 0);
|
||||
console.log("Matugen in PATH:", matugenAvailable);
|
||||
if (!matugenAvailable) {
|
||||
console.warn("Matugen missing → dynamic theme disabled");
|
||||
ToastService.wallpaperErrorStatus = "matugen_missing";
|
||||
ToastService.showWarning("matugen not found - dynamic theming disabled");
|
||||
return ;
|
||||
}
|
||||
// If extraction was requested, continue the process
|
||||
if (extractionRequested) {
|
||||
console.log("Continuing with color extraction");
|
||||
fileChecker.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fileChecker // exists & readable?
|
||||
|
||||
command: ["test", "-r", wallpaperPath]
|
||||
onExited: (code) => {
|
||||
if (code === 0) {
|
||||
matugenProcess.running = true;
|
||||
} else {
|
||||
console.error("code", code);
|
||||
console.error("Wallpaper not found:", wallpaperPath);
|
||||
ToastService.wallpaperErrorStatus = "error";
|
||||
ToastService.showError("Wallpaper processing failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────── matugen invocation ────────────────
|
||||
Process {
|
||||
id: matugenProcess
|
||||
|
||||
command: ["matugen", "-v", "image", wallpaperPath, "--json", "hex"]
|
||||
|
||||
// ── grab stdout as a stream ──
|
||||
stdout: StdioCollector {
|
||||
id: matugenCollector
|
||||
|
||||
onStreamFinished: {
|
||||
const out = matugenCollector.text;
|
||||
if (!out.length) {
|
||||
console.error("matugen produced zero bytes\nstderr:", matugenProcess.stderr);
|
||||
ToastService.wallpaperErrorStatus = "error";
|
||||
ToastService.showError("Wallpaper Processing Failed");
|
||||
return ;
|
||||
}
|
||||
try {
|
||||
root.matugenJson = out;
|
||||
root.matugenColors = JSON.parse(out);
|
||||
root.colorsUpdated();
|
||||
ToastService.clearWallpaperError();
|
||||
ToastService.showInfo("Loaded Dynamic Theme Colors");
|
||||
} catch (e) {
|
||||
console.error("JSON parse failed:", e);
|
||||
ToastService.wallpaperErrorStatus = "error";
|
||||
ToastService.showError("Wallpaper Processing Failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// grab stderr too, so we can print it above
|
||||
stderr: StdioCollector {
|
||||
id: matugenErr
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
355
Common/Prefs.qml
355
Common/Prefs.qml
@@ -1,10 +1,15 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
// "auto", "wifi", "ethernet"
|
||||
// Alphabetical tiebreaker
|
||||
|
||||
id: root
|
||||
|
||||
property int themeIndex: 0
|
||||
@@ -13,14 +18,12 @@ Singleton {
|
||||
property real topBarTransparency: 0.75
|
||||
property real popupTransparency: 0.92
|
||||
property var recentlyUsedApps: []
|
||||
|
||||
// New global preferences
|
||||
property bool use24HourClock: true
|
||||
property bool useFahrenheit: false
|
||||
property bool nightModeEnabled: false
|
||||
property string profileImage: ""
|
||||
property string weatherLocationOverride: "New York, NY"
|
||||
|
||||
// Widget visibility preferences for TopBar
|
||||
property bool showFocusedWindow: true
|
||||
property bool showWeather: true
|
||||
@@ -28,294 +31,284 @@ Singleton {
|
||||
property bool showClipboard: true
|
||||
property bool showSystemResources: true
|
||||
property bool showSystemTray: true
|
||||
|
||||
// View mode preferences for launchers
|
||||
property string appLauncherViewMode: "list"
|
||||
property string spotlightLauncherViewMode: "list"
|
||||
|
||||
// Network preference
|
||||
property string networkPreference: "auto" // "auto", "wifi", "ethernet"
|
||||
|
||||
|
||||
Component.onCompleted: loadSettings()
|
||||
|
||||
// Monitor system resources preference changes to control service monitoring
|
||||
onShowSystemResourcesChanged: {
|
||||
console.log("Prefs: System resources monitoring", showSystemResources ? "enabled" : "disabled")
|
||||
// Control SystemMonitorService based on whether system monitor widgets are visible
|
||||
if (typeof SystemMonitorService !== 'undefined') {
|
||||
SystemMonitorService.enableTopBarMonitoring(showSystemResources)
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: settingsFile
|
||||
path: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
watchChanges: true
|
||||
|
||||
onLoaded: {
|
||||
console.log("Settings file loaded successfully")
|
||||
parseSettings(settingsFile.text())
|
||||
}
|
||||
|
||||
onLoadFailed: (error) => {
|
||||
console.log("Settings file not found, using defaults. Error:", error)
|
||||
applyStoredTheme()
|
||||
}
|
||||
}
|
||||
property string networkPreference: "auto"
|
||||
|
||||
function loadSettings() {
|
||||
parseSettings(settingsFile.text())
|
||||
parseSettings(settingsFile.text());
|
||||
}
|
||||
|
||||
function parseSettings(content) {
|
||||
try {
|
||||
if (content && content.trim()) {
|
||||
var settings = JSON.parse(content)
|
||||
themeIndex = settings.themeIndex !== undefined ? settings.themeIndex : 0
|
||||
themeIsDynamic = settings.themeIsDynamic !== undefined ? settings.themeIsDynamic : false
|
||||
isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false
|
||||
topBarTransparency = settings.topBarTransparency !== undefined ?
|
||||
(settings.topBarTransparency > 1 ? settings.topBarTransparency / 100.0 : settings.topBarTransparency) : 0.75
|
||||
popupTransparency = settings.popupTransparency !== undefined ?
|
||||
(settings.popupTransparency > 1 ? settings.popupTransparency / 100.0 : settings.popupTransparency) : 0.92
|
||||
recentlyUsedApps = settings.recentlyUsedApps || []
|
||||
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true
|
||||
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false
|
||||
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false
|
||||
profileImage = settings.profileImage !== undefined ? settings.profileImage : ""
|
||||
weatherLocationOverride = settings.weatherLocationOverride !== undefined ? settings.weatherLocationOverride : "New York, NY"
|
||||
showFocusedWindow = settings.showFocusedWindow !== undefined ? settings.showFocusedWindow : true
|
||||
showWeather = settings.showWeather !== undefined ? settings.showWeather : true
|
||||
showMusic = settings.showMusic !== undefined ? settings.showMusic : true
|
||||
showClipboard = settings.showClipboard !== undefined ? settings.showClipboard : true
|
||||
showSystemResources = settings.showSystemResources !== undefined ? settings.showSystemResources : true
|
||||
showSystemTray = settings.showSystemTray !== undefined ? settings.showSystemTray : true
|
||||
appLauncherViewMode = settings.appLauncherViewMode !== undefined ? settings.appLauncherViewMode : "list"
|
||||
spotlightLauncherViewMode = settings.spotlightLauncherViewMode !== undefined ? settings.spotlightLauncherViewMode : "list"
|
||||
networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto"
|
||||
console.log("Loaded settings - themeIndex:", themeIndex, "isDynamic:", themeIsDynamic, "lightMode:", isLightMode, "transparency:", topBarTransparency, "recentApps:", recentlyUsedApps.length)
|
||||
|
||||
applyStoredTheme()
|
||||
var settings = JSON.parse(content);
|
||||
themeIndex = settings.themeIndex !== undefined ? settings.themeIndex : 0;
|
||||
themeIsDynamic = settings.themeIsDynamic !== undefined ? settings.themeIsDynamic : false;
|
||||
isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false;
|
||||
topBarTransparency = settings.topBarTransparency !== undefined ? (settings.topBarTransparency > 1 ? settings.topBarTransparency / 100 : settings.topBarTransparency) : 0.75;
|
||||
popupTransparency = settings.popupTransparency !== undefined ? (settings.popupTransparency > 1 ? settings.popupTransparency / 100 : settings.popupTransparency) : 0.92;
|
||||
recentlyUsedApps = settings.recentlyUsedApps || [];
|
||||
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true;
|
||||
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false;
|
||||
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false;
|
||||
profileImage = settings.profileImage !== undefined ? settings.profileImage : "";
|
||||
weatherLocationOverride = settings.weatherLocationOverride !== undefined ? settings.weatherLocationOverride : "New York, NY";
|
||||
showFocusedWindow = settings.showFocusedWindow !== undefined ? settings.showFocusedWindow : true;
|
||||
showWeather = settings.showWeather !== undefined ? settings.showWeather : true;
|
||||
showMusic = settings.showMusic !== undefined ? settings.showMusic : true;
|
||||
showClipboard = settings.showClipboard !== undefined ? settings.showClipboard : true;
|
||||
showSystemResources = settings.showSystemResources !== undefined ? settings.showSystemResources : true;
|
||||
showSystemTray = settings.showSystemTray !== undefined ? settings.showSystemTray : true;
|
||||
appLauncherViewMode = settings.appLauncherViewMode !== undefined ? settings.appLauncherViewMode : "list";
|
||||
spotlightLauncherViewMode = settings.spotlightLauncherViewMode !== undefined ? settings.spotlightLauncherViewMode : "list";
|
||||
networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto";
|
||||
console.log("Loaded settings - themeIndex:", themeIndex, "isDynamic:", themeIsDynamic, "lightMode:", isLightMode, "transparency:", topBarTransparency, "recentApps:", recentlyUsedApps.length);
|
||||
applyStoredTheme();
|
||||
} else {
|
||||
console.log("Settings file is empty - applying default theme")
|
||||
applyStoredTheme()
|
||||
console.log("Settings file is empty - applying default theme");
|
||||
applyStoredTheme();
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Could not parse settings, using defaults:", e)
|
||||
applyStoredTheme()
|
||||
console.log("Could not parse settings, using defaults:", e);
|
||||
applyStoredTheme();
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
settingsFile.setText(JSON.stringify({
|
||||
themeIndex,
|
||||
themeIsDynamic,
|
||||
isLightMode,
|
||||
topBarTransparency,
|
||||
popupTransparency,
|
||||
recentlyUsedApps,
|
||||
use24HourClock,
|
||||
useFahrenheit,
|
||||
nightModeEnabled,
|
||||
profileImage,
|
||||
weatherLocationOverride,
|
||||
showFocusedWindow,
|
||||
showWeather,
|
||||
showMusic,
|
||||
showClipboard,
|
||||
showSystemResources,
|
||||
showSystemTray,
|
||||
appLauncherViewMode,
|
||||
spotlightLauncherViewMode,
|
||||
networkPreference
|
||||
}, null, 2))
|
||||
console.log("Saving settings - themeIndex:", themeIndex, "isDynamic:", themeIsDynamic, "lightMode:", isLightMode, "transparency:", topBarTransparency, "recentApps:", recentlyUsedApps.length)
|
||||
"themeIndex": themeIndex,
|
||||
"themeIsDynamic": themeIsDynamic,
|
||||
"isLightMode": isLightMode,
|
||||
"topBarTransparency": topBarTransparency,
|
||||
"popupTransparency": popupTransparency,
|
||||
"recentlyUsedApps": recentlyUsedApps,
|
||||
"use24HourClock": use24HourClock,
|
||||
"useFahrenheit": useFahrenheit,
|
||||
"nightModeEnabled": nightModeEnabled,
|
||||
"profileImage": profileImage,
|
||||
"weatherLocationOverride": weatherLocationOverride,
|
||||
"showFocusedWindow": showFocusedWindow,
|
||||
"showWeather": showWeather,
|
||||
"showMusic": showMusic,
|
||||
"showClipboard": showClipboard,
|
||||
"showSystemResources": showSystemResources,
|
||||
"showSystemTray": showSystemTray,
|
||||
"appLauncherViewMode": appLauncherViewMode,
|
||||
"spotlightLauncherViewMode": spotlightLauncherViewMode,
|
||||
"networkPreference": networkPreference
|
||||
}, null, 2));
|
||||
console.log("Saving settings - themeIndex:", themeIndex, "isDynamic:", themeIsDynamic, "lightMode:", isLightMode, "transparency:", topBarTransparency, "recentApps:", recentlyUsedApps.length);
|
||||
}
|
||||
|
||||
function applyStoredTheme() {
|
||||
console.log("Applying stored theme:", themeIndex, themeIsDynamic, "lightMode:", isLightMode)
|
||||
|
||||
console.log("Applying stored theme:", themeIndex, themeIsDynamic, "lightMode:", isLightMode);
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.isLightMode = isLightMode
|
||||
Theme.switchTheme(themeIndex, themeIsDynamic, false)
|
||||
Theme.isLightMode = isLightMode;
|
||||
Theme.switchTheme(themeIndex, themeIsDynamic, false);
|
||||
} else {
|
||||
Qt.callLater(() => {
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.isLightMode = isLightMode
|
||||
Theme.switchTheme(themeIndex, themeIsDynamic, false)
|
||||
Theme.isLightMode = isLightMode;
|
||||
Theme.switchTheme(themeIndex, themeIsDynamic, false);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setTheme(index, isDynamic) {
|
||||
console.log("Prefs setTheme called - themeIndex:", index, "isDynamic:", isDynamic)
|
||||
themeIndex = index
|
||||
themeIsDynamic = isDynamic
|
||||
saveSettings()
|
||||
console.log("Prefs setTheme called - themeIndex:", index, "isDynamic:", isDynamic);
|
||||
themeIndex = index;
|
||||
themeIsDynamic = isDynamic;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setLightMode(lightMode) {
|
||||
console.log("Prefs setLightMode called - isLightMode:", lightMode)
|
||||
isLightMode = lightMode
|
||||
saveSettings()
|
||||
console.log("Prefs setLightMode called - isLightMode:", lightMode);
|
||||
isLightMode = lightMode;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setTopBarTransparency(transparency) {
|
||||
console.log("Prefs setTopBarTransparency called - topBarTransparency:", transparency)
|
||||
topBarTransparency = transparency
|
||||
saveSettings()
|
||||
console.log("Prefs setTopBarTransparency called - topBarTransparency:", transparency);
|
||||
topBarTransparency = transparency;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setPopupTransparency(transparency) {
|
||||
console.log("Prefs setPopupTransparency called - popupTransparency:", transparency)
|
||||
popupTransparency = transparency
|
||||
saveSettings()
|
||||
console.log("Prefs setPopupTransparency called - popupTransparency:", transparency);
|
||||
popupTransparency = transparency;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function addRecentApp(app) {
|
||||
if (!app) return
|
||||
if (!app)
|
||||
return ;
|
||||
|
||||
var execProp = app.execString || app.exec || ""
|
||||
if (!execProp) return
|
||||
var execProp = app.execString || app.exec || "";
|
||||
if (!execProp)
|
||||
return ;
|
||||
|
||||
var existingIndex = -1
|
||||
var existingIndex = -1;
|
||||
for (var i = 0; i < recentlyUsedApps.length; i++) {
|
||||
if (recentlyUsedApps[i].exec === execProp) {
|
||||
existingIndex = i
|
||||
break
|
||||
existingIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// App exists, increment usage count
|
||||
recentlyUsedApps[existingIndex].usageCount = (recentlyUsedApps[existingIndex].usageCount || 1) + 1
|
||||
recentlyUsedApps[existingIndex].lastUsed = Date.now()
|
||||
recentlyUsedApps[existingIndex].usageCount = (recentlyUsedApps[existingIndex].usageCount || 1) + 1;
|
||||
recentlyUsedApps[existingIndex].lastUsed = Date.now();
|
||||
} else {
|
||||
// New app, create entry
|
||||
var appData = {
|
||||
name: app.name || "",
|
||||
exec: execProp,
|
||||
icon: app.icon || "application-x-executable",
|
||||
comment: app.comment || "",
|
||||
usageCount: 1,
|
||||
lastUsed: Date.now()
|
||||
"name": app.name || "",
|
||||
"exec": execProp,
|
||||
"icon": app.icon || "application-x-executable",
|
||||
"comment": app.comment || "",
|
||||
"usageCount": 1,
|
||||
"lastUsed": Date.now()
|
||||
};
|
||||
recentlyUsedApps.push(appData);
|
||||
}
|
||||
recentlyUsedApps.push(appData)
|
||||
}
|
||||
|
||||
// Sort by usage count (descending), then alphabetically by name
|
||||
var sortedApps = recentlyUsedApps.sort(function(a, b) {
|
||||
if (a.usageCount !== b.usageCount) {
|
||||
return b.usageCount - a.usageCount // Higher usage count first
|
||||
}
|
||||
return a.name.localeCompare(b.name) // Alphabetical tiebreaker
|
||||
})
|
||||
if (a.usageCount !== b.usageCount)
|
||||
return b.usageCount - a.usageCount;
|
||||
|
||||
// Higher usage count first
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
// Limit to 10 apps
|
||||
if (sortedApps.length > 10) {
|
||||
sortedApps = sortedApps.slice(0, 10)
|
||||
}
|
||||
if (sortedApps.length > 10)
|
||||
sortedApps = sortedApps.slice(0, 10);
|
||||
|
||||
// Reassign to trigger property change signal
|
||||
recentlyUsedApps = sortedApps
|
||||
|
||||
saveSettings()
|
||||
recentlyUsedApps = sortedApps;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function getRecentApps() {
|
||||
return recentlyUsedApps
|
||||
return recentlyUsedApps;
|
||||
}
|
||||
|
||||
// New preference setters
|
||||
function setClockFormat(use24Hour) {
|
||||
console.log("Prefs setClockFormat called - use24HourClock:", use24Hour)
|
||||
use24HourClock = use24Hour
|
||||
saveSettings()
|
||||
console.log("Prefs setClockFormat called - use24HourClock:", use24Hour);
|
||||
use24HourClock = use24Hour;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setTemperatureUnit(fahrenheit) {
|
||||
console.log("Prefs setTemperatureUnit called - useFahrenheit:", fahrenheit)
|
||||
useFahrenheit = fahrenheit
|
||||
saveSettings()
|
||||
console.log("Prefs setTemperatureUnit called - useFahrenheit:", fahrenheit);
|
||||
useFahrenheit = fahrenheit;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setNightModeEnabled(enabled) {
|
||||
console.log("Prefs setNightModeEnabled called - nightModeEnabled:", enabled)
|
||||
nightModeEnabled = enabled
|
||||
saveSettings()
|
||||
console.log("Prefs setNightModeEnabled called - nightModeEnabled:", enabled);
|
||||
nightModeEnabled = enabled;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setProfileImage(imageUrl) {
|
||||
console.log("Prefs setProfileImage called - profileImage:", imageUrl)
|
||||
profileImage = imageUrl
|
||||
saveSettings()
|
||||
console.log("Prefs setProfileImage called - profileImage:", imageUrl);
|
||||
profileImage = imageUrl;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
// Widget visibility setters
|
||||
function setShowFocusedWindow(enabled) {
|
||||
console.log("Prefs setShowFocusedWindow called - showFocusedWindow:", enabled)
|
||||
showFocusedWindow = enabled
|
||||
saveSettings()
|
||||
console.log("Prefs setShowFocusedWindow called - showFocusedWindow:", enabled);
|
||||
showFocusedWindow = enabled;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setShowWeather(enabled) {
|
||||
console.log("Prefs setShowWeather called - showWeather:", enabled)
|
||||
showWeather = enabled
|
||||
saveSettings()
|
||||
console.log("Prefs setShowWeather called - showWeather:", enabled);
|
||||
showWeather = enabled;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setShowMusic(enabled) {
|
||||
console.log("Prefs setShowMusic called - showMusic:", enabled)
|
||||
showMusic = enabled
|
||||
saveSettings()
|
||||
console.log("Prefs setShowMusic called - showMusic:", enabled);
|
||||
showMusic = enabled;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setShowClipboard(enabled) {
|
||||
console.log("Prefs setShowClipboard called - showClipboard:", enabled)
|
||||
showClipboard = enabled
|
||||
saveSettings()
|
||||
console.log("Prefs setShowClipboard called - showClipboard:", enabled);
|
||||
showClipboard = enabled;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setShowSystemResources(enabled) {
|
||||
console.log("Prefs setShowSystemResources called - showSystemResources:", enabled)
|
||||
showSystemResources = enabled
|
||||
saveSettings()
|
||||
console.log("Prefs setShowSystemResources called - showSystemResources:", enabled);
|
||||
showSystemResources = enabled;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setShowSystemTray(enabled) {
|
||||
console.log("Prefs setShowSystemTray called - showSystemTray:", enabled)
|
||||
showSystemTray = enabled
|
||||
saveSettings()
|
||||
console.log("Prefs setShowSystemTray called - showSystemTray:", enabled);
|
||||
showSystemTray = enabled;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
// View mode setters
|
||||
function setAppLauncherViewMode(mode) {
|
||||
console.log("Prefs setAppLauncherViewMode called - appLauncherViewMode:", mode)
|
||||
appLauncherViewMode = mode
|
||||
saveSettings()
|
||||
console.log("Prefs setAppLauncherViewMode called - appLauncherViewMode:", mode);
|
||||
appLauncherViewMode = mode;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setSpotlightLauncherViewMode(mode) {
|
||||
console.log("Prefs setSpotlightLauncherViewMode called - spotlightLauncherViewMode:", mode)
|
||||
spotlightLauncherViewMode = mode
|
||||
saveSettings()
|
||||
console.log("Prefs setSpotlightLauncherViewMode called - spotlightLauncherViewMode:", mode);
|
||||
spotlightLauncherViewMode = mode;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
// Weather location override setter
|
||||
function setWeatherLocationOverride(location) {
|
||||
console.log("Prefs setWeatherLocationOverride called - weatherLocationOverride:", location)
|
||||
weatherLocationOverride = location
|
||||
saveSettings()
|
||||
console.log("Prefs setWeatherLocationOverride called - weatherLocationOverride:", location);
|
||||
weatherLocationOverride = location;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
// Network preference setter
|
||||
function setNetworkPreference(preference) {
|
||||
console.log("Prefs setNetworkPreference called - networkPreference:", preference)
|
||||
networkPreference = preference
|
||||
saveSettings()
|
||||
console.log("Prefs setNetworkPreference called - networkPreference:", preference);
|
||||
networkPreference = preference;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
Component.onCompleted: loadSettings()
|
||||
// Monitor system resources preference changes to control service monitoring
|
||||
onShowSystemResourcesChanged: {
|
||||
console.log("Prefs: System resources monitoring", showSystemResources ? "enabled" : "disabled");
|
||||
// Control SystemMonitorService based on whether system monitor widgets are visible
|
||||
if (typeof SystemMonitorService !== 'undefined')
|
||||
SystemMonitorService.enableTopBarMonitoring(showSystemResources);
|
||||
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: settingsFile
|
||||
|
||||
path: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
watchChanges: true
|
||||
onLoaded: {
|
||||
console.log("Settings file loaded successfully");
|
||||
parseSettings(settingsFile.text());
|
||||
}
|
||||
onLoadFailed: (error) => {
|
||||
console.log("Settings file not found, using defaults. Error:", error);
|
||||
applyStoredTheme();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
|
||||
@@ -1,40 +1,21 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Widgets
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
|
||||
|
||||
property MprisPlayer activePlayer: null
|
||||
property MprisPlayer _candidatePlayer: availablePlayers.find(p => p.isPlaying)
|
||||
property MprisPlayer activePlayer: availablePlayers.find(p => p.isPlaying)
|
||||
?? availablePlayers.find(p => p.canControl && p.canPlay)
|
||||
?? null
|
||||
|
||||
Timer {
|
||||
id: playerSwitchTimer
|
||||
interval: 300
|
||||
onTriggered: {
|
||||
if (_candidatePlayer !== activePlayer) {
|
||||
activePlayer = _candidatePlayer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
on_CandidatePlayerChanged: {
|
||||
if (_candidatePlayer === null && activePlayer !== null) {
|
||||
playerSwitchTimer.restart()
|
||||
} else if (_candidatePlayer !== null) {
|
||||
playerSwitchTimer.stop()
|
||||
activePlayer = _candidatePlayer
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "mpris"
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
import
|
||||
QtQuick
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
@@ -13,7 +13,7 @@ Singleton {
|
||||
property var wifiNetworks: []
|
||||
property var savedWifiNetworks: []
|
||||
property bool isScanning: false
|
||||
property string connectionStatus: "" // "connecting", "connected", "failed", ""
|
||||
property string connectionStatus: "" // "cosnnecting", "connected", "failed", ""
|
||||
property string connectingSSID: ""
|
||||
|
||||
Process {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,42 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Services.UPower
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import Quickshell.Services.UPower
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
property bool batteryPopupVisible: false
|
||||
|
||||
visible: batteryPopupVisible
|
||||
function isActiveProfile(profile) {
|
||||
if (typeof PowerProfiles === "undefined")
|
||||
return false;
|
||||
|
||||
return PowerProfiles.profile === profile;
|
||||
}
|
||||
|
||||
function setProfile(profile) {
|
||||
if (typeof PowerProfiles === "undefined") {
|
||||
errorToast.show();
|
||||
return ;
|
||||
}
|
||||
PowerProfiles.profile = profile;
|
||||
if (PowerProfiles.profile !== profile)
|
||||
errorToast.show();
|
||||
else
|
||||
console.log("Set power profile to: " + PowerProfile.toString(profile));
|
||||
}
|
||||
|
||||
visible: batteryPopupVisible
|
||||
implicitWidth: 400
|
||||
implicitHeight: 300
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
@@ -34,7 +50,7 @@ PanelWindow {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
batteryPopupVisible = false
|
||||
batteryPopupVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,29 +63,15 @@ PanelWindow {
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
opacity: batteryPopupVisible ? 1.0 : 0.0
|
||||
scale: batteryPopupVisible ? 1.0 : 0.85
|
||||
opacity: batteryPopupVisible ? 1 : 0
|
||||
scale: batteryPopupVisible ? 1 : 0.85
|
||||
|
||||
// Prevent click-through to background
|
||||
MouseArea {
|
||||
// Consume the click to prevent it from reaching the background
|
||||
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
// Consume the click to prevent it from reaching the background
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +96,10 @@ PanelWindow {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item { width: parent.width - 200; height: 1 }
|
||||
Item {
|
||||
width: parent.width - 200
|
||||
height: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32
|
||||
@@ -112,14 +117,17 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: closeBatteryArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
batteryPopupVisible = false
|
||||
batteryPopupVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -142,9 +150,13 @@ PanelWindow {
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSizeLarge
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error
|
||||
if (BatteryService.isCharging) return Theme.primary
|
||||
return Theme.surfaceText
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||
return Theme.error;
|
||||
|
||||
if (BatteryService.isCharging)
|
||||
return Theme.primary;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
@@ -160,9 +172,13 @@ PanelWindow {
|
||||
text: BatteryService.batteryLevel + "%"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error
|
||||
if (BatteryService.isCharging) return Theme.primary
|
||||
return Theme.surfaceText
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||
return Theme.error;
|
||||
|
||||
if (BatteryService.isCharging)
|
||||
return Theme.primary;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
@@ -171,29 +187,37 @@ PanelWindow {
|
||||
text: BatteryService.batteryStatus
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error
|
||||
if (BatteryService.isCharging) return Theme.primary
|
||||
return Theme.surfaceText
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
let time = BatteryService.formatTimeRemaining()
|
||||
if (time !== "Unknown") {
|
||||
return BatteryService.isCharging ? "Time until full: " + time : "Time remaining: " + time
|
||||
}
|
||||
return ""
|
||||
let time = BatteryService.formatTimeRemaining();
|
||||
if (time !== "Unknown")
|
||||
return BatteryService.isCharging ? "Time until full: " + time : "Time remaining: " + time;
|
||||
|
||||
return "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No battery info card
|
||||
@@ -234,8 +258,11 @@ PanelWindow {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Battery details
|
||||
@@ -271,11 +298,14 @@ PanelWindow {
|
||||
text: BatteryService.batteryHealth
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: {
|
||||
if (BatteryService.batteryHealth === "N/A") return Theme.surfaceText
|
||||
var healthNum = parseInt(BatteryService.batteryHealth)
|
||||
return healthNum < 80 ? Theme.error : Theme.surfaceText
|
||||
if (BatteryService.batteryHealth === "N/A")
|
||||
return Theme.surfaceText;
|
||||
|
||||
var healthNum = parseInt(BatteryService.batteryHealth);
|
||||
return healthNum < 80 ? Theme.error : Theme.surfaceText;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Capacity
|
||||
@@ -295,8 +325,11 @@ PanelWindow {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Power profiles
|
||||
@@ -317,16 +350,13 @@ PanelWindow {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: (typeof PowerProfiles !== "undefined") ?
|
||||
[PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) :
|
||||
[PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
||||
model: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: profileArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
(batteryControlPopup.isActiveProfile(modelData) ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
color: profileArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (batteryControlPopup.isActiveProfile(modelData) ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: batteryControlPopup.isActiveProfile(modelData) ? Theme.primary : "transparent"
|
||||
border.width: 2
|
||||
|
||||
@@ -360,22 +390,28 @@ PanelWindow {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: profileArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
batteryControlPopup.setProfile(modelData)
|
||||
batteryControlPopup.setProfile(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Degradation reason warning
|
||||
@@ -418,16 +454,44 @@ PanelWindow {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.8)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Error toast
|
||||
Rectangle {
|
||||
id: errorToast
|
||||
|
||||
function show() {
|
||||
visible = true;
|
||||
hideTimer.restart();
|
||||
}
|
||||
|
||||
width: Math.min(300, parent.width - Theme.spacingL * 2)
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
@@ -448,34 +512,11 @@ PanelWindow {
|
||||
|
||||
Timer {
|
||||
id: hideTimer
|
||||
|
||||
interval: 3000
|
||||
onTriggered: errorToast.visible = false
|
||||
}
|
||||
|
||||
function show() {
|
||||
visible = true
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
function isActiveProfile(profile) {
|
||||
if (typeof PowerProfiles === "undefined") return false
|
||||
return PowerProfiles.profile === profile
|
||||
}
|
||||
|
||||
function setProfile(profile) {
|
||||
if (typeof PowerProfiles === "undefined") {
|
||||
errorToast.show()
|
||||
return
|
||||
}
|
||||
|
||||
PowerProfiles.profile = profile
|
||||
|
||||
if (PowerProfiles.profile !== profile) {
|
||||
errorToast.show()
|
||||
} else {
|
||||
console.log("Set power profile to: " + PowerProfile.toString(profile))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import Quickshell.Services.UPower
|
||||
|
||||
Rectangle {
|
||||
id: batteryWidget
|
||||
@@ -13,9 +13,7 @@ Rectangle {
|
||||
width: BatteryService.batteryAvailable ? 70 : 40
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: batteryArea.containsMouse || batteryPopupVisible ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
color: batteryArea.containsMouse || batteryPopupVisible ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
visible: true
|
||||
|
||||
Row {
|
||||
@@ -27,19 +25,37 @@ Rectangle {
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
color: {
|
||||
if (!BatteryService.batteryAvailable) return Theme.surfaceText
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error
|
||||
if (BatteryService.isCharging) return Theme.primary
|
||||
return Theme.surfaceText
|
||||
if (!BatteryService.batteryAvailable)
|
||||
return Theme.surfaceText;
|
||||
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||
return Theme.error;
|
||||
|
||||
if (BatteryService.isCharging)
|
||||
return Theme.primary;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: BatteryService.isCharging
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { to: 0.6; duration: 1000; easing.type: Easing.InOutQuad }
|
||||
NumberAnimation { to: 1.0; duration: 1000; easing.type: Easing.InOutQuad }
|
||||
|
||||
NumberAnimation {
|
||||
to: 0.6
|
||||
duration: 1000
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
to: 1
|
||||
duration: 1000
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -47,30 +63,38 @@ Rectangle {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: {
|
||||
if (!BatteryService.batteryAvailable) return Theme.surfaceText
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error
|
||||
if (BatteryService.isCharging) return Theme.primary
|
||||
return Theme.surfaceText
|
||||
if (!BatteryService.batteryAvailable)
|
||||
return Theme.surfaceText;
|
||||
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||
return Theme.error;
|
||||
|
||||
if (BatteryService.isCharging)
|
||||
return Theme.primary;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: BatteryService.batteryAvailable
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: batteryArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
toggleBatteryPopup()
|
||||
toggleBatteryPopup();
|
||||
}
|
||||
}
|
||||
|
||||
// Tooltip on hover
|
||||
Rectangle {
|
||||
id: batteryTooltip
|
||||
|
||||
width: Math.max(120, tooltipText.contentWidth + Theme.spacingM * 2)
|
||||
height: tooltipText.contentHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadius
|
||||
@@ -78,19 +102,10 @@ Rectangle {
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 1
|
||||
visible: batteryArea.containsMouse && !batteryPopupVisible
|
||||
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
opacity: batteryArea.containsMouse ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
opacity: batteryArea.containsMouse ? 1 : 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
@@ -98,31 +113,44 @@ Rectangle {
|
||||
|
||||
Text {
|
||||
id: tooltipText
|
||||
|
||||
text: {
|
||||
if (!BatteryService.batteryAvailable) {
|
||||
if (typeof PowerProfiles === "undefined") return "Power Management"
|
||||
switch(PowerProfiles.profile) {
|
||||
case PowerProfile.PowerSaver: return "Power Profile: Power Saver"
|
||||
case PowerProfile.Performance: return "Power Profile: Performance"
|
||||
default: return "Power Profile: Balanced"
|
||||
}
|
||||
}
|
||||
if (typeof PowerProfiles === "undefined")
|
||||
return "Power Management";
|
||||
|
||||
let status = BatteryService.batteryStatus
|
||||
let level = BatteryService.batteryLevel + "%"
|
||||
let time = BatteryService.formatTimeRemaining()
|
||||
|
||||
if (time !== "Unknown") {
|
||||
return status + " • " + level + " • " + time
|
||||
} else {
|
||||
return status + " • " + level
|
||||
switch (PowerProfiles.profile) {
|
||||
case PowerProfile.PowerSaver:
|
||||
return "Power Profile: Power Saver";
|
||||
case PowerProfile.Performance:
|
||||
return "Power Profile: Performance";
|
||||
default:
|
||||
return "Power Profile: Balanced";
|
||||
}
|
||||
}
|
||||
let status = BatteryService.batteryStatus;
|
||||
let level = BatteryService.batteryLevel + "%";
|
||||
let time = BatteryService.formatTimeRemaining();
|
||||
if (time !== "Unknown")
|
||||
return status + " • " + level + " • " + time;
|
||||
else
|
||||
return status + " • " + level;
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
@@ -130,5 +158,7 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,46 +10,44 @@ Column {
|
||||
property date displayDate: new Date()
|
||||
property date selectedDate: new Date()
|
||||
|
||||
spacing: Theme.spacingM
|
||||
function loadEventsForMonth() {
|
||||
if (!CalendarService || !CalendarService.khalAvailable)
|
||||
return ;
|
||||
|
||||
// Calculate date range with padding
|
||||
let firstDay = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1);
|
||||
let dayOfWeek = firstDay.getDay();
|
||||
let startDate = new Date(firstDay);
|
||||
startDate.setDate(startDate.getDate() - dayOfWeek - 7); // Extra week padding
|
||||
let lastDay = new Date(displayDate.getFullYear(), displayDate.getMonth() + 1, 0);
|
||||
let endDate = new Date(lastDay);
|
||||
endDate.setDate(endDate.getDate() + (6 - lastDay.getDay()) + 7); // Extra week padding
|
||||
CalendarService.loadEvents(startDate, endDate);
|
||||
}
|
||||
|
||||
spacing: Theme.spacingM
|
||||
// Load events when display date changes
|
||||
onDisplayDateChanged: {
|
||||
loadEventsForMonth()
|
||||
loadEventsForMonth();
|
||||
}
|
||||
Component.onCompleted: {
|
||||
console.log("CalendarWidget: Component completed, CalendarService available:", !!CalendarService);
|
||||
if (CalendarService)
|
||||
console.log("CalendarWidget: khal available:", CalendarService.khalAvailable);
|
||||
|
||||
loadEventsForMonth();
|
||||
}
|
||||
|
||||
// Load events when calendar service becomes available
|
||||
Connections {
|
||||
function onKhalAvailableChanged() {
|
||||
if (CalendarService && CalendarService.khalAvailable)
|
||||
loadEventsForMonth();
|
||||
|
||||
}
|
||||
|
||||
target: CalendarService
|
||||
enabled: CalendarService !== null
|
||||
function onKhalAvailableChanged() {
|
||||
if (CalendarService && CalendarService.khalAvailable) {
|
||||
loadEventsForMonth()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("CalendarWidget: Component completed, CalendarService available:", !!CalendarService)
|
||||
if (CalendarService) {
|
||||
console.log("CalendarWidget: khal available:", CalendarService.khalAvailable)
|
||||
}
|
||||
loadEventsForMonth()
|
||||
}
|
||||
|
||||
function loadEventsForMonth() {
|
||||
if (!CalendarService || !CalendarService.khalAvailable) return
|
||||
|
||||
// Calculate date range with padding
|
||||
let firstDay = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1)
|
||||
let dayOfWeek = firstDay.getDay()
|
||||
let startDate = new Date(firstDay)
|
||||
startDate.setDate(startDate.getDate() - dayOfWeek - 7) // Extra week padding
|
||||
|
||||
let lastDay = new Date(displayDate.getFullYear(), displayDate.getMonth() + 1, 0)
|
||||
let endDate = new Date(lastDay)
|
||||
endDate.setDate(endDate.getDate() + (6 - lastDay.getDay()) + 7) // Extra week padding
|
||||
|
||||
CalendarService.loadEvents(startDate, endDate)
|
||||
}
|
||||
|
||||
// Month navigation header
|
||||
@@ -74,16 +72,17 @@ Column {
|
||||
|
||||
MouseArea {
|
||||
id: prevMonthArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
let newDate = new Date(displayDate)
|
||||
newDate.setMonth(newDate.getMonth() - 1)
|
||||
displayDate = newDate
|
||||
let newDate = new Date(displayDate);
|
||||
newDate.setMonth(newDate.getMonth() - 1);
|
||||
displayDate = newDate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -114,17 +113,19 @@ Column {
|
||||
|
||||
MouseArea {
|
||||
id: nextMonthArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
let newDate = new Date(displayDate)
|
||||
newDate.setMonth(newDate.getMonth() + 1)
|
||||
displayDate = newDate
|
||||
let newDate = new Date(displayDate);
|
||||
newDate.setMonth(newDate.getMonth() + 1);
|
||||
displayDate = newDate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Days of week header
|
||||
@@ -147,61 +148,59 @@ Column {
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Calendar grid
|
||||
Grid {
|
||||
property date firstDay: {
|
||||
let date = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1);
|
||||
let dayOfWeek = date.getDay();
|
||||
date.setDate(date.getDate() - dayOfWeek);
|
||||
return date;
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
height: 200 // Fixed height for calendar
|
||||
columns: 7
|
||||
rows: 6
|
||||
|
||||
property date firstDay: {
|
||||
let date = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1)
|
||||
let dayOfWeek = date.getDay()
|
||||
date.setDate(date.getDate() - dayOfWeek)
|
||||
return date
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: 42
|
||||
|
||||
Rectangle {
|
||||
width: parent.width / 7
|
||||
height: parent.height / 6
|
||||
|
||||
property date dayDate: {
|
||||
let date = new Date(parent.firstDay)
|
||||
date.setDate(date.getDate() + index)
|
||||
return date
|
||||
let date = new Date(parent.firstDay);
|
||||
date.setDate(date.getDate() + index);
|
||||
return date;
|
||||
}
|
||||
|
||||
property bool isCurrentMonth: dayDate.getMonth() === displayDate.getMonth()
|
||||
property bool isToday: dayDate.toDateString() === new Date().toDateString()
|
||||
property bool isSelected: dayDate.toDateString() === selectedDate.toDateString()
|
||||
|
||||
color: isSelected ? Theme.primary :
|
||||
isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
|
||||
dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
|
||||
width: parent.width / 7
|
||||
height: parent.height / 6
|
||||
color: isSelected ? Theme.primary : isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadiusSmall
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: dayDate.getDate()
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: isSelected ? Theme.surface :
|
||||
isToday ? Theme.primary :
|
||||
isCurrentMonth ? Theme.surfaceText :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||
color: isSelected ? Theme.surface : isToday ? Theme.primary : isCurrentMonth ? Theme.surfaceText : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||
font.weight: isToday || isSelected ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
// Event indicator - full-width elegant bar
|
||||
Rectangle {
|
||||
// Use a lighter tint of primary for selected state
|
||||
|
||||
id: eventIndicator
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -209,37 +208,32 @@ Column {
|
||||
height: 3
|
||||
radius: 1.5
|
||||
visible: CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)
|
||||
|
||||
// Dynamic color based on state with opacity
|
||||
color: {
|
||||
if (isSelected) {
|
||||
// Use a lighter tint of primary for selected state
|
||||
return Qt.lighter(Theme.primary, 1.3)
|
||||
} else if (isToday) {
|
||||
return Theme.primary
|
||||
} else {
|
||||
return Theme.primary
|
||||
if (isSelected)
|
||||
return Qt.lighter(Theme.primary, 1.3);
|
||||
else if (isToday)
|
||||
return Theme.primary;
|
||||
else
|
||||
return Theme.primary;
|
||||
}
|
||||
}
|
||||
|
||||
opacity: {
|
||||
if (isSelected) {
|
||||
return 0.9
|
||||
} else if (isToday) {
|
||||
return 0.8
|
||||
} else {
|
||||
return 0.6
|
||||
if (isSelected)
|
||||
return 0.9;
|
||||
else if (isToday)
|
||||
return 0.8;
|
||||
else
|
||||
return 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
// Subtle animation on hover
|
||||
scale: dayArea.containsMouse ? 1.05 : 1.0
|
||||
scale: dayArea.containsMouse ? 1.05 : 1
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
@@ -247,6 +241,7 @@ Column {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -254,20 +249,26 @@ Column {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dayArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
selectedDate = dayDate
|
||||
selectedDate = dayDate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -15,14 +15,11 @@ PanelWindow {
|
||||
property bool calendarVisible: false
|
||||
|
||||
visible: calendarVisible
|
||||
|
||||
implicitWidth: 480
|
||||
implicitHeight: 600
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
@@ -34,54 +31,45 @@ PanelWindow {
|
||||
|
||||
Rectangle {
|
||||
id: mainContainer
|
||||
|
||||
function calculateWidth() {
|
||||
let baseWidth = 320;
|
||||
if (leftWidgets.hasAnyWidgets)
|
||||
return Math.min(parent.width * 0.9, 600);
|
||||
|
||||
return Math.min(parent.width * 0.7, 400);
|
||||
}
|
||||
|
||||
function calculateHeight() {
|
||||
let contentHeight = Theme.spacingM * 2; // margins
|
||||
// Main row with widgets and calendar
|
||||
let widgetHeight = 160;
|
||||
// Media widget always present
|
||||
widgetHeight += 140 + Theme.spacingM;
|
||||
// Weather widget always present
|
||||
let calendarHeight = 300;
|
||||
let mainRowHeight = Math.max(widgetHeight, calendarHeight);
|
||||
contentHeight += mainRowHeight + Theme.spacingM;
|
||||
// Add events widget height - use calculated height instead of actual
|
||||
if (CalendarService && CalendarService.khalAvailable) {
|
||||
let hasEvents = eventsWidget.selectedDateEvents && eventsWidget.selectedDateEvents.length > 0;
|
||||
let eventsHeight = hasEvents ? Math.min(300, 80 + eventsWidget.selectedDateEvents.length * 60) : 120;
|
||||
contentHeight += eventsHeight;
|
||||
}
|
||||
return Math.min(contentHeight, parent.height * 0.9);
|
||||
}
|
||||
|
||||
width: calculateWidth()
|
||||
height: calculateHeight()
|
||||
x: (parent.width - width) / 2
|
||||
y: Theme.barHeight + 4
|
||||
|
||||
function calculateWidth() {
|
||||
let baseWidth = 320
|
||||
if (leftWidgets.hasAnyWidgets) {
|
||||
return Math.min(parent.width * 0.9, 600)
|
||||
}
|
||||
return Math.min(parent.width * 0.7, 400)
|
||||
}
|
||||
|
||||
function calculateHeight() {
|
||||
let contentHeight = Theme.spacingM * 2 // margins
|
||||
|
||||
// Main row with widgets and calendar
|
||||
let widgetHeight = 160 // Media widget always present
|
||||
widgetHeight += 140 + Theme.spacingM // Weather widget always present
|
||||
let calendarHeight = 300
|
||||
let mainRowHeight = Math.max(widgetHeight, calendarHeight)
|
||||
|
||||
contentHeight += mainRowHeight + Theme.spacingM
|
||||
|
||||
// Add events widget height - use calculated height instead of actual
|
||||
if (CalendarService && CalendarService.khalAvailable) {
|
||||
let hasEvents = eventsWidget.selectedDateEvents && eventsWidget.selectedDateEvents.length > 0
|
||||
let eventsHeight = hasEvents ? Math.min(300, 80 + eventsWidget.selectedDateEvents.length * 60) : 120
|
||||
contentHeight += eventsHeight
|
||||
}
|
||||
|
||||
return Math.min(contentHeight, parent.height * 0.9)
|
||||
}
|
||||
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 4
|
||||
shadowBlur: 0.5
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.15)
|
||||
shadowOpacity: 0.15
|
||||
}
|
||||
opacity: calendarVisible ? 1 : 0
|
||||
scale: calendarVisible ? 1 : 0.92
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -91,62 +79,45 @@ PanelWindow {
|
||||
SequentialAnimation on opacity {
|
||||
running: calendarVisible
|
||||
loops: Animation.Infinite
|
||||
|
||||
NumberAnimation {
|
||||
to: 0.08
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
to: 0.02
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
opacity: calendarVisible ? 1.0 : 0.0
|
||||
scale: calendarVisible ? 1.0 : 0.92
|
||||
}
|
||||
|
||||
// Update height when calendar service events change
|
||||
Connections {
|
||||
function onEventsByDateChanged() {
|
||||
mainContainer.height = mainContainer.calculateHeight();
|
||||
}
|
||||
|
||||
function onKhalAvailableChanged() {
|
||||
mainContainer.height = mainContainer.calculateHeight();
|
||||
}
|
||||
|
||||
target: CalendarService
|
||||
enabled: CalendarService !== null
|
||||
function onEventsByDateChanged() {
|
||||
mainContainer.height = mainContainer.calculateHeight()
|
||||
}
|
||||
function onKhalAvailableChanged() {
|
||||
mainContainer.height = mainContainer.calculateHeight()
|
||||
}
|
||||
}
|
||||
|
||||
// Update height when events widget's selectedDateEvents changes
|
||||
Connections {
|
||||
function onSelectedDateEventsChanged() {
|
||||
mainContainer.height = mainContainer.calculateHeight();
|
||||
}
|
||||
|
||||
target: eventsWidget
|
||||
enabled: eventsWidget !== null
|
||||
function onSelectedDateEventsChanged() {
|
||||
mainContainer.height = mainContainer.calculateHeight()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.longDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.longDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -158,24 +129,25 @@ PanelWindow {
|
||||
Row {
|
||||
width: parent.width
|
||||
height: {
|
||||
let widgetHeight = 160 // Media widget always present
|
||||
widgetHeight += 140 + Theme.spacingM // Weather widget always present
|
||||
let calendarHeight = 300
|
||||
return Math.max(widgetHeight, calendarHeight)
|
||||
let widgetHeight = 160; // Media widget always present
|
||||
widgetHeight += 140 + Theme.spacingM; // Weather widget always present
|
||||
let calendarHeight = 300;
|
||||
return Math.max(widgetHeight, calendarHeight);
|
||||
}
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// Left section for widgets
|
||||
Column {
|
||||
id: leftWidgets
|
||||
|
||||
property bool hasAnyWidgets: true // Always show media widget and weather widget
|
||||
|
||||
width: hasAnyWidgets ? parent.width * 0.45 : 0
|
||||
height: childrenRect.height
|
||||
spacing: Theme.spacingM
|
||||
visible: hasAnyWidgets
|
||||
anchors.top: parent.top
|
||||
|
||||
property bool hasAnyWidgets: true // Always show media widget and weather widget
|
||||
|
||||
MediaPlayerWidget {
|
||||
visible: true // Always visible - shows placeholder when no media
|
||||
width: parent.width
|
||||
@@ -187,31 +159,70 @@ PanelWindow {
|
||||
width: parent.width
|
||||
height: 140
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Right section for calendar
|
||||
CalendarWidget {
|
||||
id: calendarWidget
|
||||
|
||||
width: leftWidgets.hasAnyWidgets ? parent.width * 0.55 - Theme.spacingL : parent.width
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Full-width events widget below
|
||||
EventsWidget {
|
||||
id: eventsWidget
|
||||
|
||||
width: parent.width
|
||||
selectedDate: calendarWidget.selectedDate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 4
|
||||
shadowBlur: 0.5
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.15)
|
||||
shadowOpacity: 0.15
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.longDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.longDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
onClicked: {
|
||||
calendarVisible = false
|
||||
calendarVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,13 +11,22 @@ Rectangle {
|
||||
property date selectedDate: new Date()
|
||||
property var selectedDateEvents: []
|
||||
property bool hasEvents: selectedDateEvents && selectedDateEvents.length > 0
|
||||
|
||||
onSelectedDateEventsChanged: {
|
||||
console.log("EventsWidget: selectedDateEvents changed, count:", selectedDateEvents.length)
|
||||
eventsList.model = selectedDateEvents
|
||||
}
|
||||
property bool shouldShow: CalendarService && CalendarService.khalAvailable
|
||||
|
||||
function updateSelectedDateEvents() {
|
||||
if (CalendarService && CalendarService.khalAvailable) {
|
||||
let events = CalendarService.getEventsForDate(selectedDate);
|
||||
console.log("EventsWidget: Updating events for", Qt.formatDate(selectedDate, "yyyy-MM-dd"), "found", events.length, "events");
|
||||
selectedDateEvents = events;
|
||||
} else {
|
||||
selectedDateEvents = [];
|
||||
}
|
||||
}
|
||||
|
||||
onSelectedDateEventsChanged: {
|
||||
console.log("EventsWidget: selectedDateEvents changed, count:", selectedDateEvents.length);
|
||||
eventsList.model = selectedDateEvents;
|
||||
}
|
||||
width: parent.width
|
||||
height: shouldShow ? (hasEvents ? Math.min(300, 80 + selectedDateEvents.length * 60) : 120) : 0
|
||||
radius: Theme.cornerRadiusLarge
|
||||
@@ -25,58 +34,33 @@ Rectangle {
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
visible: shouldShow
|
||||
|
||||
// Material elevation shadow
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 2
|
||||
shadowBlur: 0.25
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
||||
shadowOpacity: 0.1
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
Component.onCompleted: {
|
||||
updateSelectedDateEvents();
|
||||
}
|
||||
onSelectedDateChanged: {
|
||||
updateSelectedDateEvents();
|
||||
}
|
||||
|
||||
// Update events when selected date or events change
|
||||
Connections {
|
||||
function onEventsByDateChanged() {
|
||||
updateSelectedDateEvents();
|
||||
}
|
||||
|
||||
function onKhalAvailableChanged() {
|
||||
updateSelectedDateEvents();
|
||||
}
|
||||
|
||||
target: CalendarService
|
||||
enabled: CalendarService !== null
|
||||
function onEventsByDateChanged() {
|
||||
updateSelectedDateEvents()
|
||||
}
|
||||
function onKhalAvailableChanged() {
|
||||
updateSelectedDateEvents()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateSelectedDateEvents()
|
||||
}
|
||||
|
||||
onSelectedDateChanged: {
|
||||
updateSelectedDateEvents()
|
||||
}
|
||||
|
||||
function updateSelectedDateEvents() {
|
||||
if (CalendarService && CalendarService.khalAvailable) {
|
||||
let events = CalendarService.getEventsForDate(selectedDate)
|
||||
console.log("EventsWidget: Updating events for", Qt.formatDate(selectedDate, "yyyy-MM-dd"), "found", events.length, "events")
|
||||
selectedDateEvents = events
|
||||
} else {
|
||||
selectedDateEvents = []
|
||||
}
|
||||
}
|
||||
|
||||
// Header - always visible when widget is shown
|
||||
Row {
|
||||
id: headerRow
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -92,15 +76,13 @@ Rectangle {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: hasEvents ?
|
||||
(Qt.formatDate(selectedDate, "MMM d") + " • " +
|
||||
(selectedDateEvents.length === 1 ? "1 event" : selectedDateEvents.length + " events")) :
|
||||
Qt.formatDate(selectedDate, "MMM d")
|
||||
text: hasEvents ? (Qt.formatDate(selectedDate, "MMM d") + " • " + (selectedDateEvents.length === 1 ? "1 event" : selectedDateEvents.length + " events")) : Qt.formatDate(selectedDate, "MMM d")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No events placeholder - centered in entire widget (not just content area)
|
||||
@@ -124,11 +106,13 @@ Rectangle {
|
||||
font.weight: Font.Normal
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Events list - positioned below header when there are events
|
||||
ListView {
|
||||
id: eventsList
|
||||
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -136,7 +120,7 @@ Rectangle {
|
||||
anchors.margins: Theme.spacingL
|
||||
anchors.topMargin: Theme.spacingM
|
||||
visible: opacity > 0
|
||||
opacity: hasEvents ? 1.0 : 0.0
|
||||
opacity: hasEvents ? 1 : 0
|
||||
clip: true
|
||||
spacing: Theme.spacingS
|
||||
boundsMovement: Flickable.StopAtBounds
|
||||
@@ -151,6 +135,7 @@ Rectangle {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
@@ -158,20 +143,18 @@ Rectangle {
|
||||
height: eventContent.implicitHeight + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (modelData.url && eventMouseArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
} else if (eventMouseArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06)
|
||||
}
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.06)
|
||||
if (modelData.url && eventMouseArea.containsMouse)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
||||
else if (eventMouseArea.containsMouse)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06);
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.06);
|
||||
}
|
||||
border.color: {
|
||||
if (modelData.url && eventMouseArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||
} else if (eventMouseArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15)
|
||||
}
|
||||
return "transparent"
|
||||
if (modelData.url && eventMouseArea.containsMouse)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
|
||||
else if (eventMouseArea.containsMouse)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15);
|
||||
return "transparent";
|
||||
}
|
||||
border.width: 1
|
||||
|
||||
@@ -189,6 +172,7 @@ Rectangle {
|
||||
|
||||
Column {
|
||||
id: eventContent
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -213,6 +197,7 @@ Rectangle {
|
||||
|
||||
Row {
|
||||
id: timeRow
|
||||
|
||||
spacing: 4
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -228,15 +213,14 @@ Rectangle {
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.allDay) {
|
||||
return "All day"
|
||||
return "All day";
|
||||
} else {
|
||||
let timeFormat = Prefs.use24HourClock ? "H:mm" : "h:mm AP"
|
||||
let startTime = Qt.formatTime(modelData.start, timeFormat)
|
||||
if (modelData.start.toDateString() !== modelData.end.toDateString() ||
|
||||
modelData.start.getTime() !== modelData.end.getTime()) {
|
||||
return startTime + " – " + Qt.formatTime(modelData.end, timeFormat)
|
||||
}
|
||||
return startTime
|
||||
let timeFormat = Prefs.use24HourClock ? "H:mm" : "h:mm AP";
|
||||
let startTime = Qt.formatTime(modelData.start, timeFormat);
|
||||
if (modelData.start.toDateString() !== modelData.end.toDateString() || modelData.start.getTime() !== modelData.end.getTime())
|
||||
return startTime + " – " + Qt.formatTime(modelData.end, timeFormat);
|
||||
|
||||
return startTime;
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -244,10 +228,12 @@ Rectangle {
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
id: locationRow
|
||||
|
||||
spacing: 4
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -270,22 +256,25 @@ Rectangle {
|
||||
maximumLineCount: 1
|
||||
width: Math.min(implicitWidth, 200)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: eventMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: modelData.url ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: modelData.url !== ""
|
||||
|
||||
onClicked: {
|
||||
if (modelData.url && modelData.url !== "") {
|
||||
if (Qt.openUrlExternally(modelData.url) === false) {
|
||||
console.warn("Couldn't open", modelData.url)
|
||||
}
|
||||
if (Qt.openUrlExternally(modelData.url) === false)
|
||||
console.warn("Couldn't open", modelData.url);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,6 +284,7 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
@@ -302,7 +292,28 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 2
|
||||
shadowBlur: 0.25
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
||||
shadowOpacity: 0.1
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,87 +10,76 @@ Rectangle {
|
||||
id: mediaPlayerWidget
|
||||
|
||||
property MprisPlayer activePlayer: MprisController.activePlayer
|
||||
|
||||
property string lastValidTitle: ""
|
||||
property string lastValidArtist: ""
|
||||
property string lastValidAlbum: ""
|
||||
property string lastValidArtUrl: ""
|
||||
property real currentPosition: 0
|
||||
|
||||
Timer {
|
||||
id: clearCacheTimer
|
||||
interval: 2000
|
||||
onTriggered: {
|
||||
if (!activePlayer) {
|
||||
lastValidTitle = ""
|
||||
lastValidArtist = ""
|
||||
lastValidAlbum = ""
|
||||
lastValidArtUrl = ""
|
||||
}
|
||||
}
|
||||
// Simple progress ratio calculation
|
||||
function ratio() {
|
||||
return activePlayer && activePlayer.length > 0 ? currentPosition / activePlayer.length : 0;
|
||||
}
|
||||
|
||||
onActivePlayerChanged: {
|
||||
if (!activePlayer) {
|
||||
clearCacheTimer.restart()
|
||||
} else {
|
||||
clearCacheTimer.stop()
|
||||
if (!activePlayer)
|
||||
clearCacheTimer.restart();
|
||||
else
|
||||
clearCacheTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 2
|
||||
shadowBlur: 0.5
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
||||
shadowOpacity: 0.1
|
||||
|
||||
Timer {
|
||||
id: clearCacheTimer
|
||||
|
||||
interval: 2000
|
||||
onTriggered: {
|
||||
if (!activePlayer) {
|
||||
lastValidTitle = "";
|
||||
lastValidArtist = "";
|
||||
lastValidAlbum = "";
|
||||
lastValidArtUrl = "";
|
||||
}
|
||||
}
|
||||
|
||||
property real currentPosition: 0
|
||||
|
||||
// Simple progress ratio calculation
|
||||
function ratio() {
|
||||
return activePlayer && activePlayer.length > 0 ? currentPosition / activePlayer.length : 0
|
||||
}
|
||||
|
||||
// Updates progress bar every 2 seconds when playing
|
||||
Timer {
|
||||
id: positionTimer
|
||||
|
||||
interval: 2000
|
||||
running: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing && activePlayer.length > 0 && !progressMouseArea.isSeeking
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing && !progressMouseArea.isSeeking) {
|
||||
currentPosition = activePlayer.position
|
||||
}
|
||||
if (activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing && !progressMouseArea.isSeeking)
|
||||
currentPosition = activePlayer.position;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Backend events
|
||||
Connections {
|
||||
target: activePlayer
|
||||
|
||||
function onPositionChanged() {
|
||||
if (!progressMouseArea.isSeeking) {
|
||||
currentPosition = activePlayer.position
|
||||
}
|
||||
if (!progressMouseArea.isSeeking)
|
||||
currentPosition = activePlayer.position;
|
||||
|
||||
}
|
||||
|
||||
function onPostTrackChanged() {
|
||||
currentPosition = activePlayer?.position || 0
|
||||
currentPosition = activePlayer && activePlayer.position || 0;
|
||||
}
|
||||
|
||||
function onTrackTitleChanged() {
|
||||
currentPosition = activePlayer?.position || 0
|
||||
currentPosition = activePlayer && activePlayer.position || 0;
|
||||
}
|
||||
|
||||
target: activePlayer
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -117,6 +106,7 @@ Rectangle {
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Active content in a column
|
||||
@@ -144,12 +134,13 @@ Rectangle {
|
||||
|
||||
Image {
|
||||
id: albumArt
|
||||
|
||||
anchors.fill: parent
|
||||
source: activePlayer?.trackArtUrl || lastValidArtUrl || ""
|
||||
source: activePlayer && activePlayer.trackArtUrl || lastValidArtUrl || ""
|
||||
onSourceChanged: {
|
||||
if (activePlayer?.trackArtUrl) {
|
||||
if (activePlayer && activePlayer.trackArtUrl)
|
||||
lastValidArtUrl = activePlayer.trackArtUrl;
|
||||
}
|
||||
|
||||
}
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
@@ -167,8 +158,11 @@ Rectangle {
|
||||
font.pixelSize: 28
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Track Info
|
||||
@@ -178,11 +172,11 @@ Rectangle {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
text: activePlayer?.trackTitle || lastValidTitle || "Unknown Track"
|
||||
text: activePlayer && activePlayer.trackTitle || lastValidTitle || "Unknown Track"
|
||||
onTextChanged: {
|
||||
if (activePlayer?.trackTitle) {
|
||||
if (activePlayer && activePlayer.trackTitle)
|
||||
lastValidTitle = activePlayer.trackTitle;
|
||||
}
|
||||
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Bold
|
||||
@@ -192,11 +186,11 @@ Rectangle {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: activePlayer?.trackArtist || lastValidArtist || "Unknown Artist"
|
||||
text: activePlayer && activePlayer.trackArtist || lastValidArtist || "Unknown Artist"
|
||||
onTextChanged: {
|
||||
if (activePlayer?.trackArtist) {
|
||||
if (activePlayer && activePlayer.trackArtist)
|
||||
lastValidArtist = activePlayer.trackArtist;
|
||||
}
|
||||
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
|
||||
@@ -205,11 +199,11 @@ Rectangle {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: activePlayer?.trackAlbum || lastValidAlbum || ""
|
||||
text: activePlayer && activePlayer.trackAlbum || lastValidAlbum || ""
|
||||
onTextChanged: {
|
||||
if (activePlayer?.trackAlbum) {
|
||||
if (activePlayer && activePlayer.trackAlbum)
|
||||
lastValidAlbum = activePlayer.trackAlbum;
|
||||
}
|
||||
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
@@ -217,17 +211,21 @@ Rectangle {
|
||||
elide: Text.ElideRight
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Progress bar
|
||||
Item {
|
||||
id: progressBarContainer
|
||||
|
||||
width: parent.width
|
||||
height: 24
|
||||
|
||||
Rectangle {
|
||||
id: progressBarBackground
|
||||
|
||||
width: parent.width
|
||||
height: 6
|
||||
radius: 3
|
||||
@@ -237,78 +235,83 @@ Rectangle {
|
||||
|
||||
Rectangle {
|
||||
id: progressFill
|
||||
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Theme.primary
|
||||
|
||||
width: parent.width * ratio()
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: 100 }
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Drag handle
|
||||
Rectangle {
|
||||
id: progressHandle
|
||||
|
||||
width: 12
|
||||
height: 12
|
||||
radius: 6
|
||||
color: Theme.primary
|
||||
border.color: Qt.lighter(Theme.primary, 1.3)
|
||||
border.width: 1
|
||||
|
||||
x: Math.max(0, Math.min(parent.width - width, progressFill.width - width/2))
|
||||
x: Math.max(0, Math.min(parent.width - width, progressFill.width - width / 2))
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
visible: activePlayer && activePlayer.length > 0
|
||||
scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1.0
|
||||
scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150 }
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: progressMouseArea
|
||||
|
||||
property bool isSeeking: false
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: activePlayer && activePlayer.length > 0 && activePlayer.canSeek
|
||||
preventStealing: true
|
||||
|
||||
property bool isSeeking: false
|
||||
|
||||
onPressed: function(mouse) {
|
||||
isSeeking = true
|
||||
isSeeking = true;
|
||||
if (activePlayer && activePlayer.length > 0) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
currentPosition = seekPosition
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width));
|
||||
let seekPosition = ratio * activePlayer.length;
|
||||
activePlayer.position = seekPosition;
|
||||
currentPosition = seekPosition;
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
isSeeking = false
|
||||
isSeeking = false;
|
||||
}
|
||||
|
||||
onPositionChanged: function(mouse) {
|
||||
if (pressed && isSeeking && activePlayer && activePlayer.length > 0) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
currentPosition = seekPosition
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width));
|
||||
let seekPosition = ratio * activePlayer.length;
|
||||
activePlayer.position = seekPosition;
|
||||
currentPosition = seekPosition;
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: function(mouse) {
|
||||
if (activePlayer && activePlayer.length > 0) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
currentPosition = seekPosition
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width));
|
||||
let seekPosition = ratio * activePlayer.length;
|
||||
activePlayer.position = seekPosition;
|
||||
currentPosition = seekPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,25 +319,25 @@ Rectangle {
|
||||
// Global mouse area for drag tracking
|
||||
MouseArea {
|
||||
id: progressGlobalMouseArea
|
||||
|
||||
anchors.fill: parent.parent.parent // Fill the entire media player widget
|
||||
enabled: progressMouseArea.isSeeking
|
||||
visible: false
|
||||
preventStealing: true
|
||||
|
||||
onPositionChanged: function(mouse) {
|
||||
if (progressMouseArea.isSeeking && activePlayer && activePlayer.length > 0) {
|
||||
let globalPos = mapToItem(progressBarBackground, mouse.x, mouse.y)
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / progressBarBackground.width))
|
||||
let seekPosition = ratio * activePlayer.length
|
||||
activePlayer.position = seekPosition
|
||||
currentPosition = seekPosition
|
||||
let globalPos = mapToItem(progressBarBackground, mouse.x, mouse.y);
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / progressBarBackground.width));
|
||||
let seekPosition = ratio * activePlayer.length;
|
||||
activePlayer.position = seekPosition;
|
||||
currentPosition = seekPosition;
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
progressMouseArea.isSeeking = false;
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
progressMouseArea.isSeeking = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Control buttons - always visible
|
||||
@@ -365,21 +368,24 @@ Rectangle {
|
||||
|
||||
MouseArea {
|
||||
id: prevBtnArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!activePlayer) return
|
||||
if (!activePlayer)
|
||||
return ;
|
||||
|
||||
// >8 s → jump to start, otherwise previous track
|
||||
if (currentPosition > 8 && activePlayer.canSeek) {
|
||||
activePlayer.position = 0
|
||||
currentPosition = 0
|
||||
activePlayer.position = 0;
|
||||
currentPosition = 0;
|
||||
} else {
|
||||
activePlayer.previous()
|
||||
activePlayer.previous();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Play/Pause button
|
||||
@@ -391,7 +397,7 @@ Rectangle {
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
||||
text: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: 20
|
||||
color: Theme.background
|
||||
@@ -401,8 +407,9 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: activePlayer?.togglePlaying()
|
||||
onClicked: activePlayer && activePlayer.togglePlaying()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Next button
|
||||
@@ -422,14 +429,30 @@ Rectangle {
|
||||
|
||||
MouseArea {
|
||||
id: nextBtnArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: activePlayer?.next()
|
||||
onClicked: activePlayer && activePlayer.next()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 2
|
||||
shadowBlur: 0.5
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
||||
shadowOpacity: 0.1
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,23 +7,13 @@ import qs.Services
|
||||
Rectangle {
|
||||
id: weatherWidget
|
||||
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 2
|
||||
shadowBlur: 0.5
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
||||
shadowOpacity: 0.1
|
||||
}
|
||||
|
||||
// Placeholder when no weather - centered in entire widget
|
||||
Column {
|
||||
@@ -45,6 +35,7 @@ Rectangle {
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Weather content when available - original Column structure
|
||||
@@ -86,9 +77,14 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: if (WeatherService.weather.available) Prefs.setTemperatureUnit(!Prefs.useFahrenheit)
|
||||
onClicked: {
|
||||
if (WeatherService.weather.available)
|
||||
Prefs.setTemperatureUnit(!Prefs.useFahrenheit);
|
||||
|
||||
}
|
||||
enabled: WeatherService.weather.available
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -97,8 +93,11 @@ Rectangle {
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Weather details grid
|
||||
@@ -109,6 +108,7 @@ Rectangle {
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
text: "humidity_low"
|
||||
font.family: Theme.iconFont
|
||||
@@ -116,16 +116,19 @@ Rectangle {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: WeatherService.weather.humidity ? WeatherService.weather.humidity + "%" : "--"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
text: "air"
|
||||
font.family: Theme.iconFont
|
||||
@@ -133,16 +136,19 @@ Rectangle {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: WeatherService.weather.wind || "--"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
text: "wb_twilight"
|
||||
font.family: Theme.iconFont
|
||||
@@ -150,16 +156,19 @@ Rectangle {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: WeatherService.weather.sunrise || "--"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
text: "bedtime"
|
||||
font.family: Theme.iconFont
|
||||
@@ -167,13 +176,27 @@ Rectangle {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: WeatherService.weather.sunset || "--"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 2
|
||||
shadowBlur: 0.5
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.1)
|
||||
shadowOpacity: 0.1
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -10,7 +10,6 @@ Item {
|
||||
id: audioTab
|
||||
|
||||
property int audioSubTab: 0 // 0: Output, 1: Input
|
||||
|
||||
readonly property real volumeLevel: AudioService.volumeLevel
|
||||
readonly property real micLevel: AudioService.micLevel
|
||||
readonly property bool volumeMuted: AudioService.sinkMuted
|
||||
@@ -50,6 +49,7 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: audioTab.audioSubTab = 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -72,7 +72,9 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: audioTab.audioSubTab = 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Output Tab Content
|
||||
@@ -115,16 +117,19 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: AudioService.toggleMute()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: volumeSliderContainer
|
||||
|
||||
width: parent.width - 80
|
||||
height: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: volumeSliderTrack
|
||||
|
||||
width: parent.width
|
||||
height: 8
|
||||
radius: 4
|
||||
@@ -133,93 +138,99 @@ Item {
|
||||
|
||||
Rectangle {
|
||||
id: volumeSliderFill
|
||||
|
||||
width: parent.width * (audioTab.volumeLevel / 100)
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Theme.primary
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: 100 }
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Draggable handle
|
||||
Rectangle {
|
||||
id: volumeHandle
|
||||
|
||||
width: 18
|
||||
height: 18
|
||||
radius: 9
|
||||
color: Theme.primary
|
||||
border.color: Qt.lighter(Theme.primary, 1.3)
|
||||
border.width: 2
|
||||
|
||||
x: Math.max(0, Math.min(parent.width - width, volumeSliderFill.width - width/2))
|
||||
x: Math.max(0, Math.min(parent.width - width, volumeSliderFill.width - width / 2))
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
scale: volumeMouseArea.containsMouse || volumeMouseArea.pressed ? 1.2 : 1.0
|
||||
scale: volumeMouseArea.containsMouse || volumeMouseArea.pressed ? 1.2 : 1
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150 }
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: volumeMouseArea
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
preventStealing: true
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
onPressed: (mouse) => {
|
||||
isDragging = true
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width))
|
||||
let newVolume = Math.round(ratio * 100)
|
||||
AudioService.setVolume(newVolume)
|
||||
isDragging = true;
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
||||
let newVolume = Math.round(ratio * 100);
|
||||
AudioService.setVolume(newVolume);
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
isDragging = false
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
onPositionChanged: (mouse) => {
|
||||
if (pressed && isDragging) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width))
|
||||
let newVolume = Math.round(ratio * 100)
|
||||
AudioService.setVolume(newVolume)
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
||||
let newVolume = Math.round(ratio * 100);
|
||||
AudioService.setVolume(newVolume);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: (mouse) => {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width))
|
||||
let newVolume = Math.round(ratio * 100)
|
||||
AudioService.setVolume(newVolume)
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
||||
let newVolume = Math.round(ratio * 100);
|
||||
AudioService.setVolume(newVolume);
|
||||
}
|
||||
}
|
||||
|
||||
// Global mouse area for drag tracking
|
||||
MouseArea {
|
||||
id: volumeGlobalMouseArea
|
||||
|
||||
anchors.fill: parent.parent.parent.parent.parent // Fill the entire control center
|
||||
enabled: volumeMouseArea.isDragging
|
||||
visible: false
|
||||
preventStealing: true
|
||||
|
||||
onPositionChanged: (mouse) => {
|
||||
if (volumeMouseArea.isDragging) {
|
||||
let globalPos = mapToItem(volumeSliderTrack, mouse.x, mouse.y)
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / volumeSliderTrack.width))
|
||||
let newVolume = Math.round(ratio * 100)
|
||||
AudioService.setVolume(newVolume)
|
||||
let globalPos = mapToItem(volumeSliderTrack, mouse.x, mouse.y);
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / volumeSliderTrack.width));
|
||||
let newVolume = Math.round(ratio * 100);
|
||||
AudioService.setVolume(newVolume);
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
volumeMouseArea.isDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
volumeMouseArea.isDragging = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -229,7 +240,9 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Output Devices
|
||||
@@ -273,7 +286,9 @@ Item {
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Real audio devices
|
||||
@@ -284,8 +299,7 @@ Item {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
(modelData.active ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
color: deviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData.active ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: modelData.active ? Theme.primary : "transparent"
|
||||
border.width: 1
|
||||
|
||||
@@ -297,10 +311,14 @@ Item {
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.name.includes("bluez")) return "headset"
|
||||
else if (modelData.name.includes("hdmi")) return "tv"
|
||||
else if (modelData.name.includes("usb")) return "headset"
|
||||
else return "speaker"
|
||||
if (modelData.name.includes("bluez"))
|
||||
return "headset";
|
||||
else if (modelData.name.includes("hdmi"))
|
||||
return "tv";
|
||||
else if (modelData.name.includes("usb"))
|
||||
return "headset";
|
||||
else
|
||||
return "speaker";
|
||||
}
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
@@ -321,33 +339,39 @@ Item {
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.subtitle && modelData.subtitle !== "") {
|
||||
return modelData.subtitle + (modelData.active ? " • Selected" : "")
|
||||
} else {
|
||||
return modelData.active ? "Selected" : ""
|
||||
}
|
||||
if (modelData.subtitle && modelData.subtitle !== "")
|
||||
return modelData.subtitle + (modelData.active ? " • Selected" : "");
|
||||
else
|
||||
return modelData.active ? "Selected" : "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text !== ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deviceArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
AudioService.setAudioSink(modelData.name)
|
||||
AudioService.setAudioSink(modelData.name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Input Tab Content
|
||||
@@ -390,16 +414,19 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: AudioService.toggleMicMute()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: micSliderContainer
|
||||
|
||||
width: parent.width - 80
|
||||
height: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: micSliderTrack
|
||||
|
||||
width: parent.width
|
||||
height: 8
|
||||
radius: 4
|
||||
@@ -408,93 +435,99 @@ Item {
|
||||
|
||||
Rectangle {
|
||||
id: micSliderFill
|
||||
|
||||
width: parent.width * (audioTab.micLevel / 100)
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Theme.primary
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: 100 }
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Draggable handle
|
||||
Rectangle {
|
||||
id: micHandle
|
||||
|
||||
width: 18
|
||||
height: 18
|
||||
radius: 9
|
||||
color: Theme.primary
|
||||
border.color: Qt.lighter(Theme.primary, 1.3)
|
||||
border.width: 2
|
||||
|
||||
x: Math.max(0, Math.min(parent.width - width, micSliderFill.width - width/2))
|
||||
x: Math.max(0, Math.min(parent.width - width, micSliderFill.width - width / 2))
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
scale: micMouseArea.containsMouse || micMouseArea.pressed ? 1.2 : 1.0
|
||||
scale: micMouseArea.containsMouse || micMouseArea.pressed ? 1.2 : 1
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150 }
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: micMouseArea
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
preventStealing: true
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
onPressed: (mouse) => {
|
||||
isDragging = true
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width))
|
||||
let newMicLevel = Math.round(ratio * 100)
|
||||
AudioService.setMicLevel(newMicLevel)
|
||||
isDragging = true;
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
||||
let newMicLevel = Math.round(ratio * 100);
|
||||
AudioService.setMicLevel(newMicLevel);
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
isDragging = false
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
onPositionChanged: (mouse) => {
|
||||
if (pressed && isDragging) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width))
|
||||
let newMicLevel = Math.round(ratio * 100)
|
||||
AudioService.setMicLevel(newMicLevel)
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
||||
let newMicLevel = Math.round(ratio * 100);
|
||||
AudioService.setMicLevel(newMicLevel);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: (mouse) => {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width))
|
||||
let newMicLevel = Math.round(ratio * 100)
|
||||
AudioService.setMicLevel(newMicLevel)
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
||||
let newMicLevel = Math.round(ratio * 100);
|
||||
AudioService.setMicLevel(newMicLevel);
|
||||
}
|
||||
}
|
||||
|
||||
// Global mouse area for drag tracking
|
||||
MouseArea {
|
||||
id: micGlobalMouseArea
|
||||
|
||||
anchors.fill: parent.parent.parent.parent.parent // Fill the entire control center
|
||||
enabled: micMouseArea.isDragging
|
||||
visible: false
|
||||
preventStealing: true
|
||||
|
||||
onPositionChanged: (mouse) => {
|
||||
if (micMouseArea.isDragging) {
|
||||
let globalPos = mapToItem(micSliderTrack, mouse.x, mouse.y)
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / micSliderTrack.width))
|
||||
let newMicLevel = Math.round(ratio * 100)
|
||||
AudioService.setMicLevel(newMicLevel)
|
||||
let globalPos = mapToItem(micSliderTrack, mouse.x, mouse.y);
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / micSliderTrack.width));
|
||||
let newMicLevel = Math.round(ratio * 100);
|
||||
AudioService.setMicLevel(newMicLevel);
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
micMouseArea.isDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
micMouseArea.isDragging = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -504,6 +537,7 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -549,7 +583,9 @@ Item {
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Real audio input devices
|
||||
@@ -560,8 +596,7 @@ Item {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: sourceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
(modelData.active ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
color: sourceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData.active ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: modelData.active ? Theme.primary : "transparent"
|
||||
border.width: 1
|
||||
|
||||
@@ -573,9 +608,12 @@ Item {
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.name.includes("bluez")) return "headset_mic"
|
||||
else if (modelData.name.includes("usb")) return "headset_mic"
|
||||
else return "mic"
|
||||
if (modelData.name.includes("bluez"))
|
||||
return "headset_mic";
|
||||
else if (modelData.name.includes("usb"))
|
||||
return "headset_mic";
|
||||
else
|
||||
return "mic";
|
||||
}
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
@@ -596,33 +634,41 @@ Item {
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.subtitle && modelData.subtitle !== "") {
|
||||
return modelData.subtitle + (modelData.active ? " • Selected" : "")
|
||||
} else {
|
||||
return modelData.active ? "Selected" : ""
|
||||
}
|
||||
if (modelData.subtitle && modelData.subtitle !== "")
|
||||
return modelData.subtitle + (modelData.active ? " • Selected" : "");
|
||||
else
|
||||
return modelData.active ? "Selected" : "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text !== ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: sourceArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
AudioService.setAudioSource(modelData.name)
|
||||
AudioService.setAudioSource(modelData.name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Io
|
||||
import Quickshell.Bluetooth
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -13,7 +13,6 @@ Item {
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
@@ -25,8 +24,7 @@ Item {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: bluetoothToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
|
||||
(BluetoothService.adapter && BluetoothService.adapter.enabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12))
|
||||
color: bluetoothToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (BluetoothService.adapter && BluetoothService.adapter.enabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12))
|
||||
border.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : "transparent"
|
||||
border.width: 2
|
||||
|
||||
@@ -60,19 +58,22 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: bluetoothToggle
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
BluetoothService.toggleAdapter()
|
||||
BluetoothService.toggleAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -88,14 +89,15 @@ Item {
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: BluetoothService.adapter && BluetoothService.adapter.devices ? BluetoothService.adapter.devices.values.filter(dev => dev && dev.paired && BluetoothService.isValidDevice(dev)) : []
|
||||
model: BluetoothService.adapter && BluetoothService.adapter.devices ? BluetoothService.adapter.devices.values.filter((dev) => {
|
||||
return dev && dev.paired && BluetoothService.isValidDevice(dev);
|
||||
}) : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: btDeviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
(modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
color: btDeviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: modelData.connected ? Theme.primary : "transparent"
|
||||
border.width: 1
|
||||
|
||||
@@ -135,32 +137,32 @@ Item {
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.batteryAvailable && modelData.battery > 0) {
|
||||
return "• " + Math.round(modelData.battery * 100) + "%"
|
||||
}
|
||||
var btBattery = BatteryService.bluetoothDevices.find(dev =>
|
||||
dev.name === (modelData.name || modelData.deviceName) ||
|
||||
dev.name.toLowerCase().includes((modelData.name || modelData.deviceName).toLowerCase()) ||
|
||||
(modelData.name || modelData.deviceName).toLowerCase().includes(dev.name.toLowerCase())
|
||||
)
|
||||
return btBattery ? "• " + btBattery.percentage + "%" : ""
|
||||
if (modelData.batteryAvailable && modelData.battery > 0)
|
||||
return "• " + Math.round(modelData.battery * 100) + "%";
|
||||
|
||||
var btBattery = BatteryService.bluetoothDevices.find((dev) => {
|
||||
return dev.name === (modelData.name || modelData.deviceName) || dev.name.toLowerCase().includes((modelData.name || modelData.deviceName).toLowerCase()) || (modelData.name || modelData.deviceName).toLowerCase().includes(dev.name.toLowerCase());
|
||||
});
|
||||
return btBattery ? "• " + btBattery.percentage + "%" : "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: btMenuButton
|
||||
|
||||
width: 32
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: btMenuButtonArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: btMenuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -177,36 +179,43 @@ Item {
|
||||
|
||||
MouseArea {
|
||||
id: btMenuButtonArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
bluetoothContextMenuWindow.deviceData = modelData
|
||||
let localPos = btMenuButtonArea.mapToItem(bluetoothTab, btMenuButtonArea.width / 2, btMenuButtonArea.height)
|
||||
bluetoothContextMenuWindow.show(localPos.x, localPos.y)
|
||||
bluetoothContextMenuWindow.deviceData = modelData;
|
||||
let localPos = btMenuButtonArea.mapToItem(bluetoothTab, btMenuButtonArea.width / 2, btMenuButtonArea.height);
|
||||
bluetoothContextMenuWindow.show(localPos.x, localPos.y);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: btDeviceArea
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 40
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
BluetoothService.debugDevice(modelData)
|
||||
BluetoothService.toggle(modelData.address)
|
||||
BluetoothService.debugDevice(modelData);
|
||||
BluetoothService.toggle(modelData.address);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -226,7 +235,10 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item { width: 1; height: 1 }
|
||||
Item {
|
||||
width: 1
|
||||
height: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Math.max(140, scanText.contentWidth + Theme.spacingL * 2)
|
||||
@@ -250,29 +262,32 @@ Item {
|
||||
|
||||
Text {
|
||||
id: scanText
|
||||
|
||||
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Stop Scanning" : "Start Scanning"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: scanArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (BluetoothService.adapter && BluetoothService.adapter.discovering) {
|
||||
BluetoothService.stopScan()
|
||||
} else {
|
||||
BluetoothService.startScan()
|
||||
}
|
||||
if (BluetoothService.adapter && BluetoothService.adapter.discovering)
|
||||
BluetoothService.stopScan();
|
||||
else
|
||||
BluetoothService.startScan();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Repeater {
|
||||
@@ -286,15 +301,25 @@ Item {
|
||||
height: 70
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (availableDeviceArea.containsMouse) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
if (modelData.pairing) return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
|
||||
if (modelData.blocked) return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
if (availableDeviceArea.containsMouse)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
|
||||
|
||||
if (modelData.pairing)
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
||||
|
||||
if (modelData.blocked)
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08);
|
||||
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
|
||||
}
|
||||
border.color: {
|
||||
if (modelData.pairing) return Theme.warning
|
||||
if (modelData.blocked) return Theme.error
|
||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
if (modelData.pairing)
|
||||
return Theme.warning;
|
||||
|
||||
if (modelData.blocked)
|
||||
return Theme.error;
|
||||
|
||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2);
|
||||
}
|
||||
border.width: 1
|
||||
|
||||
@@ -309,9 +334,13 @@ Item {
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: {
|
||||
if (modelData.pairing) return Theme.warning
|
||||
if (modelData.blocked) return Theme.error
|
||||
return Theme.surfaceText
|
||||
if (modelData.pairing)
|
||||
return Theme.warning;
|
||||
|
||||
if (modelData.blocked)
|
||||
return Theme.error;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
@@ -324,9 +353,13 @@ Item {
|
||||
text: modelData.name || modelData.deviceName
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: {
|
||||
if (modelData.pairing) return Theme.warning
|
||||
if (modelData.blocked) return Theme.error
|
||||
return Theme.surfaceText
|
||||
if (modelData.pairing)
|
||||
return Theme.warning;
|
||||
|
||||
if (modelData.blocked)
|
||||
return Theme.error;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
font.weight: modelData.pairing ? Font.Medium : Font.Normal
|
||||
}
|
||||
@@ -340,16 +373,23 @@ Item {
|
||||
Text {
|
||||
text: {
|
||||
switch (pairingStatus) {
|
||||
case "pairing": return "Pairing..."
|
||||
case "blocked": return "Blocked"
|
||||
default: return BluetoothService.getSignalStrength(modelData)
|
||||
case "pairing":
|
||||
return "Pairing...";
|
||||
case "blocked":
|
||||
return "Blocked";
|
||||
default:
|
||||
return BluetoothService.getSignalStrength(modelData);
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
if (modelData.pairing) return Theme.warning
|
||||
if (modelData.blocked) return Theme.error
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
if (modelData.pairing)
|
||||
return Theme.warning;
|
||||
|
||||
if (modelData.blocked)
|
||||
return Theme.error;
|
||||
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,9 +407,13 @@ Item {
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
visible: modelData.rssi !== undefined && modelData.rssi !== 0 && pairingStatus === "available"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -380,20 +424,28 @@ Item {
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: {
|
||||
if (!canPair && !modelData.pairing) return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
if (actionButtonArea.containsMouse) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
return "transparent"
|
||||
if (!canPair && !modelData.pairing)
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3);
|
||||
|
||||
if (actionButtonArea.containsMouse)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
||||
|
||||
return "transparent";
|
||||
}
|
||||
border.color: canPair || modelData.pairing ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 1
|
||||
opacity: canPair || modelData.pairing ? 1.0 : 0.5
|
||||
opacity: canPair || modelData.pairing ? 1 : 0.5
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (modelData.pairing) return "Pairing..."
|
||||
if (modelData.blocked) return "Blocked"
|
||||
return "Pair"
|
||||
if (modelData.pairing)
|
||||
return "Pairing...";
|
||||
|
||||
if (modelData.blocked)
|
||||
return "Blocked";
|
||||
|
||||
return "Pair";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: canPair || modelData.pairing ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
@@ -402,34 +454,37 @@ Item {
|
||||
|
||||
MouseArea {
|
||||
id: actionButtonArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: canPair ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: canPair
|
||||
|
||||
onClicked: {
|
||||
if (canPair) {
|
||||
BluetoothService.pair(modelData.address)
|
||||
}
|
||||
if (canPair)
|
||||
BluetoothService.pair(modelData.address);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: availableDeviceArea
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 90 // Don't overlap with action button
|
||||
hoverEnabled: true
|
||||
cursorShape: canPair ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: canPair
|
||||
|
||||
onClicked: {
|
||||
if (canPair) {
|
||||
BluetoothService.pair(modelData.address)
|
||||
}
|
||||
if (canPair)
|
||||
BluetoothService.pair(modelData.address);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -455,6 +510,7 @@ Item {
|
||||
to: 360
|
||||
duration: 2000
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -464,6 +520,7 @@ Item {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -472,6 +529,7 @@ Item {
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -483,15 +541,39 @@ Item {
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bluetoothContextMenuWindow
|
||||
|
||||
property var deviceData: null
|
||||
property bool menuVisible: false
|
||||
|
||||
function show(x, y) {
|
||||
const menuWidth = 160;
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2;
|
||||
let finalX = x - menuWidth / 2;
|
||||
let finalY = y;
|
||||
finalX = Math.max(0, Math.min(finalX, bluetoothTab.width - menuWidth));
|
||||
finalY = Math.max(0, Math.min(finalY, bluetoothTab.height - menuHeight));
|
||||
bluetoothContextMenuWindow.x = finalX;
|
||||
bluetoothContextMenuWindow.y = finalY;
|
||||
bluetoothContextMenuWindow.visible = true;
|
||||
bluetoothContextMenuWindow.menuVisible = true;
|
||||
}
|
||||
|
||||
function hide() {
|
||||
bluetoothContextMenuWindow.menuVisible = false;
|
||||
Qt.callLater(() => {
|
||||
bluetoothContextMenuWindow.visible = false;
|
||||
});
|
||||
}
|
||||
|
||||
visible: false
|
||||
width: 160
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
@@ -500,6 +582,8 @@ Item {
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
z: 1000
|
||||
opacity: menuVisible ? 1 : 0
|
||||
scale: menuVisible ? 1 : 0.85
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -512,25 +596,9 @@ Item {
|
||||
z: parent.z - 1
|
||||
}
|
||||
|
||||
opacity: menuVisible ? 1.0 : 0.0
|
||||
scale: menuVisible ? 1.0 : 0.85
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
@@ -563,19 +631,20 @@ Item {
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: connectArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (bluetoothContextMenuWindow.deviceData) {
|
||||
BluetoothService.toggle(bluetoothContextMenuWindow.deviceData.address)
|
||||
}
|
||||
bluetoothContextMenuWindow.hide()
|
||||
if (bluetoothContextMenuWindow.deviceData)
|
||||
BluetoothService.toggle(bluetoothContextMenuWindow.deviceData.address);
|
||||
|
||||
bluetoothContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,7 +653,9 @@ Item {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -599,6 +670,7 @@ Item {
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -629,19 +701,20 @@ Item {
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: forgetArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (bluetoothContextMenuWindow.deviceData) {
|
||||
BluetoothService.forget(bluetoothContextMenuWindow.deviceData.address)
|
||||
}
|
||||
bluetoothContextMenuWindow.hide()
|
||||
if (bluetoothContextMenuWindow.deviceData)
|
||||
BluetoothService.forget(bluetoothContextMenuWindow.deviceData.address);
|
||||
|
||||
bluetoothContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,37 +723,36 @@ Item {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function show(x, y) {
|
||||
const menuWidth = 160
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
|
||||
let finalX = x - menuWidth / 2
|
||||
let finalY = y
|
||||
|
||||
finalX = Math.max(0, Math.min(finalX, bluetoothTab.width - menuWidth))
|
||||
finalY = Math.max(0, Math.min(finalY, bluetoothTab.height - menuHeight))
|
||||
|
||||
bluetoothContextMenuWindow.x = finalX
|
||||
bluetoothContextMenuWindow.y = finalY
|
||||
bluetoothContextMenuWindow.visible = true
|
||||
bluetoothContextMenuWindow.menuVisible = true
|
||||
}
|
||||
|
||||
function hide() {
|
||||
bluetoothContextMenuWindow.menuVisible = false
|
||||
Qt.callLater(() => { bluetoothContextMenuWindow.visible = false })
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
visible: bluetoothContextMenuWindow.visible
|
||||
onClicked: {
|
||||
bluetoothContextMenuWindow.hide()
|
||||
bluetoothContextMenuWindow.hide();
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -691,5 +763,7 @@ Item {
|
||||
onClicked: {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -12,21 +12,19 @@ PanelWindow {
|
||||
id: root
|
||||
|
||||
property bool controlCenterVisible: false
|
||||
property string currentTab: "network" // "network", "audio", "bluetooth", "display"
|
||||
property bool powerOptionsExpanded: false
|
||||
|
||||
visible: controlCenterVisible
|
||||
|
||||
onVisibleChanged: {
|
||||
// Enable/disable WiFi auto-refresh based on control center visibility
|
||||
WifiService.autoRefreshEnabled = visible && NetworkService.wifiEnabled
|
||||
WifiService.autoRefreshEnabled = visible && NetworkService.wifiEnabled;
|
||||
}
|
||||
|
||||
implicitWidth: 600
|
||||
implicitHeight: 500
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
@@ -36,9 +34,6 @@ PanelWindow {
|
||||
bottom: true
|
||||
}
|
||||
|
||||
property string currentTab: "network" // "network", "audio", "bluetooth", "display"
|
||||
property bool powerOptionsExpanded: false
|
||||
|
||||
Rectangle {
|
||||
width: Math.min(600, Screen.width - Theme.spacingL * 2)
|
||||
height: root.powerOptionsExpanded ? 570 : 500
|
||||
@@ -48,50 +43,66 @@ PanelWindow {
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
opacity: controlCenterVisible ? 1 : 0
|
||||
// TopBar dropdown animation - optimized for performance
|
||||
transform: [
|
||||
Scale {
|
||||
id: scaleTransform
|
||||
|
||||
origin.x: 600 // Use fixed width since popup is max 600px wide
|
||||
origin.y: 0
|
||||
xScale: controlCenterVisible ? 1.0 : 0.95
|
||||
yScale: controlCenterVisible ? 1.0 : 0.8
|
||||
xScale: controlCenterVisible ? 1 : 0.95
|
||||
yScale: controlCenterVisible ? 1 : 0.8
|
||||
},
|
||||
Translate {
|
||||
id: translateTransform
|
||||
|
||||
x: controlCenterVisible ? 0 : 15 // Slide slightly left when hidden
|
||||
y: controlCenterVisible ? 0 : -30
|
||||
}
|
||||
]
|
||||
|
||||
// Single coordinated animation for better performance
|
||||
states: [
|
||||
State {
|
||||
name: "visible"
|
||||
when: controlCenterVisible
|
||||
PropertyChanges { target: scaleTransform; xScale: 1.0; yScale: 1.0 }
|
||||
PropertyChanges { target: translateTransform; x: 0; y: 0 }
|
||||
|
||||
PropertyChanges {
|
||||
target: scaleTransform
|
||||
xScale: 1
|
||||
yScale: 1
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: translateTransform
|
||||
x: 0
|
||||
y: 0
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "hidden"
|
||||
when: !controlCenterVisible
|
||||
PropertyChanges { target: scaleTransform; xScale: 0.95; yScale: 0.8 }
|
||||
PropertyChanges { target: translateTransform; x: 15; y: -30 }
|
||||
|
||||
PropertyChanges {
|
||||
target: scaleTransform
|
||||
xScale: 0.95
|
||||
yScale: 0.8
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: translateTransform
|
||||
x: 15
|
||||
y: -30
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
// Power menu height animation
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration // Faster for height changes
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "*"; to: "*"
|
||||
from: "*"
|
||||
to: "*"
|
||||
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
targets: [scaleTransform, translateTransform]
|
||||
@@ -99,19 +110,12 @@ PanelWindow {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
opacity: controlCenterVisible ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
@@ -140,11 +144,12 @@ PanelWindow {
|
||||
// Profile Picture Container
|
||||
Item {
|
||||
id: avatarContainer
|
||||
width: 64
|
||||
height: 64
|
||||
|
||||
property bool hasImage: profileImageLoader.status === Image.Ready
|
||||
|
||||
width: 64
|
||||
height: 64
|
||||
|
||||
// This rectangle provides the themed ring via its border.
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -158,12 +163,15 @@ PanelWindow {
|
||||
// Hidden Image loader. Its only purpose is to load the texture.
|
||||
Image {
|
||||
id: profileImageLoader
|
||||
|
||||
source: {
|
||||
if (Prefs.profileImage === "") return ""
|
||||
if (Prefs.profileImage.startsWith("/")) {
|
||||
return "file://" + Prefs.profileImage
|
||||
}
|
||||
return Prefs.profileImage
|
||||
if (Prefs.profileImage === "")
|
||||
return "";
|
||||
|
||||
if (Prefs.profileImage.startsWith("/"))
|
||||
return "file://" + Prefs.profileImage;
|
||||
|
||||
return Prefs.profileImage;
|
||||
}
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
@@ -180,11 +188,12 @@ PanelWindow {
|
||||
maskSource: circularMask
|
||||
visible: avatarContainer.hasImage
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1.0
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
|
||||
Item {
|
||||
id: circularMask
|
||||
|
||||
width: 64 - 10
|
||||
height: 64 - 10
|
||||
layer.enabled: true
|
||||
@@ -197,6 +206,7 @@ PanelWindow {
|
||||
color: "black"
|
||||
antialiasing: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Fallback for when there is no image.
|
||||
@@ -213,6 +223,7 @@ PanelWindow {
|
||||
font.pixelSize: Theme.iconSize + 8
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Error icon for when the image fails to load.
|
||||
@@ -224,6 +235,7 @@ PanelWindow {
|
||||
color: Theme.primaryText
|
||||
visible: Prefs.profileImage !== "" && profileImageLoader.status === Image.Error
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// User Info Text
|
||||
@@ -231,7 +243,6 @@ PanelWindow {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
|
||||
Text {
|
||||
text: UserInfoService.fullName || UserInfoService.username || "User"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
@@ -245,7 +256,9 @@ PanelWindow {
|
||||
color: Theme.surfaceVariantText
|
||||
font.weight: Font.Normal
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Action Buttons - Power and Settings
|
||||
@@ -260,9 +273,7 @@ PanelWindow {
|
||||
width: 40
|
||||
height: 40
|
||||
radius: 20
|
||||
color: powerButton.containsMouse || root.powerOptionsExpanded ?
|
||||
Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) :
|
||||
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
|
||||
color: powerButton.containsMouse || root.powerOptionsExpanded ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
@@ -285,31 +296,40 @@ PanelWindow {
|
||||
NumberAnimation {
|
||||
target: parent
|
||||
property: "opacity"
|
||||
to: 0.0
|
||||
to: 0
|
||||
duration: Theme.shortDuration / 2
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
PropertyAction { target: parent; property: "text" }
|
||||
|
||||
PropertyAction {
|
||||
target: parent
|
||||
property: "text"
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: parent
|
||||
property: "opacity"
|
||||
to: 1.0
|
||||
to: 1
|
||||
duration: Theme.shortDuration / 2
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: powerButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.powerOptionsExpanded = !root.powerOptionsExpanded
|
||||
root.powerOptionsExpanded = !root.powerOptionsExpanded;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +338,9 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Settings Button
|
||||
@@ -326,9 +348,7 @@ PanelWindow {
|
||||
width: 40
|
||||
height: 40
|
||||
radius: 20
|
||||
color: settingsButton.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)
|
||||
color: settingsButton.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)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -340,13 +360,13 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: settingsButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
controlCenterVisible = false
|
||||
settingsPopup.settingsVisible = true
|
||||
controlCenterVisible = false;
|
||||
settingsPopup.settingsVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,9 +375,13 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Animated Collapsible Power Options (optimized)
|
||||
@@ -368,24 +392,9 @@ PanelWindow {
|
||||
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 : 0.0
|
||||
opacity: root.powerOptionsExpanded ? 1 : 0
|
||||
clip: true
|
||||
|
||||
// Single coordinated animation for power options
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingL
|
||||
@@ -396,9 +405,7 @@ PanelWindow {
|
||||
width: 100
|
||||
height: 34
|
||||
radius: Theme.cornerRadius
|
||||
color: logoutButton.containsMouse ?
|
||||
Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12) :
|
||||
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
|
||||
color: logoutButton.containsMouse ? Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
@@ -419,21 +426,22 @@ PanelWindow {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: logoutButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.powerOptionsExpanded = false
|
||||
root.powerOptionsExpanded = false;
|
||||
if (typeof root !== "undefined" && root.powerConfirmDialog) {
|
||||
root.powerConfirmDialog.powerConfirmAction = "logout"
|
||||
root.powerConfirmDialog.powerConfirmTitle = "Logout"
|
||||
root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to logout?"
|
||||
root.powerConfirmDialog.powerConfirmVisible = true
|
||||
root.powerConfirmDialog.powerConfirmAction = "logout";
|
||||
root.powerConfirmDialog.powerConfirmTitle = "Logout";
|
||||
root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to logout?";
|
||||
root.powerConfirmDialog.powerConfirmVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -443,7 +451,9 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Reboot
|
||||
@@ -451,9 +461,7 @@ PanelWindow {
|
||||
width: 100
|
||||
height: 34
|
||||
radius: Theme.cornerRadius
|
||||
color: rebootButton.containsMouse ?
|
||||
Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12) :
|
||||
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
|
||||
color: rebootButton.containsMouse ? Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
@@ -474,21 +482,22 @@ PanelWindow {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: rebootButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.powerOptionsExpanded = false
|
||||
root.powerOptionsExpanded = false;
|
||||
if (typeof root !== "undefined" && root.powerConfirmDialog) {
|
||||
root.powerConfirmDialog.powerConfirmAction = "reboot"
|
||||
root.powerConfirmDialog.powerConfirmTitle = "Restart"
|
||||
root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to restart?"
|
||||
root.powerConfirmDialog.powerConfirmVisible = true
|
||||
root.powerConfirmDialog.powerConfirmAction = "reboot";
|
||||
root.powerConfirmDialog.powerConfirmTitle = "Restart";
|
||||
root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to restart?";
|
||||
root.powerConfirmDialog.powerConfirmVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -498,7 +507,9 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Shutdown
|
||||
@@ -506,9 +517,7 @@ PanelWindow {
|
||||
width: 100
|
||||
height: 34
|
||||
radius: Theme.cornerRadius
|
||||
color: shutdownButton.containsMouse ?
|
||||
Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) :
|
||||
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
|
||||
color: shutdownButton.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
@@ -529,21 +538,22 @@ PanelWindow {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: shutdownButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.powerOptionsExpanded = false
|
||||
root.powerOptionsExpanded = false;
|
||||
if (typeof root !== "undefined" && root.powerConfirmDialog) {
|
||||
root.powerConfirmDialog.powerConfirmAction = "poweroff"
|
||||
root.powerConfirmDialog.powerConfirmTitle = "Shutdown"
|
||||
root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to shutdown?"
|
||||
root.powerConfirmDialog.powerConfirmVisible = true
|
||||
root.powerConfirmDialog.powerConfirmAction = "poweroff";
|
||||
root.powerConfirmDialog.powerConfirmTitle = "Shutdown";
|
||||
root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to shutdown?";
|
||||
root.powerConfirmDialog.powerConfirmVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -553,9 +563,30 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Single coordinated animation for power options
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Tab buttons
|
||||
@@ -565,36 +596,51 @@ PanelWindow {
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
let tabs = [
|
||||
{name: "Network", icon: "wifi", id: "network", available: true}
|
||||
]
|
||||
|
||||
let tabs = [{
|
||||
"name": "Network",
|
||||
"icon": "wifi",
|
||||
"id": "network",
|
||||
"available": true
|
||||
}];
|
||||
// Always show audio
|
||||
tabs.push({name: "Audio", icon: "volume_up", id: "audio", available: true})
|
||||
|
||||
tabs.push({
|
||||
"name": "Audio",
|
||||
"icon": "volume_up",
|
||||
"id": "audio",
|
||||
"available": true
|
||||
});
|
||||
// Show Bluetooth only if available
|
||||
if (BluetoothService.available) {
|
||||
tabs.push({name: "Bluetooth", icon: "bluetooth", id: "bluetooth", available: true})
|
||||
}
|
||||
if (BluetoothService.available)
|
||||
tabs.push({
|
||||
"name": "Bluetooth",
|
||||
"icon": "bluetooth",
|
||||
"id": "bluetooth",
|
||||
"available": true
|
||||
});
|
||||
|
||||
// Always show display
|
||||
tabs.push({name: "Display", icon: "brightness_6", id: "display", available: true})
|
||||
|
||||
return tabs
|
||||
tabs.push({
|
||||
"name": "Display",
|
||||
"icon": "brightness_6",
|
||||
"id": "display",
|
||||
"available": true
|
||||
});
|
||||
return tabs;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
property int tabCount: {
|
||||
let count = 3 // Network + Audio + Display (always visible)
|
||||
if (BluetoothService.available) count++
|
||||
return count
|
||||
let count = 3; // Network + Audio + Display (always visible)
|
||||
if (BluetoothService.available)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
width: (parent.width - Theme.spacingXS * (tabCount - 1)) / tabCount
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: root.currentTab === modelData.id ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
tabArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
color: root.currentTab === modelData.id ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : tabArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
@@ -615,16 +661,17 @@ PanelWindow {
|
||||
font.weight: root.currentTab === modelData.id ? Font.Medium : Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: tabArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.currentTab = modelData.id
|
||||
root.currentTab = modelData.id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,10 +680,15 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Tab content area
|
||||
@@ -646,19 +698,11 @@ PanelWindow {
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.1)
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
// Network Tab
|
||||
NetworkTab {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
visible: root.currentTab === "network"
|
||||
|
||||
}
|
||||
|
||||
// Audio Tab
|
||||
@@ -680,10 +724,37 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
visible: root.currentTab === "display"
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Power menu height animation
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration // Faster for height changes
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Click outside to close
|
||||
@@ -691,7 +762,8 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
onClicked: {
|
||||
controlCenterVisible = false
|
||||
controlCenterVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ScrollView {
|
||||
id: displayTab
|
||||
clip: true
|
||||
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
@@ -35,11 +35,11 @@ ScrollView {
|
||||
leftIcon: "brightness_low"
|
||||
rightIcon: "brightness_high"
|
||||
enabled: BrightnessService.brightnessAvailable
|
||||
|
||||
onSliderValueChanged: function(newValue) {
|
||||
BrightnessService.setBrightness(newValue)
|
||||
BrightnessService.setBrightness(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Display settings
|
||||
@@ -64,9 +64,7 @@ ScrollView {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: Prefs.nightModeEnabled ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
|
||||
(nightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
color: Prefs.nightModeEnabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (nightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: Prefs.nightModeEnabled ? Theme.primary : "transparent"
|
||||
border.width: Prefs.nightModeEnabled ? 1 : 0
|
||||
|
||||
@@ -89,26 +87,28 @@ ScrollView {
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nightModeToggle
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (Prefs.nightModeEnabled) {
|
||||
// Disable night mode - kill any running color temperature processes
|
||||
nightModeDisableProcess.running = true
|
||||
Prefs.setNightModeEnabled(false)
|
||||
nightModeDisableProcess.running = true;
|
||||
Prefs.setNightModeEnabled(false);
|
||||
} else {
|
||||
// Enable night mode using wlsunset or redshift
|
||||
nightModeEnableProcess.running = true
|
||||
Prefs.setNightModeEnabled(true)
|
||||
nightModeEnableProcess.running = true;
|
||||
Prefs.setNightModeEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Light/Dark mode toggle
|
||||
@@ -116,9 +116,7 @@ ScrollView {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.isLightMode ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
|
||||
(lightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
color: Theme.isLightMode ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (lightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||
border.color: Theme.isLightMode ? Theme.primary : "transparent"
|
||||
border.width: Theme.isLightMode ? 1 : 0
|
||||
|
||||
@@ -141,16 +139,17 @@ ScrollView {
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: lightModeToggle
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
Theme.toggleLightMode()
|
||||
Theme.toggleLightMode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,35 +158,41 @@ ScrollView {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Night mode processes
|
||||
Process {
|
||||
id: nightModeEnableProcess
|
||||
|
||||
command: ["bash", "-c", "if command -v wlsunset > /dev/null; then pkill wlsunset; wlsunset -t 3000 & elif command -v redshift > /dev/null; then pkill redshift; redshift -P -O 3000 & else echo 'No night mode tool available'; fi"]
|
||||
running: false
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Failed to enable night mode")
|
||||
Prefs.setNightModeEnabled(false)
|
||||
console.warn("Failed to enable night mode");
|
||||
Prefs.setNightModeEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: nightModeDisableProcess
|
||||
|
||||
command: ["bash", "-c", "pkill wlsunset; pkill redshift; if command -v wlsunset > /dev/null; then wlsunset -t 6500 -T 6500 & sleep 1; pkill wlsunset; elif command -v redshift > /dev/null; then redshift -P -O 6500; redshift -x; fi"]
|
||||
running: false
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Failed to disable night mode")
|
||||
}
|
||||
if (exitCode !== 0)
|
||||
console.warn("Failed to disable night mode");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +1,26 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
// Default to WiFi when nothing is connected
|
||||
|
||||
id: networkTab
|
||||
|
||||
property int networkSubTab: {
|
||||
// Default to WiFi tab if WiFi is connected, otherwise Ethernet
|
||||
if (NetworkService.networkStatus === "wifi") return 1
|
||||
else if (NetworkService.networkStatus === "ethernet") return 0
|
||||
else return 1 // Default to WiFi when nothing is connected
|
||||
if (NetworkService.networkStatus === "wifi")
|
||||
return 1;
|
||||
else if (NetworkService.networkStatus === "ethernet")
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
@@ -30,9 +34,7 @@ Item {
|
||||
width: (parent.width - Theme.spacingXS) / 2
|
||||
height: 36
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: networkTab.networkSubTab === 0 ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
ethernetTabArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
color: networkTab.networkSubTab === 0 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : ethernetTabArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
@@ -53,27 +55,28 @@ Item {
|
||||
font.weight: networkTab.networkSubTab === 0 ? Font.Medium : Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: ethernetTabArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
networkTab.networkSubTab = 0
|
||||
WifiService.autoRefreshEnabled = false
|
||||
networkTab.networkSubTab = 0;
|
||||
WifiService.autoRefreshEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingXS) / 2
|
||||
height: 36
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: networkTab.networkSubTab === 1 ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
wifiTabArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
color: networkTab.networkSubTab === 1 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : wifiTabArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
@@ -94,22 +97,26 @@ Item {
|
||||
font.weight: networkTab.networkSubTab === 1 ? Font.Medium : Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wifiTabArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
networkTab.networkSubTab = 1
|
||||
WifiService.autoRefreshEnabled = true
|
||||
if (NetworkService.wifiEnabled) {
|
||||
WifiService.scanWifi()
|
||||
}
|
||||
networkTab.networkSubTab = 1;
|
||||
WifiService.autoRefreshEnabled = true;
|
||||
if (NetworkService.wifiEnabled)
|
||||
WifiService.scanWifi();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Ethernet Tab Content
|
||||
@@ -124,12 +131,9 @@ Item {
|
||||
flickDeceleration: 8000
|
||||
maximumFlickVelocity: 15000
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
|
||||
Column {
|
||||
id: ethernetContent
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
@@ -173,6 +177,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Force Ethernet preference button
|
||||
@@ -185,22 +190,16 @@ Item {
|
||||
radius: 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
z: 10
|
||||
opacity: networkTab.changingNetworkPreference ? 0.6 : 1.0
|
||||
opacity: networkTab.changingNetworkPreference ? 0.6 : 1
|
||||
visible: networkTab.networkStatus !== "ethernet"
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
id: ethernetPreferenceIcon
|
||||
|
||||
text: networkTab.changingNetworkPreference ? "sync" : ""
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -218,16 +217,17 @@ Item {
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
text: networkTab.changingNetworkPreference ? "Switching..." :
|
||||
(networkTab.networkStatus === "ethernet" ? "" : "Prefer over WiFi")
|
||||
text: networkTab.changingNetworkPreference ? "Switching..." : (networkTab.networkStatus === "ethernet" ? "" : "Prefer over WiFi")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: networkTab.networkStatus === "ethernet" ? Theme.background : Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -237,18 +237,29 @@ Item {
|
||||
propagateComposedEvents: false
|
||||
enabled: !networkTab.changingNetworkPreference
|
||||
onClicked: {
|
||||
console.log("*** ETHERNET PREFERENCE BUTTON CLICKED ***")
|
||||
console.log("*** ETHERNET PREFERENCE BUTTON CLICKED ***");
|
||||
if (networkTab.networkStatus !== "ethernet") {
|
||||
console.log("Setting preference to ethernet")
|
||||
NetworkService.setNetworkPreference("ethernet")
|
||||
console.log("Setting preference to ethernet");
|
||||
NetworkService.setNetworkPreference("ethernet");
|
||||
} else {
|
||||
console.log("Setting preference to auto")
|
||||
NetworkService.setNetworkPreference("auto")
|
||||
console.log("Setting preference to auto");
|
||||
NetworkService.setNetworkPreference("auto");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Ethernet control button
|
||||
@@ -279,19 +290,28 @@ Item {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: ethernetControlArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
NetworkService.toggleNetworkConnection("ethernet")
|
||||
NetworkService.toggleNetworkConnection("ethernet");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// WiFi Tab Content
|
||||
@@ -306,12 +326,9 @@ Item {
|
||||
flickDeceleration: 8000
|
||||
maximumFlickVelocity: 15000
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
|
||||
Column {
|
||||
id: wifiContent
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
@@ -322,14 +339,7 @@ Item {
|
||||
radius: Theme.cornerRadius
|
||||
color: wifiToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
visible: NetworkService.wifiAvailable
|
||||
opacity: NetworkService.wifiToggling ? 0.6 : 1.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
opacity: NetworkService.wifiToggling ? 0.6 : 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -339,6 +349,7 @@ Item {
|
||||
|
||||
Text {
|
||||
id: wifiToggleIcon
|
||||
|
||||
text: NetworkService.wifiToggling ? "sync" : "power_settings_new"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
@@ -361,7 +372,9 @@ Item {
|
||||
duration: 200
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -371,17 +384,28 @@ Item {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wifiToggleArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
NetworkService.toggleWifiRadio()
|
||||
NetworkService.toggleWifiRadio();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Current WiFi connection (if connected)
|
||||
@@ -401,11 +425,7 @@ Item {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: networkTab.networkStatus === "wifi" ?
|
||||
(WifiService.wifiSignalStrength === "excellent" ? "wifi" :
|
||||
WifiService.wifiSignalStrength === "good" ? "wifi_2_bar" :
|
||||
WifiService.wifiSignalStrength === "fair" ? "wifi_1_bar" :
|
||||
WifiService.wifiSignalStrength === "poor" ? "wifi_calling_3" : "wifi") : "wifi"
|
||||
text: networkTab.networkStatus === "wifi" ? (WifiService.wifiSignalStrength === "excellent" ? "wifi" : WifiService.wifiSignalStrength === "good" ? "wifi_2_bar" : WifiService.wifiSignalStrength === "fair" ? "wifi_1_bar" : WifiService.wifiSignalStrength === "poor" ? "wifi_calling_3" : "wifi") : "wifi"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSizeLarge
|
||||
color: networkTab.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
|
||||
@@ -428,6 +448,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Force WiFi preference button
|
||||
@@ -439,22 +460,16 @@ Item {
|
||||
border.width: 1
|
||||
radius: 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
opacity: networkTab.changingNetworkPreference ? 0.6 : 1.0
|
||||
opacity: networkTab.changingNetworkPreference ? 0.6 : 1
|
||||
visible: networkTab.networkStatus !== "wifi"
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
id: wifiPreferenceIcon
|
||||
|
||||
text: networkTab.changingNetworkPreference ? "sync" : ""
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -472,16 +487,17 @@ Item {
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
text: NetworkService.changingNetworkPreference ? "Switching..." :
|
||||
(NetworkService.networkStatus === "wifi" ? "" : "Prefer over Ethernet")
|
||||
text: NetworkService.changingNetworkPreference ? "Switching..." : (NetworkService.networkStatus === "wifi" ? "" : "Prefer over Ethernet")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: NetworkService.networkStatus === "wifi" ? Theme.background : Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -491,16 +507,26 @@ Item {
|
||||
propagateComposedEvents: false
|
||||
enabled: !networkTab.changingNetworkPreference
|
||||
onClicked: {
|
||||
console.log("Force WiFi preference clicked")
|
||||
if (NetworkService.networkStatus !== "wifi") {
|
||||
NetworkService.setNetworkPreference("wifi")
|
||||
} else {
|
||||
NetworkService.setNetworkPreference("auto")
|
||||
console.log("Force WiFi preference clicked");
|
||||
if (NetworkService.networkStatus !== "wifi")
|
||||
NetworkService.setNetworkPreference("wifi");
|
||||
else
|
||||
NetworkService.setNetworkPreference("auto");
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Available WiFi Networks
|
||||
@@ -519,17 +545,20 @@ Item {
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Item { width: parent.width - 200; height: 1 }
|
||||
Item {
|
||||
width: parent.width - 200
|
||||
height: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32
|
||||
height: 32
|
||||
radius: 16
|
||||
color: refreshArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
|
||||
WifiService.isScanning ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"
|
||||
color: refreshArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : WifiService.isScanning ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"
|
||||
|
||||
Text {
|
||||
id: refreshIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: WifiService.isScanning ? "sync" : "refresh"
|
||||
font.family: Theme.iconFont
|
||||
@@ -552,24 +581,28 @@ Item {
|
||||
duration: 200
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: refreshArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: !WifiService.isScanning
|
||||
onClicked: {
|
||||
if (NetworkService.wifiEnabled) {
|
||||
WifiService.scanWifi()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (NetworkService.wifiEnabled)
|
||||
WifiService.scanWifi();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Connection status indicator
|
||||
Rectangle {
|
||||
@@ -577,24 +610,22 @@ Item {
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (WifiService.connectionStatus === "connecting") {
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
|
||||
} else if (WifiService.connectionStatus === "failed") {
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||
} else if (WifiService.connectionStatus === "connected") {
|
||||
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.12)
|
||||
}
|
||||
return "transparent"
|
||||
if (WifiService.connectionStatus === "connecting")
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
||||
else if (WifiService.connectionStatus === "failed")
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
|
||||
else if (WifiService.connectionStatus === "connected")
|
||||
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.12);
|
||||
return "transparent";
|
||||
}
|
||||
border.color: {
|
||||
if (WifiService.connectionStatus === "connecting") {
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3)
|
||||
} else if (WifiService.connectionStatus === "failed") {
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3)
|
||||
} else if (WifiService.connectionStatus === "connected") {
|
||||
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.3)
|
||||
}
|
||||
return "transparent"
|
||||
if (WifiService.connectionStatus === "connecting")
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3);
|
||||
else if (WifiService.connectionStatus === "failed")
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3);
|
||||
else if (WifiService.connectionStatus === "connected")
|
||||
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.3);
|
||||
return "transparent";
|
||||
}
|
||||
border.width: WifiService.connectionStatus !== "" ? 1 : 0
|
||||
visible: WifiService.connectionStatus !== ""
|
||||
@@ -605,19 +636,32 @@ Item {
|
||||
|
||||
Text {
|
||||
id: connectionIcon
|
||||
|
||||
text: {
|
||||
if (WifiService.connectionStatus === "connecting") return "sync"
|
||||
if (WifiService.connectionStatus === "failed") return "error"
|
||||
if (WifiService.connectionStatus === "connected") return "check_circle"
|
||||
return ""
|
||||
if (WifiService.connectionStatus === "connecting")
|
||||
return "sync";
|
||||
|
||||
if (WifiService.connectionStatus === "failed")
|
||||
return "error";
|
||||
|
||||
if (WifiService.connectionStatus === "connected")
|
||||
return "check_circle";
|
||||
|
||||
return "";
|
||||
}
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
color: {
|
||||
if (WifiService.connectionStatus === "connecting") return Theme.warning
|
||||
if (WifiService.connectionStatus === "failed") return Theme.error
|
||||
if (WifiService.connectionStatus === "connected") return Theme.success
|
||||
return Theme.surfaceText
|
||||
if (WifiService.connectionStatus === "connecting")
|
||||
return Theme.warning;
|
||||
|
||||
if (WifiService.connectionStatus === "failed")
|
||||
return Theme.error;
|
||||
|
||||
if (WifiService.connectionStatus === "connected")
|
||||
return Theme.success;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
rotation: WifiService.connectionStatus === "connecting" ? connectionIcon.rotation : 0
|
||||
@@ -637,25 +681,40 @@ Item {
|
||||
duration: 200
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (WifiService.connectionStatus === "connecting") return "Connecting to " + WifiService.connectingSSID
|
||||
if (WifiService.connectionStatus === "failed") return "Failed to connect to " + WifiService.connectingSSID
|
||||
if (WifiService.connectionStatus === "connected") return "Connected to " + WifiService.connectingSSID
|
||||
return ""
|
||||
if (WifiService.connectionStatus === "connecting")
|
||||
return "Connecting to " + WifiService.connectingSSID;
|
||||
|
||||
if (WifiService.connectionStatus === "failed")
|
||||
return "Failed to connect to " + WifiService.connectingSSID;
|
||||
|
||||
if (WifiService.connectionStatus === "connected")
|
||||
return "Connected to " + WifiService.connectingSSID;
|
||||
|
||||
return "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: {
|
||||
if (WifiService.connectionStatus === "connecting") return Theme.warning
|
||||
if (WifiService.connectionStatus === "failed") return Theme.error
|
||||
if (WifiService.connectionStatus === "connected") return Theme.success
|
||||
return Theme.surfaceText
|
||||
if (WifiService.connectionStatus === "connecting")
|
||||
return Theme.warning;
|
||||
|
||||
if (WifiService.connectionStatus === "failed")
|
||||
return Theme.error;
|
||||
|
||||
if (WifiService.connectionStatus === "connected")
|
||||
return Theme.success;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
@@ -663,7 +722,9 @@ Item {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// WiFi networks list (only show if WiFi is available and enabled)
|
||||
@@ -674,8 +735,7 @@ Item {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: networkArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
color: networkArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
border.color: modelData.connected ? Theme.primary : "transparent"
|
||||
border.width: modelData.connected ? 1 : 0
|
||||
|
||||
@@ -686,12 +746,10 @@ Item {
|
||||
// Signal strength icon
|
||||
Text {
|
||||
id: signalIcon
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: modelData.signalStrength === "excellent" ? "wifi" :
|
||||
modelData.signalStrength === "good" ? "wifi_2_bar" :
|
||||
modelData.signalStrength === "fair" ? "wifi_1_bar" :
|
||||
modelData.signalStrength === "poor" ? "wifi_calling_3" : "wifi"
|
||||
text: modelData.signalStrength === "excellent" ? "wifi" : modelData.signalStrength === "good" ? "wifi_2_bar" : modelData.signalStrength === "fair" ? "wifi_1_bar" : modelData.signalStrength === "poor" ? "wifi_calling_3" : "wifi"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: modelData.connected ? Theme.primary : Theme.surfaceText
|
||||
@@ -718,19 +776,25 @@ Item {
|
||||
Text {
|
||||
width: parent.width
|
||||
text: {
|
||||
if (modelData.connected) return "Connected"
|
||||
if (modelData.saved) return "Saved" + (modelData.secured ? " • Secured" : " • Open")
|
||||
return modelData.secured ? "Secured" : "Open"
|
||||
if (modelData.connected)
|
||||
return "Connected";
|
||||
|
||||
if (modelData.saved)
|
||||
return "Saved" + (modelData.secured ? " • Secured" : " • Open");
|
||||
|
||||
return modelData.secured ? "Secured" : "Open";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Right side icons
|
||||
Row {
|
||||
id: rightIcons
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
@@ -763,44 +827,52 @@ Item {
|
||||
|
||||
MouseArea {
|
||||
id: forgetArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
WifiService.forgetWifiNetwork(modelData.ssid)
|
||||
WifiService.forgetWifiNetwork(modelData.ssid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// Already connected, do nothing or show info
|
||||
|
||||
id: networkArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
// Already connected, do nothing or show info
|
||||
return
|
||||
}
|
||||
if (modelData.connected)
|
||||
return ;
|
||||
|
||||
if (modelData.saved) {
|
||||
// Saved network, connect directly
|
||||
WifiService.connectToWifi(modelData.ssid)
|
||||
WifiService.connectToWifi(modelData.ssid);
|
||||
} else if (modelData.secured) {
|
||||
// Secured network, need password - use root dialog
|
||||
wifiPasswordDialog.wifiPasswordSSID = modelData.ssid
|
||||
wifiPasswordDialog.wifiPasswordInput = ""
|
||||
wifiPasswordDialog.wifiPasswordDialogVisible = true
|
||||
wifiPasswordDialog.wifiPasswordSSID = modelData.ssid;
|
||||
wifiPasswordDialog.wifiPasswordInput = "";
|
||||
wifiPasswordDialog.wifiPasswordDialogVisible = true;
|
||||
} else {
|
||||
// Open network, connect directly
|
||||
WifiService.connectToWifi(modelData.ssid)
|
||||
WifiService.connectToWifi(modelData.ssid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// WiFi disabled message
|
||||
@@ -831,9 +903,17 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import "."
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import "."
|
||||
|
||||
Rectangle {
|
||||
id: cpuWidget
|
||||
@@ -13,19 +13,17 @@ Rectangle {
|
||||
width: 55
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: cpuArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
color: cpuArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
|
||||
MouseArea {
|
||||
id: cpuArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
ProcessMonitorService.setSortBy("cpu")
|
||||
processListDropdown.toggle()
|
||||
ProcessMonitorService.setSortBy("cpu");
|
||||
processListDropdown.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +38,13 @@ Rectangle {
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: {
|
||||
if (SystemMonitorService.cpuUsage > 80) return Theme.error
|
||||
if (SystemMonitorService.cpuUsage > 60) return Theme.warning
|
||||
return Theme.surfaceText
|
||||
if (SystemMonitorService.cpuUsage > 80)
|
||||
return Theme.error;
|
||||
|
||||
if (SystemMonitorService.cpuUsage > 60)
|
||||
return Theme.warning;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
@@ -55,5 +57,7 @@ Rectangle {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,48 +50,48 @@ Item {
|
||||
// Slider track
|
||||
Rectangle {
|
||||
id: sliderTrack
|
||||
width: parent.width - (leftIconWidth + rightIconWidth + (slider.leftIcon.length > 0 ? Theme.spacingM : 0) + (slider.rightIcon.length > 0 ? Theme.spacingM : 0))
|
||||
height: 6
|
||||
radius: 3
|
||||
color: slider.enabled ?
|
||||
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) :
|
||||
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.1)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
property int leftIconWidth: slider.leftIcon.length > 0 ? Theme.iconSize : 0
|
||||
property int rightIconWidth: slider.rightIcon.length > 0 ? Theme.iconSize : 0
|
||||
|
||||
width: parent.width - (leftIconWidth + rightIconWidth + (slider.leftIcon.length > 0 ? Theme.spacingM : 0) + (slider.rightIcon.length > 0 ? Theme.spacingM : 0))
|
||||
height: 6
|
||||
radius: 3
|
||||
color: slider.enabled ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.1)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// Fill
|
||||
Rectangle {
|
||||
id: sliderFill
|
||||
|
||||
width: parent.width * ((slider.value - slider.minimum) / (slider.maximum - slider.minimum))
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: slider.enabled ? Theme.primary : Theme.surfaceVariantText
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Draggable handle
|
||||
Rectangle {
|
||||
id: sliderHandle
|
||||
|
||||
width: 18
|
||||
height: 18
|
||||
radius: 9
|
||||
color: slider.enabled ? Theme.primary : Theme.surfaceVariantText
|
||||
border.color: slider.enabled ? Qt.lighter(Theme.primary, 1.3) : Qt.lighter(Theme.surfaceVariantText, 1.3)
|
||||
border.width: 2
|
||||
|
||||
x: Math.max(0, Math.min(parent.width - width, sliderFill.width - width/2))
|
||||
x: Math.max(0, Math.min(parent.width - width, sliderFill.width - width / 2))
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
scale: sliderMouseArea.containsMouse || sliderMouseArea.pressed ? 1.2 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150 }
|
||||
}
|
||||
scale: sliderMouseArea.containsMouse || sliderMouseArea.pressed ? 1.2 : 1
|
||||
|
||||
// Handle glow effect when active
|
||||
Rectangle {
|
||||
@@ -105,57 +105,67 @@ Item {
|
||||
visible: sliderMouseArea.containsMouse && slider.enabled
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 150 }
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: sliderContainer
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
MouseArea {
|
||||
id: sliderMouseArea
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: slider.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: slider.enabled
|
||||
preventStealing: true
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
onPressed: (mouse) => {
|
||||
if (slider.enabled) {
|
||||
isDragging = true
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum))
|
||||
slider.value = newValue
|
||||
slider.sliderValueChanged(newValue)
|
||||
isDragging = true;
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width));
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum));
|
||||
slider.value = newValue;
|
||||
slider.sliderValueChanged(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
if (slider.enabled) {
|
||||
isDragging = false
|
||||
slider.sliderDragFinished(slider.value)
|
||||
isDragging = false;
|
||||
slider.sliderDragFinished(slider.value);
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: (mouse) => {
|
||||
if (pressed && isDragging && slider.enabled) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum))
|
||||
slider.value = newValue
|
||||
slider.sliderValueChanged(newValue)
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width));
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum));
|
||||
slider.value = newValue;
|
||||
slider.sliderValueChanged(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: (mouse) => {
|
||||
if (slider.enabled) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum))
|
||||
slider.value = newValue
|
||||
slider.sliderValueChanged(newValue)
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width));
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum));
|
||||
slider.value = newValue;
|
||||
slider.sliderValueChanged(newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,29 +173,30 @@ Item {
|
||||
// Global mouse area for drag tracking
|
||||
MouseArea {
|
||||
id: sliderGlobalMouseArea
|
||||
|
||||
anchors.fill: sliderContainer
|
||||
enabled: sliderMouseArea.isDragging
|
||||
visible: false
|
||||
preventStealing: true
|
||||
|
||||
onPositionChanged: (mouse) => {
|
||||
if (sliderMouseArea.isDragging && slider.enabled) {
|
||||
let globalPos = mapToItem(sliderTrack, mouse.x, mouse.y)
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / sliderTrack.width))
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum))
|
||||
slider.value = newValue
|
||||
slider.sliderValueChanged(newValue)
|
||||
let globalPos = mapToItem(sliderTrack, mouse.x, mouse.y);
|
||||
let ratio = Math.max(0, Math.min(1, globalPos.x / sliderTrack.width));
|
||||
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum));
|
||||
slider.value = newValue;
|
||||
slider.sliderValueChanged(newValue);
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
if (sliderMouseArea.isDragging && slider.enabled) {
|
||||
sliderMouseArea.isDragging = false;
|
||||
slider.sliderDragFinished(slider.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
if (sliderMouseArea.isDragging && slider.enabled) {
|
||||
sliderMouseArea.isDragging = false
|
||||
slider.sliderDragFinished(slider.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Right icon
|
||||
@@ -197,6 +208,9 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: slider.rightIcon.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
|
||||
PanelWindow {
|
||||
@@ -20,7 +20,34 @@ PanelWindow {
|
||||
signal confirmed(string value)
|
||||
signal cancelled()
|
||||
|
||||
function showDialog(title, subtitle, placeholder, isPass, confirmText, cancelText) {
|
||||
dialogTitle = title || "Input Required";
|
||||
dialogSubtitle = subtitle || "Please enter the required information";
|
||||
inputPlaceholder = placeholder || "Enter text";
|
||||
isPassword = isPass || false;
|
||||
confirmButtonText = confirmText || "Confirm";
|
||||
cancelButtonText = cancelText || "Cancel";
|
||||
inputValue = "";
|
||||
dialogVisible = true;
|
||||
}
|
||||
|
||||
function hideDialog() {
|
||||
dialogVisible = false;
|
||||
inputValue = "";
|
||||
}
|
||||
|
||||
visible: dialogVisible
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: dialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
textInput.forceActiveFocus();
|
||||
textInput.text = inputValue;
|
||||
}
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
@@ -28,54 +55,27 @@ PanelWindow {
|
||||
bottom: true
|
||||
}
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: dialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
|
||||
color: "transparent"
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
textInput.forceActiveFocus()
|
||||
textInput.text = inputValue
|
||||
}
|
||||
}
|
||||
|
||||
function showDialog(title, subtitle, placeholder, isPass, confirmText, cancelText) {
|
||||
dialogTitle = title || "Input Required"
|
||||
dialogSubtitle = subtitle || "Please enter the required information"
|
||||
inputPlaceholder = placeholder || "Enter text"
|
||||
isPassword = isPass || false
|
||||
confirmButtonText = confirmText || "Confirm"
|
||||
cancelButtonText = cancelText || "Cancel"
|
||||
inputValue = ""
|
||||
dialogVisible = true
|
||||
}
|
||||
|
||||
function hideDialog() {
|
||||
dialogVisible = false
|
||||
inputValue = ""
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.5)
|
||||
opacity: dialogVisible ? 1.0 : 0.0
|
||||
opacity: dialogVisible ? 1 : 0
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
inputDialog.cancelled();
|
||||
hideDialog();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
inputDialog.cancelled()
|
||||
hideDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -86,23 +86,8 @@ PanelWindow {
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 1
|
||||
|
||||
opacity: dialogVisible ? 1.0 : 0.0
|
||||
scale: dialogVisible ? 1.0 : 0.9
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
opacity: dialogVisible ? 1 : 0
|
||||
scale: dialogVisible ? 1 : 0.9
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
@@ -133,6 +118,7 @@ PanelWindow {
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -151,15 +137,18 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: closeDialogArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
inputDialog.cancelled()
|
||||
hideDialog()
|
||||
inputDialog.cancelled();
|
||||
hideDialog();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Text input
|
||||
@@ -173,6 +162,7 @@ PanelWindow {
|
||||
|
||||
TextInput {
|
||||
id: textInput
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -181,6 +171,18 @@ PanelWindow {
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
cursorVisible: activeFocus
|
||||
selectByMouse: true
|
||||
onTextChanged: {
|
||||
inputValue = text;
|
||||
}
|
||||
onAccepted: {
|
||||
inputDialog.confirmed(inputValue);
|
||||
hideDialog();
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (dialogVisible)
|
||||
forceActiveFocus();
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
@@ -191,29 +193,16 @@ PanelWindow {
|
||||
visible: parent.text.length === 0
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
inputValue = text
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
inputDialog.confirmed(inputValue)
|
||||
hideDialog()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (dialogVisible) {
|
||||
forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.IBeamCursor
|
||||
onClicked: {
|
||||
textInput.forceActiveFocus()
|
||||
textInput.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Show password checkbox (only visible for password inputs)
|
||||
@@ -223,6 +212,7 @@ PanelWindow {
|
||||
|
||||
Rectangle {
|
||||
id: showPasswordCheckbox
|
||||
|
||||
property bool checked: false
|
||||
|
||||
width: 20
|
||||
@@ -246,9 +236,10 @@ PanelWindow {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
showPasswordCheckbox.checked = !showPasswordCheckbox.checked
|
||||
showPasswordCheckbox.checked = !showPasswordCheckbox.checked;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -257,6 +248,7 @@ PanelWindow {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Buttons
|
||||
@@ -279,6 +271,7 @@ PanelWindow {
|
||||
|
||||
Text {
|
||||
id: cancelText
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: cancelButtonText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -288,14 +281,16 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: cancelArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
inputDialog.cancelled()
|
||||
hideDialog()
|
||||
inputDialog.cancelled();
|
||||
hideDialog();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -304,10 +299,11 @@ PanelWindow {
|
||||
radius: Theme.cornerRadius
|
||||
color: confirmArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||
enabled: inputValue.length > 0
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
opacity: enabled ? 1 : 0.5
|
||||
|
||||
Text {
|
||||
id: confirmText
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: confirmButtonText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -317,13 +313,14 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: confirmArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: parent.enabled
|
||||
onClicked: {
|
||||
inputDialog.confirmed(inputValue)
|
||||
hideDialog()
|
||||
inputDialog.confirmed(inputValue);
|
||||
hideDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,10 +329,33 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -13,14 +13,11 @@ PanelWindow {
|
||||
property bool notificationHistoryVisible: false
|
||||
|
||||
visible: notificationHistoryVisible
|
||||
|
||||
implicitWidth: 400
|
||||
implicitHeight: 500
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
@@ -34,7 +31,7 @@ PanelWindow {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
notificationHistoryVisible = false
|
||||
notificationHistoryVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,43 +44,65 @@ PanelWindow {
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0.5
|
||||
|
||||
opacity: notificationHistoryVisible ? 1 : 0
|
||||
// Animation
|
||||
transform: [
|
||||
Scale {
|
||||
id: scaleTransform
|
||||
|
||||
origin.x: 400 // Use fixed width since popup is 400px wide
|
||||
origin.y: 0
|
||||
xScale: notificationHistoryVisible ? 1.0 : 0.95
|
||||
yScale: notificationHistoryVisible ? 1.0 : 0.8
|
||||
xScale: notificationHistoryVisible ? 1 : 0.95
|
||||
yScale: notificationHistoryVisible ? 1 : 0.8
|
||||
},
|
||||
Translate {
|
||||
id: translateTransform
|
||||
|
||||
x: notificationHistoryVisible ? 0 : 15
|
||||
y: notificationHistoryVisible ? 0 : -30
|
||||
}
|
||||
]
|
||||
|
||||
opacity: notificationHistoryVisible ? 1.0 : 0.0
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "visible"
|
||||
when: notificationHistoryVisible
|
||||
PropertyChanges { target: scaleTransform; xScale: 1.0; yScale: 1.0 }
|
||||
PropertyChanges { target: translateTransform; x: 0; y: 0 }
|
||||
|
||||
PropertyChanges {
|
||||
target: scaleTransform
|
||||
xScale: 1
|
||||
yScale: 1
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: translateTransform
|
||||
x: 0
|
||||
y: 0
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "hidden"
|
||||
when: !notificationHistoryVisible
|
||||
PropertyChanges { target: scaleTransform; xScale: 0.95; yScale: 0.8 }
|
||||
PropertyChanges { target: translateTransform; x: 15; y: -30 }
|
||||
|
||||
PropertyChanges {
|
||||
target: scaleTransform
|
||||
xScale: 0.95
|
||||
yScale: 0.8
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: translateTransform
|
||||
x: 15
|
||||
y: -30
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "*"; to: "*"
|
||||
from: "*"
|
||||
to: "*"
|
||||
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
targets: [scaleTransform, translateTransform]
|
||||
@@ -91,22 +110,18 @@ PanelWindow {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent clicks from propagating to background
|
||||
MouseArea {
|
||||
// Stop propagation - do nothing
|
||||
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
// Stop propagation - do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,14 +152,8 @@ PanelWindow {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NotificationService.notifications.length > 0
|
||||
|
||||
color: clearArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
|
||||
Theme.surfaceContainer
|
||||
|
||||
border.color: clearArea.containsMouse ?
|
||||
Theme.primary :
|
||||
Theme.outline
|
||||
color: clearArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.surfaceContainer
|
||||
border.color: clearArea.containsMouse ? Theme.primary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
@@ -166,14 +175,15 @@ PanelWindow {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clearArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: NotificationService.clearAllNotifications()
|
||||
}
|
||||
|
||||
@@ -182,6 +192,7 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
@@ -189,8 +200,11 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Notification List
|
||||
@@ -203,6 +217,8 @@ PanelWindow {
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ListView {
|
||||
// Quick reply height
|
||||
|
||||
model: NotificationService.groupedNotifications
|
||||
spacing: Theme.spacingL
|
||||
interactive: true
|
||||
@@ -220,13 +236,16 @@ PanelWindow {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
properties: "height"
|
||||
from: 0
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
remove: Transition {
|
||||
@@ -235,6 +254,7 @@ PanelWindow {
|
||||
PauseAnimation {
|
||||
duration: 50
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
properties: "opacity"
|
||||
@@ -242,14 +262,18 @@ PanelWindow {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
properties: "height,anchors.topMargin,anchors.bottomMargin"
|
||||
to: 0
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
displaced: Transition {
|
||||
@@ -258,6 +282,7 @@ PanelWindow {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add move transition for internal content changes
|
||||
@@ -267,37 +292,35 @@ PanelWindow {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
|
||||
readonly property bool expanded: NotificationService.expandedGroups[modelData.key] || false
|
||||
|
||||
width: ListView.view.width
|
||||
height: {
|
||||
if (expanded) {
|
||||
// Calculate expanded height: header (48) + spacing (16) + individual notifications
|
||||
let headerHeight = 48 + Theme.spacingM
|
||||
let notificationHeight = modelData.notifications.length * (60 + Theme.spacingS) // Each notification ~60px + spacing
|
||||
let totalExpandedHeight = headerHeight + notificationHeight + Theme.spacingL * 2
|
||||
return Math.max(totalExpandedHeight, 200) // Minimum expanded height
|
||||
let headerHeight = 48 + Theme.spacingM;
|
||||
let notificationHeight = modelData.notifications.length * (60 + Theme.spacingS); // Each notification ~60px + spacing
|
||||
let totalExpandedHeight = headerHeight + notificationHeight + Theme.spacingL * 2;
|
||||
return Math.max(totalExpandedHeight, 200); // Minimum expanded height
|
||||
} else {
|
||||
// Collapsed height: icon + content + quick reply (if any)
|
||||
let collapsedHeight = 72 + Theme.spacingS * 2 // Header height + spacing
|
||||
if (modelData.latestNotification.notification.hasInlineReply) {
|
||||
collapsedHeight += 36 + Theme.spacingS // Quick reply height
|
||||
}
|
||||
return collapsedHeight + Theme.spacingL * 2
|
||||
let collapsedHeight = 72 + Theme.spacingS * 2;
|
||||
// Header height + spacing
|
||||
if (modelData.latestNotification.notification.hasInlineReply)
|
||||
collapsedHeight += 36 + Theme.spacingS;
|
||||
|
||||
return collapsedHeight + Theme.spacingL * 2;
|
||||
}
|
||||
}
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Theme.popupBackground()
|
||||
border.color: modelData.latestNotification.urgency === 2 ?
|
||||
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.color: modelData.latestNotification.urgency === 2 ? 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: modelData.latestNotification.urgency === 2 ? 2 : 1
|
||||
|
||||
clip: true
|
||||
|
||||
// Priority indicator for urgent notifications
|
||||
@@ -312,19 +335,10 @@ PanelWindow {
|
||||
visible: modelData.latestNotification.urgency === 2
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
SequentialAnimation {
|
||||
PauseAnimation { duration: 25 }
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collapsed view - shows app header and latest notification
|
||||
Column {
|
||||
id: collapsedContent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -340,6 +354,7 @@ PanelWindow {
|
||||
// App icon with proper fallback handling
|
||||
Item {
|
||||
id: iconContainer
|
||||
|
||||
width: 48
|
||||
height: 48
|
||||
anchors.left: parent.left
|
||||
@@ -358,34 +373,34 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
source: {
|
||||
if (modelData.latestNotification.appIcon && modelData.latestNotification.appIcon !== "") {
|
||||
return Quickshell.iconPath(modelData.latestNotification.appIcon, "")
|
||||
}
|
||||
return ""
|
||||
if (modelData.latestNotification.appIcon && modelData.latestNotification.appIcon !== "")
|
||||
return Quickshell.iconPath(modelData.latestNotification.appIcon, "");
|
||||
|
||||
return "";
|
||||
}
|
||||
visible: status === Image.Ready
|
||||
|
||||
onStatusChanged: {
|
||||
if (status === Image.Error || status === Image.Null || source === "") {
|
||||
fallbackIcon.visible = true
|
||||
} else if (status === Image.Ready) {
|
||||
fallbackIcon.visible = false
|
||||
}
|
||||
if (status === Image.Error || status === Image.Null || source === "")
|
||||
fallbackIcon.visible = true;
|
||||
else if (status === Image.Ready)
|
||||
fallbackIcon.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: fallbackIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
visible: true
|
||||
text: {
|
||||
const appName = modelData.appName || "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
const appName = modelData.appName || "?";
|
||||
return appName.charAt(0).toUpperCase();
|
||||
}
|
||||
font.pixelSize: 20
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Count badge for multiple notifications
|
||||
@@ -407,7 +422,9 @@ PanelWindow {
|
||||
font.pixelSize: 9
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Content area with proper spacing
|
||||
@@ -424,11 +441,10 @@ PanelWindow {
|
||||
Text {
|
||||
width: parent.width
|
||||
text: {
|
||||
if (modelData.latestNotification.timeStr.length > 0) {
|
||||
return modelData.appName + " • " + modelData.latestNotification.timeStr
|
||||
} else {
|
||||
return modelData.appName
|
||||
}
|
||||
if (modelData.latestNotification.timeStr.length > 0)
|
||||
return modelData.appName + " • " + modelData.latestNotification.timeStr;
|
||||
else
|
||||
return modelData.appName;
|
||||
}
|
||||
color: Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -460,11 +476,13 @@ PanelWindow {
|
||||
wrapMode: Text.WordWrap
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Controls with fixed positioning
|
||||
Item {
|
||||
id: controlsContainer
|
||||
|
||||
width: 72
|
||||
height: 32
|
||||
anchors.right: parent.right
|
||||
@@ -475,9 +493,7 @@ PanelWindow {
|
||||
height: 32
|
||||
radius: 16
|
||||
anchors.left: parent.left
|
||||
color: expandArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: expandArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
visible: modelData.count > 1
|
||||
|
||||
Text {
|
||||
@@ -493,16 +509,20 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: expandArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationService.toggleGroupExpansion(modelData.key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -510,9 +530,7 @@ PanelWindow {
|
||||
height: 32
|
||||
radius: 16
|
||||
anchors.right: parent.right
|
||||
color: dismissArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: dismissArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -524,13 +542,17 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: dismissArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationService.dismissGroup(modelData.key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Enhanced quick reply for conversations
|
||||
@@ -549,20 +571,24 @@ PanelWindow {
|
||||
|
||||
TextField {
|
||||
id: quickReplyField
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
placeholderText: modelData.latestNotification.notification.inlineReplyPlaceholder || "Quick reply..."
|
||||
background: Item {}
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
|
||||
onAccepted: {
|
||||
if (text.length > 0) {
|
||||
modelData.latestNotification.notification.sendInlineReply(text)
|
||||
text = ""
|
||||
modelData.latestNotification.notification.sendInlineReply(text);
|
||||
text = "";
|
||||
}
|
||||
}
|
||||
|
||||
background: Item {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -586,8 +612,8 @@ PanelWindow {
|
||||
enabled: quickReplyField.text.length > 0
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
modelData.latestNotification.notification.sendInlineReply(quickReplyField.text)
|
||||
quickReplyField.text = ""
|
||||
modelData.latestNotification.notification.sendInlineReply(quickReplyField.text);
|
||||
quickReplyField.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,14 +622,19 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Expanded view - shows all notifications stacked
|
||||
Column {
|
||||
id: expandedContent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -638,13 +669,14 @@ PanelWindow {
|
||||
anchors.centerIn: parent
|
||||
visible: !modelData.latestNotification.appIcon || modelData.latestNotification.appIcon === ""
|
||||
text: {
|
||||
const appName = modelData.appName || "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
const appName = modelData.appName || "?";
|
||||
return appName.charAt(0).toUpperCase();
|
||||
}
|
||||
font.pixelSize: 16
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -668,9 +700,7 @@ PanelWindow {
|
||||
height: 32
|
||||
radius: 16
|
||||
anchors.left: parent.left
|
||||
color: collapseArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: collapseArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -682,11 +712,13 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: collapseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationService.toggleGroupExpansion(modelData.key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -694,9 +726,7 @@ PanelWindow {
|
||||
height: 32
|
||||
radius: 16
|
||||
anchors.right: parent.right
|
||||
color: dismissAllArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: dismissAllArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -708,13 +738,17 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: dismissAllArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationService.dismissGroup(modelData.key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Individual notifications
|
||||
@@ -732,14 +766,13 @@ PanelWindow {
|
||||
height: Math.max(48, 32 + contentColumn.height + Theme.spacingM * 2) // 32 for icon height, plus content
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5)
|
||||
border.color: modelData.urgency === 2 ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) :
|
||||
"transparent"
|
||||
border.color: modelData.urgency === 2 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||
border.width: modelData.urgency === 2 ? 1 : 0
|
||||
clip: true
|
||||
|
||||
Item {
|
||||
id: notifContent
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
@@ -759,13 +792,14 @@ PanelWindow {
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
const appName = modelData.appName || "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
const appName = modelData.appName || "?";
|
||||
return appName.charAt(0).toUpperCase();
|
||||
}
|
||||
font.pixelSize: 12
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -774,9 +808,7 @@ PanelWindow {
|
||||
radius: 12
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
color: individualDismissArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: individualDismissArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -788,15 +820,18 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: individualDismissArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationService.dismissNotification(modelData)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 44
|
||||
anchors.right: parent.right
|
||||
@@ -840,20 +875,24 @@ PanelWindow {
|
||||
|
||||
TextField {
|
||||
id: replyField
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingXS
|
||||
placeholderText: modelData.notification.inlineReplyPlaceholder || "Reply..."
|
||||
background: Item {}
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: 11
|
||||
|
||||
onAccepted: {
|
||||
if (text.length > 0) {
|
||||
modelData.notification.sendInlineReply(text)
|
||||
text = ""
|
||||
modelData.notification.sendInlineReply(text);
|
||||
text = "";
|
||||
}
|
||||
}
|
||||
|
||||
background: Item {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -875,17 +914,25 @@ PanelWindow {
|
||||
enabled: replyField.text.length > 0
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
modelData.notification.sendInlineReply(replyField.text)
|
||||
replyField.text = ""
|
||||
modelData.notification.sendInlineReply(replyField.text);
|
||||
replyField.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Tap to expand for collapsed groups
|
||||
@@ -895,7 +942,24 @@ PanelWindow {
|
||||
onClicked: NotificationService.toggleGroupExpansion(modelData.key)
|
||||
z: -1
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
SequentialAnimation {
|
||||
PauseAnimation {
|
||||
duration: 25
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Empty state
|
||||
@@ -936,10 +1000,23 @@ PanelWindow {
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Click outside to close
|
||||
@@ -948,4 +1025,5 @@ PanelWindow {
|
||||
z: -1
|
||||
onClicked: notificationHistoryVisible = false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +1,13 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
PanelWindow {
|
||||
id: notificationPopup
|
||||
objectName: "notificationPopup"
|
||||
|
||||
visible: NotificationService.groupedPopups.length > 0
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
right: true
|
||||
}
|
||||
|
||||
margins {
|
||||
top: Theme.barHeight
|
||||
right: 12
|
||||
}
|
||||
|
||||
implicitWidth: 400
|
||||
implicitHeight: notificationsList.height + 32
|
||||
|
||||
// Expose key child objects for testing
|
||||
// Expose the currently visible quickReplyField for testing
|
||||
@@ -41,8 +19,28 @@ PanelWindow {
|
||||
// Expose the currently visible hoverArea for testing
|
||||
property MouseArea hoverArea: null
|
||||
|
||||
objectName: "notificationPopup"
|
||||
visible: NotificationService.groupedPopups.length > 0
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
implicitWidth: 400
|
||||
implicitHeight: notificationsList.height + 32
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
right: true
|
||||
}
|
||||
|
||||
margins {
|
||||
top: Theme.barHeight
|
||||
right: 12
|
||||
}
|
||||
|
||||
Column {
|
||||
id: notificationsList
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 16
|
||||
@@ -55,7 +53,6 @@ PanelWindow {
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
|
||||
// Context detection for popup
|
||||
readonly property bool isPopupContext: true
|
||||
readonly property bool expanded: NotificationService.expandedGroups[modelData.key] || false
|
||||
@@ -65,61 +62,32 @@ PanelWindow {
|
||||
let calculatedHeight;
|
||||
if (expanded) {
|
||||
// Calculate expanded height properly: header (48) + spacing + notifications
|
||||
let headerHeight = 48 + Theme.spacingM
|
||||
let maxNotificationsInPopup = Math.min(modelData.notifications.length, 5)
|
||||
let notificationHeight = maxNotificationsInPopup * (60 + Theme.spacingS)
|
||||
calculatedHeight = headerHeight + notificationHeight + Theme.spacingL * 2
|
||||
let headerHeight = 48 + Theme.spacingM;
|
||||
let maxNotificationsInPopup = Math.min(modelData.notifications.length, 5);
|
||||
let notificationHeight = maxNotificationsInPopup * (60 + Theme.spacingS);
|
||||
calculatedHeight = headerHeight + notificationHeight + Theme.spacingL * 2;
|
||||
} else {
|
||||
// Collapsed height: header (72) + quick reply if present
|
||||
calculatedHeight = 72 + Theme.spacingS * 2
|
||||
if (modelData.latestNotification.notification.hasInlineReply) {
|
||||
calculatedHeight += 36 + Theme.spacingS
|
||||
}
|
||||
calculatedHeight += Theme.spacingL * 2
|
||||
}
|
||||
calculatedHeight = 72 + Theme.spacingS * 2;
|
||||
if (modelData.latestNotification.notification.hasInlineReply)
|
||||
calculatedHeight += 36 + Theme.spacingS;
|
||||
|
||||
// Add extra height for single notifications in popup context
|
||||
if (isPopupContext && modelData.count === 1) {
|
||||
calculatedHeight += 12;
|
||||
calculatedHeight += Theme.spacingL * 2;
|
||||
}
|
||||
// Add extra height for single notifications in popup context
|
||||
if (isPopupContext && modelData.count === 1)
|
||||
calculatedHeight += 12;
|
||||
|
||||
return calculatedHeight;
|
||||
}
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Theme.popupBackground()
|
||||
border.color: modelData.latestNotification.urgency === 2 ?
|
||||
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.color: modelData.latestNotification.urgency === 2 ? 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: modelData.latestNotification.urgency === 2 ? 2 : 1
|
||||
|
||||
// Stabilize layout during content changes
|
||||
clip: true
|
||||
|
||||
// Smooth popup animations
|
||||
transform: Translate {
|
||||
x: notificationPopup.visible ? 0 : 400
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 350
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opacity: notificationPopup.visible ? 1.0 : 0.0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
scale: notificationPopup.visible ? 1.0 : 0.98
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 350
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
opacity: notificationPopup.visible ? 1 : 0
|
||||
scale: notificationPopup.visible ? 1 : 0.98
|
||||
|
||||
// Priority indicator for urgent notifications
|
||||
Rectangle {
|
||||
@@ -133,20 +101,10 @@ PanelWindow {
|
||||
visible: modelData.latestNotification.urgency === 2
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
enabled: !isPopupContext // Disable automatic height animation in popup to prevent glitches
|
||||
SequentialAnimation {
|
||||
PauseAnimation { duration: 25 }
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collapsed view - shows app header and latest notification
|
||||
Column {
|
||||
id: collapsedContent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -162,9 +120,10 @@ PanelWindow {
|
||||
// Round app icon with proper API usage
|
||||
Item {
|
||||
id: iconContainer
|
||||
|
||||
Component.onCompleted: {
|
||||
// Expose this iconContainer to the root for testing if visible
|
||||
notificationPopup.iconContainer = iconContainer
|
||||
notificationPopup.iconContainer = iconContainer;
|
||||
}
|
||||
width: 48
|
||||
height: 48
|
||||
@@ -184,36 +143,36 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
source: {
|
||||
if (modelData.latestNotification.appIcon && modelData.latestNotification.appIcon !== "") {
|
||||
return Quickshell.iconPath(modelData.latestNotification.appIcon, "")
|
||||
}
|
||||
return ""
|
||||
if (modelData.latestNotification.appIcon && modelData.latestNotification.appIcon !== "")
|
||||
return Quickshell.iconPath(modelData.latestNotification.appIcon, "");
|
||||
|
||||
return "";
|
||||
}
|
||||
visible: status === Image.Ready
|
||||
|
||||
onStatusChanged: {
|
||||
if (status === Image.Error || status === Image.Null || source === "") {
|
||||
fallbackIcon.visible = true
|
||||
} else if (status === Image.Ready) {
|
||||
fallbackIcon.visible = false
|
||||
}
|
||||
if (status === Image.Error || status === Image.Null || source === "")
|
||||
fallbackIcon.visible = true;
|
||||
else if (status === Image.Ready)
|
||||
fallbackIcon.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback icon - show by default, hide when real icon loads
|
||||
Text {
|
||||
id: fallbackIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
visible: true // Start visible, hide when real icon loads
|
||||
text: {
|
||||
// Use first letter of app name as fallback
|
||||
const appName = modelData.appName || "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
const appName = modelData.appName || "?";
|
||||
return appName.charAt(0).toUpperCase();
|
||||
}
|
||||
font.pixelSize: 20
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Count badge for multiple notifications - smaller circle
|
||||
@@ -235,7 +194,9 @@ PanelWindow {
|
||||
font.pixelSize: 9
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// App info and latest notification content
|
||||
@@ -252,11 +213,10 @@ PanelWindow {
|
||||
Text {
|
||||
width: parent.width
|
||||
text: {
|
||||
if (modelData.latestNotification.timeStr.length > 0) {
|
||||
return modelData.appName + " • " + modelData.latestNotification.timeStr
|
||||
} else {
|
||||
return modelData.appName
|
||||
}
|
||||
if (modelData.latestNotification.timeStr.length > 0)
|
||||
return modelData.appName + " • " + modelData.latestNotification.timeStr;
|
||||
else
|
||||
return modelData.appName;
|
||||
}
|
||||
color: Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -288,11 +248,13 @@ PanelWindow {
|
||||
wrapMode: Text.WordWrap
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Expand/dismiss controls - use anchored layout for stability
|
||||
Item {
|
||||
id: controlsContainer
|
||||
|
||||
width: 72
|
||||
height: 32
|
||||
anchors.right: parent.right
|
||||
@@ -303,9 +265,7 @@ PanelWindow {
|
||||
height: 32
|
||||
radius: 16
|
||||
anchors.left: parent.left
|
||||
color: expandArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: expandArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
visible: modelData.count > 1
|
||||
|
||||
Text {
|
||||
@@ -321,21 +281,25 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// ...existing code...
|
||||
id: expandArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
console.log("Expand clicked - pausing timer")
|
||||
dismissTimer.stop()
|
||||
NotificationService.toggleGroupExpansion(modelData.key)
|
||||
console.log("Expand clicked - pausing timer");
|
||||
dismissTimer.stop();
|
||||
NotificationService.toggleGroupExpansion(modelData.key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -343,9 +307,7 @@ PanelWindow {
|
||||
height: 32
|
||||
radius: 16
|
||||
anchors.right: parent.right
|
||||
color: dismissArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: dismissArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -357,13 +319,17 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: dismissArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationService.dismissGroup(modelData.key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Quick reply for conversations (only if latest notification supports it)
|
||||
@@ -382,20 +348,24 @@ PanelWindow {
|
||||
|
||||
TextField {
|
||||
id: quickReplyField
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
placeholderText: modelData.latestNotification.notification.inlineReplyPlaceholder || "Quick reply..."
|
||||
background: Item {}
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
|
||||
onAccepted: {
|
||||
if (text.length > 0) {
|
||||
modelData.latestNotification.notification.sendInlineReply(text)
|
||||
text = ""
|
||||
modelData.latestNotification.notification.sendInlineReply(text);
|
||||
text = "";
|
||||
}
|
||||
}
|
||||
|
||||
background: Item {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -419,8 +389,8 @@ PanelWindow {
|
||||
enabled: quickReplyField.text.length > 0
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
modelData.latestNotification.notification.sendInlineReply(quickReplyField.text)
|
||||
quickReplyField.text = ""
|
||||
modelData.latestNotification.notification.sendInlineReply(quickReplyField.text);
|
||||
quickReplyField.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,17 +399,22 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Expanded view - shows all notifications stacked
|
||||
Column {
|
||||
id: expandedContent
|
||||
|
||||
Component.onCompleted: {
|
||||
// Expose this expandedContent to the root for testing if visible
|
||||
notificationPopup.expandedContent = expandedContent
|
||||
notificationPopup.expandedContent = expandedContent;
|
||||
}
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
@@ -477,13 +452,14 @@ PanelWindow {
|
||||
anchors.centerIn: parent
|
||||
visible: !modelData.latestNotification.appIcon || modelData.latestNotification.appIcon === ""
|
||||
text: {
|
||||
const appName = modelData.appName || "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
const appName = modelData.appName || "?";
|
||||
return appName.charAt(0).toUpperCase();
|
||||
}
|
||||
font.pixelSize: 16
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// App name and count badge - centered area
|
||||
@@ -509,9 +485,7 @@ PanelWindow {
|
||||
height: 32
|
||||
radius: 16
|
||||
anchors.left: parent.left
|
||||
color: collapseArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: collapseArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -523,15 +497,17 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: collapseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
console.log("Expand clicked - pausing timer")
|
||||
dismissTimer.stop()
|
||||
NotificationService.toggleGroupExpansion(modelData.key)
|
||||
console.log("Expand clicked - pausing timer");
|
||||
dismissTimer.stop();
|
||||
NotificationService.toggleGroupExpansion(modelData.key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -539,9 +515,7 @@ PanelWindow {
|
||||
height: 32
|
||||
radius: 16
|
||||
anchors.right: parent.right
|
||||
color: dismissAllArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: dismissAllArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -553,13 +527,17 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: dismissAllArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationService.dismissGroup(modelData.key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Stacked individual notifications with smooth transitions
|
||||
@@ -577,16 +555,14 @@ PanelWindow {
|
||||
height: notifContent.height + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5)
|
||||
border.color: modelData.urgency === 2 ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) :
|
||||
"transparent"
|
||||
border.color: modelData.urgency === 2 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||
border.width: modelData.urgency === 2 ? 1 : 0
|
||||
|
||||
// Stabilize layout during dismiss operations
|
||||
clip: true
|
||||
|
||||
Item {
|
||||
id: notifContent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -617,13 +593,14 @@ PanelWindow {
|
||||
anchors.centerIn: parent
|
||||
visible: !modelData.appIcon || modelData.appIcon === ""
|
||||
text: {
|
||||
const appName = modelData.appName || "?"
|
||||
return appName.charAt(0).toUpperCase()
|
||||
const appName = modelData.appName || "?";
|
||||
return appName.charAt(0).toUpperCase();
|
||||
}
|
||||
font.pixelSize: 12
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Individual dismiss button - fixed position on right
|
||||
@@ -633,9 +610,7 @@ PanelWindow {
|
||||
radius: 12
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
color: individualDismissArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: individualDismissArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -647,16 +622,19 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: individualDismissArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationService.dismissNotification(modelData)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Notification content - fills space between icon and dismiss button
|
||||
Column {
|
||||
id: contentColumn
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 44
|
||||
anchors.right: parent.right
|
||||
@@ -702,20 +680,24 @@ PanelWindow {
|
||||
|
||||
TextField {
|
||||
id: replyField
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingXS
|
||||
placeholderText: modelData.notification.inlineReplyPlaceholder || "Reply..."
|
||||
background: Item {}
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: 11
|
||||
|
||||
onAccepted: {
|
||||
if (text.length > 0) {
|
||||
modelData.notification.sendInlineReply(text)
|
||||
text = ""
|
||||
modelData.notification.sendInlineReply(text);
|
||||
text = "";
|
||||
}
|
||||
}
|
||||
|
||||
background: Item {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -737,69 +719,129 @@ PanelWindow {
|
||||
enabled: replyField.text.length > 0
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
modelData.notification.sendInlineReply(replyField.text)
|
||||
replyField.text = ""
|
||||
modelData.notification.sendInlineReply(replyField.text);
|
||||
replyField.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Hover to pause auto-dismiss - MUST be properly configured
|
||||
MouseArea {
|
||||
id: hoverArea
|
||||
|
||||
Component.onCompleted: {
|
||||
// Expose this hoverArea to the root for testing if visible
|
||||
notificationPopup.hoverArea = hoverArea
|
||||
notificationPopup.hoverArea = hoverArea;
|
||||
}
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
z: 10 // Higher z-order to ensure hover detection
|
||||
propagateComposedEvents: true
|
||||
|
||||
onEntered: {
|
||||
console.log("Notification hover entered - pausing timer")
|
||||
dismissTimer.stop()
|
||||
console.log("Notification hover entered - pausing timer");
|
||||
dismissTimer.stop();
|
||||
}
|
||||
|
||||
onExited: {
|
||||
console.log("Notification hover exited - resuming timer")
|
||||
if (modelData.latestNotification.popup && !expanded) {
|
||||
dismissTimer.restart()
|
||||
}
|
||||
console.log("Notification hover exited - resuming timer");
|
||||
if (modelData.latestNotification.popup && !expanded)
|
||||
dismissTimer.restart();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-dismiss timer - properly pauses on hover
|
||||
Timer {
|
||||
id: dismissTimer
|
||||
|
||||
running: modelData.latestNotification.popup && !expanded
|
||||
interval: modelData.latestNotification.notification.expireTimeout > 0 ?
|
||||
modelData.latestNotification.notification.expireTimeout * 1000 : 5000
|
||||
interval: modelData.latestNotification.notification.expireTimeout > 0 ? modelData.latestNotification.notification.expireTimeout * 1000 : 5000
|
||||
onTriggered: {
|
||||
console.log("Timer triggered - hover state:", hoverArea.containsMouse, "expanded:", expanded)
|
||||
console.log("Timer triggered - hover state:", hoverArea.containsMouse, "expanded:", expanded);
|
||||
if (!hoverArea.containsMouse && !expanded) {
|
||||
console.log("Dismissing notification")
|
||||
modelData.latestNotification.popup = false
|
||||
console.log("Dismissing notification");
|
||||
modelData.latestNotification.popup = false;
|
||||
} else {
|
||||
console.log("Conditions not met - not dismissing")
|
||||
console.log("Conditions not met - not dismissing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Smooth popup animations
|
||||
transform: Translate {
|
||||
x: notificationPopup.visible ? 0 : 400
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 350
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 350
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
enabled: !isPopupContext // Disable automatic height animation in popup to prevent glitches
|
||||
|
||||
SequentialAnimation {
|
||||
PauseAnimation {
|
||||
duration: 25
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// Smooth height animation
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
|
||||
PanelWindow {
|
||||
@@ -14,15 +14,35 @@ PanelWindow {
|
||||
property string powerConfirmTitle: ""
|
||||
property string powerConfirmMessage: ""
|
||||
|
||||
visible: powerConfirmVisible
|
||||
function executePowerAction(action) {
|
||||
console.log("Executing power action:", action);
|
||||
let command = [];
|
||||
switch (action) {
|
||||
case "logout":
|
||||
command = ["niri", "msg", "action", "quit", "-s"];
|
||||
break;
|
||||
case "suspend":
|
||||
command = ["systemctl", "suspend"];
|
||||
break;
|
||||
case "reboot":
|
||||
command = ["systemctl", "reboot"];
|
||||
break;
|
||||
case "poweroff":
|
||||
command = ["systemctl", "poweroff"];
|
||||
break;
|
||||
}
|
||||
if (command.length > 0) {
|
||||
powerActionProcess.command = command;
|
||||
powerActionProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
visible: powerConfirmVisible
|
||||
implicitWidth: 400
|
||||
implicitHeight: 300
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
@@ -47,23 +67,8 @@ PanelWindow {
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 1
|
||||
|
||||
opacity: powerConfirmVisible ? 1.0 : 0.0
|
||||
scale: powerConfirmVisible ? 1.0 : 0.9
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
opacity: powerConfirmVisible ? 1 : 0
|
||||
scale: powerConfirmVisible ? 1 : 0.9
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
@@ -75,10 +80,13 @@ PanelWindow {
|
||||
text: powerConfirmTitle
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: {
|
||||
switch(powerConfirmAction) {
|
||||
case "poweroff": return Theme.error
|
||||
case "reboot": return Theme.warning
|
||||
default: return Theme.surfaceText
|
||||
switch (powerConfirmAction) {
|
||||
case "poweroff":
|
||||
return Theme.error;
|
||||
case "reboot":
|
||||
return Theme.warning;
|
||||
default:
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
}
|
||||
font.weight: Font.Medium
|
||||
@@ -96,7 +104,9 @@ PanelWindow {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Item { height: Theme.spacingL }
|
||||
Item {
|
||||
height: Theme.spacingL
|
||||
}
|
||||
|
||||
// Buttons
|
||||
Row {
|
||||
@@ -120,13 +130,15 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: cancelButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
powerConfirmVisible = false
|
||||
powerConfirmVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Confirm button
|
||||
@@ -135,15 +147,19 @@ PanelWindow {
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
let baseColor
|
||||
switch(powerConfirmAction) {
|
||||
case "poweroff": baseColor = Theme.error; break
|
||||
case "reboot": baseColor = Theme.warning; break
|
||||
default: baseColor = Theme.primary; break
|
||||
let baseColor;
|
||||
switch (powerConfirmAction) {
|
||||
case "poweroff":
|
||||
baseColor = Theme.error;
|
||||
break;
|
||||
case "reboot":
|
||||
baseColor = Theme.warning;
|
||||
break;
|
||||
default:
|
||||
baseColor = Theme.primary;
|
||||
break;
|
||||
}
|
||||
return confirmButton.containsMouse ?
|
||||
Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) :
|
||||
baseColor
|
||||
return confirmButton.containsMouse ? Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) : baseColor;
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -156,52 +172,49 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: confirmButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
powerConfirmVisible = false
|
||||
executePowerAction(powerConfirmAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
powerConfirmVisible = false;
|
||||
executePowerAction(powerConfirmAction);
|
||||
}
|
||||
}
|
||||
|
||||
function executePowerAction(action) {
|
||||
console.log("Executing power action:", action)
|
||||
|
||||
let command = []
|
||||
switch(action) {
|
||||
case "logout":
|
||||
command = ["niri", "msg", "action", "quit", "-s"]
|
||||
break
|
||||
case "suspend":
|
||||
command = ["systemctl", "suspend"]
|
||||
break
|
||||
case "reboot":
|
||||
command = ["systemctl", "reboot"]
|
||||
break
|
||||
case "poweroff":
|
||||
command = ["systemctl", "poweroff"]
|
||||
break
|
||||
}
|
||||
|
||||
if (command.length > 0) {
|
||||
powerActionProcess.command = command
|
||||
powerActionProcess.running = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Process {
|
||||
id: powerActionProcess
|
||||
running: false
|
||||
|
||||
running: false
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
console.error("Power action failed with exit code:", exitCode)
|
||||
}
|
||||
if (exitCode !== 0)
|
||||
console.error("Power action failed with exit code:", exitCode);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
|
||||
PanelWindow {
|
||||
@@ -12,14 +12,11 @@ PanelWindow {
|
||||
property bool powerMenuVisible: false
|
||||
|
||||
visible: powerMenuVisible
|
||||
|
||||
implicitWidth: 400
|
||||
implicitHeight: 320
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
@@ -33,7 +30,7 @@ PanelWindow {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
powerMenuVisible = false
|
||||
powerMenuVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,29 +43,15 @@ PanelWindow {
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
opacity: powerMenuVisible ? 1.0 : 0.0
|
||||
scale: powerMenuVisible ? 1.0 : 0.85
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
opacity: powerMenuVisible ? 1 : 0
|
||||
scale: powerMenuVisible ? 1 : 0.85
|
||||
|
||||
// Prevent click-through to background
|
||||
MouseArea {
|
||||
// Consume the click to prevent it from reaching the background
|
||||
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
// Consume the click to prevent it from reaching the background
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +72,10 @@ PanelWindow {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item { width: parent.width - 150; height: 1 }
|
||||
Item {
|
||||
width: parent.width - 150
|
||||
height: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32
|
||||
@@ -107,14 +93,17 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: closePowerArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
powerMenuVisible = false
|
||||
powerMenuVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Power options
|
||||
@@ -150,21 +139,24 @@ PanelWindow {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: logoutArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
powerMenuVisible = false
|
||||
root.powerConfirmAction = "logout"
|
||||
root.powerConfirmTitle = "Log Out"
|
||||
root.powerConfirmMessage = "Are you sure you want to log out?"
|
||||
root.powerConfirmVisible = true
|
||||
powerMenuVisible = false;
|
||||
root.powerConfirmAction = "logout";
|
||||
root.powerConfirmTitle = "Log Out";
|
||||
root.powerConfirmMessage = "Are you sure you want to log out?";
|
||||
root.powerConfirmVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Suspend
|
||||
@@ -195,21 +187,24 @@ PanelWindow {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: suspendArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
powerMenuVisible = false
|
||||
root.powerConfirmAction = "suspend"
|
||||
root.powerConfirmTitle = "Suspend"
|
||||
root.powerConfirmMessage = "Are you sure you want to suspend the system?"
|
||||
root.powerConfirmVisible = true
|
||||
powerMenuVisible = false;
|
||||
root.powerConfirmAction = "suspend";
|
||||
root.powerConfirmTitle = "Suspend";
|
||||
root.powerConfirmMessage = "Are you sure you want to suspend the system?";
|
||||
root.powerConfirmVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Reboot
|
||||
@@ -240,21 +235,24 @@ PanelWindow {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: rebootArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
powerMenuVisible = false
|
||||
root.powerConfirmAction = "reboot"
|
||||
root.powerConfirmTitle = "Reboot"
|
||||
root.powerConfirmMessage = "Are you sure you want to reboot the system?"
|
||||
root.powerConfirmVisible = true
|
||||
powerMenuVisible = false;
|
||||
root.powerConfirmAction = "reboot";
|
||||
root.powerConfirmTitle = "Reboot";
|
||||
root.powerConfirmMessage = "Are you sure you want to reboot the system?";
|
||||
root.powerConfirmVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Power Off
|
||||
@@ -285,23 +283,46 @@ PanelWindow {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: powerOffArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
powerMenuVisible = false
|
||||
root.powerConfirmAction = "poweroff"
|
||||
root.powerConfirmTitle = "Power Off"
|
||||
root.powerConfirmMessage = "Are you sure you want to power off the system?"
|
||||
root.powerConfirmVisible = true
|
||||
powerMenuVisible = false;
|
||||
root.powerConfirmAction = "poweroff";
|
||||
root.powerConfirmTitle = "Power Off";
|
||||
root.powerConfirmMessage = "Are you sure you want to power off the system?";
|
||||
root.powerConfirmVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,9 +3,9 @@ import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -15,20 +15,34 @@ PanelWindow {
|
||||
property bool isVisible: false
|
||||
property var parentWidget: null
|
||||
|
||||
visible: isVisible
|
||||
|
||||
// Monitor process dropdown visibility to enable/disable process monitoring
|
||||
onIsVisibleChanged: {
|
||||
ProcessMonitorService.enableMonitoring(isVisible)
|
||||
// Close dropdown when clicking outside
|
||||
function hide() {
|
||||
isVisible = false;
|
||||
}
|
||||
|
||||
function show() {
|
||||
isVisible = true;
|
||||
ProcessMonitorService.updateSystemInfo();
|
||||
ProcessMonitorService.updateProcessList();
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (isVisible)
|
||||
hide();
|
||||
else
|
||||
show();
|
||||
}
|
||||
|
||||
visible: isVisible
|
||||
// Monitor process dropdown visibility to enable/disable process monitoring
|
||||
onIsVisibleChanged: {
|
||||
ProcessMonitorService.enableMonitoring(isVisible);
|
||||
}
|
||||
implicitWidth: 600
|
||||
implicitHeight: 600
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
@@ -46,31 +60,35 @@ PanelWindow {
|
||||
|
||||
Rectangle {
|
||||
id: dropdownContent
|
||||
|
||||
width: Math.min(600, Screen.width - Theme.spacingL * 2)
|
||||
height: Math.min(600, Screen.height - Theme.barHeight - Theme.spacingS * 2)
|
||||
x: Math.max(Theme.spacingL, Screen.width - width - Theme.spacingL)
|
||||
y: Theme.barHeight + Theme.spacingXS
|
||||
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
opacity: processListDropdown.isVisible ? 1 : 0
|
||||
// Add shadow effect
|
||||
layer.enabled: true
|
||||
// TopBar dropdown animation - slide down from bar
|
||||
transform: [
|
||||
Scale {
|
||||
id: scaleTransform
|
||||
|
||||
origin.x: parent.width * 0.85 // Scale from top-right
|
||||
origin.y: 0
|
||||
xScale: processListDropdown.isVisible ? 1.0 : 0.95
|
||||
yScale: processListDropdown.isVisible ? 1.0 : 0.8
|
||||
xScale: processListDropdown.isVisible ? 1 : 0.95
|
||||
yScale: processListDropdown.isVisible ? 1 : 0.8
|
||||
|
||||
Behavior on xScale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on yScale {
|
||||
@@ -78,10 +96,13 @@ PanelWindow {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
Translate {
|
||||
id: translateTransform
|
||||
|
||||
x: processListDropdown.isVisible ? 0 : 20
|
||||
y: processListDropdown.isVisible ? 0 : -30
|
||||
|
||||
@@ -90,6 +111,7 @@ PanelWindow {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
@@ -97,36 +119,18 @@ PanelWindow {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
opacity: processListDropdown.isVisible ? 1.0 : 0.0
|
||||
|
||||
// Add shadow effect
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 8
|
||||
shadowBlur: 1.0
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.15)
|
||||
shadowOpacity: processListDropdown.isVisible ? 0.15 : 0
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Click inside dropdown - consume the event
|
||||
MouseArea {
|
||||
// Consume clicks inside dropdown to prevent closing
|
||||
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
// Consume clicks inside dropdown to prevent closing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,35 +155,25 @@ PanelWindow {
|
||||
height: 80
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: {
|
||||
if (ProcessMonitorService.sortBy === "cpu") {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16)
|
||||
} else if (cpuCardMouseArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
} else {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
if (ProcessMonitorService.sortBy === "cpu")
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16);
|
||||
else if (cpuCardMouseArea.containsMouse)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
||||
else
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
|
||||
}
|
||||
}
|
||||
border.color: ProcessMonitorService.sortBy === "cpu" ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) :
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
|
||||
border.color: ProcessMonitorService.sortBy === "cpu" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
|
||||
border.width: ProcessMonitorService.sortBy === "cpu" ? 2 : 1
|
||||
|
||||
MouseArea {
|
||||
id: cpuCardMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: ProcessMonitorService.setSortBy("cpu")
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
@@ -191,7 +185,7 @@ PanelWindow {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: ProcessMonitorService.sortBy === "cpu" ? Theme.primary : Theme.secondary
|
||||
opacity: ProcessMonitorService.sortBy === "cpu" ? 1.0 : 0.8
|
||||
opacity: ProcessMonitorService.sortBy === "cpu" ? 1 : 0.8
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -207,7 +201,23 @@ PanelWindow {
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Memory Overview Card (clickable for sorting)
|
||||
@@ -216,35 +226,25 @@ PanelWindow {
|
||||
height: 80
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: {
|
||||
if (ProcessMonitorService.sortBy === "memory") {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16)
|
||||
} else if (memoryCardMouseArea.containsMouse) {
|
||||
return Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.12)
|
||||
} else {
|
||||
return Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
if (ProcessMonitorService.sortBy === "memory")
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16);
|
||||
else if (memoryCardMouseArea.containsMouse)
|
||||
return Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.12);
|
||||
else
|
||||
return Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08);
|
||||
}
|
||||
}
|
||||
border.color: ProcessMonitorService.sortBy === "memory" ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) :
|
||||
Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.2)
|
||||
border.color: ProcessMonitorService.sortBy === "memory" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.2)
|
||||
border.width: ProcessMonitorService.sortBy === "memory" ? 2 : 1
|
||||
|
||||
MouseArea {
|
||||
id: memoryCardMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: ProcessMonitorService.setSortBy("memory")
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
@@ -256,7 +256,7 @@ PanelWindow {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: ProcessMonitorService.sortBy === "memory" ? Theme.primary : Theme.secondary
|
||||
opacity: ProcessMonitorService.sortBy === "memory" ? 1.0 : 0.8
|
||||
opacity: ProcessMonitorService.sortBy === "memory" ? 1 : 0.8
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -272,7 +272,23 @@ PanelWindow {
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Swap Overview Card
|
||||
@@ -280,12 +296,8 @@ PanelWindow {
|
||||
width: (parent.width - Theme.spacingM * 2) / 3
|
||||
height: 80
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: ProcessMonitorService.totalSwapKB > 0 ?
|
||||
Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.04)
|
||||
border.color: ProcessMonitorService.totalSwapKB > 0 ?
|
||||
Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.2) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12)
|
||||
color: ProcessMonitorService.totalSwapKB > 0 ? Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.04)
|
||||
border.color: ProcessMonitorService.totalSwapKB > 0 ? Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.2) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
@@ -315,10 +327,12 @@ PanelWindow {
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
@@ -326,11 +340,13 @@ PanelWindow {
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Headers
|
||||
Item {
|
||||
id: columnHeaders
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 8
|
||||
height: 24
|
||||
@@ -364,6 +380,7 @@ PanelWindow {
|
||||
opacity: 0.7
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// RAM header - positioned exactly like memory badge
|
||||
@@ -383,6 +400,7 @@ PanelWindow {
|
||||
opacity: 0.7
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// PID header - positioned exactly like PID text
|
||||
@@ -404,9 +422,7 @@ PanelWindow {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
color: sortOrderArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: sortOrderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -420,6 +436,7 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: sortOrderArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
@@ -427,9 +444,14 @@ PanelWindow {
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Process list
|
||||
@@ -438,12 +460,12 @@ PanelWindow {
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 200
|
||||
clip: true
|
||||
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
ListView {
|
||||
id: processListView
|
||||
|
||||
anchors.fill: parent
|
||||
model: ProcessMonitorService.processes
|
||||
spacing: 4
|
||||
@@ -452,37 +474,32 @@ PanelWindow {
|
||||
width: processListView.width
|
||||
height: 40
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: processMouseArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
"transparent"
|
||||
border.color: processMouseArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
|
||||
"transparent"
|
||||
color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
border.color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
border.width: 1
|
||||
|
||||
MouseArea {
|
||||
id: processMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
if (modelData && modelData.pid > 0) {
|
||||
processContextMenuWindow.processData = modelData
|
||||
let globalPos = processMouseArea.mapToGlobal(mouse.x, mouse.y)
|
||||
processContextMenuWindow.show(globalPos.x, globalPos.y)
|
||||
processContextMenuWindow.processData = modelData;
|
||||
let globalPos = processMouseArea.mapToGlobal(mouse.x, mouse.y);
|
||||
processContextMenuWindow.show(globalPos.x, globalPos.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPressAndHold: {
|
||||
// Context menu for kill process etc
|
||||
if (modelData && modelData.pid > 0) {
|
||||
processContextMenuWindow.processData = modelData
|
||||
let globalPos = processMouseArea.mapToGlobal(processMouseArea.width / 2, processMouseArea.height / 2)
|
||||
processContextMenuWindow.show(globalPos.x, globalPos.y)
|
||||
processContextMenuWindow.processData = modelData;
|
||||
let globalPos = processMouseArea.mapToGlobal(processMouseArea.width / 2, processMouseArea.height / 2);
|
||||
processContextMenuWindow.show(globalPos.x, globalPos.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -494,13 +511,18 @@ PanelWindow {
|
||||
// Process icon
|
||||
Text {
|
||||
id: processIcon
|
||||
|
||||
text: ProcessMonitorService.getProcessIcon(modelData ? modelData.command : "")
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 4
|
||||
color: {
|
||||
if (modelData && modelData.cpu > 80) return Theme.error
|
||||
if (modelData && modelData.cpu > 50) return Theme.warning
|
||||
return Theme.surfaceText
|
||||
if (modelData && modelData.cpu > 80)
|
||||
return Theme.error;
|
||||
|
||||
if (modelData && modelData.cpu > 50)
|
||||
return Theme.warning;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
opacity: 0.8
|
||||
anchors.left: parent.left
|
||||
@@ -520,17 +542,21 @@ PanelWindow {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
// CPU usage
|
||||
Rectangle {
|
||||
id: cpuBadge
|
||||
|
||||
width: 80
|
||||
height: 20
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (modelData && modelData.cpu > 80) return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||
if (modelData && modelData.cpu > 50) return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
if (modelData && modelData.cpu > 80)
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
|
||||
|
||||
if (modelData && modelData.cpu > 50)
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
||||
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08);
|
||||
}
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 194 // 28 (menu) + 12 + 50 (pid) + 12 + 80 (mem) + 12 spacing
|
||||
@@ -541,24 +567,36 @@ PanelWindow {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: {
|
||||
if (modelData && modelData.cpu > 80) return Theme.error
|
||||
if (modelData && modelData.cpu > 50) return Theme.warning
|
||||
return Theme.surfaceText
|
||||
if (modelData && modelData.cpu > 80)
|
||||
return Theme.error;
|
||||
|
||||
if (modelData && modelData.cpu > 50)
|
||||
return Theme.warning;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Memory usage
|
||||
Rectangle {
|
||||
id: memoryBadge
|
||||
|
||||
width: 80
|
||||
height: 20
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (modelData && modelData.memoryKB > 1024 * 1024) return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) // > 1GB
|
||||
if (modelData && modelData.memoryKB > 512 * 1024) return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12) // > 512MB
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
if (modelData && modelData.memoryKB > 1024 * 1024)
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
|
||||
|
||||
// > 1GB
|
||||
if (modelData && modelData.memoryKB > 512 * 1024)
|
||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
|
||||
|
||||
// > 512MB
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08);
|
||||
}
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 102 // 28 (menu) + 12 + 50 (pid) + 12 spacing
|
||||
@@ -569,12 +607,19 @@ PanelWindow {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: {
|
||||
if (modelData && modelData.memoryKB > 1024 * 1024) return Theme.error // > 1GB
|
||||
if (modelData && modelData.memoryKB > 512 * 1024) return Theme.warning // > 512MB
|
||||
return Theme.surfaceText
|
||||
if (modelData && modelData.memoryKB > 1024 * 1024)
|
||||
return Theme.error;
|
||||
|
||||
// > 1GB
|
||||
if (modelData && modelData.memoryKB > 512 * 1024)
|
||||
return Theme.warning;
|
||||
|
||||
// > 512MB
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// PID
|
||||
@@ -593,12 +638,11 @@ PanelWindow {
|
||||
// 3-dot menu button (far right)
|
||||
Rectangle {
|
||||
id: menuButton
|
||||
|
||||
width: 28
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
color: menuButtonArea.containsMouse ?
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
|
||||
"transparent"
|
||||
color: menuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -614,40 +658,96 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: menuButtonArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (modelData && modelData.pid > 0) {
|
||||
processContextMenuWindow.processData = modelData
|
||||
let globalPos = menuButtonArea.mapToGlobal(menuButtonArea.width / 2, menuButtonArea.height)
|
||||
processContextMenuWindow.show(globalPos.x, globalPos.y)
|
||||
processContextMenuWindow.processData = modelData;
|
||||
let globalPos = menuButtonArea.mapToGlobal(menuButtonArea.width / 2, menuButtonArea.height);
|
||||
processContextMenuWindow.show(globalPos.x, globalPos.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 8
|
||||
shadowBlur: 1
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.15)
|
||||
shadowOpacity: processListDropdown.isVisible ? 0.15 : 0
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Styled context menu for process actions - positioned in global coordinates
|
||||
PanelWindow {
|
||||
id: processContextMenuWindow
|
||||
|
||||
property var processData: null
|
||||
property bool menuVisible: false
|
||||
|
||||
function show(x, y) {
|
||||
// Smart positioning to prevent off-screen cutoff
|
||||
const menuWidth = 180;
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2;
|
||||
// Get screen dimensions from the monitor
|
||||
const screenWidth = processContextMenuWindow.screen ? processContextMenuWindow.screen.width : 1920;
|
||||
const screenHeight = processContextMenuWindow.screen ? processContextMenuWindow.screen.height : 1080;
|
||||
// Calculate optimal position
|
||||
let finalX = x;
|
||||
let finalY = y;
|
||||
// Check horizontal bounds - if too close to right edge, position to the left
|
||||
if (x + menuWidth > screenWidth - 20)
|
||||
finalX = x - menuWidth;
|
||||
|
||||
// Check vertical bounds - if too close to bottom edge, position above
|
||||
if (y + menuHeight > screenHeight - 20)
|
||||
finalY = y - menuHeight;
|
||||
|
||||
// Ensure we don't go off the left or top edges
|
||||
finalX = Math.max(20, finalX);
|
||||
finalY = Math.max(20, finalY);
|
||||
processContextMenu.x = finalX;
|
||||
processContextMenu.y = finalY;
|
||||
processContextMenuWindow.menuVisible = true;
|
||||
}
|
||||
|
||||
function hide() {
|
||||
processContextMenuWindow.menuVisible = false;
|
||||
}
|
||||
|
||||
visible: menuVisible
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
@@ -661,12 +761,16 @@ PanelWindow {
|
||||
|
||||
Rectangle {
|
||||
id: processContextMenu
|
||||
|
||||
width: 180
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
// Material 3 animations
|
||||
opacity: processContextMenuWindow.menuVisible ? 1 : 0
|
||||
scale: processContextMenuWindow.menuVisible ? 1 : 0.85
|
||||
|
||||
// Material 3 drop shadow
|
||||
Rectangle {
|
||||
@@ -680,26 +784,9 @@ PanelWindow {
|
||||
z: parent.z - 1
|
||||
}
|
||||
|
||||
// Material 3 animations
|
||||
opacity: processContextMenuWindow.menuVisible ? 1.0 : 0.0
|
||||
scale: processContextMenuWindow.menuVisible ? 1.0 : 0.85
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
@@ -723,16 +810,16 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: copyPidArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (processContextMenuWindow.processData) {
|
||||
copyPidProcess.command = ["wl-copy", processContextMenuWindow.processData.pid.toString()]
|
||||
copyPidProcess.running = true
|
||||
copyPidProcess.command = ["wl-copy", processContextMenuWindow.processData.pid.toString()];
|
||||
copyPidProcess.running = true;
|
||||
}
|
||||
processContextMenuWindow.hide()
|
||||
processContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,7 +828,9 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Copy Process Name
|
||||
@@ -763,17 +852,17 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: copyNameArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (processContextMenuWindow.processData) {
|
||||
let processName = processContextMenuWindow.processData.displayName || processContextMenuWindow.processData.command
|
||||
copyNameProcess.command = ["wl-copy", processName]
|
||||
copyNameProcess.running = true
|
||||
let processName = processContextMenuWindow.processData.displayName || processContextMenuWindow.processData.command;
|
||||
copyNameProcess.command = ["wl-copy", processName];
|
||||
copyNameProcess.running = true;
|
||||
}
|
||||
processContextMenuWindow.hide()
|
||||
processContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,7 +871,9 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Separator
|
||||
@@ -798,6 +889,7 @@ PanelWindow {
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Kill Process
|
||||
@@ -807,7 +899,7 @@ PanelWindow {
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: killArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
||||
enabled: processContextMenuWindow.processData && processContextMenuWindow.processData.pid > 1000
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
opacity: enabled ? 1 : 0.5
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
@@ -821,17 +913,17 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: killArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: parent.enabled
|
||||
|
||||
onClicked: {
|
||||
if (processContextMenuWindow.processData) {
|
||||
killProcess.command = ["kill", processContextMenuWindow.processData.pid.toString()]
|
||||
killProcess.running = true
|
||||
killProcess.command = ["kill", processContextMenuWindow.processData.pid.toString()];
|
||||
killProcess.running = true;
|
||||
}
|
||||
processContextMenuWindow.hide()
|
||||
processContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -840,7 +932,9 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Force Kill Process
|
||||
@@ -850,7 +944,7 @@ PanelWindow {
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: forceKillArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
||||
enabled: processContextMenuWindow.processData && processContextMenuWindow.processData.pid > 1000
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
opacity: enabled ? 1 : 0.5
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
@@ -864,17 +958,17 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: forceKillArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: parent.enabled
|
||||
|
||||
onClicked: {
|
||||
if (processContextMenuWindow.processData) {
|
||||
forceKillProcess.command = ["kill", "-9", processContextMenuWindow.processData.pid.toString()]
|
||||
forceKillProcess.running = true
|
||||
forceKillProcess.command = ["kill", "-9", processContextMenuWindow.processData.pid.toString()];
|
||||
forceKillProcess.running = true;
|
||||
}
|
||||
processContextMenuWindow.hide()
|
||||
processContextMenuWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -883,46 +977,29 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function show(x, y) {
|
||||
// Smart positioning to prevent off-screen cutoff
|
||||
const menuWidth = 180
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
|
||||
// Get screen dimensions from the monitor
|
||||
const screenWidth = processContextMenuWindow.screen ? processContextMenuWindow.screen.width : 1920
|
||||
const screenHeight = processContextMenuWindow.screen ? processContextMenuWindow.screen.height : 1080
|
||||
|
||||
// Calculate optimal position
|
||||
let finalX = x
|
||||
let finalY = y
|
||||
|
||||
// Check horizontal bounds - if too close to right edge, position to the left
|
||||
if (x + menuWidth > screenWidth - 20) {
|
||||
finalX = x - menuWidth
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
// Check vertical bounds - if too close to bottom edge, position above
|
||||
if (y + menuHeight > screenHeight - 20) {
|
||||
finalY = y - menuHeight
|
||||
}
|
||||
|
||||
// Ensure we don't go off the left or top edges
|
||||
finalX = Math.max(20, finalX)
|
||||
finalY = Math.max(20, finalY)
|
||||
|
||||
processContextMenu.x = finalX
|
||||
processContextMenu.y = finalY
|
||||
processContextMenuWindow.menuVisible = true
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function hide() {
|
||||
processContextMenuWindow.menuVisible = false
|
||||
}
|
||||
|
||||
// Click outside to close
|
||||
@@ -930,48 +1007,35 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
onClicked: {
|
||||
processContextMenuWindow.menuVisible = false
|
||||
processContextMenuWindow.menuVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Process objects for commands
|
||||
Process {
|
||||
id: copyPidProcess
|
||||
|
||||
running: false
|
||||
}
|
||||
|
||||
Process {
|
||||
id: copyNameProcess
|
||||
|
||||
running: false
|
||||
}
|
||||
|
||||
Process {
|
||||
id: killProcess
|
||||
|
||||
running: false
|
||||
}
|
||||
|
||||
Process {
|
||||
id: forceKillProcess
|
||||
|
||||
running: false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
function hide() {
|
||||
isVisible = false
|
||||
}
|
||||
|
||||
function show() {
|
||||
isVisible = true
|
||||
ProcessMonitorService.updateSystemInfo()
|
||||
ProcessMonitorService.updateProcessList()
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (isVisible) {
|
||||
hide()
|
||||
} else {
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
import "."
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import "."
|
||||
|
||||
Rectangle {
|
||||
id: ramWidget
|
||||
@@ -13,19 +13,17 @@ Rectangle {
|
||||
width: 55
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: ramArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
color: ramArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
|
||||
MouseArea {
|
||||
id: ramArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
ProcessMonitorService.setSortBy("memory")
|
||||
processListDropdown.toggle()
|
||||
ProcessMonitorService.setSortBy("memory");
|
||||
processListDropdown.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +38,13 @@ Rectangle {
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: {
|
||||
if (SystemMonitorService.memoryUsage > 90) return Theme.error
|
||||
if (SystemMonitorService.memoryUsage > 75) return Theme.warning
|
||||
return Theme.surfaceText
|
||||
if (SystemMonitorService.memoryUsage > 90)
|
||||
return Theme.error;
|
||||
|
||||
if (SystemMonitorService.memoryUsage > 75)
|
||||
return Theme.warning;
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
@@ -55,5 +57,7 @@ Rectangle {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,32 +2,29 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
|
||||
PanelWindow {
|
||||
id: settingsPopup
|
||||
|
||||
property bool settingsVisible: false
|
||||
|
||||
signal closingPopup()
|
||||
|
||||
onSettingsVisibleChanged: {
|
||||
if (!settingsVisible) {
|
||||
closingPopup()
|
||||
}
|
||||
}
|
||||
if (!settingsVisible)
|
||||
closingPopup();
|
||||
|
||||
}
|
||||
visible: settingsVisible
|
||||
|
||||
implicitWidth: 600
|
||||
implicitHeight: 700
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
@@ -47,11 +44,13 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
onClicked: settingsPopup.settingsVisible = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Main settings panel - spotlight-like centered appearance
|
||||
Rectangle {
|
||||
id: mainPanel
|
||||
|
||||
width: Math.min(600, parent.width - Theme.spacingXL * 2)
|
||||
height: Math.min(700, parent.height - Theme.spacingXL * 2)
|
||||
anchors.centerIn: parent
|
||||
@@ -59,24 +58,11 @@ PanelWindow {
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
// Simple opacity and scale control tied directly to settingsVisible
|
||||
opacity: settingsPopup.settingsVisible ? 1.0 : 0.0
|
||||
scale: settingsPopup.settingsVisible ? 1.0 : 0.95
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
opacity: settingsPopup.settingsVisible ? 1 : 0
|
||||
scale: settingsPopup.settingsVisible ? 1 : 0.95
|
||||
// Add shadow effect
|
||||
layer.enabled: true
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
@@ -114,9 +100,7 @@ PanelWindow {
|
||||
width: 32
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: closeButton.containsMouse ?
|
||||
Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) :
|
||||
"transparent"
|
||||
color: closeButton.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
||||
|
||||
Text {
|
||||
text: "close"
|
||||
@@ -128,12 +112,15 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: closeButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: settingsPopup.settingsVisible = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Settings sections
|
||||
@@ -175,11 +162,12 @@ PanelWindow {
|
||||
// Circular profile image preview
|
||||
Item {
|
||||
id: avatarContainer
|
||||
width: 54
|
||||
height: 54
|
||||
|
||||
property bool hasImage: avatarImageSource.status === Image.Ready
|
||||
|
||||
width: 54
|
||||
height: 54
|
||||
|
||||
// This rectangle provides the themed ring via its border.
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -193,12 +181,15 @@ PanelWindow {
|
||||
// Hidden Image loader. Its only purpose is to load the texture.
|
||||
Image {
|
||||
id: avatarImageSource
|
||||
|
||||
source: {
|
||||
if (profileImageInput.text === "") return ""
|
||||
if (profileImageInput.text.startsWith("/")) {
|
||||
return "file://" + profileImageInput.text
|
||||
}
|
||||
return profileImageInput.text
|
||||
if (profileImageInput.text === "")
|
||||
return "";
|
||||
|
||||
if (profileImageInput.text.startsWith("/"))
|
||||
return "file://" + profileImageInput.text;
|
||||
|
||||
return profileImageInput.text;
|
||||
}
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
@@ -215,11 +206,12 @@ PanelWindow {
|
||||
maskSource: settingsCircularMask
|
||||
visible: avatarContainer.hasImage
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1.0
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
|
||||
Item {
|
||||
id: settingsCircularMask
|
||||
|
||||
width: 54 - 10
|
||||
height: 54 - 10
|
||||
layer.enabled: true
|
||||
@@ -232,6 +224,7 @@ PanelWindow {
|
||||
color: "black"
|
||||
antialiasing: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Fallback for when there is no image.
|
||||
@@ -248,6 +241,7 @@ PanelWindow {
|
||||
font.pixelSize: Theme.iconSize + 8
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Error icon for when the image fails to load.
|
||||
@@ -259,6 +253,7 @@ PanelWindow {
|
||||
color: Theme.primaryText
|
||||
visible: profileImageInput.text !== "" && avatarImageSource.status === Image.Error
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Input field
|
||||
@@ -276,6 +271,7 @@ PanelWindow {
|
||||
|
||||
TextInput {
|
||||
id: profileImageInput
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
@@ -283,9 +279,8 @@ PanelWindow {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
text: Prefs.profileImage
|
||||
selectByMouse: true
|
||||
|
||||
onEditingFinished: {
|
||||
Prefs.setProfileImage(text)
|
||||
Prefs.setProfileImage(text);
|
||||
}
|
||||
|
||||
// Placeholder text
|
||||
@@ -303,7 +298,9 @@ PanelWindow {
|
||||
cursorShape: Qt.IBeamCursor
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -313,10 +310,15 @@ PanelWindow {
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Clock Settings
|
||||
@@ -332,9 +334,13 @@ PanelWindow {
|
||||
text: "24-Hour Format"
|
||||
description: "Use 24-hour time format instead of 12-hour AM/PM"
|
||||
checked: Prefs.use24HourClock
|
||||
onToggled: (checked) => Prefs.setClockFormat(checked)
|
||||
onToggled: (checked) => {
|
||||
return Prefs.setClockFormat(checked);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Weather Settings
|
||||
@@ -350,7 +356,9 @@ PanelWindow {
|
||||
text: "Fahrenheit"
|
||||
description: "Use Fahrenheit instead of Celsius for temperature"
|
||||
checked: Prefs.useFahrenheit
|
||||
onToggled: (checked) => Prefs.setTemperatureUnit(checked)
|
||||
onToggled: (checked) => {
|
||||
return Prefs.setTemperatureUnit(checked);
|
||||
}
|
||||
}
|
||||
|
||||
// Weather Location Setting
|
||||
@@ -375,6 +383,7 @@ PanelWindow {
|
||||
|
||||
TextInput {
|
||||
id: weatherLocationInput
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
@@ -382,9 +391,8 @@ PanelWindow {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
text: Prefs.weatherLocationOverride
|
||||
selectByMouse: true
|
||||
|
||||
onEditingFinished: {
|
||||
Prefs.setWeatherLocationOverride(text)
|
||||
Prefs.setWeatherLocationOverride(text);
|
||||
}
|
||||
|
||||
// Placeholder text
|
||||
@@ -402,7 +410,9 @@ PanelWindow {
|
||||
cursorShape: Qt.IBeamCursor
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -412,8 +422,11 @@ PanelWindow {
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Widget Visibility Settings
|
||||
@@ -429,44 +442,58 @@ PanelWindow {
|
||||
text: "Focused Window"
|
||||
description: "Show the currently focused application in the top bar"
|
||||
checked: Prefs.showFocusedWindow
|
||||
onToggled: (checked) => Prefs.setShowFocusedWindow(checked)
|
||||
onToggled: (checked) => {
|
||||
return Prefs.setShowFocusedWindow(checked);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggle {
|
||||
text: "Weather Widget"
|
||||
description: "Display weather information in the top bar"
|
||||
checked: Prefs.showWeather
|
||||
onToggled: (checked) => Prefs.setShowWeather(checked)
|
||||
onToggled: (checked) => {
|
||||
return Prefs.setShowWeather(checked);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggle {
|
||||
text: "Media Controls"
|
||||
description: "Show currently playing media in the top bar"
|
||||
checked: Prefs.showMusic
|
||||
onToggled: (checked) => Prefs.setShowMusic(checked)
|
||||
onToggled: (checked) => {
|
||||
return Prefs.setShowMusic(checked);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggle {
|
||||
text: "Clipboard Button"
|
||||
description: "Show clipboard access button in the top bar"
|
||||
checked: Prefs.showClipboard
|
||||
onToggled: (checked) => Prefs.setShowClipboard(checked)
|
||||
onToggled: (checked) => {
|
||||
return Prefs.setShowClipboard(checked);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggle {
|
||||
text: "System Resources"
|
||||
description: "Display CPU and RAM usage indicators"
|
||||
checked: Prefs.showSystemResources
|
||||
onToggled: (checked) => Prefs.setShowSystemResources(checked)
|
||||
onToggled: (checked) => {
|
||||
return Prefs.setShowSystemResources(checked);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggle {
|
||||
text: "System Tray"
|
||||
description: "Show system tray icons in the top bar"
|
||||
checked: Prefs.showSystemTray
|
||||
onToggled: (checked) => Prefs.setShowSystemTray(checked)
|
||||
onToggled: (checked) => {
|
||||
return Prefs.setShowSystemTray(checked);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Display Settings
|
||||
@@ -483,12 +510,11 @@ PanelWindow {
|
||||
description: "Apply warm color temperature to reduce eye strain"
|
||||
checked: Prefs.nightModeEnabled
|
||||
onToggled: (checked) => {
|
||||
Prefs.setNightModeEnabled(checked)
|
||||
if (checked) {
|
||||
nightModeEnableProcess.running = true
|
||||
} else {
|
||||
nightModeDisableProcess.running = true
|
||||
}
|
||||
Prefs.setNightModeEnabled(checked);
|
||||
if (checked)
|
||||
nightModeEnableProcess.running = true;
|
||||
else
|
||||
nightModeDisableProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,8 +523,8 @@ PanelWindow {
|
||||
description: "Use light theme instead of dark theme"
|
||||
checked: Prefs.isLightMode
|
||||
onToggled: (checked) => {
|
||||
Prefs.setLightMode(checked)
|
||||
Theme.isLightMode = checked
|
||||
Prefs.setLightMode(checked);
|
||||
Theme.isLightMode = checked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,10 +549,9 @@ PanelWindow {
|
||||
rightIcon: "circle"
|
||||
unit: "%"
|
||||
showValue: true
|
||||
|
||||
onSliderDragFinished: (finalValue) => {
|
||||
let transparencyValue = finalValue / 100.0
|
||||
Prefs.setTopBarTransparency(transparencyValue)
|
||||
let transparencyValue = finalValue / 100;
|
||||
Prefs.setTopBarTransparency(transparencyValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,6 +562,7 @@ PanelWindow {
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Popup Transparency
|
||||
@@ -560,10 +586,9 @@ PanelWindow {
|
||||
rightIcon: "circle"
|
||||
unit: "%"
|
||||
showValue: true
|
||||
|
||||
onSliderDragFinished: (finalValue) => {
|
||||
let transparencyValue = finalValue / 100.0
|
||||
Prefs.setPopupTransparency(transparencyValue)
|
||||
let transparencyValue = finalValue / 100;
|
||||
Prefs.setPopupTransparency(transparencyValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,6 +599,7 @@ PanelWindow {
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Theme Picker
|
||||
@@ -591,48 +617,69 @@ PanelWindow {
|
||||
ThemePicker {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add shadow effect
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 8
|
||||
shadowBlur: 1.0
|
||||
shadowBlur: 1
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
||||
shadowOpacity: 0.3
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Night mode processes
|
||||
Process {
|
||||
id: nightModeEnableProcess
|
||||
|
||||
command: ["bash", "-c", "if command -v wlsunset > /dev/null; then pkill wlsunset; wlsunset -t 3000 & elif command -v redshift > /dev/null; then pkill redshift; redshift -P -O 3000 & else echo 'No night mode tool available'; fi"]
|
||||
running: false
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Failed to enable night mode")
|
||||
Prefs.setNightModeEnabled(false)
|
||||
console.warn("Failed to enable night mode");
|
||||
Prefs.setNightModeEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: nightModeDisableProcess
|
||||
|
||||
command: ["bash", "-c", "pkill wlsunset; pkill redshift; if command -v wlsunset > /dev/null; then wlsunset -t 6500 -T 6500 & sleep 1; pkill wlsunset; elif command -v redshift > /dev/null; then redshift -P -O 6500; redshift -x; fi"]
|
||||
running: false
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Failed to disable night mode")
|
||||
}
|
||||
if (exitCode !== 0)
|
||||
console.warn("Failed to disable night mode");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,7 +687,7 @@ PanelWindow {
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: settingsPopup.settingsVisible
|
||||
|
||||
Keys.onEscapePressed: settingsPopup.settingsVisible = false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,7 @@ Column {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Divider
|
||||
@@ -43,6 +44,8 @@ Column {
|
||||
// Content
|
||||
Loader {
|
||||
id: contentLoader
|
||||
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,16 +13,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: toggleArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
color: toggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -51,30 +42,26 @@ Rectangle {
|
||||
width: Math.min(implicitWidth, root.width - 120)
|
||||
visible: root.description.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Toggle switch
|
||||
Rectangle {
|
||||
id: toggle
|
||||
|
||||
width: 48
|
||||
height: 24
|
||||
radius: 12
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
color: root.checked ? Theme.primary : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: toggleHandle
|
||||
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
@@ -87,6 +74,7 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
@@ -94,19 +82,39 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: toggleArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.checked = !root.checked
|
||||
root.toggled(root.checked)
|
||||
root.checked = !root.checked;
|
||||
root.toggled(root.checked);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ import qs.Services
|
||||
|
||||
Column {
|
||||
id: themePicker
|
||||
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
@@ -17,22 +18,11 @@ Column {
|
||||
// Theme description
|
||||
Text {
|
||||
text: {
|
||||
if (Theme.isDynamicTheme) {
|
||||
return "Wallpaper-based dynamic colors"
|
||||
}
|
||||
var descriptions = [
|
||||
"Material blue inspired by modern interfaces",
|
||||
"Deep blue inspired by material 3",
|
||||
"Rich purple tones for BB elegance",
|
||||
"Natural green for productivity",
|
||||
"Energetic orange for creativity",
|
||||
"Bold red for impact",
|
||||
"Cool cyan for tranquility",
|
||||
"Vibrant pink for expression",
|
||||
"Warm amber for comfort",
|
||||
"Soft coral for gentle warmth"
|
||||
]
|
||||
return descriptions[Theme.currentThemeIndex] || "Select a theme"
|
||||
if (Theme.isDynamicTheme)
|
||||
return "Wallpaper-based dynamic colors";
|
||||
|
||||
var descriptions = ["Material blue inspired by modern interfaces", "Deep blue inspired by material 3", "Rich purple tones for BB elegance", "Natural green for productivity", "Energetic orange for creativity", "Bold red for impact", "Cool cyan for tranquility", "Vibrant pink for expression", "Warm amber for comfort", "Soft coral for gentle warmth"];
|
||||
return descriptions[Theme.currentThemeIndex] || "Select a theme";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
@@ -62,22 +52,7 @@ Column {
|
||||
color: Theme.themes[index].primary
|
||||
border.color: Theme.outline
|
||||
border.width: (Theme.currentThemeIndex === index && !Theme.isDynamicTheme) ? 2 : 1
|
||||
|
||||
scale: (Theme.currentThemeIndex === index && !Theme.isDynamicTheme) ? 1.1 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
scale: (Theme.currentThemeIndex === index && !Theme.isDynamicTheme) ? 1.1 : 1
|
||||
|
||||
// Theme name tooltip
|
||||
Rectangle {
|
||||
@@ -94,24 +69,46 @@ Column {
|
||||
|
||||
Text {
|
||||
id: nameText
|
||||
|
||||
text: Theme.themes[index].name
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Theme.switchTheme(index, false)
|
||||
Theme.switchTheme(index, false);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Second row - Red, Cyan, Pink, Amber, Coral
|
||||
@@ -124,6 +121,7 @@ Column {
|
||||
|
||||
Rectangle {
|
||||
property int themeIndex: index + 5
|
||||
|
||||
width: 32
|
||||
height: 32
|
||||
radius: 16
|
||||
@@ -131,22 +129,7 @@ Column {
|
||||
border.color: Theme.outline
|
||||
border.width: Theme.currentThemeIndex === themeIndex ? 2 : 1
|
||||
visible: themeIndex < Theme.themes.length
|
||||
|
||||
scale: Theme.currentThemeIndex === themeIndex ? 1.1 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
scale: Theme.currentThemeIndex === themeIndex ? 1.1 : 1
|
||||
|
||||
// Theme name tooltip
|
||||
Rectangle {
|
||||
@@ -163,26 +146,48 @@ Column {
|
||||
|
||||
Text {
|
||||
id: nameText2
|
||||
|
||||
text: themeIndex < Theme.themes.length ? Theme.themes[themeIndex].name : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea2
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (themeIndex < Theme.themes.length) {
|
||||
Theme.switchTheme(themeIndex)
|
||||
if (themeIndex < Theme.themes.length)
|
||||
Theme.switchTheme(themeIndex);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Spacer for better visual separation
|
||||
@@ -197,26 +202,22 @@ Column {
|
||||
height: 40
|
||||
radius: 20
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
color: {
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") {
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||
} else {
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
|
||||
else
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
border.color: {
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") {
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.5)
|
||||
} else if (Theme.isDynamicTheme) {
|
||||
return Theme.primary
|
||||
} else {
|
||||
return Theme.outline
|
||||
}
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.5);
|
||||
else if (Theme.isDynamicTheme)
|
||||
return Theme.primary;
|
||||
else
|
||||
return Theme.outline;
|
||||
}
|
||||
border.width: Theme.isDynamicTheme ? 2 : 1
|
||||
|
||||
scale: Theme.isDynamicTheme ? 1.1 : (autoMouseArea.containsMouse ? 1.02 : 1)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
@@ -224,14 +225,18 @@ Column {
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") return "error"
|
||||
else return "palette"
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return "error";
|
||||
else
|
||||
return "palette";
|
||||
}
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: 16
|
||||
color: {
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") return Theme.error
|
||||
else return Theme.surfaceText
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return Theme.error;
|
||||
else
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
font.weight: Theme.iconFontWeight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -239,51 +244,34 @@ Column {
|
||||
|
||||
Text {
|
||||
text: {
|
||||
if (ToastService.wallpaperErrorStatus === "error") return "Error"
|
||||
else if (ToastService.wallpaperErrorStatus === "matugen_missing") return "No matugen"
|
||||
else return "Auto"
|
||||
if (ToastService.wallpaperErrorStatus === "error")
|
||||
return "Error";
|
||||
else if (ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return "No matugen";
|
||||
else
|
||||
return "Auto";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: {
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") return Theme.error
|
||||
else return Theme.surfaceText
|
||||
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return Theme.error;
|
||||
else
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
scale: Theme.isDynamicTheme ? 1.1 : (autoMouseArea.containsMouse ? 1.02 : 1.0)
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: autoMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
Theme.switchTheme(10, true)
|
||||
Theme.switchTheme(10, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,14 +290,14 @@ Column {
|
||||
|
||||
Text {
|
||||
id: autoTooltipText
|
||||
|
||||
text: {
|
||||
if (ToastService.wallpaperErrorStatus === "error") {
|
||||
return "Wallpaper symlink missing at ~/quickshell/current_wallpaper"
|
||||
} else if (ToastService.wallpaperErrorStatus === "matugen_missing") {
|
||||
return "Install matugen package for dynamic themes"
|
||||
} else {
|
||||
return "Dynamic wallpaper-based colors"
|
||||
}
|
||||
if (ToastService.wallpaperErrorStatus === "error")
|
||||
return "Wallpaper symlink missing at ~/quickshell/current_wallpaper";
|
||||
else if (ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
return "Install matugen package for dynamic themes";
|
||||
else
|
||||
return "Dynamic wallpaper-based colors";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") ? Theme.error : Theme.surfaceText
|
||||
@@ -318,7 +306,35 @@ Column {
|
||||
width: Math.min(implicitWidth, 250)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -10,11 +10,9 @@ PanelWindow {
|
||||
id: root
|
||||
|
||||
visible: ToastService.toastVisible
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
@@ -24,80 +22,47 @@ PanelWindow {
|
||||
bottom: true
|
||||
}
|
||||
|
||||
// Makes the background transparent to mouse events
|
||||
mask: Region {
|
||||
item: toast
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: toast
|
||||
|
||||
width: Math.min(400, Screen.width - Theme.spacingL * 2)
|
||||
height: toastContent.height + Theme.spacingL * 2
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: Theme.barHeight + Theme.spacingL
|
||||
|
||||
color: {
|
||||
switch (ToastService.currentLevel) {
|
||||
case ToastService.levelError: return Theme.error
|
||||
case ToastService.levelWarn: return Theme.warning
|
||||
case ToastService.levelInfo: return Theme.primary
|
||||
default: return Theme.primary
|
||||
case ToastService.levelError:
|
||||
return Theme.error;
|
||||
case ToastService.levelWarn:
|
||||
return Theme.warning;
|
||||
case ToastService.levelInfo:
|
||||
return Theme.primary;
|
||||
default:
|
||||
return Theme.primary;
|
||||
}
|
||||
}
|
||||
|
||||
radius: Theme.cornerRadiusLarge
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 4
|
||||
shadowBlur: 0.8
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
||||
shadowOpacity: 0.3
|
||||
}
|
||||
|
||||
opacity: ToastService.toastVisible ? 0.9 : 0.0
|
||||
scale: ToastService.toastVisible ? 1.0 : 0.9
|
||||
|
||||
transform: Translate {
|
||||
y: ToastService.toastVisible ? 0 : -20
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
opacity: ToastService.toastVisible ? 0.9 : 0
|
||||
scale: ToastService.toastVisible ? 1 : 0.9
|
||||
|
||||
Row {
|
||||
id: toastContent
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: {
|
||||
switch (ToastService.currentLevel) {
|
||||
case ToastService.levelError: return "error"
|
||||
case ToastService.levelWarn: return "warning"
|
||||
case ToastService.levelInfo: return "info"
|
||||
default: return "info"
|
||||
case ToastService.levelError:
|
||||
return "error";
|
||||
case ToastService.levelWarn:
|
||||
return "warning";
|
||||
case ToastService.levelInfo:
|
||||
return "info";
|
||||
default:
|
||||
return "info";
|
||||
}
|
||||
}
|
||||
font.family: Theme.iconFont
|
||||
@@ -115,11 +80,56 @@ PanelWindow {
|
||||
width: Math.min(implicitWidth, 300)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: ToastService.hideToast()
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 4
|
||||
shadowBlur: 0.8
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
||||
shadowOpacity: 0.3
|
||||
}
|
||||
|
||||
transform: Translate {
|
||||
y: ToastService.toastVisible ? 0 : -20
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Makes the background transparent to mouse events
|
||||
mask: Region {
|
||||
item: toast
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import qs.Services
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property list<real> audioLevels: [0, 0, 0, 0]
|
||||
property var audioLevels: [0, 0, 0, 0]
|
||||
readonly property MprisPlayer activePlayer: MprisController.activePlayer
|
||||
readonly property bool hasActiveMedia: activePlayer !== null
|
||||
property bool cavaAvailable: false
|
||||
@@ -18,56 +18,62 @@ Item {
|
||||
|
||||
Process {
|
||||
id: cavaCheck
|
||||
|
||||
command: ["which", "cava"]
|
||||
running: true
|
||||
onExited: (exitCode) => {
|
||||
root.cavaAvailable = exitCode === 0
|
||||
root.cavaAvailable = exitCode === 0;
|
||||
if (root.cavaAvailable) {
|
||||
console.log("cava found - enabling real audio visualization")
|
||||
cavaProcess.running = Qt.binding(() => root.hasActiveMedia && root.activePlayer?.playbackState === MprisPlaybackState.Playing)
|
||||
console.log("cava found - enabling real audio visualization");
|
||||
cavaProcess.running = Qt.binding(() => {
|
||||
return root.hasActiveMedia && root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing;
|
||||
});
|
||||
} else {
|
||||
console.log("cava not found - using fallback animation")
|
||||
fallbackTimer.running = Qt.binding(() => root.hasActiveMedia && root.activePlayer?.playbackState === MprisPlaybackState.Playing)
|
||||
console.log("cava not found - using fallback animation");
|
||||
fallbackTimer.running = Qt.binding(() => {
|
||||
return root.hasActiveMedia && root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: cavaProcess
|
||||
|
||||
running: false
|
||||
command: ["sh", "-c", `printf '[general]\nmode=normal\nframerate=30\nautosens=0\nsensitivity=50\nbars=4\n[output]\nmethod=raw\nraw_target=/dev/stdout\ndata_format=ascii\nchannels=mono\nmono_option=average\n[smoothing]\nnoise_reduction=20' | cava -p /dev/stdin`]
|
||||
onRunningChanged: {
|
||||
if (!running)
|
||||
root.audioLevels = [0, 0, 0, 0];
|
||||
|
||||
}
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: data => {
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
let points = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p))
|
||||
if (points.length >= 4) {
|
||||
root.audioLevels = [points[0], points[1], points[2], points[3]]
|
||||
}
|
||||
let points = data.split(";").map((p) => {
|
||||
return parseFloat(p.trim());
|
||||
}).filter((p) => {
|
||||
return !isNaN(p);
|
||||
});
|
||||
if (points.length >= 4)
|
||||
root.audioLevels = [points[0], points[1], points[2], points[3]];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
root.audioLevels = [0, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: fallbackTimer
|
||||
|
||||
running: false
|
||||
interval: 100
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
root.audioLevels = [
|
||||
Math.random() * 40 + 10,
|
||||
Math.random() * 60 + 20,
|
||||
Math.random() * 50 + 15,
|
||||
Math.random() * 35 + 20
|
||||
]
|
||||
root.audioLevels = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,14 +87,14 @@ Item {
|
||||
Rectangle {
|
||||
width: 3
|
||||
height: {
|
||||
if (root.activePlayer?.playbackState === MprisPlaybackState.Playing && root.audioLevels.length > index) {
|
||||
const rawLevel = root.audioLevels[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)
|
||||
if (root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing && root.audioLevels.length > index) {
|
||||
const rawLevel = root.audioLevels[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
|
||||
return 3;
|
||||
}
|
||||
radius: 1.5
|
||||
color: Theme.primary
|
||||
@@ -99,8 +105,13 @@ Item {
|
||||
duration: 80
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,19 +12,14 @@ Rectangle {
|
||||
width: clockRow.implicitWidth + Theme.spacingS * 2
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: clockMouseArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
color: clockMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
Component.onCompleted: {
|
||||
root.currentDate = systemClock.date;
|
||||
}
|
||||
|
||||
Row {
|
||||
id: clockRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
@@ -50,26 +45,33 @@ Rectangle {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SystemClock {
|
||||
id: systemClock
|
||||
|
||||
precision: SystemClock.Seconds
|
||||
onDateChanged: root.currentDate = systemClock.date
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.currentDate = systemClock.date
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clockMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.clockClicked()
|
||||
root.clockClicked();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,29 +12,35 @@ Rectangle {
|
||||
width: Math.max(80, controlIndicators.implicitWidth + Theme.spacingS * 2)
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: controlCenterArea.containsMouse || root.isActive ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
color: controlCenterArea.containsMouse || root.isActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
|
||||
Row {
|
||||
id: controlIndicators
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
// Network Status Icon
|
||||
Text {
|
||||
text: {
|
||||
if (NetworkService.networkStatus === "ethernet") return "lan"
|
||||
else if (NetworkService.networkStatus === "wifi") {
|
||||
if (NetworkService.networkStatus === "ethernet") {
|
||||
return "lan";
|
||||
} else if (NetworkService.networkStatus === "wifi") {
|
||||
switch (WifiService.wifiSignalStrength) {
|
||||
case "excellent": return "wifi"
|
||||
case "good": return "wifi_2_bar"
|
||||
case "fair": return "wifi_1_bar"
|
||||
case "poor": return "wifi_calling_3"
|
||||
default: return "wifi"
|
||||
case "excellent":
|
||||
return "wifi";
|
||||
case "good":
|
||||
return "wifi_2_bar";
|
||||
case "fair":
|
||||
return "wifi_1_bar";
|
||||
case "poor":
|
||||
return "wifi_calling_3";
|
||||
default:
|
||||
return "wifi";
|
||||
}
|
||||
} else {
|
||||
return "wifi_off";
|
||||
}
|
||||
else return "wifi_off"
|
||||
}
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
@@ -64,39 +70,37 @@ Rectangle {
|
||||
|
||||
Text {
|
||||
id: audioIcon
|
||||
text: AudioService.sinkMuted ? "volume_off" :
|
||||
AudioService.volumeLevel < 33 ? "volume_down" : "volume_up"
|
||||
|
||||
text: AudioService.sinkMuted ? "volume_off" : AudioService.volumeLevel < 33 ? "volume_down" : "volume_up"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: audioWheelArea.containsMouse || controlCenterArea.containsMouse || root.isActive ?
|
||||
Theme.primary : Theme.surfaceText
|
||||
color: audioWheelArea.containsMouse || controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// Scroll up - increase volume
|
||||
// Scroll down - decrease volume
|
||||
|
||||
id: audioWheelArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
|
||||
onWheel: function(wheelEvent) {
|
||||
let delta = wheelEvent.angleDelta.y
|
||||
let currentVolume = AudioService.volumeLevel
|
||||
let newVolume
|
||||
|
||||
if (delta > 0) {
|
||||
// Scroll up - increase volume
|
||||
newVolume = Math.min(100, currentVolume + 5)
|
||||
} else {
|
||||
// Scroll down - decrease volume
|
||||
newVolume = Math.max(0, currentVolume - 5)
|
||||
let delta = wheelEvent.angleDelta.y;
|
||||
let currentVolume = AudioService.volumeLevel;
|
||||
let newVolume;
|
||||
if (delta > 0)
|
||||
newVolume = Math.min(100, currentVolume + 5);
|
||||
else
|
||||
newVolume = Math.max(0, currentVolume - 5);
|
||||
AudioService.setVolume(newVolume);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
AudioService.setVolume(newVolume)
|
||||
wheelEvent.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Microphone Icon (when active)
|
||||
@@ -109,16 +113,17 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: false // TODO: Add mic detection
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: controlCenterArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.clicked()
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,5 +132,7 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,33 +8,24 @@ Rectangle {
|
||||
width: Math.max(contentRow.implicitWidth + Theme.spacingS * 2, 60)
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: mouseArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
color: mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
clip: true
|
||||
|
||||
visible: FocusedWindowService.niriAvailable && (FocusedWindowService.focusedAppName || FocusedWindowService.focusedWindowTitle)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
id: appText
|
||||
|
||||
text: FocusedWindowService.focusedAppName || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// Limit app name width
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
@@ -51,25 +42,35 @@ Rectangle {
|
||||
|
||||
Text {
|
||||
id: titleText
|
||||
|
||||
text: FocusedWindowService.focusedWindowTitle || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// Limit title width - increased for longer titles
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
width: Math.min(implicitWidth, 350)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// Non-interactive widget - just provides hover state for visual feedback
|
||||
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
// Non-interactive widget - just provides hover state for visual feedback
|
||||
}
|
||||
|
||||
// Smooth width animation when the text changes
|
||||
@@ -78,5 +79,7 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,12 +21,12 @@ Rectangle {
|
||||
|
||||
MouseArea {
|
||||
id: launcherArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
LauncherService.toggleAppLauncher()
|
||||
LauncherService.toggleAppLauncher();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,5 +35,7 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,37 @@ Rectangle {
|
||||
|
||||
}
|
||||
]
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "shown"
|
||||
to: "hidden"
|
||||
|
||||
SequentialAnimation {
|
||||
PauseAnimation {
|
||||
duration: 500
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
properties: "opacity,width"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
Transition {
|
||||
from: "hidden"
|
||||
to: "shown"
|
||||
|
||||
NumberAnimation {
|
||||
properties: "opacity,width"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
Row {
|
||||
id: mediaRow
|
||||
@@ -64,9 +95,8 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 140
|
||||
text: {
|
||||
if (!activePlayer || !activePlayer.trackTitle) {
|
||||
if (!activePlayer || !activePlayer.trackTitle)
|
||||
return "";
|
||||
}
|
||||
|
||||
let identity = activePlayer.identity || "";
|
||||
let isWebMedia = identity.toLowerCase().includes("firefox") || identity.toLowerCase().includes("chrome") || identity.toLowerCase().includes("chromium") || identity.toLowerCase().includes("edge") || identity.toLowerCase().includes("safari");
|
||||
@@ -209,38 +239,6 @@ Rectangle {
|
||||
|
||||
}
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "shown"
|
||||
to: "hidden"
|
||||
|
||||
SequentialAnimation {
|
||||
PauseAnimation {
|
||||
duration: 500
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
properties: "opacity,width"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
Transition {
|
||||
from: "hidden"
|
||||
to: "shown"
|
||||
|
||||
NumberAnimation {
|
||||
properties: "opacity,width"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
|
||||
@@ -6,14 +6,13 @@ Rectangle {
|
||||
|
||||
property bool hasUnread: false
|
||||
property bool isActive: false
|
||||
|
||||
signal clicked()
|
||||
|
||||
width: 40
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: notificationArea.containsMouse || root.isActive ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
color: notificationArea.containsMouse || root.isActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -21,8 +20,7 @@ Rectangle {
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: notificationArea.containsMouse || root.isActive ?
|
||||
Theme.primary : Theme.surfaceText
|
||||
color: notificationArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
// Notification dot indicator
|
||||
@@ -40,12 +38,12 @@ Rectangle {
|
||||
|
||||
MouseArea {
|
||||
id: notificationArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.clicked()
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,5 +52,7 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,25 +15,27 @@ Rectangle {
|
||||
|
||||
Row {
|
||||
id: systemTrayRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items
|
||||
|
||||
delegate: Rectangle {
|
||||
property var trayItem: modelData
|
||||
|
||||
width: 24
|
||||
height: 24
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: trayItemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
property var trayItem: modelData
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: 18
|
||||
height: 18
|
||||
source: {
|
||||
let icon = trayItem?.icon;
|
||||
let icon = trayItem && trayItem.icon;
|
||||
if (typeof icon === 'string' || icon instanceof String) {
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=");
|
||||
@@ -51,22 +53,23 @@ Rectangle {
|
||||
|
||||
MouseArea {
|
||||
id: trayItemArea
|
||||
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: (mouse) => {
|
||||
if (!trayItem) return;
|
||||
if (!trayItem)
|
||||
return ;
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (!trayItem.onlyMenu) {
|
||||
trayItem.activate()
|
||||
}
|
||||
if (!trayItem.onlyMenu)
|
||||
trayItem.activate();
|
||||
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (trayItem.hasMenu) {
|
||||
customTrayMenu.showMenu(mouse.x, mouse.y)
|
||||
}
|
||||
if (trayItem.hasMenu)
|
||||
customTrayMenu.showMenu(mouse.x, mouse.y);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,13 +80,14 @@ Rectangle {
|
||||
property bool menuVisible: false
|
||||
|
||||
function showMenu(x, y) {
|
||||
root.menuRequested(customTrayMenu, trayItem, x, y)
|
||||
menuVisible = true
|
||||
root.menuRequested(customTrayMenu, trayItem, x, y);
|
||||
menuVisible = true;
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
menuVisible = false
|
||||
menuVisible = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
@@ -91,8 +95,13 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +1,54 @@
|
||||
import "../../Common/Utilities.js" as Utils
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import "../../Common/Utilities.js" as Utils
|
||||
|
||||
PanelWindow {
|
||||
// Proxy objects for external connections
|
||||
|
||||
id: root
|
||||
|
||||
property var modelData
|
||||
screen: modelData
|
||||
property string screenName: modelData.name
|
||||
|
||||
// Transparency property for the top bar background
|
||||
property real backgroundTransparency: Prefs.topBarTransparency
|
||||
|
||||
Connections {
|
||||
target: Prefs
|
||||
function onTopBarTransparencyChanged() {
|
||||
root.backgroundTransparency = Prefs.topBarTransparency
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Notification properties
|
||||
readonly property int notificationCount: NotificationService.notifications.length
|
||||
|
||||
screen: modelData
|
||||
implicitHeight: Theme.barHeight - 4
|
||||
color: "transparent"
|
||||
|
||||
Connections {
|
||||
function onTopBarTransparencyChanged() {
|
||||
root.backgroundTransparency = Prefs.topBarTransparency;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Proxy objects for external connections
|
||||
target: Prefs
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: notificationHistory
|
||||
|
||||
property int count: 0
|
||||
}
|
||||
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
implicitHeight: Theme.barHeight - 4
|
||||
color: "transparent"
|
||||
|
||||
// Floating panel container with margins
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
@@ -69,16 +62,7 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadiusXLarge
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, root.backgroundTransparency)
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 4
|
||||
shadowBlur: 0.5 // radius/32, adjusted for visual match
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.15)
|
||||
shadowOpacity: 0.15
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -96,18 +80,32 @@ PanelWindow {
|
||||
SequentialAnimation on opacity {
|
||||
running: false
|
||||
loops: Animation.Infinite
|
||||
|
||||
NumberAnimation {
|
||||
to: 0.08
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
to: 0.02
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 4
|
||||
shadowBlur: 0.5 // radius/32, adjusted for visual match
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.15)
|
||||
shadowOpacity: 0.15
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -120,6 +118,7 @@ PanelWindow {
|
||||
|
||||
Row {
|
||||
id: leftSection
|
||||
|
||||
height: parent.height
|
||||
spacing: Theme.spacingXS
|
||||
anchors.left: parent.left
|
||||
@@ -138,14 +137,15 @@ PanelWindow {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: Prefs.showFocusedWindow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ClockWidget {
|
||||
id: clockWidget
|
||||
anchors.centerIn: parent
|
||||
|
||||
anchors.centerIn: parent
|
||||
onClockClicked: {
|
||||
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible
|
||||
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,27 +154,26 @@ PanelWindow {
|
||||
anchors.right: clockWidget.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
visible: Prefs.showMusic && MprisController.activePlayer
|
||||
|
||||
onClicked: {
|
||||
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible
|
||||
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible;
|
||||
}
|
||||
}
|
||||
|
||||
WeatherWidget {
|
||||
id: weatherWidget
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: clockWidget.right
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
|
||||
visible: Prefs.showWeather && WeatherService.weather.available && WeatherService.weather.temp > 0 && WeatherService.weather.tempF > 0
|
||||
|
||||
onClicked: {
|
||||
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible
|
||||
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible;
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: rightSection
|
||||
|
||||
height: parent.height
|
||||
spacing: Theme.spacingXS
|
||||
anchors.right: parent.right
|
||||
@@ -184,12 +183,12 @@ PanelWindow {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: Prefs.showSystemTray
|
||||
onMenuRequested: (menu, item, x, y) => {
|
||||
trayMenuPopup.currentTrayMenu = menu
|
||||
trayMenuPopup.currentTrayItem = item
|
||||
trayMenuPopup.trayMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL
|
||||
trayMenuPopup.trayMenuY = Theme.barHeight - Theme.spacingXS
|
||||
trayMenuPopup.showTrayMenu = true
|
||||
menu.menuVisible = true
|
||||
trayMenuPopup.currentTrayMenu = menu;
|
||||
trayMenuPopup.currentTrayItem = item;
|
||||
trayMenuPopup.trayMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL;
|
||||
trayMenuPopup.trayMenuY = Theme.barHeight - Theme.spacingXS;
|
||||
trayMenuPopup.showTrayMenu = true;
|
||||
menu.menuVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,12 +211,12 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: clipboardArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
clipboardHistoryPopup.toggle()
|
||||
clipboardHistoryPopup.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +225,9 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// System Monitor Widgets
|
||||
@@ -245,7 +246,7 @@ PanelWindow {
|
||||
hasUnread: root.notificationCount > 0
|
||||
isActive: notificationCenter.notificationHistoryVisible
|
||||
onClicked: {
|
||||
notificationCenter.notificationHistoryVisible = !notificationCenter.notificationHistoryVisible
|
||||
notificationCenter.notificationHistoryVisible = !notificationCenter.notificationHistoryVisible;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,25 +255,29 @@ PanelWindow {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
batteryPopupVisible: batteryControlPopup.batteryPopupVisible
|
||||
onToggleBatteryPopup: {
|
||||
batteryControlPopup.batteryPopupVisible = !batteryControlPopup.batteryPopupVisible
|
||||
batteryControlPopup.batteryPopupVisible = !batteryControlPopup.batteryPopupVisible;
|
||||
}
|
||||
}
|
||||
|
||||
ControlCenterButton {
|
||||
// Bluetooth devices are automatically updated via signals
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
isActive: controlCenterPopup.controlCenterVisible
|
||||
|
||||
onClicked: {
|
||||
controlCenterPopup.controlCenterVisible = !controlCenterPopup.controlCenterVisible
|
||||
controlCenterPopup.controlCenterVisible = !controlCenterPopup.controlCenterVisible;
|
||||
if (controlCenterPopup.controlCenterVisible) {
|
||||
if (NetworkService.wifiEnabled) {
|
||||
WifiService.scanWifi()
|
||||
}
|
||||
// Bluetooth devices are automatically updated via signals
|
||||
if (NetworkService.wifiEnabled)
|
||||
WifiService.scanWifi();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,33 +5,17 @@ import qs.Services
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
|
||||
signal clicked()
|
||||
|
||||
// Visibility is now controlled by TopBar.qml
|
||||
width: visible ? Math.min(100, weatherRow.implicitWidth + Theme.spacingS * 2) : 0
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: weatherArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
color: weatherArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
|
||||
Row {
|
||||
id: weatherRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
@@ -50,14 +34,32 @@ Rectangle {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: weatherArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,41 @@ Rectangle {
|
||||
id: root
|
||||
|
||||
property string screenName: ""
|
||||
property int currentWorkspace: getDisplayActiveWorkspace()
|
||||
property var workspaceList: getDisplayWorkspaces()
|
||||
|
||||
function getDisplayWorkspaces() {
|
||||
if (!NiriWorkspaceService.niriAvailable || NiriWorkspaceService.allWorkspaces.length === 0)
|
||||
return [1, 2];
|
||||
|
||||
if (!root.screenName)
|
||||
return NiriWorkspaceService.getCurrentOutputWorkspaceNumbers();
|
||||
|
||||
var displayWorkspaces = [];
|
||||
for (var i = 0; i < NiriWorkspaceService.allWorkspaces.length; i++) {
|
||||
var ws = NiriWorkspaceService.allWorkspaces[i];
|
||||
if (ws.output === root.screenName)
|
||||
displayWorkspaces.push(ws.idx + 1);
|
||||
|
||||
}
|
||||
return displayWorkspaces.length > 0 ? displayWorkspaces : [1, 2];
|
||||
}
|
||||
|
||||
function getDisplayActiveWorkspace() {
|
||||
if (!NiriWorkspaceService.niriAvailable || NiriWorkspaceService.allWorkspaces.length === 0)
|
||||
return 1;
|
||||
|
||||
if (!root.screenName)
|
||||
return NiriWorkspaceService.getCurrentWorkspaceNumber();
|
||||
|
||||
for (var i = 0; i < NiriWorkspaceService.allWorkspaces.length; i++) {
|
||||
var ws = NiriWorkspaceService.allWorkspaces[i];
|
||||
if (ws.output === root.screenName && ws.is_active)
|
||||
return ws.idx + 1;
|
||||
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
width: Math.max(120, workspaceRow.implicitWidth + Theme.spacingL * 2)
|
||||
height: 30
|
||||
@@ -14,67 +49,29 @@ Rectangle {
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
visible: NiriWorkspaceService.niriAvailable
|
||||
|
||||
property int currentWorkspace: getDisplayActiveWorkspace()
|
||||
property var workspaceList: getDisplayWorkspaces()
|
||||
|
||||
function getDisplayWorkspaces() {
|
||||
if (!NiriWorkspaceService.niriAvailable || NiriWorkspaceService.allWorkspaces.length === 0) {
|
||||
return [1, 2]
|
||||
}
|
||||
|
||||
if (!root.screenName) {
|
||||
return NiriWorkspaceService.getCurrentOutputWorkspaceNumbers()
|
||||
}
|
||||
|
||||
var displayWorkspaces = []
|
||||
for (var i = 0; i < NiriWorkspaceService.allWorkspaces.length; i++) {
|
||||
var ws = NiriWorkspaceService.allWorkspaces[i]
|
||||
if (ws.output === root.screenName) {
|
||||
displayWorkspaces.push(ws.idx + 1)
|
||||
}
|
||||
}
|
||||
|
||||
return displayWorkspaces.length > 0 ? displayWorkspaces : [1, 2]
|
||||
}
|
||||
|
||||
function getDisplayActiveWorkspace() {
|
||||
if (!NiriWorkspaceService.niriAvailable || NiriWorkspaceService.allWorkspaces.length === 0) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (!root.screenName) {
|
||||
return NiriWorkspaceService.getCurrentWorkspaceNumber()
|
||||
}
|
||||
|
||||
for (var i = 0; i < NiriWorkspaceService.allWorkspaces.length; i++) {
|
||||
var ws = NiriWorkspaceService.allWorkspaces[i]
|
||||
if (ws.output === root.screenName && ws.is_active) {
|
||||
return ws.idx + 1
|
||||
}
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NiriWorkspaceService
|
||||
function onAllWorkspacesChanged() {
|
||||
root.workspaceList = root.getDisplayWorkspaces()
|
||||
root.currentWorkspace = root.getDisplayActiveWorkspace()
|
||||
root.workspaceList = root.getDisplayWorkspaces();
|
||||
root.currentWorkspace = root.getDisplayActiveWorkspace();
|
||||
}
|
||||
|
||||
function onFocusedWorkspaceIndexChanged() {
|
||||
root.currentWorkspace = root.getDisplayActiveWorkspace()
|
||||
root.currentWorkspace = root.getDisplayActiveWorkspace();
|
||||
}
|
||||
|
||||
function onNiriAvailableChanged() {
|
||||
if (NiriWorkspaceService.niriAvailable) {
|
||||
root.workspaceList = root.getDisplayWorkspaces()
|
||||
root.currentWorkspace = root.getDisplayActiveWorkspace()
|
||||
root.workspaceList = root.getDisplayWorkspaces();
|
||||
root.currentWorkspace = root.getDisplayActiveWorkspace();
|
||||
}
|
||||
}
|
||||
|
||||
target: NiriWorkspaceService
|
||||
}
|
||||
|
||||
Row {
|
||||
id: workspaceRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
@@ -89,15 +86,25 @@ Rectangle {
|
||||
width: isActive ? Theme.spacingXL + Theme.spacingM : Theme.spacingL + Theme.spacingXS
|
||||
height: Theme.spacingM
|
||||
radius: height / 2
|
||||
color: isActive ? Theme.primary :
|
||||
isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
||||
color: isActive ? Theme.primary : isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", sequentialNumber.toString()]);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
@@ -105,19 +112,13 @@ Rectangle {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", sequentialNumber.toString()])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -14,6 +14,16 @@ PanelWindow {
|
||||
property string wifiPasswordInput: ""
|
||||
|
||||
visible: wifiPasswordDialogVisible
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: wifiPasswordDialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
passwordInput.forceActiveFocus();
|
||||
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
@@ -21,37 +31,27 @@ PanelWindow {
|
||||
bottom: true
|
||||
}
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: wifiPasswordDialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
|
||||
color: "transparent"
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
passwordInput.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.5)
|
||||
opacity: wifiPasswordDialogVisible ? 1.0 : 0.0
|
||||
opacity: wifiPasswordDialogVisible ? 1 : 0
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
wifiPasswordDialogVisible = false;
|
||||
wifiPasswordInput = "";
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
wifiPasswordDialogVisible = false
|
||||
wifiPasswordInput = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -62,23 +62,8 @@ PanelWindow {
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 1
|
||||
|
||||
opacity: wifiPasswordDialogVisible ? 1.0 : 0.0
|
||||
scale: wifiPasswordDialogVisible ? 1.0 : 0.9
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
opacity: wifiPasswordDialogVisible ? 1 : 0
|
||||
scale: wifiPasswordDialogVisible ? 1 : 0.9
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
@@ -107,6 +92,7 @@ PanelWindow {
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -125,15 +111,18 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: closeDialogArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
wifiPasswordDialogVisible = false
|
||||
wifiPasswordInput = ""
|
||||
wifiPasswordDialogVisible = false;
|
||||
wifiPasswordInput = "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Password input
|
||||
@@ -147,6 +136,7 @@ PanelWindow {
|
||||
|
||||
TextInput {
|
||||
id: passwordInput
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -155,6 +145,17 @@ PanelWindow {
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
cursorVisible: activeFocus
|
||||
selectByMouse: true
|
||||
onTextChanged: {
|
||||
wifiPasswordInput = text;
|
||||
}
|
||||
onAccepted: {
|
||||
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput);
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (wifiPasswordDialogVisible)
|
||||
forceActiveFocus();
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
@@ -165,28 +166,16 @@ PanelWindow {
|
||||
visible: parent.text.length === 0
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
wifiPasswordInput = text
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (wifiPasswordDialogVisible) {
|
||||
forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.IBeamCursor
|
||||
onClicked: {
|
||||
passwordInput.forceActiveFocus()
|
||||
passwordInput.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Show password checkbox
|
||||
@@ -195,6 +184,7 @@ PanelWindow {
|
||||
|
||||
Rectangle {
|
||||
id: showPasswordCheckbox
|
||||
|
||||
property bool checked: false
|
||||
|
||||
width: 20
|
||||
@@ -218,9 +208,10 @@ PanelWindow {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
showPasswordCheckbox.checked = !showPasswordCheckbox.checked
|
||||
showPasswordCheckbox.checked = !showPasswordCheckbox.checked;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -229,6 +220,7 @@ PanelWindow {
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Buttons
|
||||
@@ -251,6 +243,7 @@ PanelWindow {
|
||||
|
||||
Text {
|
||||
id: cancelText
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "Cancel"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -260,14 +253,16 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: cancelArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
wifiPasswordDialogVisible = false
|
||||
wifiPasswordInput = ""
|
||||
wifiPasswordDialogVisible = false;
|
||||
wifiPasswordInput = "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -276,10 +271,11 @@ PanelWindow {
|
||||
radius: Theme.cornerRadius
|
||||
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||
enabled: wifiPasswordInput.length > 0
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
opacity: enabled ? 1 : 0.5
|
||||
|
||||
Text {
|
||||
id: connectText
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "Connect"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -289,12 +285,13 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
id: connectArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: parent.enabled
|
||||
onClicked: {
|
||||
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput)
|
||||
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,10 +300,33 @@ PanelWindow {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
10
shell.qml
10
shell.qml
@@ -12,36 +12,46 @@ ShellRoot {
|
||||
// Multi-monitor support using Variants
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
delegate: TopBar {
|
||||
modelData: item
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Global popup windows
|
||||
CenterCommandCenter {
|
||||
id: centerCommandCenter
|
||||
}
|
||||
|
||||
TrayMenuPopup {
|
||||
id: trayMenuPopup
|
||||
}
|
||||
|
||||
NotificationCenter {
|
||||
id: notificationCenter
|
||||
}
|
||||
|
||||
ControlCenterPopup {
|
||||
id: controlCenterPopup
|
||||
}
|
||||
|
||||
WifiPasswordDialog {
|
||||
id: wifiPasswordDialog
|
||||
}
|
||||
|
||||
InputDialog {
|
||||
id: globalInputDialog
|
||||
}
|
||||
|
||||
BatteryControlPopup {
|
||||
id: batteryControlPopup
|
||||
}
|
||||
|
||||
PowerMenuPopup {
|
||||
id: powerMenuPopup
|
||||
}
|
||||
|
||||
PowerConfirmDialog {
|
||||
id: powerConfirmDialog
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user