1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-29 16:02:51 -05:00

Initial qmlformat

This commit is contained in:
bbedward
2025-07-17 17:58:56 -04:00
parent cbb42df3a9
commit 023b6bc0d1
62 changed files with 7805 additions and 6606 deletions

View File

@@ -25,6 +25,10 @@ quickshell -p shell.qml
# Or use the shorthand # Or use the shorthand
qs -p . 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 ## Architecture Overview
@@ -119,13 +123,19 @@ shell.qml # Main entry point (minimal orchestration)
- `id` should be the first property - `id` should be the first property
- Properties before signal handlers before child components - Properties before signal handlers before child components
- Prefer property bindings over imperative code - 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**: 2. **Naming Conventions**:
- **Services**: Use `Singleton` type with `id: root` - **Services**: Use `Singleton` type with `id: root`
- **Components**: Use descriptive names (e.g., `CustomSlider`, `TopBar`) - **Components**: Use descriptive names (e.g., `CustomSlider`, `TopBar`)
- **Properties**: camelCase for properties, PascalCase for types - **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 ```qml
// For regular components // For regular components
Item { Item {
@@ -230,11 +240,12 @@ The shell uses Quickshell's `Variants` pattern for multi-monitor support:
When modifying the shell: When modifying the shell:
1. **Test changes**: `qs -p .` (automatic reload on file changes) 1. **Test changes**: `qs -p .` (automatic reload on file changes)
2. **Performance**: Ensure animations remain smooth (60 FPS target) 2. **Code quality**: Run `qmlformat -i **/*.qml` and `qmllint **/*.qml` to ensure proper formatting and syntax
3. **Theming**: Use `Theme.propertyName` for Material Design 3 consistency 3. **Performance**: Ensure animations remain smooth (60 FPS target)
4. **Wayland compatibility**: Test on Wayland session 4. **Theming**: Use `Theme.propertyName` for Material Design 3 consistency
5. **Multi-monitor**: Verify behavior with multiple displays 5. **Wayland compatibility**: Test on Wayland session
6. **Feature detection**: Test on systems with/without required tools 6. **Multi-monitor**: Verify behavior with multiple displays
7. **Feature detection**: Test on systems with/without required tools
### Adding New Widgets ### Adding New Widgets

View File

@@ -1,180 +1,181 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound
import Qt.labs.platform
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Qt.labs.platform
import qs.Services import qs.Services
Singleton { Singleton {
// This dependency forces re-evaluation when colorUpdateTrigger changes
// Just check if matugen is available
id: root id: root
/* ──────────────── basic state ──────────────── */
signal colorsUpdated()
readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation) readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
readonly property string homeDir: _homeUrl.startsWith("file://") readonly property string homeDir: _homeUrl.startsWith("file://") ? _homeUrl.substring(7) : _homeUrl
? _homeUrl.substring(7)
: _homeUrl
readonly property string wallpaperPath: homeDir + "/quickshell/current_wallpaper" readonly property string wallpaperPath: homeDir + "/quickshell/current_wallpaper"
readonly property string notifyPath: homeDir + "/quickshell/wallpaper_changed" readonly property string notifyPath: homeDir + "/quickshell/wallpaper_changed"
property bool matugenAvailable: false
property string matugenJson: ""
property var matugenColors: ({
})
property bool extractionRequested: false
property int colorUpdateTrigger: 0 // Force property re-evaluation
// ──────────────── wallpaper change monitor ────────────────
property string lastWallpaperTimestamp: ""
// ──────────────── 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
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
property color surfaceText: getMatugenColor("on_background", "#e3e8ef")
property color primaryText: getMatugenColor("on_primary", "#ffffff")
property color surfaceVariantText: getMatugenColor("on_surface_variant", "#c4c7c5")
// containers & misc
property color primaryContainer: getMatugenColor("primary_container", "#1976d2")
property color surfaceTint: getMatugenColor("surface_tint", "#8ab4f8")
property color outline: getMatugenColor("outline", "#8e918f")
// legacy aliases
property color accentHi: primary
property color accentLo: secondary
property bool matugenAvailable: false // ──────────────── basic state ────────────────
property string matugenJson: "" signal colorsUpdated()
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() { function onLightModeChanged() {
// Force color properties to update when light mode changes // Force color properties to update when light mode changes
if (matugenColors && Object.keys(matugenColors).length > 0) { if (matugenColors && Object.keys(matugenColors).length > 0) {
console.log("Light mode changed - updating dynamic colors") console.log("Light mode changed - updating dynamic colors");
colorUpdateTrigger++ // This will trigger re-evaluation of all color properties colorUpdateTrigger++; // This will trigger re-evaluation of all color properties
colorsUpdated() colorsUpdated();
} }
} }
/* ──────────────── availability checks ──────────────── */ // ──────────────── public helper ────────────────
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 ──────────────── */
property string lastWallpaperTimestamp: ""
/* ──────────────── public helper ──────────────── */
function extractColors() { function extractColors() {
console.log("Colors.extractColors() called, matugenAvailable:", matugenAvailable) console.log("Colors.extractColors() called, matugenAvailable:", matugenAvailable);
extractionRequested = true extractionRequested = true;
if (matugenAvailable) if (matugenAvailable)
fileChecker.running = true fileChecker.running = true;
else else
matugenCheck.running = true matugenCheck.running = true;
} }
function getMatugenColor(path, fallback) { function getMatugenColor(path, fallback) {
// Include colorUpdateTrigger in the function to make properties reactive to changes // Include colorUpdateTrigger in the function to make properties reactive to changes
colorUpdateTrigger // This dependency forces re-evaluation when colorUpdateTrigger changes colorUpdateTrigger;
// Use light or dark colors based on Theme.isLightMode // Use light or dark colors based on Theme.isLightMode
const colorMode = (typeof Theme !== "undefined" && Theme.isLightMode) ? "light" : "dark" const colorMode = (typeof Theme !== "undefined" && Theme.isLightMode) ? "light" : "dark";
let cur = matugenColors?.colors?.[colorMode] let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode];
for (const part of path.split(".")) { for (const part of path.split(".")) {
if (!cur || typeof cur !== "object" || !(part in cur)) if (!cur || typeof cur !== "object" || !(part in cur))
return fallback return fallback;
cur = cur[part]
cur = cur[part];
} }
return cur || fallback return cur || fallback;
} }
/* ──────────────── 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 */
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 */
property color surfaceText: getMatugenColor("on_background", "#e3e8ef")
property color primaryText: getMatugenColor("on_primary", "#ffffff")
property color surfaceVariantText: getMatugenColor("on_surface_variant", "#c4c7c5")
/* containers & misc */
property color primaryContainer: getMatugenColor("primary_container", "#1976d2")
property color surfaceTint: getMatugenColor("surface_tint", "#8ab4f8")
property color outline: getMatugenColor("outline", "#8e918f")
/* legacy aliases */
property color accentHi: primary
property color accentLo: secondary
function isColorDark(c) { function isColorDark(c) {
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5 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
}
}
} }

View File

@@ -1,321 +1,314 @@
pragma Singleton pragma Singleton
import QtQuick pragma ComponentBehavior: Bound
import QtCore import QtCore
import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
Singleton { Singleton {
// "auto", "wifi", "ethernet"
// Alphabetical tiebreaker
id: root id: root
property int themeIndex: 0 property int themeIndex: 0
property bool themeIsDynamic: false property bool themeIsDynamic: false
property bool isLightMode: false property bool isLightMode: false
property real topBarTransparency: 0.75 property real topBarTransparency: 0.75
property real popupTransparency: 0.92 property real popupTransparency: 0.92
property var recentlyUsedApps: [] property var recentlyUsedApps: []
// New global preferences // New global preferences
property bool use24HourClock: true property bool use24HourClock: true
property bool useFahrenheit: false property bool useFahrenheit: false
property bool nightModeEnabled: false property bool nightModeEnabled: false
property string profileImage: "" property string profileImage: ""
property string weatherLocationOverride: "New York, NY" property string weatherLocationOverride: "New York, NY"
// Widget visibility preferences for TopBar // Widget visibility preferences for TopBar
property bool showFocusedWindow: true property bool showFocusedWindow: true
property bool showWeather: true property bool showWeather: true
property bool showMusic: true property bool showMusic: true
property bool showClipboard: true property bool showClipboard: true
property bool showSystemResources: true property bool showSystemResources: true
property bool showSystemTray: true property bool showSystemTray: true
// View mode preferences for launchers // View mode preferences for launchers
property string appLauncherViewMode: "list" property string appLauncherViewMode: "list"
property string spotlightLauncherViewMode: "list" property string spotlightLauncherViewMode: "list"
// Network preference // Network preference
property string networkPreference: "auto" // "auto", "wifi", "ethernet" property string networkPreference: "auto"
function loadSettings() {
Component.onCompleted: loadSettings() parseSettings(settingsFile.text());
}
// Monitor system resources preference changes to control service monitoring
onShowSystemResourcesChanged: { function parseSettings(content) {
console.log("Prefs: System resources monitoring", showSystemResources ? "enabled" : "disabled") try {
// Control SystemMonitorService based on whether system monitor widgets are visible if (content && content.trim()) {
if (typeof SystemMonitorService !== 'undefined') { var settings = JSON.parse(content);
SystemMonitorService.enableTopBarMonitoring(showSystemResources) 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();
}
} catch (e) {
console.log("Could not parse settings, using defaults:", e);
applyStoredTheme();
} }
} }
function saveSettings() {
settingsFile.setText(JSON.stringify({
"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);
if (typeof Theme !== "undefined") {
Theme.isLightMode = isLightMode;
Theme.switchTheme(themeIndex, themeIsDynamic, false);
} else {
Qt.callLater(() => {
if (typeof Theme !== "undefined") {
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();
}
function setLightMode(lightMode) {
console.log("Prefs setLightMode called - isLightMode:", lightMode);
isLightMode = lightMode;
saveSettings();
}
function setTopBarTransparency(transparency) {
console.log("Prefs setTopBarTransparency called - topBarTransparency:", transparency);
topBarTransparency = transparency;
saveSettings();
}
function setPopupTransparency(transparency) {
console.log("Prefs setPopupTransparency called - popupTransparency:", transparency);
popupTransparency = transparency;
saveSettings();
}
function addRecentApp(app) {
if (!app)
return ;
var execProp = app.execString || app.exec || "";
if (!execProp)
return ;
var existingIndex = -1;
for (var i = 0; i < recentlyUsedApps.length; i++) {
if (recentlyUsedApps[i].exec === execProp) {
existingIndex = i;
break;
}
}
if (existingIndex >= 0) {
// App exists, increment usage count
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()
};
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);
});
// Limit to 10 apps
if (sortedApps.length > 10)
sortedApps = sortedApps.slice(0, 10);
// Reassign to trigger property change signal
recentlyUsedApps = sortedApps;
saveSettings();
}
function getRecentApps() {
return recentlyUsedApps;
}
// New preference setters
function setClockFormat(use24Hour) {
console.log("Prefs setClockFormat called - use24HourClock:", use24Hour);
use24HourClock = use24Hour;
saveSettings();
}
function setTemperatureUnit(fahrenheit) {
console.log("Prefs setTemperatureUnit called - useFahrenheit:", fahrenheit);
useFahrenheit = fahrenheit;
saveSettings();
}
function setNightModeEnabled(enabled) {
console.log("Prefs setNightModeEnabled called - nightModeEnabled:", enabled);
nightModeEnabled = enabled;
saveSettings();
}
function setProfileImage(imageUrl) {
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();
}
function setShowWeather(enabled) {
console.log("Prefs setShowWeather called - showWeather:", enabled);
showWeather = enabled;
saveSettings();
}
function setShowMusic(enabled) {
console.log("Prefs setShowMusic called - showMusic:", enabled);
showMusic = enabled;
saveSettings();
}
function setShowClipboard(enabled) {
console.log("Prefs setShowClipboard called - showClipboard:", enabled);
showClipboard = enabled;
saveSettings();
}
function setShowSystemResources(enabled) {
console.log("Prefs setShowSystemResources called - showSystemResources:", enabled);
showSystemResources = enabled;
saveSettings();
}
function setShowSystemTray(enabled) {
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();
}
function setSpotlightLauncherViewMode(mode) {
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();
}
// Network preference setter
function setNetworkPreference(preference) {
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 { FileView {
id: settingsFile id: settingsFile
path: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json" path: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
blockLoading: true blockLoading: true
blockWrites: true blockWrites: true
watchChanges: true watchChanges: true
onLoaded: { onLoaded: {
console.log("Settings file loaded successfully") console.log("Settings file loaded successfully");
parseSettings(settingsFile.text()) parseSettings(settingsFile.text());
} }
onLoadFailed: (error) => { onLoadFailed: (error) => {
console.log("Settings file not found, using defaults. Error:", error) console.log("Settings file not found, using defaults. Error:", error);
applyStoredTheme() applyStoredTheme();
} }
} }
function loadSettings() { }
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()
} else {
console.log("Settings file is empty - applying default theme")
applyStoredTheme()
}
} catch (e) {
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)
}
function applyStoredTheme() {
console.log("Applying stored theme:", themeIndex, themeIsDynamic, "lightMode:", isLightMode)
if (typeof Theme !== "undefined") {
Theme.isLightMode = isLightMode
Theme.switchTheme(themeIndex, themeIsDynamic, false)
} else {
Qt.callLater(() => {
if (typeof Theme !== "undefined") {
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()
}
function setLightMode(lightMode) {
console.log("Prefs setLightMode called - isLightMode:", lightMode)
isLightMode = lightMode
saveSettings()
}
function setTopBarTransparency(transparency) {
console.log("Prefs setTopBarTransparency called - topBarTransparency:", transparency)
topBarTransparency = transparency
saveSettings()
}
function setPopupTransparency(transparency) {
console.log("Prefs setPopupTransparency called - popupTransparency:", transparency)
popupTransparency = transparency
saveSettings()
}
function addRecentApp(app) {
if (!app) return
var execProp = app.execString || app.exec || ""
if (!execProp) return
var existingIndex = -1
for (var i = 0; i < recentlyUsedApps.length; i++) {
if (recentlyUsedApps[i].exec === execProp) {
existingIndex = i
break
}
}
if (existingIndex >= 0) {
// App exists, increment usage count
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()
}
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
})
// Limit to 10 apps
if (sortedApps.length > 10) {
sortedApps = sortedApps.slice(0, 10)
}
// Reassign to trigger property change signal
recentlyUsedApps = sortedApps
saveSettings()
}
function getRecentApps() {
return recentlyUsedApps
}
// New preference setters
function setClockFormat(use24Hour) {
console.log("Prefs setClockFormat called - use24HourClock:", use24Hour)
use24HourClock = use24Hour
saveSettings()
}
function setTemperatureUnit(fahrenheit) {
console.log("Prefs setTemperatureUnit called - useFahrenheit:", fahrenheit)
useFahrenheit = fahrenheit
saveSettings()
}
function setNightModeEnabled(enabled) {
console.log("Prefs setNightModeEnabled called - nightModeEnabled:", enabled)
nightModeEnabled = enabled
saveSettings()
}
function setProfileImage(imageUrl) {
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()
}
function setShowWeather(enabled) {
console.log("Prefs setShowWeather called - showWeather:", enabled)
showWeather = enabled
saveSettings()
}
function setShowMusic(enabled) {
console.log("Prefs setShowMusic called - showMusic:", enabled)
showMusic = enabled
saveSettings()
}
function setShowClipboard(enabled) {
console.log("Prefs setShowClipboard called - showClipboard:", enabled)
showClipboard = enabled
saveSettings()
}
function setShowSystemResources(enabled) {
console.log("Prefs setShowSystemResources called - showSystemResources:", enabled)
showSystemResources = enabled
saveSettings()
}
function setShowSystemTray(enabled) {
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()
}
function setSpotlightLauncherViewMode(mode) {
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()
}
// Network preference setter
function setNetworkPreference(preference) {
console.log("Prefs setNetworkPreference called - networkPreference:", preference)
networkPreference = preference
saveSettings()
}
}

View File

@@ -1,5 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io

View File

@@ -1,5 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io

View File

@@ -1,5 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire

View File

@@ -1,5 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Services.UPower import Quickshell.Services.UPower

View File

@@ -1,5 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Bluetooth import Quickshell.Bluetooth

View File

@@ -1,5 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io

View File

@@ -1,5 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io

View File

@@ -1,5 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io

View File

@@ -1,5 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell

View File

@@ -1,39 +1,20 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
import Quickshell.Widgets import Quickshell.Widgets
pragma Singleton
pragma ComponentBehavior: Bound
Singleton { Singleton {
id: root id: root
readonly property list<MprisPlayer> availablePlayers: Mpris.players.values readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
property MprisPlayer activePlayer: null property MprisPlayer activePlayer: availablePlayers.find(p => p.isPlaying)
property MprisPlayer _candidatePlayer: availablePlayers.find(p => p.isPlaying)
?? availablePlayers.find(p => p.canControl && p.canPlay) ?? availablePlayers.find(p => p.canControl && p.canPlay)
?? null ?? 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 { IpcHandler {
target: "mpris" target: "mpris"

View File

@@ -1,9 +1,10 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.Common import qs.Common
pragma Singleton
pragma ComponentBehavior: Bound
Singleton { Singleton {
id: root id: root

View File

@@ -1,5 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io

View File

@@ -1,7 +1,7 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import
QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io

View File

@@ -1,8 +1,9 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
pragma Singleton
pragma ComponentBehavior: Bound
Singleton { Singleton {
id: root id: root

View File

@@ -1,9 +1,10 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.Common import qs.Common
pragma Singleton
pragma ComponentBehavior: Bound
Singleton { Singleton {
id: root id: root

View File

@@ -13,7 +13,7 @@ Singleton {
property var wifiNetworks: [] property var wifiNetworks: []
property var savedWifiNetworks: [] property var savedWifiNetworks: []
property bool isScanning: false property bool isScanning: false
property string connectionStatus: "" // "connecting", "connected", "failed", "" property string connectionStatus: "" // "cosnnecting", "connected", "failed", ""
property string connectingSSID: "" property string connectingSSID: ""
Process { Process {

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +1,59 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Services.UPower
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
import Quickshell.Services.UPower
PanelWindow { PanelWindow {
id: root id: root
property bool batteryPopupVisible: false property bool batteryPopupVisible: false
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 visible: batteryPopupVisible
implicitWidth: 400 implicitWidth: 400
implicitHeight: 300 implicitHeight: 300
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent" color: "transparent"
anchors { anchors {
top: true top: true
left: true left: true
right: true right: true
bottom: true bottom: true
} }
// Click outside to dismiss overlay // Click outside to dismiss overlay
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
batteryPopupVisible = false batteryPopupVisible = false;
} }
} }
Rectangle { Rectangle {
width: Math.min(380, parent.width - Theme.spacingL * 2) width: Math.min(380, parent.width - Theme.spacingL * 2)
height: Math.min(450, parent.height - Theme.barHeight - Theme.spacingS * 2) height: Math.min(450, parent.height - Theme.barHeight - Theme.spacingS * 2)
@@ -47,45 +63,31 @@ PanelWindow {
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
opacity: batteryPopupVisible ? 1 : 0
opacity: batteryPopupVisible ? 1.0 : 0.0 scale: batteryPopupVisible ? 1 : 0.85
scale: batteryPopupVisible ? 1.0 : 0.85
// Prevent click-through to background // Prevent click-through to background
MouseArea { MouseArea {
// Consume the click to prevent it from reaching the background
anchors.fill: parent anchors.fill: parent
onClicked: { 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
}
}
ScrollView { ScrollView {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
clip: true clip: true
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingL spacing: Theme.spacingL
// Header // Header
Row { Row {
width: parent.width width: parent.width
Text { Text {
text: BatteryService.batteryAvailable ? "Battery Information" : "Power Management" text: BatteryService.batteryAvailable ? "Battery Information" : "Power Management"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
@@ -93,15 +95,18 @@ PanelWindow {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Item { width: parent.width - 200; height: 1 } Item {
width: parent.width - 200
height: 1
}
Rectangle { Rectangle {
width: 32 width: 32
height: 32 height: 32
radius: 16 radius: 16
color: closeBatteryArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" color: closeBatteryArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "close" text: "close"
@@ -109,19 +114,22 @@ PanelWindow {
font.pixelSize: Theme.iconSize - 4 font.pixelSize: Theme.iconSize - 4
color: closeBatteryArea.containsMouse ? Theme.error : Theme.surfaceText color: closeBatteryArea.containsMouse ? Theme.error : Theme.surfaceText
} }
MouseArea { MouseArea {
id: closeBatteryArea id: closeBatteryArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
batteryPopupVisible = false batteryPopupVisible = false;
} }
} }
} }
} }
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 80 height: 80
@@ -130,72 +138,88 @@ PanelWindow {
border.color: BatteryService.isCharging ? Theme.primary : (BatteryService.isLowBattery ? Theme.error : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)) border.color: BatteryService.isCharging ? Theme.primary : (BatteryService.isLowBattery ? Theme.error : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12))
border.width: BatteryService.isCharging || BatteryService.isLowBattery ? 2 : 1 border.width: BatteryService.isCharging || BatteryService.isLowBattery ? 2 : 1
visible: BatteryService.batteryAvailable visible: BatteryService.batteryAvailable
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingL anchors.leftMargin: Theme.spacingL
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingL spacing: Theme.spacingL
Text { Text {
text: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable) text: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSizeLarge font.pixelSize: Theme.iconSizeLarge
color: { color: {
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error if (BatteryService.isLowBattery && !BatteryService.isCharging)
if (BatteryService.isCharging) return Theme.primary return Theme.error;
return Theme.surfaceText
if (BatteryService.isCharging)
return Theme.primary;
return Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
spacing: 2 spacing: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Row { Row {
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: BatteryService.batteryLevel + "%" text: BatteryService.batteryLevel + "%"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: { color: {
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error if (BatteryService.isLowBattery && !BatteryService.isCharging)
if (BatteryService.isCharging) return Theme.primary return Theme.error;
return Theme.surfaceText
if (BatteryService.isCharging)
return Theme.primary;
return Theme.surfaceText;
} }
font.weight: Font.Bold font.weight: Font.Bold
} }
Text { Text {
text: BatteryService.batteryStatus text: BatteryService.batteryStatus
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: { color: {
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error if (BatteryService.isLowBattery && !BatteryService.isCharging)
if (BatteryService.isCharging) return Theme.primary return Theme.error;
return Theme.surfaceText
if (BatteryService.isCharging)
return Theme.primary;
return Theme.surfaceText;
} }
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
Text { Text {
text: { text: {
let time = BatteryService.formatTimeRemaining() let time = BatteryService.formatTimeRemaining();
if (time !== "Unknown") { if (time !== "Unknown")
return BatteryService.isCharging ? "Time until full: " + time : "Time remaining: " + time return BatteryService.isCharging ? "Time until full: " + time : "Time remaining: " + time;
}
return "" return "";
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: text.length > 0 visible: text.length > 0
} }
} }
} }
} }
// No battery info card // No battery info card
Rectangle { Rectangle {
width: parent.width width: parent.width
@@ -205,11 +229,11 @@ PanelWindow {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1 border.width: 1
visible: !BatteryService.batteryAvailable visible: !BatteryService.batteryAvailable
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingL spacing: Theme.spacingL
Text { Text {
text: Theme.getBatteryIcon(0, false, false) text: Theme.getBatteryIcon(0, false, false)
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -217,125 +241,131 @@ PanelWindow {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
spacing: Theme.spacingS spacing: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: "No Battery Detected" text: "No Battery Detected"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { Text {
text: "Power profile management is available" text: "Power profile management is available"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
} }
} }
} }
} }
// Battery details // Battery details
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: BatteryService.batteryAvailable visible: BatteryService.batteryAvailable
Text { Text {
text: "Battery Details" text: "Battery Details"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingXL spacing: Theme.spacingXL
// Health // Health
Column { Column {
spacing: 2 spacing: 2
width: (parent.width - Theme.spacingXL) / 2 width: (parent.width - Theme.spacingXL) / 2
Text { Text {
text: "Health" text: "Health"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { Text {
text: BatteryService.batteryHealth text: BatteryService.batteryHealth
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: { color: {
if (BatteryService.batteryHealth === "N/A") return Theme.surfaceText if (BatteryService.batteryHealth === "N/A")
var healthNum = parseInt(BatteryService.batteryHealth) return Theme.surfaceText;
return healthNum < 80 ? Theme.error : Theme.surfaceText
var healthNum = parseInt(BatteryService.batteryHealth);
return healthNum < 80 ? Theme.error : Theme.surfaceText;
} }
} }
} }
// Capacity // Capacity
Column { Column {
spacing: 2 spacing: 2
width: (parent.width - Theme.spacingXL) / 2 width: (parent.width - Theme.spacingXL) / 2
Text { Text {
text: "Capacity" text: "Capacity"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { Text {
text: BatteryService.batteryCapacity > 0 ? BatteryService.batteryCapacity.toFixed(1) + " Wh" : "Unknown" text: BatteryService.batteryCapacity > 0 ? BatteryService.batteryCapacity.toFixed(1) + " Wh" : "Unknown"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
} }
} }
} }
} }
// Power profiles // Power profiles
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: true visible: true
Text { Text {
text: "Power Profile" text: "Power Profile"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Repeater { Repeater {
model: (typeof PowerProfiles !== "undefined") ? model: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
[PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) :
[PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: profileArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.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))
(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.color: batteryControlPopup.isActiveProfile(modelData) ? Theme.primary : "transparent"
border.width: 2 border.width: 2
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingL anchors.leftMargin: Theme.spacingL
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: Theme.getPowerProfileIcon(PowerProfile.toString(modelData)) text: Theme.getPowerProfileIcon(PowerProfile.toString(modelData))
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -343,41 +373,47 @@ PanelWindow {
color: batteryControlPopup.isActiveProfile(modelData) ? Theme.primary : Theme.surfaceText color: batteryControlPopup.isActiveProfile(modelData) ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
spacing: 2 spacing: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: Theme.getPowerProfileLabel(PowerProfile.toString(modelData)) text: Theme.getPowerProfileLabel(PowerProfile.toString(modelData))
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: batteryControlPopup.isActiveProfile(modelData) ? Theme.primary : Theme.surfaceText color: batteryControlPopup.isActiveProfile(modelData) ? Theme.primary : Theme.surfaceText
font.weight: batteryControlPopup.isActiveProfile(modelData) ? Font.Medium : Font.Normal font.weight: batteryControlPopup.isActiveProfile(modelData) ? Font.Medium : Font.Normal
} }
Text { Text {
text: Theme.getPowerProfileDescription(PowerProfile.toString(modelData)) text: Theme.getPowerProfileDescription(PowerProfile.toString(modelData))
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
} }
} }
} }
MouseArea { MouseArea {
id: profileArea id: profileArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
batteryControlPopup.setProfile(modelData) batteryControlPopup.setProfile(modelData);
} }
} }
} }
} }
} }
} }
// Degradation reason warning // Degradation reason warning
Rectangle { Rectangle {
width: parent.width width: parent.width
@@ -387,13 +423,13 @@ PanelWindow {
border.color: Theme.error border.color: Theme.error
border.width: 2 border.width: 2
visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingL anchors.leftMargin: Theme.spacingL
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "warning" text: "warning"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -401,33 +437,61 @@ PanelWindow {
color: Theme.error color: Theme.error
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
spacing: 2 spacing: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: "Power Profile Degradation" text: "Power Profile Degradation"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.error color: Theme.error
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { Text {
text: (typeof PowerProfiles !== "undefined") ? PerformanceDegradationReason.toString(PowerProfiles.degradationReason) : "" text: (typeof PowerProfiles !== "undefined") ? PerformanceDegradationReason.toString(PowerProfiles.degradationReason) : ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.8) 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 // Error toast
Rectangle { Rectangle {
id: errorToast id: errorToast
function show() {
visible = true;
hideTimer.restart();
}
width: Math.min(300, parent.width - Theme.spacingL * 2) width: Math.min(300, parent.width - Theme.spacingL * 2)
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
@@ -437,7 +501,7 @@ PanelWindow {
anchors.topMargin: Theme.spacingL anchors.topMargin: Theme.spacingL
visible: false visible: false
z: 1000 z: 1000
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "power-profiles-daemon not available" text: "power-profiles-daemon not available"
@@ -445,37 +509,14 @@ PanelWindow {
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium font.weight: Font.Medium
} }
Timer { Timer {
id: hideTimer id: hideTimer
interval: 3000 interval: 3000
onTriggered: errorToast.visible = false 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))
}
} }
} }

View File

@@ -1,76 +1,100 @@
import QtQuick import QtQuick
import Quickshell.Services.UPower
import qs.Common import qs.Common
import qs.Services import qs.Services
import Quickshell.Services.UPower
Rectangle { Rectangle {
id: batteryWidget id: batteryWidget
property bool batteryPopupVisible: false property bool batteryPopupVisible: false
signal toggleBatteryPopup() signal toggleBatteryPopup()
width: BatteryService.batteryAvailable ? 70 : 40 width: BatteryService.batteryAvailable ? 70 : 40
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: batteryArea.containsMouse || batteryPopupVisible ? 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)
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 visible: true
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 4 spacing: 4
Text { Text {
text: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable) text: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 6 font.pixelSize: Theme.iconSize - 6
color: { color: {
if (!BatteryService.batteryAvailable) return Theme.surfaceText if (!BatteryService.batteryAvailable)
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error return Theme.surfaceText;
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 anchors.verticalCenter: parent.verticalCenter
SequentialAnimation on opacity { SequentialAnimation on opacity {
running: BatteryService.isCharging running: BatteryService.isCharging
loops: Animation.Infinite 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 { Text {
text: BatteryService.batteryLevel + "%" text: BatteryService.batteryLevel + "%"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: { color: {
if (!BatteryService.batteryAvailable) return Theme.surfaceText if (!BatteryService.batteryAvailable)
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error return Theme.surfaceText;
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 anchors.verticalCenter: parent.verticalCenter
visible: BatteryService.batteryAvailable visible: BatteryService.batteryAvailable
} }
} }
MouseArea { MouseArea {
id: batteryArea id: batteryArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
toggleBatteryPopup() toggleBatteryPopup();
} }
} }
// Tooltip on hover // Tooltip on hover
Rectangle { Rectangle {
id: batteryTooltip id: batteryTooltip
width: Math.max(120, tooltipText.contentWidth + Theme.spacingM * 2) width: Math.max(120, tooltipText.contentWidth + Theme.spacingM * 2)
height: tooltipText.contentHeight + Theme.spacingS * 2 height: tooltipText.contentHeight + Theme.spacingS * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
@@ -78,57 +102,63 @@ Rectangle {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1 border.width: 1
visible: batteryArea.containsMouse && !batteryPopupVisible visible: batteryArea.containsMouse && !batteryPopupVisible
anchors.bottom: parent.top anchors.bottom: parent.top
anchors.bottomMargin: Theme.spacingS anchors.bottomMargin: Theme.spacingS
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
opacity: batteryArea.containsMouse ? 1 : 0
opacity: batteryArea.containsMouse ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 2 spacing: 2
Text { Text {
id: tooltipText id: tooltipText
text: { text: {
if (!BatteryService.batteryAvailable) { if (!BatteryService.batteryAvailable) {
if (typeof PowerProfiles === "undefined") return "Power Management" if (typeof PowerProfiles === "undefined")
switch(PowerProfiles.profile) { return "Power Management";
case PowerProfile.PowerSaver: return "Power Profile: Power Saver"
case PowerProfile.Performance: return "Power Profile: Performance" switch (PowerProfiles.profile) {
default: return "Power Profile: Balanced" 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 status = BatteryService.batteryStatus let level = BatteryService.batteryLevel + "%";
let level = BatteryService.batteryLevel + "%" let time = BatteryService.formatTimeRemaining();
let time = BatteryService.formatTimeRemaining() if (time !== "Unknown")
return status + " • " + level + " • " + time;
if (time !== "Unknown") { else
return status + " • " + level + " • " + time return status + " • " + level;
} else {
return status + " • " + level
}
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
} }
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
}
}

View File

@@ -6,63 +6,61 @@ import qs.Services
Column { Column {
id: calendarWidget id: calendarWidget
property date displayDate: new Date() property date displayDate: new Date()
property date selectedDate: new Date() property date selectedDate: new Date()
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 spacing: Theme.spacingM
// Load events when display date changes // Load events when display date changes
onDisplayDateChanged: { 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 // Load events when calendar service becomes available
Connections { Connections {
function onKhalAvailableChanged() {
if (CalendarService && CalendarService.khalAvailable)
loadEventsForMonth();
}
target: CalendarService target: CalendarService
enabled: CalendarService !== null 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 // Month navigation header
Row { Row {
width: parent.width width: parent.width
height: 40 height: 40
Rectangle { Rectangle {
width: 40 width: 40
height: 40 height: 40
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: prevMonthArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: prevMonthArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "chevron_left" text: "chevron_left"
@@ -71,21 +69,22 @@ Column {
color: Theme.primary color: Theme.primary
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
} }
MouseArea { MouseArea {
id: prevMonthArea id: prevMonthArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
let newDate = new Date(displayDate) let newDate = new Date(displayDate);
newDate.setMonth(newDate.getMonth() - 1) newDate.setMonth(newDate.getMonth() - 1);
displayDate = newDate displayDate = newDate;
} }
} }
} }
Text { Text {
width: parent.width - 80 width: parent.width - 80
height: 40 height: 40
@@ -96,13 +95,13 @@ Column {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
Rectangle { Rectangle {
width: 40 width: 40
height: 40 height: 40
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: nextMonthArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: nextMonthArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "chevron_right" text: "chevron_right"
@@ -111,35 +110,37 @@ Column {
color: Theme.primary color: Theme.primary
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
} }
MouseArea { MouseArea {
id: nextMonthArea id: nextMonthArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
let newDate = new Date(displayDate) let newDate = new Date(displayDate);
newDate.setMonth(newDate.getMonth() + 1) newDate.setMonth(newDate.getMonth() + 1);
displayDate = newDate displayDate = newDate;
} }
} }
} }
} }
// Days of week header // Days of week header
Row { Row {
width: parent.width width: parent.width
height: 32 height: 32
Repeater { Repeater {
model: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] model: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
Rectangle { Rectangle {
width: parent.width / 7 width: parent.width / 7
height: 32 height: 32
color: "transparent" color: "transparent"
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: modelData text: modelData
@@ -147,61 +148,59 @@ Column {
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
font.weight: Font.Medium font.weight: Font.Medium
} }
} }
} }
} }
// Calendar grid // Calendar grid
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 width: parent.width
height: 200 // Fixed height for calendar height: 200 // Fixed height for calendar
columns: 7 columns: 7
rows: 6 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 { Repeater {
model: 42 model: 42
Rectangle { Rectangle {
width: parent.width / 7
height: parent.height / 6
property date dayDate: { property date dayDate: {
let date = new Date(parent.firstDay) let date = new Date(parent.firstDay);
date.setDate(date.getDate() + index) date.setDate(date.getDate() + index);
return date return date;
} }
property bool isCurrentMonth: dayDate.getMonth() === displayDate.getMonth() property bool isCurrentMonth: dayDate.getMonth() === displayDate.getMonth()
property bool isToday: dayDate.toDateString() === new Date().toDateString() property bool isToday: dayDate.toDateString() === new Date().toDateString()
property bool isSelected: dayDate.toDateString() === selectedDate.toDateString() property bool isSelected: dayDate.toDateString() === selectedDate.toDateString()
color: isSelected ? Theme.primary : width: parent.width / 7
isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : height: parent.height / 6
dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent" color: isSelected ? Theme.primary : isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: dayDate.getDate() text: dayDate.getDate()
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: isSelected ? Theme.surface : color: isSelected ? Theme.surface : isToday ? Theme.primary : isCurrentMonth ? Theme.surfaceText : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
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 font.weight: isToday || isSelected ? Font.Medium : Font.Normal
} }
// Event indicator - full-width elegant bar // Event indicator - full-width elegant bar
Rectangle { Rectangle {
// Use a lighter tint of primary for selected state
id: eventIndicator id: eventIndicator
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@@ -209,65 +208,67 @@ Column {
height: 3 height: 3
radius: 1.5 radius: 1.5
visible: CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate) visible: CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)
// Dynamic color based on state with opacity // Dynamic color based on state with opacity
color: { color: {
if (isSelected) { if (isSelected)
// Use a lighter tint of primary for selected state return Qt.lighter(Theme.primary, 1.3);
return Qt.lighter(Theme.primary, 1.3) else if (isToday)
} else if (isToday) { return Theme.primary;
return Theme.primary else
} else { return Theme.primary;
return Theme.primary
}
} }
opacity: { opacity: {
if (isSelected) { if (isSelected)
return 0.9 return 0.9;
} else if (isToday) { else if (isToday)
return 0.8 return 0.8;
} else { else
return 0.6 return 0.6;
}
} }
// Subtle animation on hover // Subtle animation on hover
scale: dayArea.containsMouse ? 1.05 : 1.0 scale: dayArea.containsMouse ? 1.05 : 1
Behavior on scale { Behavior on scale {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
MouseArea { MouseArea {
id: dayArea id: dayArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
selectedDate = dayDate selectedDate = dayDate;
} }
} }
} }
} }
} }
}
}

View File

@@ -2,78 +2,186 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
PanelWindow { PanelWindow {
id: root id: root
readonly property bool hasActiveMedia: MprisController.activePlayer !== null readonly property bool hasActiveMedia: MprisController.activePlayer !== null
property bool calendarVisible: false property bool calendarVisible: false
visible: calendarVisible visible: calendarVisible
implicitWidth: 480 implicitWidth: 480
implicitHeight: 600 implicitHeight: 600
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent" color: "transparent"
anchors { anchors {
top: true top: true
left: true left: true
right: true right: true
bottom: true bottom: true
} }
Rectangle { Rectangle {
id: mainContainer 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() width: calculateWidth()
height: calculateHeight() height: calculateHeight()
x: (parent.width - width) / 2 x: (parent.width - width) / 2
y: Theme.barHeight + 4 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 color: Theme.surfaceContainer
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
layer.enabled: true layer.enabled: true
opacity: calendarVisible ? 1 : 0
scale: calendarVisible ? 1 : 0.92
Rectangle {
anchors.fill: parent
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
radius: parent.radius
SequentialAnimation on opacity {
running: calendarVisible
loops: Animation.Infinite
NumberAnimation {
to: 0.08
duration: Theme.extraLongDuration
easing.type: Theme.standardEasing
}
NumberAnimation {
to: 0.02
duration: Theme.extraLongDuration
easing.type: Theme.standardEasing
}
}
}
// 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
}
// Update height when events widget's selectedDateEvents changes
Connections {
function onSelectedDateEventsChanged() {
mainContainer.height = mainContainer.calculateHeight();
}
target: eventsWidget
enabled: eventsWidget !== null
}
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
// Main row with widgets and calendar
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);
}
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
MediaPlayerWidget {
visible: true // Always visible - shows placeholder when no media
width: parent.width
height: 160
}
WeatherWidget {
visible: true // Always visible - shows placeholder when no weather
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 { layer.effect: MultiEffect {
shadowEnabled: true shadowEnabled: true
shadowHorizontalOffset: 0 shadowHorizontalOffset: 0
@@ -82,136 +190,39 @@ PanelWindow {
shadowColor: Qt.rgba(0, 0, 0, 0.15) shadowColor: Qt.rgba(0, 0, 0, 0.15)
shadowOpacity: 0.15 shadowOpacity: 0.15
} }
Rectangle {
anchors.fill: parent
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
radius: parent.radius
SequentialAnimation on opacity {
running: calendarVisible
loops: Animation.Infinite
NumberAnimation {
to: 0.08
duration: Theme.extraLongDuration
easing.type: Theme.standardEasing
}
NumberAnimation {
to: 0.02
duration: Theme.extraLongDuration
easing.type: Theme.standardEasing
}
}
}
opacity: calendarVisible ? 1.0 : 0.0
scale: calendarVisible ? 1.0 : 0.92
// Update height when calendar service events change
Connections {
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 {
target: eventsWidget
enabled: eventsWidget !== null
function onSelectedDateEventsChanged() {
mainContainer.height = mainContainer.calculateHeight()
}
}
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Theme.longDuration duration: Theme.longDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
} }
} }
Behavior on scale { Behavior on scale {
NumberAnimation { NumberAnimation {
duration: Theme.longDuration duration: Theme.longDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
} }
} }
Behavior on height { Behavior on height {
NumberAnimation { NumberAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
// Main row with widgets and calendar
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)
}
spacing: Theme.spacingM
// Left section for widgets
Column {
id: leftWidgets
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
height: 160
}
WeatherWidget {
visible: true // Always visible - shows placeholder when no weather
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
}
}
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
z: -1 z: -1
onClicked: { onClicked: {
calendarVisible = false calendarVisible = false;
} }
} }
}
}

View File

@@ -7,17 +7,26 @@ import qs.Services
// Events widget for selected date - Material Design 3 style // Events widget for selected date - Material Design 3 style
Rectangle { Rectangle {
id: eventsWidget id: eventsWidget
property date selectedDate: new Date() property date selectedDate: new Date()
property var selectedDateEvents: [] property var selectedDateEvents: []
property bool hasEvents: selectedDateEvents && selectedDateEvents.length > 0 property bool hasEvents: selectedDateEvents && selectedDateEvents.length > 0
onSelectedDateEventsChanged: {
console.log("EventsWidget: selectedDateEvents changed, count:", selectedDateEvents.length)
eventsList.model = selectedDateEvents
}
property bool shouldShow: CalendarService && CalendarService.khalAvailable 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 width: parent.width
height: shouldShow ? (hasEvents ? Math.min(300, 80 + selectedDateEvents.length * 60) : 120) : 0 height: shouldShow ? (hasEvents ? Math.min(300, 80 + selectedDateEvents.length * 60) : 120) : 0
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
@@ -25,64 +34,39 @@ Rectangle {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
visible: shouldShow visible: shouldShow
// Material elevation shadow // Material elevation shadow
layer.enabled: true 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
}
}
// Update events when selected date or events change
Connections {
target: CalendarService
enabled: CalendarService !== null
function onEventsByDateChanged() {
updateSelectedDateEvents()
}
function onKhalAvailableChanged() {
updateSelectedDateEvents()
}
}
Component.onCompleted: { Component.onCompleted: {
updateSelectedDateEvents() updateSelectedDateEvents();
}
onSelectedDateChanged: {
updateSelectedDateEvents();
} }
onSelectedDateChanged: { // Update events when selected date or events change
updateSelectedDateEvents() Connections {
} function onEventsByDateChanged() {
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 = []
} }
function onKhalAvailableChanged() {
updateSelectedDateEvents();
}
target: CalendarService
enabled: CalendarService !== null
} }
// Header - always visible when widget is shown // Header - always visible when widget is shown
Row { Row {
id: headerRow id: headerRow
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: "event" text: "event"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -90,19 +74,17 @@ Rectangle {
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: hasEvents ? text: hasEvents ? (Qt.formatDate(selectedDate, "MMM d") + " • " + (selectedDateEvents.length === 1 ? "1 event" : selectedDateEvents.length + " events")) : Qt.formatDate(selectedDate, "MMM d")
(Qt.formatDate(selectedDate, "MMM d") + " • " +
(selectedDateEvents.length === 1 ? "1 event" : selectedDateEvents.length + " events")) :
Qt.formatDate(selectedDate, "MMM d")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
// No events placeholder - centered in entire widget (not just content area) // No events placeholder - centered in entire widget (not just content area)
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@@ -116,7 +98,7 @@ Rectangle {
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
Text { Text {
text: "No events" text: "No events"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -124,11 +106,13 @@ Rectangle {
font.weight: Font.Normal font.weight: Font.Normal
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
// Events list - positioned below header when there are events // Events list - positioned below header when there are events
ListView { ListView {
id: eventsList id: eventsList
anchors.top: headerRow.bottom anchors.top: headerRow.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@@ -136,45 +120,44 @@ Rectangle {
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
anchors.topMargin: Theme.spacingM anchors.topMargin: Theme.spacingM
visible: opacity > 0 visible: opacity > 0
opacity: hasEvents ? 1.0 : 0.0 opacity: hasEvents ? 1 : 0
clip: true clip: true
spacing: Theme.spacingS spacing: Theme.spacingS
boundsMovement: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
policy: eventsList.contentHeight > eventsList.height ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff policy: eventsList.contentHeight > eventsList.height ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff
} }
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
} }
} }
delegate: Rectangle { delegate: Rectangle {
width: eventsList.width width: eventsList.width
height: eventContent.implicitHeight + Theme.spacingM height: eventContent.implicitHeight + Theme.spacingM
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
if (modelData.url && eventMouseArea.containsMouse) { if (modelData.url && eventMouseArea.containsMouse)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
} else if (eventMouseArea.containsMouse) { else if (eventMouseArea.containsMouse)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) 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);
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.06)
} }
border.color: { border.color: {
if (modelData.url && eventMouseArea.containsMouse) { if (modelData.url && eventMouseArea.containsMouse)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
} else if (eventMouseArea.containsMouse) { else if (eventMouseArea.containsMouse)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15);
} return "transparent";
return "transparent"
} }
border.width: 1 border.width: 1
// Event indicator strip // Event indicator strip
Rectangle { Rectangle {
width: 4 width: 4
@@ -186,123 +169,151 @@ Rectangle {
color: Theme.primary color: Theme.primary
opacity: 0.8 opacity: 0.8
} }
Column { Column {
id: eventContent id: eventContent
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingL + 4 anchors.leftMargin: Theme.spacingL + 4
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
spacing: 6 spacing: 6
Text { Text {
width: parent.width width: parent.width
text: modelData.title text: modelData.title
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.Wrap wrapMode: Text.Wrap
maximumLineCount: 2 maximumLineCount: 2
} }
Item { Item {
width: parent.width width: parent.width
height: Math.max(timeRow.height, locationRow.height) height: Math.max(timeRow.height, locationRow.height)
Row { Row {
id: timeRow id: timeRow
spacing: 4
anchors.left: parent.left spacing: 4
anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
Text {
text: "schedule" Text {
font.family: Theme.iconFont text: "schedule"
font.pixelSize: Theme.fontSizeSmall font.family: Theme.iconFont
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) font.pixelSize: Theme.fontSizeSmall
anchors.verticalCenter: parent.verticalCenter color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
} anchors.verticalCenter: parent.verticalCenter
Text {
text: {
if (modelData.allDay) {
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
}
}
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
id: locationRow
spacing: 4
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
visible: modelData.location !== ""
Text {
text: "location_on"
font.family: Theme.iconFont
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: modelData.location
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
maximumLineCount: 1
width: Math.min(implicitWidth, 200)
}
}
}
} }
Text {
text: {
if (modelData.allDay) {
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;
}
}
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
id: locationRow
spacing: 4
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
visible: modelData.location !== ""
Text {
text: "location_on"
font.family: Theme.iconFont
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: modelData.location
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
maximumLineCount: 1
width: Math.min(implicitWidth, 200)
}
}
}
}
MouseArea { MouseArea {
id: eventMouseArea id: eventMouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: modelData.url ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: modelData.url ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: modelData.url !== "" enabled: modelData.url !== ""
onClicked: { onClicked: {
if (modelData.url && modelData.url !== "") { if (modelData.url && modelData.url !== "") {
if (Qt.openUrlExternally(modelData.url) === false) { if (Qt.openUrlExternally(modelData.url) === false)
console.warn("Couldn't open", modelData.url) console.warn("Couldn't open", modelData.url);
}
} }
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
Behavior on border.color { Behavior on border.color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing 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
}
}
}

View File

@@ -8,43 +8,444 @@ import qs.Services
Rectangle { Rectangle {
id: mediaPlayerWidget id: mediaPlayerWidget
property MprisPlayer activePlayer: MprisController.activePlayer property MprisPlayer activePlayer: MprisController.activePlayer
property string lastValidTitle: "" property string lastValidTitle: ""
property string lastValidArtist: "" property string lastValidArtist: ""
property string lastValidAlbum: "" property string lastValidAlbum: ""
property string lastValidArtUrl: "" property string lastValidArtUrl: ""
property real currentPosition: 0
Timer {
id: clearCacheTimer // Simple progress ratio calculation
interval: 2000 function ratio() {
onTriggered: { return activePlayer && activePlayer.length > 0 ? currentPosition / activePlayer.length : 0;
if (!activePlayer) {
lastValidTitle = ""
lastValidArtist = ""
lastValidAlbum = ""
lastValidArtUrl = ""
}
}
} }
onActivePlayerChanged: { onActivePlayerChanged: {
if (!activePlayer) { if (!activePlayer)
clearCacheTimer.restart() clearCacheTimer.restart();
} else { else
clearCacheTimer.stop() clearCacheTimer.stop();
}
} }
width: parent.width width: parent.width
height: parent.height height: parent.height
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
layer.enabled: true layer.enabled: true
Timer {
id: clearCacheTimer
interval: 2000
onTriggered: {
if (!activePlayer) {
lastValidTitle = "";
lastValidArtist = "";
lastValidAlbum = "";
lastValidArtUrl = "";
}
}
}
// 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;
}
}
// Backend events
Connections {
function onPositionChanged() {
if (!progressMouseArea.isSeeking)
currentPosition = activePlayer.position;
}
function onPostTrackChanged() {
currentPosition = activePlayer && activePlayer.position || 0;
}
function onTrackTitleChanged() {
currentPosition = activePlayer && activePlayer.position || 0;
}
target: activePlayer
}
Item {
anchors.fill: parent
anchors.margins: Theme.spacingS
// Placeholder when no media - centered in entire widget
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
visible: (!activePlayer && !lastValidTitle) || (activePlayer && activePlayer.trackTitle === "" && lastValidTitle === "")
Text {
text: "music_note"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize + 8
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
text: "No Media Playing"
font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter
}
}
// Active content in a column
Column {
anchors.fill: parent
spacing: Theme.spacingS
visible: activePlayer && activePlayer.trackTitle !== "" || lastValidTitle !== ""
// Normal media info when playing
Row {
width: parent.width
height: 60
spacing: Theme.spacingM
// Album Art
Rectangle {
width: 60
height: 60
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Item {
anchors.fill: parent
clip: true
Image {
id: albumArt
anchors.fill: parent
source: activePlayer && activePlayer.trackArtUrl || lastValidArtUrl || ""
onSourceChanged: {
if (activePlayer && activePlayer.trackArtUrl)
lastValidArtUrl = activePlayer.trackArtUrl;
}
fillMode: Image.PreserveAspectCrop
smooth: true
}
Rectangle {
anchors.fill: parent
visible: albumArt.status !== Image.Ready
color: "transparent"
Text {
anchors.centerIn: parent
text: "album"
font.family: Theme.iconFont
font.pixelSize: 28
color: Theme.surfaceVariantText
}
}
}
}
// Track Info
Column {
width: parent.width - 60 - Theme.spacingM
height: parent.height
spacing: Theme.spacingXS
Text {
text: activePlayer && activePlayer.trackTitle || lastValidTitle || "Unknown Track"
onTextChanged: {
if (activePlayer && activePlayer.trackTitle)
lastValidTitle = activePlayer.trackTitle;
}
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width
elide: Text.ElideRight
}
Text {
text: activePlayer && activePlayer.trackArtist || lastValidArtist || "Unknown Artist"
onTextChanged: {
if (activePlayer && activePlayer.trackArtist)
lastValidArtist = activePlayer.trackArtist;
}
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
width: parent.width
elide: Text.ElideRight
}
Text {
text: activePlayer && activePlayer.trackAlbum || lastValidAlbum || ""
onTextChanged: {
if (activePlayer && activePlayer.trackAlbum)
lastValidAlbum = activePlayer.trackAlbum;
}
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
width: parent.width
elide: Text.ElideRight
visible: text.length > 0
}
}
}
// Progress bar
Item {
id: progressBarContainer
width: parent.width
height: 24
Rectangle {
id: progressBarBackground
width: parent.width
height: 6
radius: 3
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
visible: activePlayer !== null
anchors.verticalCenter: parent.verticalCenter
Rectangle {
id: progressFill
height: parent.height
radius: parent.radius
color: Theme.primary
width: parent.width * ratio()
Behavior on width {
NumberAnimation {
duration: 100
}
}
}
// 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))
anchors.verticalCenter: parent.verticalCenter
visible: activePlayer && activePlayer.length > 0
scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1
Behavior on scale {
NumberAnimation {
duration: 150
}
}
}
}
MouseArea {
id: progressMouseArea
property bool isSeeking: false
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: activePlayer && activePlayer.length > 0 && activePlayer.canSeek
preventStealing: true
onPressed: function(mouse) {
isSeeking = true;
if (activePlayer && activePlayer.length > 0) {
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width));
let seekPosition = ratio * activePlayer.length;
activePlayer.position = seekPosition;
currentPosition = seekPosition;
}
}
onReleased: {
isSeeking = false;
}
onPositionChanged: function(mouse) {
if (pressed && isSeeking && activePlayer && activePlayer.length > 0) {
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width));
let seekPosition = ratio * activePlayer.length;
activePlayer.position = seekPosition;
currentPosition = seekPosition;
}
}
onClicked: function(mouse) {
if (activePlayer && activePlayer.length > 0) {
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width));
let seekPosition = ratio * activePlayer.length;
activePlayer.position = seekPosition;
currentPosition = seekPosition;
}
}
}
// 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;
}
}
onReleased: {
progressMouseArea.isSeeking = false;
}
}
}
// Control buttons - always visible
Item {
width: parent.width
height: 32
visible: activePlayer !== null
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM
height: parent.height
// Previous button
Rectangle {
width: 28
height: 28
radius: 14
color: prevBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "skip_previous"
font.family: Theme.iconFont
font.pixelSize: 16
color: Theme.surfaceText
}
MouseArea {
id: prevBtnArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!activePlayer)
return ;
// >8 s → jump to start, otherwise previous track
if (currentPosition > 8 && activePlayer.canSeek) {
activePlayer.position = 0;
currentPosition = 0;
} else {
activePlayer.previous();
}
}
}
}
// Play/Pause button
Rectangle {
width: 32
height: 32
radius: 16
color: Theme.primary
Text {
anchors.centerIn: parent
text: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
font.family: Theme.iconFont
font.pixelSize: 20
color: Theme.background
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: activePlayer && activePlayer.togglePlaying()
}
}
// Next button
Rectangle {
width: 28
height: 28
radius: 14
color: nextBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "skip_next"
font.family: Theme.iconFont
font.pixelSize: 16
color: Theme.surfaceText
}
MouseArea {
id: nextBtnArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: activePlayer && activePlayer.next()
}
}
}
}
}
}
layer.effect: MultiEffect { layer.effect: MultiEffect {
shadowEnabled: true shadowEnabled: true
shadowHorizontalOffset: 0 shadowHorizontalOffset: 0
@@ -53,383 +454,5 @@ Rectangle {
shadowColor: Qt.rgba(0, 0, 0, 0.1) shadowColor: Qt.rgba(0, 0, 0, 0.1)
shadowOpacity: 0.1 shadowOpacity: 0.1
} }
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
}
}
}
// Backend events
Connections {
target: activePlayer
function onPositionChanged() {
if (!progressMouseArea.isSeeking) {
currentPosition = activePlayer.position
}
}
function onPostTrackChanged() {
currentPosition = activePlayer?.position || 0
}
function onTrackTitleChanged() {
currentPosition = activePlayer?.position || 0
}
}
Item {
anchors.fill: parent
anchors.margins: Theme.spacingS
// Placeholder when no media - centered in entire widget
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
visible: (!activePlayer && !lastValidTitle) || (activePlayer && activePlayer.trackTitle === "" && lastValidTitle === "")
Text {
text: "music_note"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize + 8
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
text: "No Media Playing"
font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter
}
}
// Active content in a column
Column {
anchors.fill: parent
spacing: Theme.spacingS
visible: activePlayer && activePlayer.trackTitle !== "" || lastValidTitle !== ""
// Normal media info when playing
Row {
width: parent.width
height: 60
spacing: Theme.spacingM
// Album Art
Rectangle {
width: 60
height: 60
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Item {
anchors.fill: parent
clip: true
Image {
id: albumArt
anchors.fill: parent
source: activePlayer?.trackArtUrl || lastValidArtUrl || ""
onSourceChanged: {
if (activePlayer?.trackArtUrl) {
lastValidArtUrl = activePlayer.trackArtUrl;
}
}
fillMode: Image.PreserveAspectCrop
smooth: true
}
Rectangle {
anchors.fill: parent
visible: albumArt.status !== Image.Ready
color: "transparent"
Text {
anchors.centerIn: parent
text: "album"
font.family: Theme.iconFont
font.pixelSize: 28
color: Theme.surfaceVariantText
}
}
}
}
// Track Info
Column {
width: parent.width - 60 - Theme.spacingM
height: parent.height
spacing: Theme.spacingXS
Text {
text: activePlayer?.trackTitle || lastValidTitle || "Unknown Track"
onTextChanged: {
if (activePlayer?.trackTitle) {
lastValidTitle = activePlayer.trackTitle;
}
}
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width
elide: Text.ElideRight
}
Text {
text: activePlayer?.trackArtist || lastValidArtist || "Unknown Artist"
onTextChanged: {
if (activePlayer?.trackArtist) {
lastValidArtist = activePlayer.trackArtist;
}
}
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
width: parent.width
elide: Text.ElideRight
}
Text {
text: activePlayer?.trackAlbum || lastValidAlbum || ""
onTextChanged: {
if (activePlayer?.trackAlbum) {
lastValidAlbum = activePlayer.trackAlbum;
}
}
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
width: parent.width
elide: Text.ElideRight
visible: text.length > 0
}
}
}
// Progress bar
Item {
id: progressBarContainer
width: parent.width
height: 24
Rectangle {
id: progressBarBackground
width: parent.width
height: 6
radius: 3
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
visible: activePlayer !== null
anchors.verticalCenter: parent.verticalCenter
Rectangle {
id: progressFill
height: parent.height
radius: parent.radius
color: Theme.primary
width: parent.width * ratio()
Behavior on width {
NumberAnimation { duration: 100 }
}
}
// 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))
anchors.verticalCenter: parent.verticalCenter
visible: activePlayer && activePlayer.length > 0
scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1.0
Behavior on scale {
NumberAnimation { duration: 150 }
}
}
}
MouseArea {
id: progressMouseArea
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
if (activePlayer && activePlayer.length > 0) {
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width))
let seekPosition = ratio * activePlayer.length
activePlayer.position = seekPosition
currentPosition = seekPosition
}
}
onReleased: {
isSeeking = false
}
onPositionChanged: function(mouse) {
if (pressed && isSeeking && activePlayer && activePlayer.length > 0) {
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width))
let seekPosition = ratio * activePlayer.length
activePlayer.position = seekPosition
currentPosition = seekPosition
}
}
onClicked: function(mouse) {
if (activePlayer && activePlayer.length > 0) {
let ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width))
let seekPosition = ratio * activePlayer.length
activePlayer.position = seekPosition
currentPosition = seekPosition
}
}
}
// 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
}
}
onReleased: {
progressMouseArea.isSeeking = false
}
}
}
// Control buttons - always visible
Item {
width: parent.width
height: 32
visible: activePlayer !== null
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM
height: parent.height
// Previous button
Rectangle {
width: 28
height: 28
radius: 14
color: prevBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "skip_previous"
font.family: Theme.iconFont
font.pixelSize: 16
color: Theme.surfaceText
}
MouseArea {
id: prevBtnArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!activePlayer) return
// >8 s → jump to start, otherwise previous track
if (currentPosition > 8 && activePlayer.canSeek) {
activePlayer.position = 0
currentPosition = 0
} else {
activePlayer.previous()
}
}
}
}
// Play/Pause button
Rectangle {
width: 32
height: 32
radius: 16
color: Theme.primary
Text {
anchors.centerIn: parent
text: activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
font.family: Theme.iconFont
font.pixelSize: 20
color: Theme.background
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: activePlayer?.togglePlaying()
}
}
// Next button
Rectangle {
width: 28
height: 28
radius: 14
color: nextBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "skip_next"
font.family: Theme.iconFont
font.pixelSize: 16
color: Theme.surfaceText
}
MouseArea {
id: nextBtnArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: activePlayer?.next()
}
}
}
}
}
}
}

View File

@@ -6,31 +6,21 @@ import qs.Services
Rectangle { Rectangle {
id: weatherWidget id: weatherWidget
width: parent.width width: parent.width
height: parent.height height: parent.height
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
layer.enabled: true 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 // Placeholder when no weather - centered in entire widget
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
visible: !WeatherService.weather.available || WeatherService.weather.temp === 0 visible: !WeatherService.weather.available || WeatherService.weather.temp === 0
Text { Text {
text: "cloud_off" text: "cloud_off"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -38,31 +28,32 @@ Rectangle {
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
Text { Text {
text: "No Weather Data" text: "No Weather Data"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
// Weather content when available - original Column structure // Weather content when available - original Column structure
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
spacing: Theme.spacingS spacing: Theme.spacingS
visible: WeatherService.weather.available && WeatherService.weather.temp !== 0 visible: WeatherService.weather.available && WeatherService.weather.temp !== 0
// Weather header info // Weather header info
Item { Item {
width: parent.width width: parent.width
height: 60 height: 60
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingL spacing: Theme.spacingL
// Weather icon // Weather icon
Text { Text {
text: WeatherService.getWeatherIcon(WeatherService.weather.wCode) text: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
@@ -71,44 +62,53 @@ Rectangle {
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
spacing: Theme.spacingXS spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: (Prefs.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + (Prefs.useFahrenheit ? "F" : "C") text: (Prefs.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + (Prefs.useFahrenheit ? "F" : "C")
font.pixelSize: Theme.fontSizeXLarge font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Light font.weight: Font.Light
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor 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 enabled: WeatherService.weather.available
} }
} }
Text { Text {
text: WeatherService.weather.city || "" text: WeatherService.weather.city || ""
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: text.length > 0 visible: text.length > 0
} }
} }
} }
} }
// Weather details grid // Weather details grid
Grid { Grid {
columns: 2 columns: 2
spacing: Theme.spacingM spacing: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
Row { Row {
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: "humidity_low" text: "humidity_low"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -116,16 +116,19 @@ Rectangle {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: WeatherService.weather.humidity ? WeatherService.weather.humidity + "%" : "--" text: WeatherService.weather.humidity ? WeatherService.weather.humidity + "%" : "--"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
Row { Row {
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: "air" text: "air"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -133,16 +136,19 @@ Rectangle {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: WeatherService.weather.wind || "--" text: WeatherService.weather.wind || "--"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
Row { Row {
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: "wb_twilight" text: "wb_twilight"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -150,16 +156,19 @@ Rectangle {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: WeatherService.weather.sunrise || "--" text: WeatherService.weather.sunrise || "--"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
Row { Row {
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: "bedtime" text: "bedtime"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -167,13 +176,27 @@ Rectangle {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: WeatherService.weather.sunset || "--" text: WeatherService.weather.sunset || "--"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter 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

View File

@@ -1,16 +1,15 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Io import Quickshell.Io
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
Item { Item {
id: audioTab id: audioTab
property int audioSubTab: 0 // 0: Output, 1: Input property int audioSubTab: 0 // 0: Output, 1: Input
readonly property real volumeLevel: AudioService.volumeLevel readonly property real volumeLevel: AudioService.volumeLevel
readonly property real micLevel: AudioService.micLevel readonly property real micLevel: AudioService.micLevel
readonly property bool volumeMuted: AudioService.sinkMuted readonly property bool volumeMuted: AudioService.sinkMuted
@@ -19,23 +18,23 @@ Item {
readonly property string currentAudioSource: AudioService.currentAudioSource readonly property string currentAudioSource: AudioService.currentAudioSource
readonly property var audioSinks: AudioService.audioSinks readonly property var audioSinks: AudioService.audioSinks
readonly property var audioSources: AudioService.audioSources readonly property var audioSources: AudioService.audioSources
Column { Column {
anchors.fill: parent anchors.fill: parent
spacing: Theme.spacingM spacing: Theme.spacingM
// Audio Sub-tabs // Audio Sub-tabs
Row { Row {
width: parent.width width: parent.width
height: 40 height: 40
spacing: 2 spacing: 2
Rectangle { Rectangle {
width: parent.width / 2 - 1 width: parent.width / 2 - 1
height: parent.height height: parent.height
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: audioTab.audioSubTab === 0 ? Theme.primary : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) color: audioTab.audioSubTab === 0 ? Theme.primary : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "Output" text: "Output"
@@ -43,21 +42,22 @@ Item {
color: audioTab.audioSubTab === 0 ? Theme.primaryText : Theme.surfaceText color: audioTab.audioSubTab === 0 ? Theme.primaryText : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: audioTab.audioSubTab = 0 onClicked: audioTab.audioSubTab = 0
} }
} }
Rectangle { Rectangle {
width: parent.width / 2 - 1 width: parent.width / 2 - 1
height: parent.height height: parent.height
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: audioTab.audioSubTab === 1 ? Theme.primary : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) color: audioTab.audioSubTab === 1 ? Theme.primary : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "Input" text: "Input"
@@ -65,163 +65,174 @@ Item {
color: audioTab.audioSubTab === 1 ? Theme.primaryText : Theme.surfaceText color: audioTab.audioSubTab === 1 ? Theme.primaryText : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: audioTab.audioSubTab = 1 onClicked: audioTab.audioSubTab = 1
} }
} }
} }
// Output Tab Content // Output Tab Content
ScrollView { ScrollView {
width: parent.width width: parent.width
height: parent.height - 48 height: parent.height - 48
visible: audioTab.audioSubTab === 0 visible: audioTab.audioSubTab === 0
clip: true clip: true
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingL spacing: Theme.spacingL
// Volume Control // Volume Control
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "Volume" text: "Volume"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: audioTab.volumeMuted ? "volume_off" : "volume_down" text: audioTab.volumeMuted ? "volume_off" : "volume_down"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize font.pixelSize: Theme.iconSize
color: audioTab.volumeMuted ? Theme.error : Theme.surfaceText color: audioTab.volumeMuted ? Theme.error : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: AudioService.toggleMute() onClicked: AudioService.toggleMute()
} }
} }
Item { Item {
id: volumeSliderContainer id: volumeSliderContainer
width: parent.width - 80 width: parent.width - 80
height: 32 height: 32
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Rectangle { Rectangle {
id: volumeSliderTrack id: volumeSliderTrack
width: parent.width width: parent.width
height: 8 height: 8
radius: 4 radius: 4
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Rectangle { Rectangle {
id: volumeSliderFill id: volumeSliderFill
width: parent.width * (audioTab.volumeLevel / 100) width: parent.width * (audioTab.volumeLevel / 100)
height: parent.height height: parent.height
radius: parent.radius radius: parent.radius
color: Theme.primary color: Theme.primary
Behavior on width { Behavior on width {
NumberAnimation { duration: 100 } NumberAnimation {
duration: 100
}
} }
} }
// Draggable handle // Draggable handle
Rectangle { Rectangle {
id: volumeHandle id: volumeHandle
width: 18 width: 18
height: 18 height: 18
radius: 9 radius: 9
color: Theme.primary color: Theme.primary
border.color: Qt.lighter(Theme.primary, 1.3) border.color: Qt.lighter(Theme.primary, 1.3)
border.width: 2 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 anchors.verticalCenter: parent.verticalCenter
scale: volumeMouseArea.containsMouse || volumeMouseArea.pressed ? 1.2 : 1
scale: volumeMouseArea.containsMouse || volumeMouseArea.pressed ? 1.2 : 1.0
Behavior on scale { Behavior on scale {
NumberAnimation { duration: 150 } NumberAnimation {
duration: 150
}
} }
} }
} }
MouseArea { MouseArea {
id: volumeMouseArea id: volumeMouseArea
property bool isDragging: false
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
preventStealing: true preventStealing: true
property bool isDragging: false
onPressed: (mouse) => { onPressed: (mouse) => {
isDragging = true isDragging = true;
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width)) let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
let newVolume = Math.round(ratio * 100) let newVolume = Math.round(ratio * 100);
AudioService.setVolume(newVolume) AudioService.setVolume(newVolume);
} }
onReleased: { onReleased: {
isDragging = false isDragging = false;
} }
onPositionChanged: (mouse) => { onPositionChanged: (mouse) => {
if (pressed && isDragging) { if (pressed && isDragging) {
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width)) let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
let newVolume = Math.round(ratio * 100) let newVolume = Math.round(ratio * 100);
AudioService.setVolume(newVolume) AudioService.setVolume(newVolume);
} }
} }
onClicked: (mouse) => { onClicked: (mouse) => {
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width)) let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
let newVolume = Math.round(ratio * 100) let newVolume = Math.round(ratio * 100);
AudioService.setVolume(newVolume) AudioService.setVolume(newVolume);
} }
} }
// Global mouse area for drag tracking // Global mouse area for drag tracking
MouseArea { MouseArea {
id: volumeGlobalMouseArea id: volumeGlobalMouseArea
anchors.fill: parent.parent.parent.parent.parent // Fill the entire control center anchors.fill: parent.parent.parent.parent.parent // Fill the entire control center
enabled: volumeMouseArea.isDragging enabled: volumeMouseArea.isDragging
visible: false visible: false
preventStealing: true preventStealing: true
onPositionChanged: (mouse) => { onPositionChanged: (mouse) => {
if (volumeMouseArea.isDragging) { if (volumeMouseArea.isDragging) {
let globalPos = mapToItem(volumeSliderTrack, mouse.x, mouse.y) let globalPos = mapToItem(volumeSliderTrack, mouse.x, mouse.y);
let ratio = Math.max(0, Math.min(1, globalPos.x / volumeSliderTrack.width)) let ratio = Math.max(0, Math.min(1, globalPos.x / volumeSliderTrack.width));
let newVolume = Math.round(ratio * 100) let newVolume = Math.round(ratio * 100);
AudioService.setVolume(newVolume) AudioService.setVolume(newVolume);
} }
} }
onReleased: { onReleased: {
volumeMouseArea.isDragging = false volumeMouseArea.isDragging = false;
} }
} }
} }
Text { Text {
text: "volume_up" text: "volume_up"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -229,21 +240,23 @@ Item {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
} }
// Output Devices // Output Devices
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "Output Device" text: "Output Device"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
// Current device indicator // Current device indicator
Rectangle { Rectangle {
width: parent.width width: parent.width
@@ -253,250 +266,270 @@ Item {
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
border.width: 1 border.width: 1
visible: audioTab.currentAudioSink !== "" visible: audioTab.currentAudioSink !== ""
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: "check_circle" text: "check_circle"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4 font.pixelSize: Theme.iconSize - 4
color: Theme.primary color: Theme.primary
} }
Text { Text {
text: "Current: " + (AudioService.currentSinkDisplayName || "None") text: "Current: " + (AudioService.currentSinkDisplayName || "None")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
} }
} }
} }
// Real audio devices // Real audio devices
Repeater { Repeater {
model: audioTab.audioSinks model: audioTab.audioSinks
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: deviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.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))
(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.color: modelData.active ? Theme.primary : "transparent"
border.width: 1 border.width: 1
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: { text: {
if (modelData.name.includes("bluez")) return "headset" if (modelData.name.includes("bluez"))
else if (modelData.name.includes("hdmi")) return "tv" return "headset";
else if (modelData.name.includes("usb")) return "headset" else if (modelData.name.includes("hdmi"))
else return "speaker" return "tv";
else if (modelData.name.includes("usb"))
return "headset";
else
return "speaker";
} }
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize font.pixelSize: Theme.iconSize
color: modelData.active ? Theme.primary : Theme.surfaceText color: modelData.active ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
spacing: 2 spacing: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: modelData.displayName text: modelData.displayName
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: modelData.active ? Theme.primary : Theme.surfaceText color: modelData.active ? Theme.primary : Theme.surfaceText
font.weight: modelData.active ? Font.Medium : Font.Normal font.weight: modelData.active ? Font.Medium : Font.Normal
} }
Text { Text {
text: { text: {
if (modelData.subtitle && modelData.subtitle !== "") { if (modelData.subtitle && modelData.subtitle !== "")
return modelData.subtitle + (modelData.active ? " • Selected" : "") return modelData.subtitle + (modelData.active ? " • Selected" : "");
} else { else
return modelData.active ? "Selected" : "" return modelData.active ? "Selected" : "";
}
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: text !== "" visible: text !== ""
} }
} }
} }
MouseArea { MouseArea {
id: deviceArea id: deviceArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
AudioService.setAudioSink(modelData.name) AudioService.setAudioSink(modelData.name);
} }
} }
} }
} }
} }
} }
} }
// Input Tab Content // Input Tab Content
ScrollView { ScrollView {
width: parent.width width: parent.width
height: parent.height - 48 height: parent.height - 48
visible: audioTab.audioSubTab === 1 visible: audioTab.audioSubTab === 1
clip: true clip: true
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingL spacing: Theme.spacingL
// Microphone Level Control // Microphone Level Control
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "Microphone Level" text: "Microphone Level"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: audioTab.micMuted ? "mic_off" : "mic" text: audioTab.micMuted ? "mic_off" : "mic"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize font.pixelSize: Theme.iconSize
color: audioTab.micMuted ? Theme.error : Theme.surfaceText color: audioTab.micMuted ? Theme.error : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: AudioService.toggleMicMute() onClicked: AudioService.toggleMicMute()
} }
} }
Item { Item {
id: micSliderContainer id: micSliderContainer
width: parent.width - 80 width: parent.width - 80
height: 32 height: 32
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Rectangle { Rectangle {
id: micSliderTrack id: micSliderTrack
width: parent.width width: parent.width
height: 8 height: 8
radius: 4 radius: 4
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Rectangle { Rectangle {
id: micSliderFill id: micSliderFill
width: parent.width * (audioTab.micLevel / 100) width: parent.width * (audioTab.micLevel / 100)
height: parent.height height: parent.height
radius: parent.radius radius: parent.radius
color: Theme.primary color: Theme.primary
Behavior on width { Behavior on width {
NumberAnimation { duration: 100 } NumberAnimation {
duration: 100
}
} }
} }
// Draggable handle // Draggable handle
Rectangle { Rectangle {
id: micHandle id: micHandle
width: 18 width: 18
height: 18 height: 18
radius: 9 radius: 9
color: Theme.primary color: Theme.primary
border.color: Qt.lighter(Theme.primary, 1.3) border.color: Qt.lighter(Theme.primary, 1.3)
border.width: 2 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 anchors.verticalCenter: parent.verticalCenter
scale: micMouseArea.containsMouse || micMouseArea.pressed ? 1.2 : 1
scale: micMouseArea.containsMouse || micMouseArea.pressed ? 1.2 : 1.0
Behavior on scale { Behavior on scale {
NumberAnimation { duration: 150 } NumberAnimation {
duration: 150
}
} }
} }
} }
MouseArea { MouseArea {
id: micMouseArea id: micMouseArea
property bool isDragging: false
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
preventStealing: true preventStealing: true
property bool isDragging: false
onPressed: (mouse) => { onPressed: (mouse) => {
isDragging = true isDragging = true;
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width)) let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
let newMicLevel = Math.round(ratio * 100) let newMicLevel = Math.round(ratio * 100);
AudioService.setMicLevel(newMicLevel) AudioService.setMicLevel(newMicLevel);
} }
onReleased: { onReleased: {
isDragging = false isDragging = false;
} }
onPositionChanged: (mouse) => { onPositionChanged: (mouse) => {
if (pressed && isDragging) { if (pressed && isDragging) {
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width)) let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
let newMicLevel = Math.round(ratio * 100) let newMicLevel = Math.round(ratio * 100);
AudioService.setMicLevel(newMicLevel) AudioService.setMicLevel(newMicLevel);
} }
} }
onClicked: (mouse) => { onClicked: (mouse) => {
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width)) let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
let newMicLevel = Math.round(ratio * 100) let newMicLevel = Math.round(ratio * 100);
AudioService.setMicLevel(newMicLevel) AudioService.setMicLevel(newMicLevel);
} }
} }
// Global mouse area for drag tracking // Global mouse area for drag tracking
MouseArea { MouseArea {
id: micGlobalMouseArea id: micGlobalMouseArea
anchors.fill: parent.parent.parent.parent.parent // Fill the entire control center anchors.fill: parent.parent.parent.parent.parent // Fill the entire control center
enabled: micMouseArea.isDragging enabled: micMouseArea.isDragging
visible: false visible: false
preventStealing: true preventStealing: true
onPositionChanged: (mouse) => { onPositionChanged: (mouse) => {
if (micMouseArea.isDragging) { if (micMouseArea.isDragging) {
let globalPos = mapToItem(micSliderTrack, mouse.x, mouse.y) let globalPos = mapToItem(micSliderTrack, mouse.x, mouse.y);
let ratio = Math.max(0, Math.min(1, globalPos.x / micSliderTrack.width)) let ratio = Math.max(0, Math.min(1, globalPos.x / micSliderTrack.width));
let newMicLevel = Math.round(ratio * 100) let newMicLevel = Math.round(ratio * 100);
AudioService.setMicLevel(newMicLevel) AudioService.setMicLevel(newMicLevel);
} }
} }
onReleased: { onReleased: {
micMouseArea.isDragging = false micMouseArea.isDragging = false;
} }
} }
} }
Text { Text {
text: "mic" text: "mic"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -504,22 +537,23 @@ Item {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
} }
// Input Devices // Input Devices
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "Input Device" text: "Input Device"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
// Current device indicator // Current device indicator
Rectangle { Rectangle {
width: parent.width width: parent.width
@@ -529,100 +563,112 @@ Item {
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
border.width: 1 border.width: 1
visible: audioTab.currentAudioSource !== "" visible: audioTab.currentAudioSource !== ""
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: "check_circle" text: "check_circle"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4 font.pixelSize: Theme.iconSize - 4
color: Theme.primary color: Theme.primary
} }
Text { Text {
text: "Current: " + (AudioService.currentSourceDisplayName || "None") text: "Current: " + (AudioService.currentSourceDisplayName || "None")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
} }
} }
} }
// Real audio input devices // Real audio input devices
Repeater { Repeater {
model: audioTab.audioSources model: audioTab.audioSources
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: sourceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.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))
(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.color: modelData.active ? Theme.primary : "transparent"
border.width: 1 border.width: 1
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: { text: {
if (modelData.name.includes("bluez")) return "headset_mic" if (modelData.name.includes("bluez"))
else if (modelData.name.includes("usb")) return "headset_mic" return "headset_mic";
else return "mic" else if (modelData.name.includes("usb"))
return "headset_mic";
else
return "mic";
} }
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize font.pixelSize: Theme.iconSize
color: modelData.active ? Theme.primary : Theme.surfaceText color: modelData.active ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
spacing: 2 spacing: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: modelData.displayName text: modelData.displayName
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: modelData.active ? Theme.primary : Theme.surfaceText color: modelData.active ? Theme.primary : Theme.surfaceText
font.weight: modelData.active ? Font.Medium : Font.Normal font.weight: modelData.active ? Font.Medium : Font.Normal
} }
Text { Text {
text: { text: {
if (modelData.subtitle && modelData.subtitle !== "") { if (modelData.subtitle && modelData.subtitle !== "")
return modelData.subtitle + (modelData.active ? " • Selected" : "") return modelData.subtitle + (modelData.active ? " • Selected" : "");
} else { else
return modelData.active ? "Selected" : "" return modelData.active ? "Selected" : "";
}
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: text !== "" visible: text !== ""
} }
} }
} }
MouseArea { MouseArea {
id: sourceArea id: sourceArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
AudioService.setAudioSource(modelData.name) AudioService.setAudioSource(modelData.name);
} }
} }
} }
} }
} }
} }
} }
} }
}
}

View File

@@ -1,41 +1,39 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Io
import Quickshell.Bluetooth import Quickshell.Bluetooth
import Quickshell.Io
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
Item { Item {
id: bluetoothTab id: bluetoothTab
ScrollView { ScrollView {
anchors.fill: parent anchors.fill: parent
clip: true clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingL spacing: Theme.spacingL
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 60 height: 60
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: bluetoothToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : 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))
(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.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : "transparent"
border.width: 2 border.width: 2
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingL anchors.leftMargin: Theme.spacingL
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "bluetooth" text: "bluetooth"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -43,68 +41,72 @@ Item {
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
spacing: 2 spacing: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: "Bluetooth" text: "Bluetooth"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { Text {
text: BluetoothService.adapter && BluetoothService.adapter.enabled ? "Enabled" : "Disabled" text: BluetoothService.adapter && BluetoothService.adapter.enabled ? "Enabled" : "Disabled"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
} }
} }
} }
MouseArea { MouseArea {
id: bluetoothToggle id: bluetoothToggle
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
BluetoothService.toggleAdapter() BluetoothService.toggleAdapter();
} }
} }
} }
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: BluetoothService.adapter && BluetoothService.adapter.enabled visible: BluetoothService.adapter && BluetoothService.adapter.enabled
Text { Text {
text: "Paired Devices" text: "Paired Devices"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Repeater { Repeater {
model: BluetoothService.adapter && BluetoothService.adapter.devices ? BluetoothService.adapter.devices.values.filter(dev => 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 { Rectangle {
width: parent.width width: parent.width
height: 60 height: 60
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: btDeviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : 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))
(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.color: modelData.connected ? Theme.primary : "transparent"
border.width: 1 border.width: 1
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: BluetoothService.getDeviceIcon(modelData) text: BluetoothService.getDeviceIcon(modelData)
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -112,59 +114,59 @@ Item {
color: modelData.connected ? Theme.primary : Theme.surfaceText color: modelData.connected ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
spacing: 2 spacing: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: modelData.name || modelData.deviceName text: modelData.name || modelData.deviceName
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: modelData.connected ? Theme.primary : Theme.surfaceText color: modelData.connected ? Theme.primary : Theme.surfaceText
font.weight: modelData.connected ? Font.Medium : Font.Normal font.weight: modelData.connected ? Font.Medium : Font.Normal
} }
Row { Row {
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: modelData.connected ? "Connected" : "Disconnected" text: modelData.connected ? "Connected" : "Disconnected"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
} }
Text { Text {
text: { text: {
if (modelData.batteryAvailable && modelData.battery > 0) { if (modelData.batteryAvailable && modelData.battery > 0)
return "• " + Math.round(modelData.battery * 100) + "%" return "• " + Math.round(modelData.battery * 100) + "%";
}
var btBattery = BatteryService.bluetoothDevices.find(dev => var btBattery = BatteryService.bluetoothDevices.find((dev) => {
dev.name === (modelData.name || modelData.deviceName) || 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());
dev.name.toLowerCase().includes((modelData.name || modelData.deviceName).toLowerCase()) || });
(modelData.name || modelData.deviceName).toLowerCase().includes(dev.name.toLowerCase()) return btBattery ? "• " + btBattery.percentage + "%" : "";
)
return btBattery ? "• " + btBattery.percentage + "%" : ""
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: text.length > 0 visible: text.length > 0
} }
} }
} }
} }
Rectangle { Rectangle {
id: btMenuButton id: btMenuButton
width: 32 width: 32
height: 32 height: 32
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: btMenuButtonArea.containsMouse ? color: btMenuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
"transparent"
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: "more_vert" text: "more_vert"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -174,50 +176,57 @@ Item {
opacity: 0.6 opacity: 0.6
anchors.centerIn: parent anchors.centerIn: parent
} }
MouseArea { MouseArea {
id: btMenuButtonArea id: btMenuButtonArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
bluetoothContextMenuWindow.deviceData = modelData bluetoothContextMenuWindow.deviceData = modelData;
let localPos = btMenuButtonArea.mapToItem(bluetoothTab, btMenuButtonArea.width / 2, btMenuButtonArea.height) let localPos = btMenuButtonArea.mapToItem(bluetoothTab, btMenuButtonArea.width / 2, btMenuButtonArea.height);
bluetoothContextMenuWindow.show(localPos.x, localPos.y) bluetoothContextMenuWindow.show(localPos.x, localPos.y);
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { duration: Theme.shortDuration } ColorAnimation {
duration: Theme.shortDuration
}
} }
} }
MouseArea { MouseArea {
id: btDeviceArea id: btDeviceArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 40 anchors.rightMargin: 40
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
BluetoothService.debugDevice(modelData) BluetoothService.debugDevice(modelData);
BluetoothService.toggle(modelData.address) BluetoothService.toggle(modelData.address);
} }
} }
} }
} }
} }
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: BluetoothService.adapter && BluetoothService.adapter.enabled visible: BluetoothService.adapter && BluetoothService.adapter.enabled
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "Available Devices" text: "Available Devices"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
@@ -225,9 +234,12 @@ Item {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Item { width: 1; height: 1 } Item {
width: 1
height: 1
}
Rectangle { Rectangle {
width: Math.max(140, scanText.contentWidth + Theme.spacingL * 2) width: Math.max(140, scanText.contentWidth + Theme.spacingL * 2)
height: 36 height: 36
@@ -235,11 +247,11 @@ Item {
color: scanArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) color: scanArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
border.color: Theme.primary border.color: Theme.primary
border.width: 1 border.width: 1
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "bluetooth_searching" text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "bluetooth_searching"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -247,112 +259,140 @@ Item {
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
id: scanText id: scanText
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Stop Scanning" : "Start Scanning" text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Stop Scanning" : "Start Scanning"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: scanArea id: scanArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (BluetoothService.adapter && BluetoothService.adapter.discovering) { if (BluetoothService.adapter && BluetoothService.adapter.discovering)
BluetoothService.stopScan() BluetoothService.stopScan();
} else { else
BluetoothService.startScan() BluetoothService.startScan();
}
} }
} }
} }
} }
Repeater { Repeater {
model: BluetoothService.availableDevices model: BluetoothService.availableDevices
Rectangle { Rectangle {
property bool canPair: BluetoothService.canPair(modelData) property bool canPair: BluetoothService.canPair(modelData)
property string pairingStatus: BluetoothService.getPairingStatus(modelData) property string pairingStatus: BluetoothService.getPairingStatus(modelData)
width: parent.width width: parent.width
height: 70 height: 70
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
if (availableDeviceArea.containsMouse) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) if (availableDeviceArea.containsMouse)
if (modelData.pairing) return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
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 (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: { border.color: {
if (modelData.pairing) return Theme.warning if (modelData.pairing)
if (modelData.blocked) return Theme.error return Theme.warning;
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
if (modelData.blocked)
return Theme.error;
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2);
} }
border.width: 1 border.width: 1
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: BluetoothService.getDeviceIcon(modelData) text: BluetoothService.getDeviceIcon(modelData)
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize font.pixelSize: Theme.iconSize
color: { color: {
if (modelData.pairing) return Theme.warning if (modelData.pairing)
if (modelData.blocked) return Theme.error return Theme.warning;
return Theme.surfaceText
if (modelData.blocked)
return Theme.error;
return Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Column { Column {
spacing: 2 spacing: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: modelData.name || modelData.deviceName text: modelData.name || modelData.deviceName
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: { color: {
if (modelData.pairing) return Theme.warning if (modelData.pairing)
if (modelData.blocked) return Theme.error return Theme.warning;
return Theme.surfaceText
if (modelData.blocked)
return Theme.error;
return Theme.surfaceText;
} }
font.weight: modelData.pairing ? Font.Medium : Font.Normal font.weight: modelData.pairing ? Font.Medium : Font.Normal
} }
Row { Row {
spacing: Theme.spacingXS spacing: Theme.spacingXS
Row { Row {
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: { text: {
switch (pairingStatus) { switch (pairingStatus) {
case "pairing": return "Pairing..." case "pairing":
case "blocked": return "Blocked" return "Pairing...";
default: return BluetoothService.getSignalStrength(modelData) case "blocked":
return "Blocked";
default:
return BluetoothService.getSignalStrength(modelData);
} }
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: { color: {
if (modelData.pairing) return Theme.warning if (modelData.pairing)
if (modelData.blocked) return Theme.error return Theme.warning;
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
if (modelData.blocked)
return Theme.error;
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
} }
} }
Text { Text {
text: BluetoothService.getSignalIcon(modelData) text: BluetoothService.getSignalIcon(modelData)
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -360,18 +400,22 @@ Item {
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: modelData.rssi !== undefined && modelData.rssi !== 0 && pairingStatus === "available" visible: modelData.rssi !== undefined && modelData.rssi !== 0 && pairingStatus === "available"
} }
Text { Text {
text: (modelData.rssi !== undefined && modelData.rssi !== 0) ? modelData.rssi + "dBm" : "" text: (modelData.rssi !== undefined && modelData.rssi !== 0) ? modelData.rssi + "dBm" : ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
visible: modelData.rssi !== undefined && modelData.rssi !== 0 && pairingStatus === "available" visible: modelData.rssi !== undefined && modelData.rssi !== 0 && pairingStatus === "available"
} }
} }
} }
} }
} }
Rectangle { Rectangle {
width: 80 width: 80
height: 28 height: 28
@@ -380,74 +424,85 @@ Item {
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: { color: {
if (!canPair && !modelData.pairing) return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) if (!canPair && !modelData.pairing)
if (actionButtonArea.containsMouse) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3);
return "transparent"
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.color: canPair || modelData.pairing ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1 border.width: 1
opacity: canPair || modelData.pairing ? 1.0 : 0.5 opacity: canPair || modelData.pairing ? 1 : 0.5
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: { text: {
if (modelData.pairing) return "Pairing..." if (modelData.pairing)
if (modelData.blocked) return "Blocked" return "Pairing...";
return "Pair"
if (modelData.blocked)
return "Blocked";
return "Pair";
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: canPair || modelData.pairing ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5) color: canPair || modelData.pairing ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
font.weight: Font.Medium font.weight: Font.Medium
} }
MouseArea { MouseArea {
id: actionButtonArea id: actionButtonArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: canPair ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: canPair ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: canPair enabled: canPair
onClicked: { onClicked: {
if (canPair) { if (canPair)
BluetoothService.pair(modelData.address) BluetoothService.pair(modelData.address);
}
} }
} }
} }
MouseArea { MouseArea {
id: availableDeviceArea id: availableDeviceArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 90 // Don't overlap with action button anchors.rightMargin: 90 // Don't overlap with action button
hoverEnabled: true hoverEnabled: true
cursorShape: canPair ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: canPair ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: canPair enabled: canPair
onClicked: { onClicked: {
if (canPair) { if (canPair)
BluetoothService.pair(modelData.address) BluetoothService.pair(modelData.address);
}
} }
} }
} }
} }
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: BluetoothService.adapter && BluetoothService.adapter.discovering && BluetoothService.availableDevices.length === 0 visible: BluetoothService.adapter && BluetoothService.adapter.discovering && BluetoothService.availableDevices.length === 0
Row { Row {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "sync" text: "sync"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSizeLarge font.pixelSize: Theme.iconSizeLarge
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
RotationAnimation on rotation { RotationAnimation on rotation {
running: true running: true
loops: Animation.Infinite loops: Animation.Infinite
@@ -455,8 +510,9 @@ Item {
to: 360 to: 360
duration: 2000 duration: 2000
} }
} }
Text { Text {
text: "Scanning for devices..." text: "Scanning for devices..."
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
@@ -464,16 +520,18 @@ Item {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
Text { Text {
text: "Make sure your device is in pairing mode" text: "Make sure your device is in pairing mode"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
Text { Text {
text: "No devices found. Put your device in pairing mode and click Start Scanning." text: "No devices found. Put your device in pairing mode and click Start Scanning."
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -483,15 +541,39 @@ Item {
width: parent.width width: parent.width
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
} }
} }
} }
Rectangle { Rectangle {
id: bluetoothContextMenuWindow id: bluetoothContextMenuWindow
property var deviceData: null property var deviceData: null
property bool menuVisible: false 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 visible: false
width: 160 width: 160
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
@@ -500,7 +582,9 @@ Item {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
z: 1000 z: 1000
opacity: menuVisible ? 1 : 0
scale: menuVisible ? 1 : 0.85
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 4 anchors.topMargin: 4
@@ -511,42 +595,26 @@ Item {
color: Qt.rgba(0, 0, 0, 0.15) color: Qt.rgba(0, 0, 0, 0.15)
z: parent.z - 1 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 { Column {
id: menuColumn id: menuColumn
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
spacing: 1 spacing: 1
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 32 height: 32
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
color: connectArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: connectArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: bluetoothContextMenuWindow.deviceData && bluetoothContextMenuWindow.deviceData.connected ? "link_off" : "link" text: bluetoothContextMenuWindow.deviceData && bluetoothContextMenuWindow.deviceData.connected ? "link_off" : "link"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -555,7 +623,7 @@ Item {
opacity: 0.7 opacity: 0.7
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: bluetoothContextMenuWindow.deviceData && bluetoothContextMenuWindow.deviceData.connected ? "Disconnect" : "Connect" text: bluetoothContextMenuWindow.deviceData && bluetoothContextMenuWindow.deviceData.connected ? "Disconnect" : "Connect"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -563,56 +631,60 @@ Item {
font.weight: Font.Normal font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: connectArea id: connectArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (bluetoothContextMenuWindow.deviceData) { if (bluetoothContextMenuWindow.deviceData)
BluetoothService.toggle(bluetoothContextMenuWindow.deviceData.address) BluetoothService.toggle(bluetoothContextMenuWindow.deviceData.address);
}
bluetoothContextMenuWindow.hide() bluetoothContextMenuWindow.hide();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
Rectangle { Rectangle {
width: parent.width - Theme.spacingS * 2 width: parent.width - Theme.spacingS * 2
height: 5 height: 5
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
color: "transparent" color: "transparent"
Rectangle { Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width width: parent.width
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
} }
} }
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 32 height: 32
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
color: forgetArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" color: forgetArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: "delete" text: "delete"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -621,7 +693,7 @@ Item {
opacity: 0.7 opacity: 0.7
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: "Forget Device" text: "Forget Device"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -629,60 +701,60 @@ Item {
font.weight: Font.Normal font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: forgetArea id: forgetArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (bluetoothContextMenuWindow.deviceData) { if (bluetoothContextMenuWindow.deviceData)
BluetoothService.forget(bluetoothContextMenuWindow.deviceData.address) BluetoothService.forget(bluetoothContextMenuWindow.deviceData.address);
}
bluetoothContextMenuWindow.hide() bluetoothContextMenuWindow.hide();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
function show(x, y) { Behavior on opacity {
const menuWidth = 160 NumberAnimation {
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2 duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
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() { Behavior on scale {
bluetoothContextMenuWindow.menuVisible = false NumberAnimation {
Qt.callLater(() => { bluetoothContextMenuWindow.visible = false }) duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
} }
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
visible: bluetoothContextMenuWindow.visible visible: bluetoothContextMenuWindow.visible
onClicked: { onClicked: {
bluetoothContextMenuWindow.hide() bluetoothContextMenuWindow.hide();
} }
MouseArea { MouseArea {
x: bluetoothContextMenuWindow.x x: bluetoothContextMenuWindow.x
y: bluetoothContextMenuWindow.y y: bluetoothContextMenuWindow.y
@@ -691,5 +763,7 @@ Item {
onClicked: { onClicked: {
} }
} }
} }
}
}

View File

@@ -2,43 +2,38 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
PanelWindow { PanelWindow {
id: root id: root
property bool controlCenterVisible: false property bool controlCenterVisible: false
property string currentTab: "network" // "network", "audio", "bluetooth", "display"
property bool powerOptionsExpanded: false
visible: controlCenterVisible visible: controlCenterVisible
onVisibleChanged: { onVisibleChanged: {
// Enable/disable WiFi auto-refresh based on control center visibility // Enable/disable WiFi auto-refresh based on control center visibility
WifiService.autoRefreshEnabled = visible && NetworkService.wifiEnabled WifiService.autoRefreshEnabled = visible && NetworkService.wifiEnabled;
} }
implicitWidth: 600 implicitWidth: 600
implicitHeight: 500 implicitHeight: 500
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent" color: "transparent"
anchors { anchors {
top: true top: true
left: true left: true
right: true right: true
bottom: true bottom: true
} }
property string currentTab: "network" // "network", "audio", "bluetooth", "display"
property bool powerOptionsExpanded: false
Rectangle { Rectangle {
width: Math.min(600, Screen.width - Theme.spacingL * 2) width: Math.min(600, Screen.width - Theme.spacingL * 2)
height: root.powerOptionsExpanded ? 570 : 500 height: root.powerOptionsExpanded ? 570 : 500
@@ -48,50 +43,66 @@ PanelWindow {
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
opacity: controlCenterVisible ? 1 : 0
// TopBar dropdown animation - optimized for performance // TopBar dropdown animation - optimized for performance
transform: [ transform: [
Scale { Scale {
id: scaleTransform id: scaleTransform
origin.x: 600 // Use fixed width since popup is max 600px wide
origin.x: 600 // Use fixed width since popup is max 600px wide
origin.y: 0 origin.y: 0
xScale: controlCenterVisible ? 1.0 : 0.95 xScale: controlCenterVisible ? 1 : 0.95
yScale: controlCenterVisible ? 1.0 : 0.8 yScale: controlCenterVisible ? 1 : 0.8
}, },
Translate { Translate {
id: translateTransform id: translateTransform
x: controlCenterVisible ? 0 : 15 // Slide slightly left when hidden
x: controlCenterVisible ? 0 : 15 // Slide slightly left when hidden
y: controlCenterVisible ? 0 : -30 y: controlCenterVisible ? 0 : -30
} }
] ]
// Single coordinated animation for better performance // Single coordinated animation for better performance
states: [ states: [
State { State {
name: "visible" name: "visible"
when: controlCenterVisible 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 { State {
name: "hidden" name: "hidden"
when: !controlCenterVisible 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: [ transitions: [
Transition { Transition {
from: "*"; to: "*" from: "*"
to: "*"
ParallelAnimation { ParallelAnimation {
NumberAnimation { NumberAnimation {
targets: [scaleTransform, translateTransform] targets: [scaleTransform, translateTransform]
@@ -99,29 +110,22 @@ PanelWindow {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
} }
} }
} }
] ]
opacity: controlCenterVisible ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
spacing: Theme.spacingM spacing: Theme.spacingM
// Elegant User Header // Elegant User Header
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingL spacing: Theme.spacingL
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 90 height: 90
@@ -129,22 +133,23 @@ PanelWindow {
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.4) 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.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingL anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL anchors.rightMargin: Theme.spacingL
spacing: Theme.spacingL spacing: Theme.spacingL
// Profile Picture Container // Profile Picture Container
Item { Item {
id: avatarContainer id: avatarContainer
width: 64
height: 64
property bool hasImage: profileImageLoader.status === Image.Ready property bool hasImage: profileImageLoader.status === Image.Ready
width: 64
height: 64
// This rectangle provides the themed ring via its border. // This rectangle provides the themed ring via its border.
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -158,12 +163,15 @@ PanelWindow {
// Hidden Image loader. Its only purpose is to load the texture. // Hidden Image loader. Its only purpose is to load the texture.
Image { Image {
id: profileImageLoader id: profileImageLoader
source: { source: {
if (Prefs.profileImage === "") return "" if (Prefs.profileImage === "")
if (Prefs.profileImage.startsWith("/")) { return "";
return "file://" + Prefs.profileImage
} if (Prefs.profileImage.startsWith("/"))
return Prefs.profileImage return "file://" + Prefs.profileImage;
return Prefs.profileImage;
} }
smooth: true smooth: true
asynchronous: true asynchronous: true
@@ -180,11 +188,12 @@ PanelWindow {
maskSource: circularMask maskSource: circularMask
visible: avatarContainer.hasImage visible: avatarContainer.hasImage
maskThresholdMin: 0.5 maskThresholdMin: 0.5
maskSpreadAtMin: 1.0 maskSpreadAtMin: 1
} }
Item { Item {
id: circularMask id: circularMask
width: 64 - 10 width: 64 - 10
height: 64 - 10 height: 64 - 10
layer.enabled: true layer.enabled: true
@@ -197,6 +206,7 @@ PanelWindow {
color: "black" color: "black"
antialiasing: true antialiasing: true
} }
} }
// Fallback for when there is no image. // Fallback for when there is no image.
@@ -213,6 +223,7 @@ PanelWindow {
font.pixelSize: Theme.iconSize + 8 font.pixelSize: Theme.iconSize + 8
color: Theme.primaryText color: Theme.primaryText
} }
} }
// Error icon for when the image fails to load. // Error icon for when the image fails to load.
@@ -224,46 +235,46 @@ PanelWindow {
color: Theme.primaryText color: Theme.primaryText
visible: Prefs.profileImage !== "" && profileImageLoader.status === Image.Error visible: Prefs.profileImage !== "" && profileImageLoader.status === Image.Error
} }
} }
// User Info Text // User Info Text
Column { Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: UserInfoService.fullName || UserInfoService.username || "User" text: UserInfoService.fullName || UserInfoService.username || "User"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { Text {
text: "Uptime: " + (UserInfoService.uptime || "Unknown") text: "Uptime: " + (UserInfoService.uptime || "Unknown")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
font.weight: Font.Normal font.weight: Font.Normal
} }
} }
} }
// Action Buttons - Power and Settings // Action Buttons - Power and Settings
Row { Row {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Theme.spacingL anchors.rightMargin: Theme.spacingL
spacing: Theme.spacingS spacing: Theme.spacingS
// Power Button // Power Button
Rectangle { Rectangle {
width: 40 width: 40
height: 40 height: 40
radius: 20 radius: 20
color: powerButton.containsMouse || root.powerOptionsExpanded ? 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)
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 { Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width width: parent.width
@@ -271,65 +282,74 @@ PanelWindow {
radius: parent.radius radius: parent.radius
color: "transparent" color: "transparent"
clip: true clip: true
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: root.powerOptionsExpanded ? "expand_less" : "power_settings_new" text: root.powerOptionsExpanded ? "expand_less" : "power_settings_new"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 2 font.pixelSize: Theme.iconSize - 2
color: powerButton.containsMouse || root.powerOptionsExpanded ? Theme.error : Theme.surfaceText color: powerButton.containsMouse || root.powerOptionsExpanded ? Theme.error : Theme.surfaceText
Behavior on text { Behavior on text {
// Smooth icon transition // Smooth icon transition
SequentialAnimation { SequentialAnimation {
NumberAnimation { NumberAnimation {
target: parent target: parent
property: "opacity" property: "opacity"
to: 0.0 to: 0
duration: Theme.shortDuration / 2 duration: Theme.shortDuration / 2
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
PropertyAction { target: parent; property: "text" }
PropertyAction {
target: parent
property: "text"
}
NumberAnimation { NumberAnimation {
target: parent target: parent
property: "opacity" property: "opacity"
to: 1.0 to: 1
duration: Theme.shortDuration / 2 duration: Theme.shortDuration / 2
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
} }
MouseArea { MouseArea {
id: powerButton id: powerButton
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.powerOptionsExpanded = !root.powerOptionsExpanded root.powerOptionsExpanded = !root.powerOptionsExpanded;
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
// Settings Button // Settings Button
Rectangle { Rectangle {
width: 40 width: 40
height: 40 height: 40
radius: 20 radius: 20
color: settingsButton.containsMouse ? 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)
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 { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "settings" text: "settings"
@@ -337,29 +357,33 @@ PanelWindow {
font.pixelSize: Theme.iconSize - 2 font.pixelSize: Theme.iconSize - 2
color: Theme.surfaceText color: Theme.surfaceText
} }
MouseArea { MouseArea {
id: settingsButton id: settingsButton
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
controlCenterVisible = false controlCenterVisible = false;
settingsPopup.settingsVisible = true settingsPopup.settingsVisible = true;
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
} }
// Animated Collapsible Power Options (optimized) // Animated Collapsible Power Options (optimized)
Rectangle { Rectangle {
width: parent.width width: parent.width
@@ -368,42 +392,25 @@ PanelWindow {
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.4) 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.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: root.powerOptionsExpanded ? 1 : 0 border.width: root.powerOptionsExpanded ? 1 : 0
opacity: root.powerOptionsExpanded ? 1.0 : 0.0 opacity: root.powerOptionsExpanded ? 1 : 0
clip: true 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 { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingL spacing: Theme.spacingL
visible: root.powerOptionsExpanded visible: root.powerOptionsExpanded
// Logout // Logout
Rectangle { Rectangle {
width: 100 width: 100
height: 34 height: 34
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: logoutButton.containsMouse ? 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)
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 { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: "logout" text: "logout"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -411,7 +418,7 @@ PanelWindow {
color: logoutButton.containsMouse ? Theme.warning : Theme.surfaceText color: logoutButton.containsMouse ? Theme.warning : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: "Logout" text: "Logout"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -419,46 +426,47 @@ PanelWindow {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: logoutButton id: logoutButton
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.powerOptionsExpanded = false root.powerOptionsExpanded = false;
if (typeof root !== "undefined" && root.powerConfirmDialog) { if (typeof root !== "undefined" && root.powerConfirmDialog) {
root.powerConfirmDialog.powerConfirmAction = "logout" root.powerConfirmDialog.powerConfirmAction = "logout";
root.powerConfirmDialog.powerConfirmTitle = "Logout" root.powerConfirmDialog.powerConfirmTitle = "Logout";
root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to logout?" root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to logout?";
root.powerConfirmDialog.powerConfirmVisible = true root.powerConfirmDialog.powerConfirmVisible = true;
} }
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
// Reboot // Reboot
Rectangle { Rectangle {
width: 100 width: 100
height: 34 height: 34
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: rebootButton.containsMouse ? 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)
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 { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: "restart_alt" text: "restart_alt"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -466,7 +474,7 @@ PanelWindow {
color: rebootButton.containsMouse ? Theme.warning : Theme.surfaceText color: rebootButton.containsMouse ? Theme.warning : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: "Restart" text: "Restart"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -474,46 +482,47 @@ PanelWindow {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: rebootButton id: rebootButton
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.powerOptionsExpanded = false root.powerOptionsExpanded = false;
if (typeof root !== "undefined" && root.powerConfirmDialog) { if (typeof root !== "undefined" && root.powerConfirmDialog) {
root.powerConfirmDialog.powerConfirmAction = "reboot" root.powerConfirmDialog.powerConfirmAction = "reboot";
root.powerConfirmDialog.powerConfirmTitle = "Restart" root.powerConfirmDialog.powerConfirmTitle = "Restart";
root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to restart?" root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to restart?";
root.powerConfirmDialog.powerConfirmVisible = true root.powerConfirmDialog.powerConfirmVisible = true;
} }
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
// Shutdown // Shutdown
Rectangle { Rectangle {
width: 100 width: 100
height: 34 height: 34
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: shutdownButton.containsMouse ? 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)
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 { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: "power_settings_new" text: "power_settings_new"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -521,7 +530,7 @@ PanelWindow {
color: shutdownButton.containsMouse ? Theme.error : Theme.surfaceText color: shutdownButton.containsMouse ? Theme.error : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: "Shutdown" text: "Shutdown"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -529,77 +538,114 @@ PanelWindow {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: shutdownButton id: shutdownButton
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.powerOptionsExpanded = false root.powerOptionsExpanded = false;
if (typeof root !== "undefined" && root.powerConfirmDialog) { if (typeof root !== "undefined" && root.powerConfirmDialog) {
root.powerConfirmDialog.powerConfirmAction = "poweroff" root.powerConfirmDialog.powerConfirmAction = "poweroff";
root.powerConfirmDialog.powerConfirmTitle = "Shutdown" root.powerConfirmDialog.powerConfirmTitle = "Shutdown";
root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to shutdown?" root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to shutdown?";
root.powerConfirmDialog.powerConfirmVisible = true root.powerConfirmDialog.powerConfirmVisible = true;
} }
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing 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 // Tab buttons
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingXS spacing: Theme.spacingXS
Repeater { Repeater {
model: { model: {
let tabs = [ let tabs = [{
{name: "Network", icon: "wifi", id: "network", available: true} "name": "Network",
] "icon": "wifi",
"id": "network",
"available": true
}];
// Always show audio // 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 // Show Bluetooth only if available
if (BluetoothService.available) { if (BluetoothService.available)
tabs.push({name: "Bluetooth", icon: "bluetooth", id: "bluetooth", available: true}) tabs.push({
} "name": "Bluetooth",
"icon": "bluetooth",
"id": "bluetooth",
"available": true
});
// Always show display // Always show display
tabs.push({name: "Display", icon: "brightness_6", id: "display", available: true}) tabs.push({
"name": "Display",
return tabs "icon": "brightness_6",
"id": "display",
"available": true
});
return tabs;
} }
Rectangle { Rectangle {
property int tabCount: { property int tabCount: {
let count = 3 // Network + Audio + Display (always visible) let count = 3; // Network + Audio + Display (always visible)
if (BluetoothService.available) count++ if (BluetoothService.available)
return count count++;
return count;
} }
width: (parent.width - Theme.spacingXS * (tabCount - 1)) / tabCount width: (parent.width - Theme.spacingXS * (tabCount - 1)) / tabCount
height: 40 height: 40
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: root.currentTab === modelData.id ? 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"
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 { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: modelData.icon text: modelData.icon
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -607,7 +653,7 @@ PanelWindow {
color: root.currentTab === modelData.id ? Theme.primary : Theme.surfaceText color: root.currentTab === modelData.id ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: modelData.name text: modelData.name
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -615,75 +661,100 @@ PanelWindow {
font.weight: root.currentTab === modelData.id ? Font.Medium : Font.Normal font.weight: root.currentTab === modelData.id ? Font.Medium : Font.Normal
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: tabArea id: tabArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.currentTab = modelData.id root.currentTab = modelData.id;
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
} }
} }
// Tab content area // Tab content area
Rectangle { Rectangle {
width: parent.width width: parent.width
height: root.powerOptionsExpanded ? 240 : 300 height: root.powerOptionsExpanded ? 240 : 300
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.1) 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 // Network Tab
NetworkTab { NetworkTab {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
visible: root.currentTab === "network" visible: root.currentTab === "network"
} }
// Audio Tab // Audio Tab
AudioTab { AudioTab {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
visible: root.currentTab === "audio" visible: root.currentTab === "audio"
} }
// Bluetooth Tab // Bluetooth Tab
BluetoothTab { BluetoothTab {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
visible: BluetoothService.available && root.currentTab === "bluetooth" visible: BluetoothService.available && root.currentTab === "bluetooth"
} }
// Display Tab // Display Tab
DisplayTab { DisplayTab {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
visible: root.currentTab === "display" 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 // Click outside to close
@@ -691,7 +762,8 @@ PanelWindow {
anchors.fill: parent anchors.fill: parent
z: -1 z: -1
onClicked: { onClicked: {
controlCenterVisible = false controlCenterVisible = false;
} }
} }
}
}

View File

@@ -1,79 +1,77 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Io import Quickshell.Io
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
ScrollView { ScrollView {
id: displayTab id: displayTab
clip: true clip: true
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingL spacing: Theme.spacingL
// Brightness Control // Brightness Control
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: BrightnessService.brightnessAvailable visible: BrightnessService.brightnessAvailable
Text { Text {
text: "Brightness" text: "Brightness"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
CustomSlider { CustomSlider {
width: parent.width width: parent.width
value: BrightnessService.brightnessLevel value: BrightnessService.brightnessLevel
leftIcon: "brightness_low" leftIcon: "brightness_low"
rightIcon: "brightness_high" rightIcon: "brightness_high"
enabled: BrightnessService.brightnessAvailable enabled: BrightnessService.brightnessAvailable
onSliderValueChanged: function(newValue) { onSliderValueChanged: function(newValue) {
BrightnessService.setBrightness(newValue) BrightnessService.setBrightness(newValue);
} }
} }
} }
// Display settings // Display settings
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "Display Settings" text: "Display Settings"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
// Mode toggles row (Night Mode + Light/Dark Mode) // Mode toggles row (Night Mode + Light/Dark Mode)
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
// Night mode toggle // Night mode toggle
Rectangle { Rectangle {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: 80 height: 80
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Prefs.nightModeEnabled ? 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))
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.color: Prefs.nightModeEnabled ? Theme.primary : "transparent"
border.width: Prefs.nightModeEnabled ? 1 : 0 border.width: Prefs.nightModeEnabled ? 1 : 0
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: Prefs.nightModeEnabled ? "nightlight" : "dark_mode" text: Prefs.nightModeEnabled ? "nightlight" : "dark_mode"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -81,7 +79,7 @@ ScrollView {
color: Prefs.nightModeEnabled ? Theme.primary : Theme.surfaceText color: Prefs.nightModeEnabled ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
Text { Text {
text: "Night Mode" text: "Night Mode"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -89,43 +87,43 @@ ScrollView {
font.weight: Font.Medium font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
MouseArea { MouseArea {
id: nightModeToggle id: nightModeToggle
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (Prefs.nightModeEnabled) { if (Prefs.nightModeEnabled) {
// Disable night mode - kill any running color temperature processes // Disable night mode - kill any running color temperature processes
nightModeDisableProcess.running = true nightModeDisableProcess.running = true;
Prefs.setNightModeEnabled(false) Prefs.setNightModeEnabled(false);
} else { } else {
// Enable night mode using wlsunset or redshift // Enable night mode using wlsunset or redshift
nightModeEnableProcess.running = true nightModeEnableProcess.running = true;
Prefs.setNightModeEnabled(true) Prefs.setNightModeEnabled(true);
} }
} }
} }
} }
// Light/Dark mode toggle // Light/Dark mode toggle
Rectangle { Rectangle {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: 80 height: 80
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.isLightMode ? 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))
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.color: Theme.isLightMode ? Theme.primary : "transparent"
border.width: Theme.isLightMode ? 1 : 0 border.width: Theme.isLightMode ? 1 : 0
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: Theme.isLightMode ? "light_mode" : "palette" text: Theme.isLightMode ? "light_mode" : "palette"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -133,7 +131,7 @@ ScrollView {
color: Theme.isLightMode ? Theme.primary : Theme.surfaceText color: Theme.isLightMode ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
Text { Text {
text: Theme.isLightMode ? "Light Mode" : "Dark Mode" text: Theme.isLightMode ? "Light Mode" : "Dark Mode"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -141,53 +139,60 @@ ScrollView {
font.weight: Font.Medium font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
MouseArea { MouseArea {
id: lightModeToggle id: lightModeToggle
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
Theme.toggleLightMode() Theme.toggleLightMode();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
} }
} }
// Night mode processes // Night mode processes
Process { Process {
id: nightModeEnableProcess id: nightModeEnableProcess
command: ["bash", "-c", "if command -v wlsunset > /dev/null; then pkill wlsunset; wlsunset -t 3000 & elif command -v redshift > /dev/null; then pkill redshift; redshift -P -O 3000 & else echo 'No night mode tool available'; fi"] command: ["bash", "-c", "if command -v wlsunset > /dev/null; then pkill wlsunset; wlsunset -t 3000 & elif command -v redshift > /dev/null; then pkill redshift; redshift -P -O 3000 & else echo 'No night mode tool available'; fi"]
running: false running: false
onExited: (exitCode) => { onExited: (exitCode) => {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("Failed to enable night mode") console.warn("Failed to enable night mode");
Prefs.setNightModeEnabled(false) Prefs.setNightModeEnabled(false);
} }
} }
} }
Process { Process {
id: nightModeDisableProcess 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"] 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 running: false
onExited: (exitCode) => { onExited: (exitCode) => {
if (exitCode !== 0) { if (exitCode !== 0)
console.warn("Failed to disable night mode") console.warn("Failed to disable night mode");
}
} }
} }
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +1,54 @@
import "."
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Common import qs.Common
import qs.Services import qs.Services
import "."
Rectangle { Rectangle {
id: cpuWidget id: cpuWidget
property bool showPercentage: true property bool showPercentage: true
property bool showIcon: true property bool showIcon: true
width: 55 width: 55
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: cpuArea.containsMouse ? 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)
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 { MouseArea {
id: cpuArea id: cpuArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
ProcessMonitorService.setSortBy("cpu") ProcessMonitorService.setSortBy("cpu");
processListDropdown.toggle() processListDropdown.toggle();
} }
} }
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 3 spacing: 3
// CPU icon // CPU icon
Text { Text {
text: "memory" // Material Design memory icon (swapped from RAM widget) text: "memory" // Material Design memory icon (swapped from RAM widget)
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 8 font.pixelSize: Theme.iconSize - 8
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
color: { color: {
if (SystemMonitorService.cpuUsage > 80) return Theme.error if (SystemMonitorService.cpuUsage > 80)
if (SystemMonitorService.cpuUsage > 60) return Theme.warning return Theme.error;
return Theme.surfaceText
if (SystemMonitorService.cpuUsage > 60)
return Theme.warning;
return Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
// Percentage text // Percentage text
Text { Text {
text: (SystemMonitorService.cpuUsage || 0).toFixed(0) + "%" text: (SystemMonitorService.cpuUsage || 0).toFixed(0) + "%"
@@ -55,5 +57,7 @@ Rectangle {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
} }

View File

@@ -3,7 +3,7 @@ import qs.Common
Item { Item {
id: slider id: slider
property int value: 50 property int value: 50
property int minimum: 0 property int minimum: 0
property int maximum: 100 property int maximum: 100
@@ -12,16 +12,16 @@ Item {
property bool enabled: true property bool enabled: true
property string unit: "%" property string unit: "%"
property bool showValue: true property bool showValue: true
signal sliderValueChanged(int newValue) signal sliderValueChanged(int newValue)
signal sliderDragFinished(int finalValue) signal sliderDragFinished(int finalValue)
height: 80 height: 80
Column { Column {
anchors.fill: parent anchors.fill: parent
spacing: Theme.spacingM spacing: Theme.spacingM
// Value display // Value display
Text { Text {
text: slider.value + slider.unit text: slider.value + slider.unit
@@ -31,12 +31,12 @@ Item {
visible: slider.showValue visible: slider.showValue
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
// Slider row // Slider row
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
// Left icon // Left icon
Text { Text {
text: slider.leftIcon text: slider.leftIcon
@@ -46,53 +46,53 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: slider.leftIcon.length > 0 visible: slider.leftIcon.length > 0
} }
// Slider track // Slider track
Rectangle { Rectangle {
id: sliderTrack id: sliderTrack
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)) width: parent.width - (leftIconWidth + rightIconWidth + (slider.leftIcon.length > 0 ? Theme.spacingM : 0) + (slider.rightIcon.length > 0 ? Theme.spacingM : 0))
height: 6 height: 6
radius: 3 radius: 3
color: slider.enabled ? 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)
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 anchors.verticalCenter: parent.verticalCenter
property int leftIconWidth: slider.leftIcon.length > 0 ? Theme.iconSize : 0
property int rightIconWidth: slider.rightIcon.length > 0 ? Theme.iconSize : 0
// Fill // Fill
Rectangle { Rectangle {
id: sliderFill id: sliderFill
width: parent.width * ((slider.value - slider.minimum) / (slider.maximum - slider.minimum)) width: parent.width * ((slider.value - slider.minimum) / (slider.maximum - slider.minimum))
height: parent.height height: parent.height
radius: parent.radius radius: parent.radius
color: slider.enabled ? Theme.primary : Theme.surfaceVariantText color: slider.enabled ? Theme.primary : Theme.surfaceVariantText
Behavior on width { Behavior on width {
NumberAnimation { duration: 150; easing.type: Easing.OutCubic } NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
} }
} }
// Draggable handle // Draggable handle
Rectangle { Rectangle {
id: sliderHandle id: sliderHandle
width: 18 width: 18
height: 18 height: 18
radius: 9 radius: 9
color: slider.enabled ? Theme.primary : Theme.surfaceVariantText color: slider.enabled ? Theme.primary : Theme.surfaceVariantText
border.color: slider.enabled ? Qt.lighter(Theme.primary, 1.3) : Qt.lighter(Theme.surfaceVariantText, 1.3) border.color: slider.enabled ? Qt.lighter(Theme.primary, 1.3) : Qt.lighter(Theme.surfaceVariantText, 1.3)
border.width: 2 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 anchors.verticalCenter: parent.verticalCenter
scale: sliderMouseArea.containsMouse || sliderMouseArea.pressed ? 1.2 : 1
scale: sliderMouseArea.containsMouse || sliderMouseArea.pressed ? 1.2 : 1.0
Behavior on scale {
NumberAnimation { duration: 150 }
}
// Handle glow effect when active // Handle glow effect when active
Rectangle { Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
@@ -103,91 +103,102 @@ Item {
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
border.width: 2 border.width: 2
visible: sliderMouseArea.containsMouse && slider.enabled visible: sliderMouseArea.containsMouse && slider.enabled
Behavior on opacity { Behavior on opacity {
NumberAnimation { duration: 150 } NumberAnimation {
duration: 150
}
} }
} }
Behavior on scale {
NumberAnimation {
duration: 150
}
}
} }
Item { Item {
id: sliderContainer id: sliderContainer
anchors.fill: parent anchors.fill: parent
MouseArea { MouseArea {
id: sliderMouseArea id: sliderMouseArea
property bool isDragging: false
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: slider.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: slider.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: slider.enabled enabled: slider.enabled
preventStealing: true preventStealing: true
property bool isDragging: false
onPressed: (mouse) => { onPressed: (mouse) => {
if (slider.enabled) { if (slider.enabled) {
isDragging = true isDragging = true;
let ratio = Math.max(0, Math.min(1, mouse.x / width)) let ratio = Math.max(0, Math.min(1, mouse.x / width));
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum)) let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum));
slider.value = newValue slider.value = newValue;
slider.sliderValueChanged(newValue) slider.sliderValueChanged(newValue);
} }
} }
onReleased: { onReleased: {
if (slider.enabled) { if (slider.enabled) {
isDragging = false isDragging = false;
slider.sliderDragFinished(slider.value) slider.sliderDragFinished(slider.value);
} }
} }
onPositionChanged: (mouse) => { onPositionChanged: (mouse) => {
if (pressed && isDragging && slider.enabled) { if (pressed && isDragging && slider.enabled) {
let ratio = Math.max(0, Math.min(1, mouse.x / width)) let ratio = Math.max(0, Math.min(1, mouse.x / width));
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum)) let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum));
slider.value = newValue slider.value = newValue;
slider.sliderValueChanged(newValue) slider.sliderValueChanged(newValue);
} }
} }
onClicked: (mouse) => { onClicked: (mouse) => {
if (slider.enabled) { if (slider.enabled) {
let ratio = Math.max(0, Math.min(1, mouse.x / width)) let ratio = Math.max(0, Math.min(1, mouse.x / width));
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum)) let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum));
slider.value = newValue slider.value = newValue;
slider.sliderValueChanged(newValue) slider.sliderValueChanged(newValue);
} }
} }
} }
// Global mouse area for drag tracking // Global mouse area for drag tracking
MouseArea { MouseArea {
id: sliderGlobalMouseArea id: sliderGlobalMouseArea
anchors.fill: sliderContainer anchors.fill: sliderContainer
enabled: sliderMouseArea.isDragging enabled: sliderMouseArea.isDragging
visible: false visible: false
preventStealing: true preventStealing: true
onPositionChanged: (mouse) => { onPositionChanged: (mouse) => {
if (sliderMouseArea.isDragging && slider.enabled) { if (sliderMouseArea.isDragging && slider.enabled) {
let globalPos = mapToItem(sliderTrack, mouse.x, mouse.y) let globalPos = mapToItem(sliderTrack, mouse.x, mouse.y);
let ratio = Math.max(0, Math.min(1, globalPos.x / sliderTrack.width)) let ratio = Math.max(0, Math.min(1, globalPos.x / sliderTrack.width));
let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum)) let newValue = Math.round(slider.minimum + ratio * (slider.maximum - slider.minimum));
slider.value = newValue slider.value = newValue;
slider.sliderValueChanged(newValue) slider.sliderValueChanged(newValue);
} }
} }
onReleased: { onReleased: {
if (sliderMouseArea.isDragging && slider.enabled) { if (sliderMouseArea.isDragging && slider.enabled) {
sliderMouseArea.isDragging = false sliderMouseArea.isDragging = false;
slider.sliderDragFinished(slider.value) slider.sliderDragFinished(slider.value);
} }
} }
} }
} }
} }
// Right icon // Right icon
Text { Text {
text: slider.rightIcon text: slider.rightIcon
@@ -197,6 +208,9 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: slider.rightIcon.length > 0 visible: slider.rightIcon.length > 0
} }
} }
} }
}
}

View File

@@ -1,13 +1,13 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
PanelWindow { PanelWindow {
id: inputDialog id: inputDialog
property bool dialogVisible: false property bool dialogVisible: false
property string dialogTitle: "Input Required" property string dialogTitle: "Input Required"
property string dialogSubtitle: "Please enter the required information" property string dialogSubtitle: "Please enter the required information"
@@ -16,68 +16,68 @@ PanelWindow {
property bool isPassword: false property bool isPassword: false
property string confirmButtonText: "Confirm" property string confirmButtonText: "Confirm"
property string cancelButtonText: "Cancel" property string cancelButtonText: "Cancel"
signal confirmed(string value) signal confirmed(string value)
signal cancelled() 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 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 { anchors {
top: true top: true
left: true left: true
right: true right: true
bottom: true 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 { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.5) 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 { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
MouseArea {
anchors.fill: parent
onClicked: {
inputDialog.cancelled()
hideDialog()
}
}
} }
Rectangle { Rectangle {
width: Math.min(400, parent.width - Theme.spacingL * 2) width: Math.min(400, parent.width - Theme.spacingL * 2)
height: Math.min(250, parent.height - Theme.spacingL * 2) height: Math.min(250, parent.height - Theme.spacingL * 2)
@@ -86,44 +86,29 @@ PanelWindow {
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1 border.width: 1
opacity: dialogVisible ? 1 : 0
opacity: dialogVisible ? 1.0 : 0.0 scale: dialogVisible ? 1 : 0.9
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
}
}
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
spacing: Theme.spacingL spacing: Theme.spacingL
// Header // Header
Row { Row {
width: parent.width width: parent.width
Column { Column {
width: parent.width - 40 width: parent.width - 40
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: dialogTitle text: dialogTitle
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { Text {
text: dialogSubtitle text: dialogSubtitle
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -133,14 +118,15 @@ PanelWindow {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
maximumLineCount: 2 maximumLineCount: 2
} }
} }
Rectangle { Rectangle {
width: 32 width: 32
height: 32 height: 32
radius: 16 radius: 16
color: closeDialogArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" color: closeDialogArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "close" text: "close"
@@ -148,20 +134,23 @@ PanelWindow {
font.pixelSize: Theme.iconSize - 4 font.pixelSize: Theme.iconSize - 4
color: closeDialogArea.containsMouse ? Theme.error : Theme.surfaceText color: closeDialogArea.containsMouse ? Theme.error : Theme.surfaceText
} }
MouseArea { MouseArea {
id: closeDialogArea id: closeDialogArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
inputDialog.cancelled() inputDialog.cancelled();
hideDialog() hideDialog();
} }
} }
} }
} }
// Text input // Text input
Rectangle { Rectangle {
width: parent.width width: parent.width
@@ -170,9 +159,10 @@ PanelWindow {
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
border.color: textInput.activeFocus ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: textInput.activeFocus ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: textInput.activeFocus ? 2 : 1 border.width: textInput.activeFocus ? 2 : 1
TextInput { TextInput {
id: textInput id: textInput
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -181,7 +171,19 @@ PanelWindow {
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
cursorVisible: activeFocus cursorVisible: activeFocus
selectByMouse: true selectByMouse: true
onTextChanged: {
inputValue = text;
}
onAccepted: {
inputDialog.confirmed(inputValue);
hideDialog();
}
Component.onCompleted: {
if (dialogVisible)
forceActiveFocus();
}
Text { Text {
anchors.fill: parent anchors.fill: parent
text: inputPlaceholder text: inputPlaceholder
@@ -190,48 +192,36 @@ PanelWindow {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
visible: parent.text.length === 0 visible: parent.text.length === 0
} }
onTextChanged: {
inputValue = text
}
onAccepted: {
inputDialog.confirmed(inputValue)
hideDialog()
}
Component.onCompleted: {
if (dialogVisible) {
forceActiveFocus()
}
}
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.IBeamCursor cursorShape: Qt.IBeamCursor
onClicked: { onClicked: {
textInput.forceActiveFocus() textInput.forceActiveFocus();
} }
} }
} }
// Show password checkbox (only visible for password inputs) // Show password checkbox (only visible for password inputs)
Row { Row {
spacing: Theme.spacingS spacing: Theme.spacingS
visible: isPassword visible: isPassword
Rectangle { Rectangle {
id: showPasswordCheckbox id: showPasswordCheckbox
property bool checked: false property bool checked: false
width: 20 width: 20
height: 20 height: 20
radius: 4 radius: 4
color: checked ? Theme.primary : "transparent" color: checked ? Theme.primary : "transparent"
border.color: checked ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.5) border.color: checked ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.5)
border.width: 2 border.width: 2
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "check" text: "check"
@@ -240,35 +230,37 @@ PanelWindow {
color: Theme.background color: Theme.background
visible: parent.checked visible: parent.checked
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
showPasswordCheckbox.checked = !showPasswordCheckbox.checked showPasswordCheckbox.checked = !showPasswordCheckbox.checked;
} }
} }
} }
Text { Text {
text: "Show password" text: "Show password"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
// Buttons // Buttons
Item { Item {
width: parent.width width: parent.width
height: 40 height: 40
Row { Row {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Rectangle { Rectangle {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2) width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36 height: 36
@@ -276,66 +268,94 @@ PanelWindow {
color: cancelArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent" color: cancelArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1 border.width: 1
Text { Text {
id: cancelText id: cancelText
anchors.centerIn: parent anchors.centerIn: parent
text: cancelButtonText text: cancelButtonText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
MouseArea { MouseArea {
id: cancelArea id: cancelArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
inputDialog.cancelled() inputDialog.cancelled();
hideDialog() hideDialog();
} }
} }
} }
Rectangle { Rectangle {
width: Math.max(80, confirmText.contentWidth + Theme.spacingM * 2) width: Math.max(80, confirmText.contentWidth + Theme.spacingM * 2)
height: 36 height: 36
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: confirmArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary color: confirmArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: inputValue.length > 0 enabled: inputValue.length > 0
opacity: enabled ? 1.0 : 0.5 opacity: enabled ? 1 : 0.5
Text { Text {
id: confirmText id: confirmText
anchors.centerIn: parent anchors.centerIn: parent
text: confirmButtonText text: confirmButtonText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium
} }
MouseArea { MouseArea {
id: confirmArea id: confirmArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: parent.enabled enabled: parent.enabled
onClicked: { onClicked: {
inputDialog.confirmed(inputValue) inputDialog.confirmed(inputValue);
hideDialog() hideDialog();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing 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
}
}
} }
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +1,64 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
PanelWindow { PanelWindow {
id: root id: root
property bool powerConfirmVisible: false property bool powerConfirmVisible: false
property string powerConfirmAction: "" property string powerConfirmAction: ""
property string powerConfirmTitle: "" property string powerConfirmTitle: ""
property string powerConfirmMessage: "" property string powerConfirmMessage: ""
function executePowerAction(action) {
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 visible: powerConfirmVisible
implicitWidth: 400 implicitWidth: 400
implicitHeight: 300 implicitHeight: 300
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
color: "transparent" color: "transparent"
anchors { anchors {
top: true top: true
left: true left: true
right: true right: true
bottom: true bottom: true
} }
// Darkened background // Darkened background
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: "black" color: "black"
opacity: 0.5 opacity: 0.5
} }
Rectangle { Rectangle {
width: Math.min(400, parent.width - Theme.spacingL * 2) width: Math.min(400, parent.width - Theme.spacingL * 2)
height: Math.min(200, parent.height - Theme.spacingL * 2) height: Math.min(200, parent.height - Theme.spacingL * 2)
@@ -47,45 +67,33 @@ PanelWindow {
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1 border.width: 1
opacity: powerConfirmVisible ? 1 : 0
opacity: powerConfirmVisible ? 1.0 : 0.0 scale: powerConfirmVisible ? 1 : 0.9
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
}
}
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - Theme.spacingL * 2 width: parent.width - Theme.spacingL * 2
spacing: Theme.spacingL spacing: Theme.spacingL
// Title // Title
Text { Text {
text: powerConfirmTitle text: powerConfirmTitle
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: { color: {
switch(powerConfirmAction) { switch (powerConfirmAction) {
case "poweroff": return Theme.error case "poweroff":
case "reboot": return Theme.warning return Theme.error;
default: return Theme.surfaceText case "reboot":
return Theme.warning;
default:
return Theme.surfaceText;
} }
} }
font.weight: Font.Medium font.weight: Font.Medium
width: parent.width width: parent.width
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
// Message // Message
Text { Text {
text: powerConfirmMessage text: powerConfirmMessage
@@ -95,21 +103,23 @@ PanelWindow {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Item { height: Theme.spacingL } Item {
height: Theme.spacingL
}
// Buttons // Buttons
Row { Row {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
// Cancel button // Cancel button
Rectangle { Rectangle {
width: 120 width: 120
height: 40 height: 40
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: cancelButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) color: cancelButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Text { Text {
text: "Cancel" text: "Cancel"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -117,35 +127,41 @@ PanelWindow {
font.weight: Font.Medium font.weight: Font.Medium
anchors.centerIn: parent anchors.centerIn: parent
} }
MouseArea { MouseArea {
id: cancelButton id: cancelButton
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
powerConfirmVisible = false powerConfirmVisible = false;
} }
} }
} }
// Confirm button // Confirm button
Rectangle { Rectangle {
width: 120 width: 120
height: 40 height: 40
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
let baseColor let baseColor;
switch(powerConfirmAction) { switch (powerConfirmAction) {
case "poweroff": baseColor = Theme.error; break case "poweroff":
case "reboot": baseColor = Theme.warning; break baseColor = Theme.error;
default: baseColor = Theme.primary; break break;
case "reboot":
baseColor = Theme.warning;
break;
default:
baseColor = Theme.primary;
break;
} }
return confirmButton.containsMouse ? return confirmButton.containsMouse ? Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) : baseColor;
Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) :
baseColor
} }
Text { Text {
text: "Confirm" text: "Confirm"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -153,55 +169,52 @@ PanelWindow {
font.weight: Font.Medium font.weight: Font.Medium
anchors.centerIn: parent anchors.centerIn: parent
} }
MouseArea { MouseArea {
id: confirmButton id: confirmButton
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
powerConfirmVisible = false powerConfirmVisible = false;
executePowerAction(powerConfirmAction) executePowerAction(powerConfirmAction);
} }
} }
} }
} }
} }
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
} }
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
}
}
Process { Process {
id: powerActionProcess id: powerActionProcess
running: false running: false
onExited: (exitCode) => { onExited: (exitCode) => {
if (exitCode !== 0) { if (exitCode !== 0)
console.error("Power action failed with exit code:", exitCode) console.error("Power action failed with exit code:", exitCode);
}
} }
} }
}
}

View File

@@ -1,86 +1,69 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
PanelWindow { PanelWindow {
id: root id: root
property bool powerMenuVisible: false property bool powerMenuVisible: false
visible: powerMenuVisible visible: powerMenuVisible
implicitWidth: 400 implicitWidth: 400
implicitHeight: 320 implicitHeight: 320
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent" color: "transparent"
anchors { anchors {
top: true top: true
left: true left: true
right: true right: true
bottom: true bottom: true
} }
// Click outside to dismiss overlay // Click outside to dismiss overlay
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
powerMenuVisible = false powerMenuVisible = false;
} }
} }
Rectangle { Rectangle {
width: Math.min(320, parent.width - Theme.spacingL * 2) width: Math.min(320, parent.width - Theme.spacingL * 2)
height: 320 // Fixed height to prevent cropping height: 320 // Fixed height to prevent cropping
x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL) x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL)
y: Theme.barHeight + Theme.spacingXS y: Theme.barHeight + Theme.spacingXS
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
opacity: powerMenuVisible ? 1 : 0
opacity: powerMenuVisible ? 1.0 : 0.0 scale: powerMenuVisible ? 1 : 0.85
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
}
}
// Prevent click-through to background // Prevent click-through to background
MouseArea { MouseArea {
// Consume the click to prevent it from reaching the background
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
// Consume the click to prevent it from reaching the background
} }
} }
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
spacing: Theme.spacingM spacing: Theme.spacingM
// Header // Header
Row { Row {
width: parent.width width: parent.width
Text { Text {
text: "Power Options" text: "Power Options"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
@@ -88,15 +71,18 @@ PanelWindow {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Item { width: parent.width - 150; height: 1 } Item {
width: parent.width - 150
height: 1
}
Rectangle { Rectangle {
width: 32 width: 32
height: 32 height: 32
radius: 16 radius: 16
color: closePowerArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" color: closePowerArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "close" text: "close"
@@ -104,37 +90,40 @@ PanelWindow {
font.pixelSize: Theme.iconSize - 4 font.pixelSize: Theme.iconSize - 4
color: closePowerArea.containsMouse ? Theme.error : Theme.surfaceText color: closePowerArea.containsMouse ? Theme.error : Theme.surfaceText
} }
MouseArea { MouseArea {
id: closePowerArea id: closePowerArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
powerMenuVisible = false powerMenuVisible = false;
} }
} }
} }
} }
// Power options // Power options
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
// Log Out // Log Out
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: logoutArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) color: logoutArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "logout" text: "logout"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -142,7 +131,7 @@ PanelWindow {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: "Log Out" text: "Log Out"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -150,36 +139,39 @@ PanelWindow {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: logoutArea id: logoutArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
powerMenuVisible = false powerMenuVisible = false;
root.powerConfirmAction = "logout" root.powerConfirmAction = "logout";
root.powerConfirmTitle = "Log Out" root.powerConfirmTitle = "Log Out";
root.powerConfirmMessage = "Are you sure you want to log out?" root.powerConfirmMessage = "Are you sure you want to log out?";
root.powerConfirmVisible = true root.powerConfirmVisible = true;
} }
} }
} }
// Suspend // Suspend
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: suspendArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) color: suspendArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "bedtime" text: "bedtime"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -187,7 +179,7 @@ PanelWindow {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: "Suspend" text: "Suspend"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -195,36 +187,39 @@ PanelWindow {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: suspendArea id: suspendArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
powerMenuVisible = false powerMenuVisible = false;
root.powerConfirmAction = "suspend" root.powerConfirmAction = "suspend";
root.powerConfirmTitle = "Suspend" root.powerConfirmTitle = "Suspend";
root.powerConfirmMessage = "Are you sure you want to suspend the system?" root.powerConfirmMessage = "Are you sure you want to suspend the system?";
root.powerConfirmVisible = true root.powerConfirmVisible = true;
} }
} }
} }
// Reboot // Reboot
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: rebootArea.containsMouse ? Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) color: rebootArea.containsMouse ? Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "restart_alt" text: "restart_alt"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -232,7 +227,7 @@ PanelWindow {
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: "Reboot" text: "Reboot"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -240,36 +235,39 @@ PanelWindow {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: rebootArea id: rebootArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
powerMenuVisible = false powerMenuVisible = false;
root.powerConfirmAction = "reboot" root.powerConfirmAction = "reboot";
root.powerConfirmTitle = "Reboot" root.powerConfirmTitle = "Reboot";
root.powerConfirmMessage = "Are you sure you want to reboot the system?" root.powerConfirmMessage = "Are you sure you want to reboot the system?";
root.powerConfirmVisible = true root.powerConfirmVisible = true;
} }
} }
} }
// Power Off // Power Off
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: powerOffArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) color: powerOffArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "power_settings_new" text: "power_settings_new"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -277,7 +275,7 @@ PanelWindow {
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: "Power Off" text: "Power Off"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -285,23 +283,46 @@ PanelWindow {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: powerOffArea id: powerOffArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
powerMenuVisible = false powerMenuVisible = false;
root.powerConfirmAction = "poweroff" root.powerConfirmAction = "poweroff";
root.powerConfirmTitle = "Power Off" root.powerConfirmTitle = "Power Off";
root.powerConfirmMessage = "Are you sure you want to power off the system?" root.powerConfirmMessage = "Are you sure you want to power off the system?";
root.powerConfirmVisible = true 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
}
}
} }
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +1,54 @@
import "."
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Common import qs.Common
import qs.Services import qs.Services
import "."
Rectangle { Rectangle {
id: ramWidget id: ramWidget
property bool showPercentage: true property bool showPercentage: true
property bool showIcon: true property bool showIcon: true
width: 55 width: 55
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: ramArea.containsMouse ? 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)
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 { MouseArea {
id: ramArea id: ramArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
ProcessMonitorService.setSortBy("memory") ProcessMonitorService.setSortBy("memory");
processListDropdown.toggle() processListDropdown.toggle();
} }
} }
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 3 spacing: 3
// RAM icon // RAM icon
Text { Text {
text: "developer_board" // Material Design CPU/processor icon (swapped from CPU widget) text: "developer_board" // Material Design CPU/processor icon (swapped from CPU widget)
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 8 font.pixelSize: Theme.iconSize - 8
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
color: { color: {
if (SystemMonitorService.memoryUsage > 90) return Theme.error if (SystemMonitorService.memoryUsage > 90)
if (SystemMonitorService.memoryUsage > 75) return Theme.warning return Theme.error;
return Theme.surfaceText
if (SystemMonitorService.memoryUsage > 75)
return Theme.warning;
return Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
// Percentage text // Percentage text
Text { Text {
text: (SystemMonitorService.memoryUsage || 0).toFixed(0) + "%" text: (SystemMonitorService.memoryUsage || 0).toFixed(0) + "%"
@@ -55,5 +57,7 @@ Rectangle {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
} }

View File

@@ -2,56 +2,55 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
PanelWindow { PanelWindow {
id: settingsPopup id: settingsPopup
property bool settingsVisible: false property bool settingsVisible: false
signal closingPopup() signal closingPopup()
onSettingsVisibleChanged: { onSettingsVisibleChanged: {
if (!settingsVisible) { if (!settingsVisible)
closingPopup() closingPopup();
}
} }
visible: settingsVisible visible: settingsVisible
implicitWidth: 600 implicitWidth: 600
implicitHeight: 700 implicitHeight: 700
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
color: "transparent" color: "transparent"
anchors { anchors {
top: true top: true
left: true left: true
right: true right: true
bottom: true bottom: true
} }
// Darkened background // Darkened background
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: "black" color: "black"
opacity: 0.5 opacity: 0.5
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: settingsPopup.settingsVisible = false onClicked: settingsPopup.settingsVisible = false
} }
} }
// Main settings panel - spotlight-like centered appearance // Main settings panel - spotlight-like centered appearance
Rectangle { Rectangle {
id: mainPanel id: mainPanel
width: Math.min(600, parent.width - Theme.spacingXL * 2) width: Math.min(600, parent.width - Theme.spacingXL * 2)
height: Math.min(700, parent.height - Theme.spacingXL * 2) height: Math.min(700, parent.height - Theme.spacingXL * 2)
anchors.centerIn: parent anchors.centerIn: parent
@@ -59,35 +58,22 @@ PanelWindow {
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
// Simple opacity and scale control tied directly to settingsVisible // Simple opacity and scale control tied directly to settingsVisible
opacity: settingsPopup.settingsVisible ? 1.0 : 0.0 opacity: settingsPopup.settingsVisible ? 1 : 0
scale: settingsPopup.settingsVisible ? 1.0 : 0.95 scale: settingsPopup.settingsVisible ? 1 : 0.95
// Add shadow effect
Behavior on opacity { layer.enabled: true
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
spacing: Theme.spacingL spacing: Theme.spacingL
// Header // Header
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "settings" text: "settings"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -95,7 +81,7 @@ PanelWindow {
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: "Settings" text: "Settings"
font.pixelSize: Theme.fontSizeXLarge font.pixelSize: Theme.fontSizeXLarge
@@ -103,21 +89,19 @@ PanelWindow {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Item { Item {
width: parent.width - 175 // Spacer to push close button to the right width: parent.width - 175 // Spacer to push close button to the right
height: 1 height: 1
} }
// Close button // Close button
Rectangle { Rectangle {
width: 32 width: 32
height: 32 height: 32
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: closeButton.containsMouse ? color: closeButton.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) :
"transparent"
Text { Text {
text: "close" text: "close"
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -125,61 +109,65 @@ PanelWindow {
color: Theme.surfaceText color: Theme.surfaceText
anchors.centerIn: parent anchors.centerIn: parent
} }
MouseArea { MouseArea {
id: closeButton id: closeButton
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: settingsPopup.settingsVisible = false onClicked: settingsPopup.settingsVisible = false
} }
} }
} }
// Settings sections // Settings sections
ScrollView { ScrollView {
width: parent.width width: parent.width
height: parent.height - 80 height: parent.height - 80
clip: true clip: true
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingL spacing: Theme.spacingL
// Profile Settings // Profile Settings
SettingsSection { SettingsSection {
title: "Profile" title: "Profile"
iconName: "person" iconName: "person"
content: Column { content: Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
// Profile Image Preview and Input // Profile Image Preview and Input
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: "Profile Image" text: "Profile Image"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
// Profile Image Preview with circular crop // Profile Image Preview with circular crop
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
// Circular profile image preview // Circular profile image preview
Item { Item {
id: avatarContainer id: avatarContainer
width: 54
height: 54
property bool hasImage: avatarImageSource.status === Image.Ready property bool hasImage: avatarImageSource.status === Image.Ready
width: 54
height: 54
// This rectangle provides the themed ring via its border. // This rectangle provides the themed ring via its border.
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -193,12 +181,15 @@ PanelWindow {
// Hidden Image loader. Its only purpose is to load the texture. // Hidden Image loader. Its only purpose is to load the texture.
Image { Image {
id: avatarImageSource id: avatarImageSource
source: { source: {
if (profileImageInput.text === "") return "" if (profileImageInput.text === "")
if (profileImageInput.text.startsWith("/")) { return "";
return "file://" + profileImageInput.text
} if (profileImageInput.text.startsWith("/"))
return profileImageInput.text return "file://" + profileImageInput.text;
return profileImageInput.text;
} }
smooth: true smooth: true
asynchronous: true asynchronous: true
@@ -215,11 +206,12 @@ PanelWindow {
maskSource: settingsCircularMask maskSource: settingsCircularMask
visible: avatarContainer.hasImage visible: avatarContainer.hasImage
maskThresholdMin: 0.5 maskThresholdMin: 0.5
maskSpreadAtMin: 1.0 maskSpreadAtMin: 1
} }
Item { Item {
id: settingsCircularMask id: settingsCircularMask
width: 54 - 10 width: 54 - 10
height: 54 - 10 height: 54 - 10
layer.enabled: true layer.enabled: true
@@ -232,6 +224,7 @@ PanelWindow {
color: "black" color: "black"
antialiasing: true antialiasing: true
} }
} }
// Fallback for when there is no image. // Fallback for when there is no image.
@@ -248,6 +241,7 @@ PanelWindow {
font.pixelSize: Theme.iconSize + 8 font.pixelSize: Theme.iconSize + 8
color: Theme.primaryText color: Theme.primaryText
} }
} }
// Error icon for when the image fails to load. // Error icon for when the image fails to load.
@@ -259,13 +253,14 @@ PanelWindow {
color: Theme.primaryText color: Theme.primaryText
visible: profileImageInput.text !== "" && avatarImageSource.status === Image.Error visible: profileImageInput.text !== "" && avatarImageSource.status === Image.Error
} }
} }
// Input field // Input field
Column { Column {
width: parent.width - 80 - Theme.spacingM width: parent.width - 80 - Theme.spacingM
spacing: Theme.spacingS spacing: Theme.spacingS
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 48 height: 48
@@ -273,9 +268,10 @@ PanelWindow {
color: Theme.surfaceVariant color: Theme.surfaceVariant
border.color: profileImageInput.activeFocus ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) border.color: profileImageInput.activeFocus ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
border.width: profileImageInput.activeFocus ? 2 : 1 border.width: profileImageInput.activeFocus ? 2 : 1
TextInput { TextInput {
id: profileImageInput id: profileImageInput
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
@@ -283,11 +279,10 @@ PanelWindow {
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
text: Prefs.profileImage text: Prefs.profileImage
selectByMouse: true selectByMouse: true
onEditingFinished: { onEditingFinished: {
Prefs.setProfileImage(text) Prefs.setProfileImage(text);
} }
// Placeholder text // Placeholder text
Text { Text {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -296,16 +291,18 @@ PanelWindow {
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
visible: profileImageInput.text.length === 0 && !profileImageInput.activeFocus visible: profileImageInput.text.length === 0 && !profileImageInput.activeFocus
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.IBeamCursor cursorShape: Qt.IBeamCursor
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
} }
} }
} }
Text { Text {
text: "Local filesystem path or URL to an image file." text: "Local filesystem path or URL to an image file."
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -313,58 +310,69 @@ PanelWindow {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
} }
} }
} }
} }
} }
} }
// Clock Settings // Clock Settings
SettingsSection { SettingsSection {
title: "Clock & Time" title: "Clock & Time"
iconName: "schedule" iconName: "schedule"
content: Column { content: Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
SettingsToggle { SettingsToggle {
text: "24-Hour Format" text: "24-Hour Format"
description: "Use 24-hour time format instead of 12-hour AM/PM" description: "Use 24-hour time format instead of 12-hour AM/PM"
checked: Prefs.use24HourClock checked: Prefs.use24HourClock
onToggled: (checked) => Prefs.setClockFormat(checked) onToggled: (checked) => {
return Prefs.setClockFormat(checked);
}
} }
} }
} }
// Weather Settings // Weather Settings
SettingsSection { SettingsSection {
title: "Weather" title: "Weather"
iconName: "wb_sunny" iconName: "wb_sunny"
content: Column { content: Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
SettingsToggle { SettingsToggle {
text: "Fahrenheit" text: "Fahrenheit"
description: "Use Fahrenheit instead of Celsius for temperature" description: "Use Fahrenheit instead of Celsius for temperature"
checked: Prefs.useFahrenheit checked: Prefs.useFahrenheit
onToggled: (checked) => Prefs.setTemperatureUnit(checked) onToggled: (checked) => {
return Prefs.setTemperatureUnit(checked);
}
} }
// Weather Location Setting // Weather Location Setting
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: "Location" text: "Location"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 48 height: 48
@@ -372,9 +380,10 @@ PanelWindow {
color: Theme.surfaceVariant color: Theme.surfaceVariant
border.color: weatherLocationInput.activeFocus ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) border.color: weatherLocationInput.activeFocus ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
border.width: weatherLocationInput.activeFocus ? 2 : 1 border.width: weatherLocationInput.activeFocus ? 2 : 1
TextInput { TextInput {
id: weatherLocationInput id: weatherLocationInput
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
@@ -382,11 +391,10 @@ PanelWindow {
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
text: Prefs.weatherLocationOverride text: Prefs.weatherLocationOverride
selectByMouse: true selectByMouse: true
onEditingFinished: { onEditingFinished: {
Prefs.setWeatherLocationOverride(text) Prefs.setWeatherLocationOverride(text);
} }
// Placeholder text // Placeholder text
Text { Text {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -395,16 +403,18 @@ PanelWindow {
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
visible: weatherLocationInput.text.length === 0 && !weatherLocationInput.activeFocus visible: weatherLocationInput.text.length === 0 && !weatherLocationInput.activeFocus
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.IBeamCursor cursorShape: Qt.IBeamCursor
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
} }
} }
} }
Text { Text {
text: "Examples: \"New York, NY\", \"London\", \"Tokyo\"" text: "Examples: \"New York, NY\", \"London\", \"Tokyo\""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -412,108 +422,124 @@ PanelWindow {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
} }
} }
} }
} }
// Widget Visibility Settings // Widget Visibility Settings
SettingsSection { SettingsSection {
title: "Top Bar Widgets" title: "Top Bar Widgets"
iconName: "widgets" iconName: "widgets"
content: Column { content: Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
SettingsToggle { SettingsToggle {
text: "Focused Window" text: "Focused Window"
description: "Show the currently focused application in the top bar" description: "Show the currently focused application in the top bar"
checked: Prefs.showFocusedWindow checked: Prefs.showFocusedWindow
onToggled: (checked) => Prefs.setShowFocusedWindow(checked) onToggled: (checked) => {
return Prefs.setShowFocusedWindow(checked);
}
} }
SettingsToggle { SettingsToggle {
text: "Weather Widget" text: "Weather Widget"
description: "Display weather information in the top bar" description: "Display weather information in the top bar"
checked: Prefs.showWeather checked: Prefs.showWeather
onToggled: (checked) => Prefs.setShowWeather(checked) onToggled: (checked) => {
return Prefs.setShowWeather(checked);
}
} }
SettingsToggle { SettingsToggle {
text: "Media Controls" text: "Media Controls"
description: "Show currently playing media in the top bar" description: "Show currently playing media in the top bar"
checked: Prefs.showMusic checked: Prefs.showMusic
onToggled: (checked) => Prefs.setShowMusic(checked) onToggled: (checked) => {
return Prefs.setShowMusic(checked);
}
} }
SettingsToggle { SettingsToggle {
text: "Clipboard Button" text: "Clipboard Button"
description: "Show clipboard access button in the top bar" description: "Show clipboard access button in the top bar"
checked: Prefs.showClipboard checked: Prefs.showClipboard
onToggled: (checked) => Prefs.setShowClipboard(checked) onToggled: (checked) => {
return Prefs.setShowClipboard(checked);
}
} }
SettingsToggle { SettingsToggle {
text: "System Resources" text: "System Resources"
description: "Display CPU and RAM usage indicators" description: "Display CPU and RAM usage indicators"
checked: Prefs.showSystemResources checked: Prefs.showSystemResources
onToggled: (checked) => Prefs.setShowSystemResources(checked) onToggled: (checked) => {
return Prefs.setShowSystemResources(checked);
}
} }
SettingsToggle { SettingsToggle {
text: "System Tray" text: "System Tray"
description: "Show system tray icons in the top bar" description: "Show system tray icons in the top bar"
checked: Prefs.showSystemTray checked: Prefs.showSystemTray
onToggled: (checked) => Prefs.setShowSystemTray(checked) onToggled: (checked) => {
return Prefs.setShowSystemTray(checked);
}
} }
} }
} }
// Display Settings // Display Settings
SettingsSection { SettingsSection {
title: "Display & Appearance" title: "Display & Appearance"
iconName: "palette" iconName: "palette"
content: Column { content: Column {
width: parent.width width: parent.width
spacing: Theme.spacingL spacing: Theme.spacingL
SettingsToggle { SettingsToggle {
text: "Night Mode" text: "Night Mode"
description: "Apply warm color temperature to reduce eye strain" description: "Apply warm color temperature to reduce eye strain"
checked: Prefs.nightModeEnabled checked: Prefs.nightModeEnabled
onToggled: (checked) => { onToggled: (checked) => {
Prefs.setNightModeEnabled(checked) Prefs.setNightModeEnabled(checked);
if (checked) { if (checked)
nightModeEnableProcess.running = true nightModeEnableProcess.running = true;
} else { else
nightModeDisableProcess.running = true nightModeDisableProcess.running = true;
}
} }
} }
SettingsToggle { SettingsToggle {
text: "Light Mode" text: "Light Mode"
description: "Use light theme instead of dark theme" description: "Use light theme instead of dark theme"
checked: Prefs.isLightMode checked: Prefs.isLightMode
onToggled: (checked) => { onToggled: (checked) => {
Prefs.setLightMode(checked) Prefs.setLightMode(checked);
Theme.isLightMode = checked Theme.isLightMode = checked;
} }
} }
// Top Bar Transparency // Top Bar Transparency
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: "Top Bar Transparency" text: "Top Bar Transparency"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
CustomSlider { CustomSlider {
width: parent.width width: parent.width
value: Math.round(Prefs.topBarTransparency * 100) value: Math.round(Prefs.topBarTransparency * 100)
@@ -523,13 +549,12 @@ PanelWindow {
rightIcon: "circle" rightIcon: "circle"
unit: "%" unit: "%"
showValue: true showValue: true
onSliderDragFinished: (finalValue) => { onSliderDragFinished: (finalValue) => {
let transparencyValue = finalValue / 100.0 let transparencyValue = finalValue / 100;
Prefs.setTopBarTransparency(transparencyValue) Prefs.setTopBarTransparency(transparencyValue);
} }
} }
Text { Text {
text: "Adjust the transparency of the top bar background" text: "Adjust the transparency of the top bar background"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -537,20 +562,21 @@ PanelWindow {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
} }
} }
// Popup Transparency // Popup Transparency
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: "Popup Transparency" text: "Popup Transparency"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
CustomSlider { CustomSlider {
width: parent.width width: parent.width
value: Math.round(Prefs.popupTransparency * 100) value: Math.round(Prefs.popupTransparency * 100)
@@ -560,13 +586,12 @@ PanelWindow {
rightIcon: "circle" rightIcon: "circle"
unit: "%" unit: "%"
showValue: true showValue: true
onSliderDragFinished: (finalValue) => { onSliderDragFinished: (finalValue) => {
let transparencyValue = finalValue / 100.0 let transparencyValue = finalValue / 100;
Prefs.setPopupTransparency(transparencyValue) Prefs.setPopupTransparency(transparencyValue);
} }
} }
Text { Text {
text: "Adjust transparency for dialogs, menus, and popups" text: "Adjust transparency for dialogs, menus, and popups"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -574,65 +599,87 @@ PanelWindow {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
} }
} }
// Theme Picker // Theme Picker
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: "Theme Color" text: "Theme Color"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
ThemePicker { ThemePicker {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
} }
} }
} }
} }
} }
// Add shadow effect Behavior on opacity {
layer.enabled: true NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
layer.effect: MultiEffect { layer.effect: MultiEffect {
shadowEnabled: true shadowEnabled: true
shadowHorizontalOffset: 0 shadowHorizontalOffset: 0
shadowVerticalOffset: 8 shadowVerticalOffset: 8
shadowBlur: 1.0 shadowBlur: 1
shadowColor: Qt.rgba(0, 0, 0, 0.3) shadowColor: Qt.rgba(0, 0, 0, 0.3)
shadowOpacity: 0.3 shadowOpacity: 0.3
} }
} }
// Night mode processes // Night mode processes
Process { Process {
id: nightModeEnableProcess id: nightModeEnableProcess
command: ["bash", "-c", "if command -v wlsunset > /dev/null; then pkill wlsunset; wlsunset -t 3000 & elif command -v redshift > /dev/null; then pkill redshift; redshift -P -O 3000 & else echo 'No night mode tool available'; fi"] command: ["bash", "-c", "if command -v wlsunset > /dev/null; then pkill wlsunset; wlsunset -t 3000 & elif command -v redshift > /dev/null; then pkill redshift; redshift -P -O 3000 & else echo 'No night mode tool available'; fi"]
running: false running: false
onExited: (exitCode) => { onExited: (exitCode) => {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("Failed to enable night mode") console.warn("Failed to enable night mode");
Prefs.setNightModeEnabled(false) Prefs.setNightModeEnabled(false);
} }
} }
} }
Process { Process {
id: nightModeDisableProcess 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"] 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 running: false
onExited: (exitCode) => { onExited: (exitCode) => {
if (exitCode !== 0) { if (exitCode !== 0)
console.warn("Failed to disable night mode") console.warn("Failed to disable night mode");
}
} }
} }
@@ -640,7 +687,7 @@ PanelWindow {
FocusScope { FocusScope {
anchors.fill: parent anchors.fill: parent
focus: settingsPopup.settingsVisible focus: settingsPopup.settingsVisible
Keys.onEscapePressed: settingsPopup.settingsVisible = false Keys.onEscapePressed: settingsPopup.settingsVisible = false
} }
}
}

View File

@@ -3,19 +3,19 @@ import qs.Common
Column { Column {
id: root id: root
property string title: "" property string title: ""
property string iconName: "" property string iconName: ""
property alias content: contentLoader.sourceComponent property alias content: contentLoader.sourceComponent
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
// Section header // Section header
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: iconName text: iconName
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -23,7 +23,7 @@ Column {
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: title text: title
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
@@ -31,18 +31,21 @@ Column {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
// Divider // Divider
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
} }
// Content // Content
Loader { Loader {
id: contentLoader id: contentLoader
width: parent.width width: parent.width
} }
}
}

View File

@@ -3,27 +3,18 @@ import qs.Common
Rectangle { Rectangle {
id: root id: root
property string text: "" property string text: ""
property string description: "" property string description: ""
property bool checked: false property bool checked: false
signal toggled(bool checked) signal toggled(bool checked)
width: parent.width width: parent.width
height: 60 height: 60
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: toggleArea.containsMouse ? 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)
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
}
}
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.right: toggle.left anchors.right: toggle.left
@@ -31,18 +22,18 @@ Rectangle {
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingXS spacing: Theme.spacingXS
Column { Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: root.text text: root.text
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { Text {
text: root.description text: root.description
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -51,62 +42,79 @@ Rectangle {
width: Math.min(implicitWidth, root.width - 120) width: Math.min(implicitWidth, root.width - 120)
visible: root.description.length > 0 visible: root.description.length > 0
} }
} }
} }
// Toggle switch // Toggle switch
Rectangle { Rectangle {
id: toggle id: toggle
width: 48 width: 48
height: 24 height: 24
radius: 12 radius: 12
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: root.checked ? Theme.primary : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5) 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 { Rectangle {
id: toggleHandle id: toggleHandle
width: 20 width: 20
height: 20 height: 20
radius: 10 radius: 10
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
x: root.checked ? parent.width - width - 2 : 2 x: root.checked ? parent.width - width - 2 : 2
color: root.checked ? Theme.primaryText : Theme.surfaceText color: root.checked ? Theme.primaryText : Theme.surfaceText
Behavior on x { Behavior on x {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
MouseArea { MouseArea {
id: toggleArea id: toggleArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.checked = !root.checked root.checked = !root.checked;
root.toggled(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

View File

@@ -4,8 +4,9 @@ import qs.Services
Column { Column {
id: themePicker id: themePicker
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: "Current Theme: " + (Theme.isDynamicTheme ? "Auto" : (Theme.currentThemeIndex < Theme.themes.length ? Theme.themes[Theme.currentThemeIndex].name : "Blue")) text: "Current Theme: " + (Theme.isDynamicTheme ? "Auto" : (Theme.currentThemeIndex < Theme.themes.length ? Theme.themes[Theme.currentThemeIndex].name : "Blue"))
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -13,26 +14,15 @@ Column {
font.weight: Font.Medium font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
// Theme description // Theme description
Text { Text {
text: { text: {
if (Theme.isDynamicTheme) { if (Theme.isDynamicTheme)
return "Wallpaper-based dynamic colors" return "Wallpaper-based dynamic colors";
}
var descriptions = [ 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"];
"Material blue inspired by modern interfaces", return descriptions[Theme.currentThemeIndex] || "Select a theme";
"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 font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
@@ -41,20 +31,20 @@ Column {
width: Math.min(parent.width, 200) width: Math.min(parent.width, 200)
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
// Grid layout for 10 themes (2 rows of 5) // Grid layout for 10 themes (2 rows of 5)
Column { Column {
spacing: Theme.spacingS spacing: Theme.spacingS
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
// First row - Blue, Deep Blue, Purple, Green, Orange // First row - Blue, Deep Blue, Purple, Green, Orange
Row { Row {
spacing: Theme.spacingM spacing: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
Repeater { Repeater {
model: 5 model: 5
Rectangle { Rectangle {
width: 32 width: 32
height: 32 height: 32
@@ -62,23 +52,8 @@ Column {
color: Theme.themes[index].primary color: Theme.themes[index].primary
border.color: Theme.outline border.color: Theme.outline
border.width: (Theme.currentThemeIndex === index && !Theme.isDynamicTheme) ? 2 : 1 border.width: (Theme.currentThemeIndex === index && !Theme.isDynamicTheme) ? 2 : 1
scale: (Theme.currentThemeIndex === index && !Theme.isDynamicTheme) ? 1.1 : 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
}
}
// Theme name tooltip // Theme name tooltip
Rectangle { Rectangle {
width: nameText.contentWidth + Theme.spacingS * 2 width: nameText.contentWidth + Theme.spacingS * 2
@@ -91,39 +66,62 @@ Column {
anchors.bottomMargin: Theme.spacingXS anchors.bottomMargin: Theme.spacingXS
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: mouseArea.containsMouse visible: mouseArea.containsMouse
Text { Text {
id: nameText id: nameText
text: Theme.themes[index].name text: Theme.themes[index].name
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
anchors.centerIn: parent anchors.centerIn: parent
} }
} }
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { 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 // Second row - Red, Cyan, Pink, Amber, Coral
Row { Row {
spacing: Theme.spacingM spacing: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
Repeater { Repeater {
model: 5 model: 5
Rectangle { Rectangle {
property int themeIndex: index + 5 property int themeIndex: index + 5
width: 32 width: 32
height: 32 height: 32
radius: 16 radius: 16
@@ -131,23 +129,8 @@ Column {
border.color: Theme.outline border.color: Theme.outline
border.width: Theme.currentThemeIndex === themeIndex ? 2 : 1 border.width: Theme.currentThemeIndex === themeIndex ? 2 : 1
visible: themeIndex < Theme.themes.length visible: themeIndex < Theme.themes.length
scale: Theme.currentThemeIndex === themeIndex ? 1.1 : 1
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
}
}
// Theme name tooltip // Theme name tooltip
Rectangle { Rectangle {
width: nameText2.contentWidth + Theme.spacingS * 2 width: nameText2.contentWidth + Theme.spacingS * 2
@@ -160,133 +143,138 @@ Column {
anchors.bottomMargin: Theme.spacingXS anchors.bottomMargin: Theme.spacingXS
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: mouseArea2.containsMouse && themeIndex < Theme.themes.length visible: mouseArea2.containsMouse && themeIndex < Theme.themes.length
Text { Text {
id: nameText2 id: nameText2
text: themeIndex < Theme.themes.length ? Theme.themes[themeIndex].name : "" text: themeIndex < Theme.themes.length ? Theme.themes[themeIndex].name : ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
anchors.centerIn: parent anchors.centerIn: parent
} }
} }
MouseArea { MouseArea {
id: mouseArea2 id: mouseArea2
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (themeIndex < Theme.themes.length) { if (themeIndex < Theme.themes.length)
Theme.switchTheme(themeIndex) 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 // Spacer for better visual separation
Item { Item {
width: 1 width: 1
height: Theme.spacingM height: Theme.spacingM
} }
// Auto theme button - prominent oval below the grid // Auto theme button - prominent oval below the grid
Rectangle { Rectangle {
width: 120 width: 120
height: 40 height: 40
radius: 20 radius: 20
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
color: { color: {
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") { if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
} else { else
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3);
}
} }
border.color: { border.color: {
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") { if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.5) return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.5);
} else if (Theme.isDynamicTheme) { else if (Theme.isDynamicTheme)
return Theme.primary return Theme.primary;
} else { else
return Theme.outline return Theme.outline;
}
} }
border.width: Theme.isDynamicTheme ? 2 : 1 border.width: Theme.isDynamicTheme ? 2 : 1
scale: Theme.isDynamicTheme ? 1.1 : (autoMouseArea.containsMouse ? 1.02 : 1)
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: { text: {
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") return "error" if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
else return "palette" return "error";
else
return "palette";
} }
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: 16 font.pixelSize: 16
color: { color: {
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") return Theme.error if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
else return Theme.surfaceText return Theme.error;
else
return Theme.surfaceText;
} }
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: { text: {
if (ToastService.wallpaperErrorStatus === "error") return "Error" if (ToastService.wallpaperErrorStatus === "error")
else if (ToastService.wallpaperErrorStatus === "matugen_missing") return "No matugen" return "Error";
else return "Auto" else if (ToastService.wallpaperErrorStatus === "matugen_missing")
return "No matugen";
else
return "Auto";
} }
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: { color: {
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") return Theme.error if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
else return Theme.surfaceText return Theme.error;
else
return Theme.surfaceText;
} }
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter 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 { MouseArea {
id: autoMouseArea id: autoMouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
Theme.switchTheme(10, true) Theme.switchTheme(10, true);
} }
} }
// Tooltip for Auto button // Tooltip for Auto button
Rectangle { Rectangle {
width: autoTooltipText.contentWidth + Theme.spacingM * 2 width: autoTooltipText.contentWidth + Theme.spacingM * 2
@@ -299,17 +287,17 @@ Column {
anchors.bottomMargin: Theme.spacingS anchors.bottomMargin: Theme.spacingS
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: autoMouseArea.containsMouse && (!Theme.isDynamicTheme || ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") visible: autoMouseArea.containsMouse && (!Theme.isDynamicTheme || ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
Text { Text {
id: autoTooltipText id: autoTooltipText
text: { text: {
if (ToastService.wallpaperErrorStatus === "error") { if (ToastService.wallpaperErrorStatus === "error")
return "Wallpaper symlink missing at ~/quickshell/current_wallpaper" return "Wallpaper symlink missing at ~/quickshell/current_wallpaper";
} else if (ToastService.wallpaperErrorStatus === "matugen_missing") { else if (ToastService.wallpaperErrorStatus === "matugen_missing")
return "Install matugen package for dynamic themes" return "Install matugen package for dynamic themes";
} else { else
return "Dynamic wallpaper-based colors" return "Dynamic wallpaper-based colors";
}
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") ? Theme.error : Theme.surfaceText color: (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") ? Theme.error : Theme.surfaceText
@@ -318,7 +306,35 @@ Column {
width: Math.min(implicitWidth, 250) width: Math.min(implicitWidth, 250)
horizontalAlignment: Text.AlignHCenter 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
}
}
} }
} }
} }

View File

@@ -1,103 +1,68 @@
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
PanelWindow { PanelWindow {
id: root id: root
visible: ToastService.toastVisible visible: ToastService.toastVisible
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent" color: "transparent"
anchors { anchors {
top: true top: true
left: true left: true
right: true right: true
bottom: true bottom: true
} }
// Makes the background transparent to mouse events
mask: Region {
item: toast
}
Rectangle { Rectangle {
id: toast id: toast
width: Math.min(400, Screen.width - Theme.spacingL * 2) width: Math.min(400, Screen.width - Theme.spacingL * 2)
height: toastContent.height + Theme.spacingL * 2 height: toastContent.height + Theme.spacingL * 2
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
y: Theme.barHeight + Theme.spacingL y: Theme.barHeight + Theme.spacingL
color: { color: {
switch (ToastService.currentLevel) { switch (ToastService.currentLevel) {
case ToastService.levelError: return Theme.error case ToastService.levelError:
case ToastService.levelWarn: return Theme.warning return Theme.error;
case ToastService.levelInfo: return Theme.primary case ToastService.levelWarn:
default: return Theme.primary return Theme.warning;
case ToastService.levelInfo:
return Theme.primary;
default:
return Theme.primary;
} }
} }
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
layer.enabled: true layer.enabled: true
layer.effect: MultiEffect { opacity: ToastService.toastVisible ? 0.9 : 0
shadowEnabled: true scale: ToastService.toastVisible ? 1 : 0.9
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
}
}
Row { Row {
id: toastContent id: toastContent
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: { text: {
switch (ToastService.currentLevel) { switch (ToastService.currentLevel) {
case ToastService.levelError: return "error" case ToastService.levelError:
case ToastService.levelWarn: return "warning" return "error";
case ToastService.levelInfo: return "info" case ToastService.levelWarn:
default: return "info" return "warning";
case ToastService.levelInfo:
return "info";
default:
return "info";
} }
} }
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -105,7 +70,7 @@ PanelWindow {
color: Theme.background color: Theme.background
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: ToastService.currentMessage text: ToastService.currentMessage
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -115,11 +80,56 @@ PanelWindow {
width: Math.min(implicitWidth, 300) width: Math.min(implicitWidth, 300)
elide: Text.ElideRight elide: Text.ElideRight
} }
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: ToastService.hideToast() 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
}
}

View File

@@ -7,100 +7,111 @@ import qs.Services
Item { Item {
id: root 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 MprisPlayer activePlayer: MprisController.activePlayer
readonly property bool hasActiveMedia: activePlayer !== null readonly property bool hasActiveMedia: activePlayer !== null
property bool cavaAvailable: false property bool cavaAvailable: false
width: 20 width: 20
height: Theme.iconSize height: Theme.iconSize
Process { Process {
id: cavaCheck id: cavaCheck
command: ["which", "cava"] command: ["which", "cava"]
running: true running: true
onExited: (exitCode) => { onExited: (exitCode) => {
root.cavaAvailable = exitCode === 0 root.cavaAvailable = exitCode === 0;
if (root.cavaAvailable) { if (root.cavaAvailable) {
console.log("cava found - enabling real audio visualization") console.log("cava found - enabling real audio visualization");
cavaProcess.running = Qt.binding(() => root.hasActiveMedia && root.activePlayer?.playbackState === MprisPlaybackState.Playing) cavaProcess.running = Qt.binding(() => {
return root.hasActiveMedia && root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing;
});
} else { } else {
console.log("cava not found - using fallback animation") console.log("cava not found - using fallback animation");
fallbackTimer.running = Qt.binding(() => root.hasActiveMedia && root.activePlayer?.playbackState === MprisPlaybackState.Playing) fallbackTimer.running = Qt.binding(() => {
return root.hasActiveMedia && root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing;
});
} }
} }
} }
Process { Process {
id: cavaProcess id: cavaProcess
running: false 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`] 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 { stdout: SplitParser {
splitMarker: "\n" splitMarker: "\n"
onRead: data => { onRead: (data) => {
if (data.trim()) { if (data.trim()) {
let points = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p)) let points = data.split(";").map((p) => {
if (points.length >= 4) { return parseFloat(p.trim());
root.audioLevels = [points[0], points[1], points[2], points[3]] }).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 { Timer {
id: fallbackTimer id: fallbackTimer
running: false running: false
interval: 100 interval: 100
repeat: true repeat: true
onTriggered: { onTriggered: {
root.audioLevels = [ root.audioLevels = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20];
Math.random() * 40 + 10,
Math.random() * 60 + 20,
Math.random() * 50 + 15,
Math.random() * 35 + 20
]
} }
} }
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 2 spacing: 2
Repeater { Repeater {
model: 4 model: 4
Rectangle { Rectangle {
width: 3 width: 3
height: { height: {
if (root.activePlayer?.playbackState === MprisPlaybackState.Playing && root.audioLevels.length > index) { if (root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing && root.audioLevels.length > index) {
const rawLevel = root.audioLevels[index] || 0 const rawLevel = root.audioLevels[index] || 0;
const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100 const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100;
const maxHeight = Theme.iconSize - 2 const maxHeight = Theme.iconSize - 2;
const minHeight = 3 const minHeight = 3;
return minHeight + (scaledLevel / 100) * (maxHeight - minHeight) return minHeight + (scaledLevel / 100) * (maxHeight - minHeight);
} }
return 3 return 3;
} }
radius: 1.5 radius: 1.5
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Behavior on height { Behavior on height {
NumberAnimation { NumberAnimation {
duration: 80 duration: 80
easing.type: Easing.OutQuad easing.type: Easing.OutQuad
} }
} }
} }
} }
} }
}
}

