mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-29 16:02:51 -05:00
Initial qmlformat
This commit is contained in:
23
CLAUDE.md
23
CLAUDE.md
@@ -25,6 +25,10 @@ quickshell -p shell.qml
|
|||||||
|
|
||||||
# Or use the shorthand
|
# 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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
551
Common/Prefs.qml
551
Common/Prefs.qml
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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()])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
32
shell.qml
32
shell.qml
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user