View File

@@ -4,30 +4,25 @@ import qs.Common
Rectangle { Rectangle {
id: root id: root
property date currentDate: new Date() property date currentDate: new Date()
signal clockClicked() signal clockClicked()
width: clockRow.implicitWidth + Theme.spacingS * 2 width: clockRow.implicitWidth + Theme.spacingS * 2
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: clockMouseArea.containsMouse ? 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)
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Component.onCompleted: {
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) root.currentDate = systemClock.date;
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
} }
Row { Row {
id: clockRow id: clockRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
text: Prefs.use24HourClock ? Qt.formatTime(root.currentDate, "H:mm") : Qt.formatTime(root.currentDate, "h:mm AP") text: Prefs.use24HourClock ? Qt.formatTime(root.currentDate, "H:mm") : Qt.formatTime(root.currentDate, "h:mm AP")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -35,14 +30,14 @@ Rectangle {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: "•" text: "•"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: Qt.formatDate(root.currentDate, "ddd d") text: Qt.formatDate(root.currentDate, "ddd d")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -50,26 +45,33 @@ Rectangle {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
SystemClock { SystemClock {
id: systemClock id: systemClock
precision: SystemClock.Seconds precision: SystemClock.Seconds
onDateChanged: root.currentDate = systemClock.date onDateChanged: root.currentDate = systemClock.date
} }
Component.onCompleted: {
root.currentDate = systemClock.date
}
MouseArea { MouseArea {
id: clockMouseArea id: clockMouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.clockClicked() root.clockClicked();
} }
} }
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}

View File

@@ -4,37 +4,43 @@ import qs.Services
Rectangle { Rectangle {
id: root id: root
property bool isActive: false property bool isActive: false
signal clicked() signal clicked()
width: Math.max(80, controlIndicators.implicitWidth + Theme.spacingS * 2) width: Math.max(80, controlIndicators.implicitWidth + Theme.spacingS * 2)
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: controlCenterArea.containsMouse || root.isActive ? 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)
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 { Row {
id: controlIndicators id: controlIndicators
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
// Network Status Icon // Network Status Icon
Text { Text {
text: { text: {
if (NetworkService.networkStatus === "ethernet") return "lan" if (NetworkService.networkStatus === "ethernet") {
else if (NetworkService.networkStatus === "wifi") { return "lan";
} else if (NetworkService.networkStatus === "wifi") {
switch (WifiService.wifiSignalStrength) { switch (WifiService.wifiSignalStrength) {
case "excellent": return "wifi" case "excellent":
case "good": return "wifi_2_bar" return "wifi";
case "fair": return "wifi_1_bar" case "good":
case "poor": return "wifi_calling_3" return "wifi_2_bar";
default: return "wifi" 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.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 8 font.pixelSize: Theme.iconSize - 8
@@ -43,7 +49,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: true visible: true
} }
// Bluetooth Icon (when available and enabled) - moved next to network // Bluetooth Icon (when available and enabled) - moved next to network
Text { Text {
text: "bluetooth" text: "bluetooth"
@@ -54,51 +60,49 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: BluetoothService.available && BluetoothService.enabled visible: BluetoothService.available && BluetoothService.enabled
} }
// Audio Icon with scroll wheel support // Audio Icon with scroll wheel support
Rectangle { Rectangle {
width: audioIcon.implicitWidth + 4 width: audioIcon.implicitWidth + 4
height: audioIcon.implicitHeight + 4 height: audioIcon.implicitHeight + 4
color: "transparent" color: "transparent"
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
id: audioIcon 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.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 8 font.pixelSize: Theme.iconSize - 8
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
color: audioWheelArea.containsMouse || controlCenterArea.containsMouse || root.isActive ? color: audioWheelArea.containsMouse || controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
Theme.primary : Theme.surfaceText
anchors.centerIn: parent anchors.centerIn: parent
} }
MouseArea { MouseArea {
// Scroll up - increase volume
// Scroll down - decrease volume
id: audioWheelArea id: audioWheelArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) { onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y let delta = wheelEvent.angleDelta.y;
let currentVolume = AudioService.volumeLevel let currentVolume = AudioService.volumeLevel;
let newVolume let newVolume;
if (delta > 0)
if (delta > 0) { newVolume = Math.min(100, currentVolume + 5);
// Scroll up - increase volume else
newVolume = Math.min(100, currentVolume + 5) newVolume = Math.max(0, currentVolume - 5);
} else { AudioService.setVolume(newVolume);
// Scroll down - decrease volume wheelEvent.accepted = true;
newVolume = Math.max(0, currentVolume - 5)
}
AudioService.setVolume(newVolume)
wheelEvent.accepted = true
} }
} }
} }
// Microphone Icon (when active) // Microphone Icon (when active)
Text { Text {
text: "mic" text: "mic"
@@ -109,23 +113,26 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: false // TODO: Add mic detection visible: false // TODO: Add mic detection
} }
} }
MouseArea { MouseArea {
id: controlCenterArea id: controlCenterArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.clicked() root.clicked();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
}
}

View File

@@ -4,43 +4,34 @@ import qs.Services
Rectangle { Rectangle {
id: root id: root
width: Math.max(contentRow.implicitWidth + Theme.spacingS * 2, 60) width: Math.max(contentRow.implicitWidth + Theme.spacingS * 2, 60)
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: mouseArea.containsMouse ? 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)
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 clip: true
visible: FocusedWindowService.niriAvailable && (FocusedWindowService.focusedAppName || FocusedWindowService.focusedWindowTitle) visible: FocusedWindowService.niriAvailable && (FocusedWindowService.focusedAppName || FocusedWindowService.focusedWindowTitle)
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Row { Row {
id: contentRow id: contentRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
Text { Text {
id: appText id: appText
text: FocusedWindowService.focusedAppName || "" text: FocusedWindowService.focusedAppName || ""
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
// Limit app name width // Limit app name width
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1 maximumLineCount: 1
width: Math.min(implicitWidth, 120) width: Math.min(implicitWidth, 120)
} }
Text { Text {
text: "•" text: "•"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -48,35 +39,47 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: appText.text && titleText.text visible: appText.text && titleText.text
} }
Text { Text {
id: titleText id: titleText
text: FocusedWindowService.focusedWindowTitle || "" text: FocusedWindowService.focusedWindowTitle || ""
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
// Limit title width - increased for longer titles // Limit title width - increased for longer titles
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1 maximumLineCount: 1
width: Math.min(implicitWidth, 350) width: Math.min(implicitWidth, 350)
} }
} }
MouseArea { MouseArea {
// Non-interactive widget - just provides hover state for visual feedback
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
// Non-interactive widget - just provides hover state for visual feedback
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
// Smooth width animation when the text changes // Smooth width animation when the text changes
Behavior on width { Behavior on width {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
}
}

View File

@@ -4,12 +4,12 @@ import qs.Services
Rectangle { Rectangle {
id: root id: root
width: 40 width: 40
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: launcherArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) color: launcherArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: OSDetectorService.osLogo || "apps" text: OSDetectorService.osLogo || "apps"
@@ -18,22 +18,24 @@ Rectangle {
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
color: Theme.surfaceText color: Theme.surfaceText
} }
MouseArea { MouseArea {
id: launcherArea id: launcherArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
LauncherService.toggleAppLauncher() LauncherService.toggleAppLauncher();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
}
}

View File

@@ -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 { Row {
id: mediaRow id: mediaRow
@@ -64,9 +95,8 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 140 width: 140
text: { text: {
if (!activePlayer || !activePlayer.trackTitle) { if (!activePlayer || !activePlayer.trackTitle)
return ""; return "";
}
let identity = activePlayer.identity || ""; let identity = activePlayer.identity || "";
let isWebMedia = identity.toLowerCase().includes("firefox") || identity.toLowerCase().includes("chrome") || identity.toLowerCase().includes("chromium") || identity.toLowerCase().includes("edge") || identity.toLowerCase().includes("safari"); let 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 { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration

View File

@@ -3,28 +3,26 @@ import qs.Common
Rectangle { Rectangle {
id: root id: root
property bool hasUnread: false property bool hasUnread: false
property bool isActive: false property bool isActive: false
signal clicked() signal clicked()
width: 40 width: 40
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: notificationArea.containsMouse || root.isActive ? 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)
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 { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "notifications" text: "notifications"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 6 font.pixelSize: Theme.iconSize - 6
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
color: notificationArea.containsMouse || root.isActive ? color: notificationArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
Theme.primary : Theme.surfaceText
} }
// Notification dot indicator // Notification dot indicator
Rectangle { Rectangle {
width: 8 width: 8
@@ -37,22 +35,24 @@ Rectangle {
anchors.topMargin: 6 anchors.topMargin: 6
visible: root.hasUnread visible: root.hasUnread
} }
MouseArea { MouseArea {
id: notificationArea id: notificationArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.clicked() root.clicked();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
}
}

View File

@@ -4,36 +4,38 @@ import qs.Common
Rectangle { Rectangle {
id: root id: root
signal menuRequested(var menu, var item, real x, real y) signal menuRequested(var menu, var item, real x, real y)
width: Math.max(40, systemTrayRow.implicitWidth + Theme.spacingS * 2) width: Math.max(40, systemTrayRow.implicitWidth + Theme.spacingS * 2)
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08) color: Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
visible: systemTrayRow.children.length > 0 visible: systemTrayRow.children.length > 0
Row { Row {
id: systemTrayRow id: systemTrayRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
Repeater { Repeater {
model: SystemTray.items model: SystemTray.items
delegate: Rectangle { delegate: Rectangle {
property var trayItem: modelData
width: 24 width: 24
height: 24 height: 24
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
color: trayItemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: trayItemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
property var trayItem: modelData
Image { Image {
anchors.centerIn: parent anchors.centerIn: parent
width: 18 width: 18
height: 18 height: 18
source: { source: {
let icon = trayItem?.icon; let icon = trayItem && trayItem.icon;
if (typeof icon === 'string' || icon instanceof String) { if (typeof icon === 'string' || icon instanceof String) {
if (icon.includes("?path=")) { if (icon.includes("?path=")) {
const [name, path] = icon.split("?path="); const [name, path] = icon.split("?path=");
@@ -48,51 +50,58 @@ Rectangle {
smooth: true smooth: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
} }
MouseArea { MouseArea {
id: trayItemArea id: trayItemArea
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => { onClicked: (mouse) => {
if (!trayItem) return; if (!trayItem)
return ;
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
if (!trayItem.onlyMenu) { if (!trayItem.onlyMenu)
trayItem.activate() trayItem.activate();
}
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
if (trayItem.hasMenu) { if (trayItem.hasMenu)
customTrayMenu.showMenu(mouse.x, mouse.y) customTrayMenu.showMenu(mouse.x, mouse.y);
}
} }
} }
} }
QtObject { QtObject {
id: customTrayMenu id: customTrayMenu
property bool menuVisible: false property bool menuVisible: false
function showMenu(x, y) { function showMenu(x, y) {
root.menuRequested(customTrayMenu, trayItem, x, y) root.menuRequested(customTrayMenu, trayItem, x, y);
menuVisible = true menuVisible = true;
} }
function hideMenu() { function hideMenu() {
menuVisible = false menuVisible = false;
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
} }
}
}

View File

@@ -1,61 +1,54 @@
import "../../Common/Utilities.js" as Utils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Io import Quickshell.Io
import Quickshell.Services.SystemTray
import Quickshell.Services.Notifications
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
import Quickshell.Services.Notifications
import Quickshell.Services.SystemTray
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import "../../Common/Utilities.js" as Utils
PanelWindow { PanelWindow {
// Proxy objects for external connections
id: root id: root
property var modelData property var modelData
screen: modelData
property string screenName: modelData.name property string screenName: modelData.name
// Transparency property for the top bar background // Transparency property for the top bar background
property real backgroundTransparency: Prefs.topBarTransparency property real backgroundTransparency: Prefs.topBarTransparency
Connections {
target: Prefs
function onTopBarTransparencyChanged() {
root.backgroundTransparency = Prefs.topBarTransparency
}
}
// Notification properties // Notification properties
readonly property int notificationCount: NotificationService.notifications.length readonly property int notificationCount: NotificationService.notifications.length
screen: modelData
implicitHeight: Theme.barHeight - 4
color: "transparent"
Connections {
// Proxy objects for external connections function onTopBarTransparencyChanged() {
root.backgroundTransparency = Prefs.topBarTransparency;
}
target: Prefs
}
QtObject { QtObject {
id: notificationHistory id: notificationHistory
property int count: 0 property int count: 0
} }
anchors { anchors {
top: true top: true
left: true left: true
right: true right: true
} }
implicitHeight: Theme.barHeight - 4
color: "transparent"
// Floating panel container with margins // Floating panel container with margins
Item { Item {
anchors.fill: parent anchors.fill: parent
@@ -64,22 +57,13 @@ PanelWindow {
anchors.bottomMargin: 0 anchors.bottomMargin: 0
anchors.leftMargin: 8 anchors.leftMargin: 8
anchors.rightMargin: 8 anchors.rightMargin: 8
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Theme.cornerRadiusXLarge radius: Theme.cornerRadiusXLarge
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, root.backgroundTransparency) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, root.backgroundTransparency)
layer.enabled: true 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 { Rectangle {
anchors.fill: parent anchors.fill: parent
color: "transparent" color: "transparent"
@@ -87,29 +71,43 @@ PanelWindow {
border.width: 1 border.width: 1
radius: parent.radius radius: parent.radius
} }
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04) color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
radius: parent.radius radius: parent.radius
SequentialAnimation on opacity { SequentialAnimation on opacity {
running: false running: false
loops: Animation.Infinite loops: Animation.Infinite
NumberAnimation { NumberAnimation {
to: 0.08 to: 0.08
duration: Theme.extraLongDuration duration: Theme.extraLongDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
NumberAnimation { NumberAnimation {
to: 0.02 to: 0.02
duration: Theme.extraLongDuration duration: Theme.extraLongDuration
easing.type: Theme.standardEasing 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 { Item {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
@@ -117,82 +115,83 @@ PanelWindow {
anchors.topMargin: Theme.spacingXS anchors.topMargin: Theme.spacingXS
anchors.bottomMargin: Theme.spacingXS anchors.bottomMargin: Theme.spacingXS
clip: true clip: true
Row { Row {
id: leftSection id: leftSection
height: parent.height height: parent.height
spacing: Theme.spacingXS spacing: Theme.spacingXS
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
LauncherButton { LauncherButton {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
WorkspaceSwitcher { WorkspaceSwitcher {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
screenName: root.screenName screenName: root.screenName
} }
FocusedAppWidget { FocusedAppWidget {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showFocusedWindow visible: Prefs.showFocusedWindow
} }
} }
ClockWidget { ClockWidget {
id: clockWidget id: clockWidget
anchors.centerIn: parent anchors.centerIn: parent
onClockClicked: { onClockClicked: {
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible;
} }
} }
MediaWidget { MediaWidget {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: clockWidget.left anchors.right: clockWidget.left
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
visible: Prefs.showMusic && MprisController.activePlayer visible: Prefs.showMusic && MprisController.activePlayer
onClicked: { onClicked: {
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible;
} }
} }
WeatherWidget { WeatherWidget {
id: weatherWidget id: weatherWidget
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: clockWidget.right anchors.left: clockWidget.right
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
visible: Prefs.showWeather && WeatherService.weather.available && WeatherService.weather.temp > 0 && WeatherService.weather.tempF > 0 visible: Prefs.showWeather && WeatherService.weather.available && WeatherService.weather.temp > 0 && WeatherService.weather.tempF > 0
onClicked: { onClicked: {
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible;
} }
} }
Row { Row {
id: rightSection id: rightSection
height: parent.height height: parent.height
spacing: Theme.spacingXS spacing: Theme.spacingXS
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
SystemTrayWidget { SystemTrayWidget {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemTray visible: Prefs.showSystemTray
onMenuRequested: (menu, item, x, y) => { onMenuRequested: (menu, item, x, y) => {
trayMenuPopup.currentTrayMenu = menu trayMenuPopup.currentTrayMenu = menu;
trayMenuPopup.currentTrayItem = item trayMenuPopup.currentTrayItem = item;
trayMenuPopup.trayMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL trayMenuPopup.trayMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL;
trayMenuPopup.trayMenuY = Theme.barHeight - Theme.spacingXS trayMenuPopup.trayMenuY = Theme.barHeight - Theme.spacingXS;
trayMenuPopup.showTrayMenu = true trayMenuPopup.showTrayMenu = true;
menu.menuVisible = true menu.menuVisible = true;
} }
} }
Rectangle { Rectangle {
width: 40 width: 40
height: 30 height: 30
@@ -200,7 +199,7 @@ PanelWindow {
color: clipboardArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08) color: clipboardArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showClipboard visible: Prefs.showClipboard
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "content_paste" text: "content_paste"
@@ -209,26 +208,28 @@ PanelWindow {
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
color: Theme.surfaceText color: Theme.surfaceText
} }
MouseArea { MouseArea {
id: clipboardArea id: clipboardArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
clipboardHistoryPopup.toggle() clipboardHistoryPopup.toggle();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
// System Monitor Widgets // System Monitor Widgets
CpuMonitorWidget { CpuMonitorWidget {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -239,40 +240,44 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemResources visible: Prefs.showSystemResources
} }
NotificationCenterButton { NotificationCenterButton {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
hasUnread: root.notificationCount > 0 hasUnread: root.notificationCount > 0
isActive: notificationCenter.notificationHistoryVisible isActive: notificationCenter.notificationHistoryVisible
onClicked: { onClicked: {
notificationCenter.notificationHistoryVisible = !notificationCenter.notificationHistoryVisible notificationCenter.notificationHistoryVisible = !notificationCenter.notificationHistoryVisible;
} }
} }
// Battery Widget // Battery Widget
BatteryWidget { BatteryWidget {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
batteryPopupVisible: batteryControlPopup.batteryPopupVisible batteryPopupVisible: batteryControlPopup.batteryPopupVisible
onToggleBatteryPopup: { onToggleBatteryPopup: {
batteryControlPopup.batteryPopupVisible = !batteryControlPopup.batteryPopupVisible batteryControlPopup.batteryPopupVisible = !batteryControlPopup.batteryPopupVisible;
} }
} }
ControlCenterButton { ControlCenterButton {
// Bluetooth devices are automatically updated via signals
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
isActive: controlCenterPopup.controlCenterVisible isActive: controlCenterPopup.controlCenterVisible
onClicked: { onClicked: {
controlCenterPopup.controlCenterVisible = !controlCenterPopup.controlCenterVisible controlCenterPopup.controlCenterVisible = !controlCenterPopup.controlCenterVisible;
if (controlCenterPopup.controlCenterVisible) { if (controlCenterPopup.controlCenterVisible) {
if (NetworkService.wifiEnabled) { if (NetworkService.wifiEnabled)
WifiService.scanWifi() WifiService.scanWifi();
}
// Bluetooth devices are automatically updated via signals
} }
} }
} }
} }
} }
} }
}
}

View File

@@ -4,37 +4,21 @@ import qs.Services
Rectangle { Rectangle {
id: root id: root
signal clicked() signal clicked()
// Visibility is now controlled by TopBar.qml // Visibility is now controlled by TopBar.qml
width: visible ? Math.min(100, weatherRow.implicitWidth + Theme.spacingS * 2) : 0 width: visible ? Math.min(100, weatherRow.implicitWidth + Theme.spacingS * 2) : 0
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: weatherArea.containsMouse ? 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)
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
}
}
Row { Row {
id: weatherRow id: weatherRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: WeatherService.getWeatherIcon(WeatherService.weather.wCode) text: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
font.family: Theme.iconFont font.family: Theme.iconFont
@@ -42,7 +26,7 @@ Rectangle {
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: (Prefs.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + (Prefs.useFahrenheit ? "F" : "C") text: (Prefs.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + (Prefs.useFahrenheit ? "F" : "C")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -50,14 +34,32 @@ Rectangle {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: weatherArea id: weatherArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: root.clicked() 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
}
}
}

View File

@@ -5,119 +5,120 @@ import qs.Services
Rectangle { Rectangle {
id: root id: root
property string screenName: "" 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) width: Math.max(120, workspaceRow.implicitWidth + Theme.spacingL * 2)
height: 30 height: 30
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
visible: NiriWorkspaceService.niriAvailable 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 { Connections {
target: NiriWorkspaceService
function onAllWorkspacesChanged() { function onAllWorkspacesChanged() {
root.workspaceList = root.getDisplayWorkspaces() root.workspaceList = root.getDisplayWorkspaces();
root.currentWorkspace = root.getDisplayActiveWorkspace() root.currentWorkspace = root.getDisplayActiveWorkspace();
} }
function onFocusedWorkspaceIndexChanged() { function onFocusedWorkspaceIndexChanged() {
root.currentWorkspace = root.getDisplayActiveWorkspace() root.currentWorkspace = root.getDisplayActiveWorkspace();
} }
function onNiriAvailableChanged() { function onNiriAvailableChanged() {
if (NiriWorkspaceService.niriAvailable) { if (NiriWorkspaceService.niriAvailable) {
root.workspaceList = root.getDisplayWorkspaces() root.workspaceList = root.getDisplayWorkspaces();
root.currentWorkspace = root.getDisplayActiveWorkspace() root.currentWorkspace = root.getDisplayActiveWorkspace();
} }
} }
target: NiriWorkspaceService
} }
Row { Row {
id: workspaceRow id: workspaceRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
Repeater { Repeater {
model: root.workspaceList model: root.workspaceList
Rectangle { Rectangle {
property bool isActive: modelData === root.currentWorkspace property bool isActive: modelData === root.currentWorkspace
property bool isHovered: mouseArea.containsMouse property bool isHovered: mouseArea.containsMouse
property int sequentialNumber: index + 1 property int sequentialNumber: index + 1
width: isActive ? Theme.spacingXL + Theme.spacingM : Theme.spacingL + Theme.spacingXS width: isActive ? Theme.spacingXL + Theme.spacingM : Theme.spacingL + Theme.spacingXS
height: Theme.spacingM height: Theme.spacingM
radius: height / 2 radius: height / 2
color: isActive ? Theme.primary : 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)
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 { Behavior on width {
NumberAnimation { NumberAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing 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()])
}
}
} }
} }
} }
}
}

View File

@@ -1,59 +1,59 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
PanelWindow { PanelWindow {
id: root id: root
property bool wifiPasswordDialogVisible: false property bool wifiPasswordDialogVisible: false
property string wifiPasswordSSID: "" property string wifiPasswordSSID: ""
property string wifiPasswordInput: "" property string wifiPasswordInput: ""
visible: wifiPasswordDialogVisible visible: wifiPasswordDialogVisible
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: wifiPasswordDialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
color: "transparent"
onVisibleChanged: {
if (visible)
passwordInput.forceActiveFocus();
}
anchors { anchors {
top: true top: true
left: true left: true
right: true right: true
bottom: true bottom: true
} }
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: wifiPasswordDialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
color: "transparent"
onVisibleChanged: {
if (visible) {
passwordInput.forceActiveFocus()
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.5) 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 { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
MouseArea {
anchors.fill: parent
onClicked: {
wifiPasswordDialogVisible = false
wifiPasswordInput = ""
}
}
} }
Rectangle { Rectangle {
width: Math.min(400, parent.width - Theme.spacingL * 2) width: Math.min(400, parent.width - Theme.spacingL * 2)
height: Math.min(250, parent.height - Theme.spacingL * 2) height: Math.min(250, parent.height - Theme.spacingL * 2)
@@ -62,44 +62,29 @@ PanelWindow {
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1 border.width: 1
opacity: wifiPasswordDialogVisible ? 1 : 0
opacity: wifiPasswordDialogVisible ? 1.0 : 0.0 scale: wifiPasswordDialogVisible ? 1 : 0.9
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
}
}
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
spacing: Theme.spacingL spacing: Theme.spacingL
// Header // Header
Row { Row {
width: parent.width width: parent.width
Column { Column {
width: parent.width - 40 width: parent.width - 40
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: "Connect to Wi-Fi" text: "Connect to Wi-Fi"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { Text {
text: "Enter password for \"" + wifiPasswordSSID + "\"" text: "Enter password for \"" + wifiPasswordSSID + "\""
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -107,14 +92,15 @@ PanelWindow {
width: parent.width width: parent.width
elide: Text.ElideRight elide: Text.ElideRight
} }
} }
Rectangle { Rectangle {
width: 32 width: 32
height: 32 height: 32
radius: 16 radius: 16
color: closeDialogArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" color: closeDialogArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "close" text: "close"
@@ -122,20 +108,23 @@ PanelWindow {
font.pixelSize: Theme.iconSize - 4 font.pixelSize: Theme.iconSize - 4
color: closeDialogArea.containsMouse ? Theme.error : Theme.surfaceText color: closeDialogArea.containsMouse ? Theme.error : Theme.surfaceText
} }
MouseArea { MouseArea {
id: closeDialogArea id: closeDialogArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
wifiPasswordDialogVisible = false wifiPasswordDialogVisible = false;
wifiPasswordInput = "" wifiPasswordInput = "";
} }
} }
} }
} }
// Password input // Password input
Rectangle { Rectangle {
width: parent.width width: parent.width
@@ -144,9 +133,10 @@ PanelWindow {
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
border.color: passwordInput.activeFocus ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: passwordInput.activeFocus ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: passwordInput.activeFocus ? 2 : 1 border.width: passwordInput.activeFocus ? 2 : 1
TextInput { TextInput {
id: passwordInput id: passwordInput
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -155,7 +145,18 @@ PanelWindow {
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
cursorVisible: activeFocus cursorVisible: activeFocus
selectByMouse: true selectByMouse: true
onTextChanged: {
wifiPasswordInput = text;
}
onAccepted: {
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput);
}
Component.onCompleted: {
if (wifiPasswordDialogVisible)
forceActiveFocus();
}
Text { Text {
anchors.fill: parent anchors.fill: parent
text: "Enter password" text: "Enter password"
@@ -164,46 +165,35 @@ PanelWindow {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
visible: parent.text.length === 0 visible: parent.text.length === 0
} }
onTextChanged: {
wifiPasswordInput = text
}
onAccepted: {
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput)
}
Component.onCompleted: {
if (wifiPasswordDialogVisible) {
forceActiveFocus()
}
}
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.IBeamCursor cursorShape: Qt.IBeamCursor
onClicked: { onClicked: {
passwordInput.forceActiveFocus() passwordInput.forceActiveFocus();
} }
} }
} }
// Show password checkbox // Show password checkbox
Row { Row {
spacing: Theme.spacingS spacing: Theme.spacingS
Rectangle { Rectangle {
id: showPasswordCheckbox id: showPasswordCheckbox
property bool checked: false property bool checked: false
width: 20 width: 20
height: 20 height: 20
radius: 4 radius: 4
color: checked ? Theme.primary : "transparent" color: checked ? Theme.primary : "transparent"
border.color: checked ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.5) border.color: checked ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.5)
border.width: 2 border.width: 2
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "check" text: "check"
@@ -212,35 +202,37 @@ PanelWindow {
color: Theme.background color: Theme.background
visible: parent.checked visible: parent.checked
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
showPasswordCheckbox.checked = !showPasswordCheckbox.checked showPasswordCheckbox.checked = !showPasswordCheckbox.checked;
} }
} }
} }
Text { Text {
text: "Show password" text: "Show password"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
// Buttons // Buttons
Item { Item {
width: parent.width width: parent.width
height: 40 height: 40
Row { Row {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Rectangle { Rectangle {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2) width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36 height: 36
@@ -248,65 +240,93 @@ PanelWindow {
color: cancelArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent" color: cancelArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1 border.width: 1
Text { Text {
id: cancelText id: cancelText
anchors.centerIn: parent anchors.centerIn: parent
text: "Cancel" text: "Cancel"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
MouseArea { MouseArea {
id: cancelArea id: cancelArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
wifiPasswordDialogVisible = false wifiPasswordDialogVisible = false;
wifiPasswordInput = "" wifiPasswordInput = "";
} }
} }
} }
Rectangle { Rectangle {
width: Math.max(80, connectText.contentWidth + Theme.spacingM * 2) width: Math.max(80, connectText.contentWidth + Theme.spacingM * 2)
height: 36 height: 36
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: wifiPasswordInput.length > 0 enabled: wifiPasswordInput.length > 0
opacity: enabled ? 1.0 : 0.5 opacity: enabled ? 1 : 0.5
Text { Text {
id: connectText id: connectText
anchors.centerIn: parent anchors.centerIn: parent
text: "Connect" text: "Connect"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium
} }
MouseArea { MouseArea {
id: connectArea id: connectArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: parent.enabled enabled: parent.enabled
onClicked: { onClicked: {
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput) WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput);
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing 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
}
}
} }
}
}

View File

@@ -8,71 +8,81 @@ import qs.Widgets.TopBar
ShellRoot { ShellRoot {
id: root id: root
// Multi-monitor support using Variants // Multi-monitor support using Variants
Variants { Variants {
model: Quickshell.screens model: Quickshell.screens
delegate: TopBar { delegate: TopBar {
modelData: item modelData: item
} }
} }
// Global popup windows // Global popup windows
CenterCommandCenter { CenterCommandCenter {
id: centerCommandCenter id: centerCommandCenter
} }
TrayMenuPopup { TrayMenuPopup {
id: trayMenuPopup id: trayMenuPopup
} }
NotificationCenter { NotificationCenter {
id: notificationCenter id: notificationCenter
} }
ControlCenterPopup { ControlCenterPopup {
id: controlCenterPopup id: controlCenterPopup
} }
WifiPasswordDialog { WifiPasswordDialog {
id: wifiPasswordDialog id: wifiPasswordDialog
} }
InputDialog { InputDialog {
id: globalInputDialog id: globalInputDialog
} }
BatteryControlPopup { BatteryControlPopup {
id: batteryControlPopup id: batteryControlPopup
} }
PowerMenuPopup { PowerMenuPopup {
id: powerMenuPopup id: powerMenuPopup
} }
PowerConfirmDialog { PowerConfirmDialog {
id: powerConfirmDialog id: powerConfirmDialog
} }
ProcessListDropdown { ProcessListDropdown {
id: processListDropdown id: processListDropdown
} }
SettingsPopup { SettingsPopup {
id: settingsPopup id: settingsPopup
} }
// Application and clipboard components // Application and clipboard components
AppLauncher { AppLauncher {
id: appLauncher id: appLauncher
} }
SpotlightLauncher { SpotlightLauncher {
id: spotlightLauncher id: spotlightLauncher
} }
ProcessListWidget { ProcessListWidget {
id: processListWidget id: processListWidget
} }
ClipboardHistory { ClipboardHistory {
id: clipboardHistoryPopup id: clipboardHistoryPopup
} }
ToastWidget { ToastWidget {
id: toastWidget id: toastWidget
} }
} }