mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2025-12-06 06:36:15 +00:00
Switched to qmlformat.
This commit is contained in:
@@ -2,38 +2,28 @@
|
||||
set -euo pipefail
|
||||
|
||||
# QML Formatter Script
|
||||
# Uses: https://github.com/jesperhh/qmlfmt
|
||||
# Install: AUR package "qmlfmt-git" (requires qt6-5compat)
|
||||
|
||||
if command -v qmlfmt &>/dev/null; then
|
||||
echo "Using 'qmlfmt' for formatting."
|
||||
format_file() { qmlfmt -e -b 360 -t 2 -i 2 -w "$1" || { echo "Failed: $1" >&2; return 1; }; }
|
||||
# Find qmlformat binary
|
||||
if command -v qmlformat &>/dev/null; then
|
||||
QMLFORMAT="qmlformat"
|
||||
elif [ -x "/usr/lib/qt6/bin/qmlformat" ]; then
|
||||
QMLFORMAT="/usr/lib/qt6/bin/qmlformat"
|
||||
else
|
||||
echo "No 'qmlfmt' found in PATH." >&2
|
||||
echo "No 'qmlformat' found in PATH or /usr/lib/qt6/bin." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using 'qmlformat' for formatting: $QMLFORMAT"
|
||||
export QMLFORMAT
|
||||
format_file() { "$QMLFORMAT" -w 2 -W 360 -S --semicolon-rule always -i "$1" || { echo "Failed: $1" >&2; return 1; }; }
|
||||
|
||||
export -f format_file
|
||||
|
||||
# Find all .qml files
|
||||
mapfile -t all_files < <(find "${1:-.}" -name "*.qml" -type f)
|
||||
[ ${#all_files[@]} -eq 0 ] && { echo "No QML files found"; exit 0; }
|
||||
|
||||
echo "Scanning ${#all_files[@]} files for array destructuring..."
|
||||
safe_files=()
|
||||
for file in "${all_files[@]}"; do
|
||||
# Checks for a comma inside brackets followed by an equals sign aka "array destructuring"
|
||||
# as this ES6 syntax is not supported by qmlfmt and will result in breakage.
|
||||
if grep -qE '\[.*,.*\]\s*=' "$file"; then
|
||||
echo "-> Skipping (Array destructuring detected): $file" >&2
|
||||
else
|
||||
safe_files+=("$file")
|
||||
fi
|
||||
done
|
||||
|
||||
[ ${#safe_files[@]} -eq 0 ] && { echo "No safe files to format after filtering."; exit 0; }
|
||||
|
||||
echo "Formatting ${#safe_files[@]} files..."
|
||||
printf '%s\0' "${safe_files[@]}" | \
|
||||
echo "Formatting ${#all_files[@]} files..."
|
||||
printf '%s\0' "${all_files[@]}" | \
|
||||
xargs -0 -P "${QMLFMT_JOBS:-$(nproc)}" -I {} bash -c 'format_file "$@"' _ {} \
|
||||
&& echo "Done" || { echo "Errors occurred" >&2; exit 1; }
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env -S bash
|
||||
set -euo pipefail
|
||||
|
||||
# QML Formatter Script
|
||||
|
||||
# Find qmlformat binary
|
||||
if command -v qmlformat &>/dev/null; then
|
||||
QMLFORMAT="qmlformat"
|
||||
elif [ -x "/usr/lib/qt6/bin/qmlformat" ]; then
|
||||
QMLFORMAT="/usr/lib/qt6/bin/qmlformat"
|
||||
else
|
||||
echo "No 'qmlformat' found in PATH or /usr/lib/qt6/bin." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using 'qmlformat' for formatting: $QMLFORMAT"
|
||||
export QMLFORMAT
|
||||
format_file() { "$QMLFORMAT" -w 2 -W 360 -S --semicolon-rule always -i "$1" || { echo "Failed: $1" >&2; return 1; }; }
|
||||
|
||||
export -f format_file
|
||||
|
||||
# Find all .qml files
|
||||
mapfile -t all_files < <(find "${1:-.}" -name "*.qml" -type f)
|
||||
[ ${#all_files[@]} -eq 0 ] && { echo "No QML files found"; exit 0; }
|
||||
|
||||
echo "Formatting ${#all_files[@]} files..."
|
||||
printf '%s\0' "${all_files[@]}" | \
|
||||
xargs -0 -P "${QMLFMT_JOBS:-$(nproc)}" -I {} bash -c 'format_file "$@"' _ {} \
|
||||
&& echo "Done" || { echo "Errors occurred" >&2; exit 1; }
|
||||
@@ -5,16 +5,15 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
|
||||
|
||||
/*
|
||||
Noctalia is not strictly a Material Design project, it supports both some predefined
|
||||
color schemes and dynamic color generation from the wallpaper (using Matugen).
|
||||
Noctalia is not strictly a Material Design project, it supports both some predefined
|
||||
color schemes and dynamic color generation from the wallpaper (using Matugen).
|
||||
|
||||
We ultimately decided to use a restricted set of colors that follows the
|
||||
Material Design 3 naming convention.
|
||||
We ultimately decided to use a restricted set of colors that follows the
|
||||
Material Design 3 naming convention.
|
||||
|
||||
NOTE: All color names are prefixed with 'm' (e.g., mPrimary) to prevent QML from
|
||||
misinterpreting them as signals (e.g., the 'onPrimary' property name).
|
||||
NOTE: All color names are prefixed with 'm' (e.g., mPrimary) to prevent QML from
|
||||
misinterpreting them as signals (e.g., the 'onPrimary' property name).
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -87,25 +86,25 @@ Singleton {
|
||||
printErrors: false
|
||||
watchChanges: true
|
||||
onFileChanged: {
|
||||
Logger.i("Color", "Reloading colors from disk")
|
||||
reload()
|
||||
Logger.i("Color", "Reloading colors from disk");
|
||||
reload();
|
||||
}
|
||||
onAdapterUpdated: {
|
||||
Logger.i("Color", "Writing colors to disk")
|
||||
writeAdapter()
|
||||
Logger.i("Color", "Writing colors to disk");
|
||||
writeAdapter();
|
||||
}
|
||||
|
||||
// Trigger initial load when path changes from empty to actual path
|
||||
onPathChanged: {
|
||||
if (path !== undefined) {
|
||||
reload()
|
||||
reload();
|
||||
}
|
||||
}
|
||||
onLoadFailed: function (error) {
|
||||
// Error code 2 = ENOENT (No such file or directory)
|
||||
if (error === 2 || error.toString().includes("No such file")) {
|
||||
// File doesn't exist, create it with default values
|
||||
writeAdapter()
|
||||
writeAdapter();
|
||||
}
|
||||
}
|
||||
JsonAdapter {
|
||||
|
||||
253
Commons/I18n.qml
253
Commons/I18n.qml
@@ -33,13 +33,13 @@ Singleton {
|
||||
|
||||
onExited: function (exitCode, exitStatus) {
|
||||
if (exitCode === 0) {
|
||||
var output = stdoutCollector.text || ""
|
||||
parseDirectoryListing(output)
|
||||
var output = stdoutCollector.text || "";
|
||||
parseDirectoryListing(output);
|
||||
} else {
|
||||
Logger.e("I18n", `Failed to scan translation directory`)
|
||||
Logger.e("I18n", `Failed to scan translation directory`);
|
||||
// Fallback to default languages
|
||||
availableLanguages = ["en"]
|
||||
detectLanguage()
|
||||
availableLanguages = ["en"];
|
||||
detectLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,21 +51,21 @@ Singleton {
|
||||
onFileChanged: reload()
|
||||
onLoaded: {
|
||||
try {
|
||||
var data = JSON.parse(text())
|
||||
root.translations = data
|
||||
Logger.i("I18n", `Loaded translations for "${root.langCode}"`)
|
||||
Logger.d("I18n", `Available root keys: ${Object.keys(data).join(", ")}`)
|
||||
var data = JSON.parse(text());
|
||||
root.translations = data;
|
||||
Logger.i("I18n", `Loaded translations for "${root.langCode}"`);
|
||||
Logger.d("I18n", `Available root keys: ${Object.keys(data).join(", ")}`);
|
||||
|
||||
root.isLoaded = true
|
||||
root.translationsLoaded()
|
||||
root.isLoaded = true;
|
||||
root.translationsLoaded();
|
||||
} catch (e) {
|
||||
Logger.e("I18n", `Failed to parse translation file: ${e}`)
|
||||
setLanguage("en")
|
||||
Logger.e("I18n", `Failed to parse translation file: ${e}`);
|
||||
setLanguage("en");
|
||||
}
|
||||
}
|
||||
onLoadFailed: function (error) {
|
||||
setLanguage("en")
|
||||
Logger.e("I18n", `Failed to load translation file: ${error}`)
|
||||
setLanguage("en");
|
||||
Logger.e("I18n", `Failed to load translation file: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,162 +76,161 @@ Singleton {
|
||||
onFileChanged: reload()
|
||||
onLoaded: {
|
||||
try {
|
||||
var data = JSON.parse(text())
|
||||
root.fallbackTranslations = data
|
||||
Logger.d("I18n", `Loaded english fallback translations`)
|
||||
var data = JSON.parse(text());
|
||||
root.fallbackTranslations = data;
|
||||
Logger.d("I18n", `Loaded english fallback translations`);
|
||||
} catch (e) {
|
||||
Logger.e("I18n", `Failed to parse fallback translation file: ${e}`)
|
||||
Logger.e("I18n", `Failed to parse fallback translation file: ${e}`);
|
||||
}
|
||||
}
|
||||
onLoadFailed: function (error) {
|
||||
Logger.e("I18n", `Failed to load fallback translation file: ${error}`)
|
||||
Logger.e("I18n", `Failed to load fallback translation file: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.i("I18n", "Service started")
|
||||
scanAvailableLanguages()
|
||||
Logger.i("I18n", "Service started");
|
||||
scanAvailableLanguages();
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
function scanAvailableLanguages() {
|
||||
Logger.d("I18n", "Scanning for available translation files...")
|
||||
directoryScanner.running = true
|
||||
Logger.d("I18n", "Scanning for available translation files...");
|
||||
directoryScanner.running = true;
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
function parseDirectoryListing(output) {
|
||||
var languages = []
|
||||
var languages = [];
|
||||
|
||||
try {
|
||||
if (!output || output.trim() === "") {
|
||||
Logger.w("I18n", "Empty directory listing output")
|
||||
availableLanguages = ["en"]
|
||||
detectLanguage()
|
||||
return
|
||||
Logger.w("I18n", "Empty directory listing output");
|
||||
availableLanguages = ["en"];
|
||||
detectLanguage();
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = output.trim().split('\n')
|
||||
const entries = output.trim().split('\n');
|
||||
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i].trim()
|
||||
const entry = entries[i].trim();
|
||||
if (entry && entry.endsWith('.json')) {
|
||||
// Extract language code from filename (e.g., "en.json" -> "en")
|
||||
const langCode = entry.substring(0, entry.lastIndexOf('.json'))
|
||||
const langCode = entry.substring(0, entry.lastIndexOf('.json'));
|
||||
if (langCode.length >= 2 && langCode.length <= 5) {
|
||||
// Basic validation for language codes
|
||||
languages.push(langCode)
|
||||
languages.push(langCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort languages alphabetically, but ensure "en" comes first if available
|
||||
languages.sort()
|
||||
const enIndex = languages.indexOf("en")
|
||||
languages.sort();
|
||||
const enIndex = languages.indexOf("en");
|
||||
if (enIndex > 0) {
|
||||
languages.splice(enIndex, 1)
|
||||
languages.unshift("en")
|
||||
languages.splice(enIndex, 1);
|
||||
languages.unshift("en");
|
||||
}
|
||||
|
||||
if (languages.length === 0) {
|
||||
Logger.w("I18n", "No translation files found, using fallback")
|
||||
languages = ["en"] // Fallback
|
||||
Logger.w("I18n", "No translation files found, using fallback");
|
||||
languages = ["en"]; // Fallback
|
||||
}
|
||||
|
||||
availableLanguages = languages
|
||||
Logger.d("I18n", `Found ${languages.length} available languages: ${languages.join(', ')}`)
|
||||
availableLanguages = languages;
|
||||
Logger.d("I18n", `Found ${languages.length} available languages: ${languages.join(', ')}`);
|
||||
|
||||
// Detect language after scanning
|
||||
detectLanguage()
|
||||
detectLanguage();
|
||||
} catch (e) {
|
||||
Logger.e("I18n", `Failed to parse directory listing: ${e}`)
|
||||
Logger.e("I18n", `Failed to parse directory listing: ${e}`);
|
||||
// Fallback to default languages
|
||||
availableLanguages = ["en"]
|
||||
detectLanguage()
|
||||
availableLanguages = ["en"];
|
||||
detectLanguage();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
function detectLanguage() {
|
||||
Logger.d("I18n", `detectLanguage() called. Available languages: [${availableLanguages.join(', ')}]`)
|
||||
Logger.d("I18n", `detectLanguage() called. Available languages: [${availableLanguages.join(', ')}]`);
|
||||
|
||||
if (availableLanguages.length === 0) {
|
||||
Logger.w("I18n", "No available languages found")
|
||||
return
|
||||
Logger.w("I18n", "No available languages found");
|
||||
return;
|
||||
}
|
||||
|
||||
var detectedLang = ""
|
||||
var detectedFullLocale = ""
|
||||
var detectedLang = "";
|
||||
var detectedFullLocale = "";
|
||||
|
||||
// First, determine the system's preferred language
|
||||
for (var i = 0; i < Qt.locale().uiLanguages.length; i++) {
|
||||
const fullUserLang = Qt.locale().uiLanguages[i]
|
||||
const fullUserLang = Qt.locale().uiLanguages[i];
|
||||
|
||||
if (availableLanguages.includes(fullUserLang)) {
|
||||
detectedLang = fullUserLang
|
||||
detectedFullLocale = fullUserLang
|
||||
break
|
||||
detectedLang = fullUserLang;
|
||||
detectedFullLocale = fullUserLang;
|
||||
break;
|
||||
}
|
||||
|
||||
const shortUserLang = fullUserLang.substring(0, 2)
|
||||
const shortUserLang = fullUserLang.substring(0, 2);
|
||||
if (availableLanguages.includes(shortUserLang)) {
|
||||
detectedLang = shortUserLang
|
||||
detectedFullLocale = fullUserLang
|
||||
break
|
||||
detectedLang = shortUserLang;
|
||||
detectedFullLocale = fullUserLang;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no system language is found among available languages, fallback
|
||||
if (detectedLang === "") {
|
||||
detectedLang = availableLanguages.includes("en") ? "en" : availableLanguages[0]
|
||||
detectedFullLocale = detectedLang
|
||||
detectedLang = availableLanguages.includes("en") ? "en" : availableLanguages[0];
|
||||
detectedFullLocale = detectedLang;
|
||||
}
|
||||
|
||||
root.systemDetectedLangCode = detectedLang
|
||||
root.fullLocaleCode = detectedFullLocale
|
||||
Logger.d("I18n", `System detected language: "${root.systemDetectedLangCode}" (full locale: "${root.fullLocaleCode}")`)
|
||||
root.systemDetectedLangCode = detectedLang;
|
||||
root.fullLocaleCode = detectedFullLocale;
|
||||
Logger.d("I18n", `System detected language: "${root.systemDetectedLangCode}" (full locale: "${root.fullLocaleCode}")`);
|
||||
|
||||
// Now, apply the language: user-defined, then system-detected
|
||||
if (Settings.data.general.language !== "" && availableLanguages.includes(Settings.data.general.language)) {
|
||||
Logger.d("I18n", `User-defined language found: "${Settings.data.general.language}"`)
|
||||
setLanguage(Settings.data.general.language)
|
||||
Logger.d("I18n", `User-defined language found: "${Settings.data.general.language}"`);
|
||||
setLanguage(Settings.data.general.language);
|
||||
} else {
|
||||
Logger.d("I18n", `No user-defined language, using system detected: "${root.systemDetectedLangCode}"`)
|
||||
setLanguage(root.systemDetectedLangCode, root.fullLocaleCode)
|
||||
Logger.d("I18n", `No user-defined language, using system detected: "${root.systemDetectedLangCode}"`);
|
||||
setLanguage(root.systemDetectedLangCode, root.fullLocaleCode);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
function setLanguage(newLangCode, fullLocale) {
|
||||
if (typeof fullLocale === "undefined") {
|
||||
fullLocale = newLangCode
|
||||
fullLocale = newLangCode;
|
||||
}
|
||||
|
||||
if (newLangCode !== langCode && availableLanguages.includes(newLangCode)) {
|
||||
langCode = newLangCode
|
||||
fullLocaleCode = fullLocale
|
||||
locale = Qt.locale(fullLocale)
|
||||
Logger.i("I18n", `Language set to "${langCode}" with locale "${fullLocale}"`)
|
||||
languageChanged(langCode)
|
||||
loadTranslations()
|
||||
langCode = newLangCode;
|
||||
fullLocaleCode = fullLocale;
|
||||
locale = Qt.locale(fullLocale);
|
||||
Logger.i("I18n", `Language set to "${langCode}" with locale "${fullLocale}"`);
|
||||
languageChanged(langCode);
|
||||
loadTranslations();
|
||||
} else if (!availableLanguages.includes(newLangCode)) {
|
||||
Logger.w("I18n", `Language "${newLangCode}" is not available`)
|
||||
Logger.w("I18n", `Language "${newLangCode}" is not available`);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
function loadTranslations() {
|
||||
if (langCode === "")
|
||||
return
|
||||
|
||||
const filePath = `file://${Quickshell.shellDir}/Assets/Translations/${langCode}.json`
|
||||
fileView.path = filePath
|
||||
isLoaded = false
|
||||
Logger.d("I18n", `Loading translations: ${langCode}`)
|
||||
return;
|
||||
const filePath = `file://${Quickshell.shellDir}/Assets/Translations/${langCode}.json`;
|
||||
fileView.path = filePath;
|
||||
isLoaded = false;
|
||||
Logger.d("I18n", `Loading translations: ${langCode}`);
|
||||
|
||||
// Only load fallback translations if we are not using english and english is available
|
||||
if (langCode !== "en" && availableLanguages.includes("en")) {
|
||||
fallbackFileView.path = `file://${Quickshell.shellDir}/Assets/Translations/en.json`
|
||||
fallbackFileView.path = `file://${Quickshell.shellDir}/Assets/Translations/en.json`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,133 +238,133 @@ Singleton {
|
||||
// Check if a translation exists
|
||||
function hasTranslation(key) {
|
||||
if (!isLoaded)
|
||||
return false
|
||||
return false;
|
||||
|
||||
const keys = key.split(".")
|
||||
var value = translations
|
||||
const keys = key.split(".");
|
||||
var value = translations;
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (value && typeof value === "object" && keys[i] in value) {
|
||||
value = value[keys[i]]
|
||||
value = value[keys[i]];
|
||||
} else {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return typeof value === "string"
|
||||
return typeof value === "string";
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Get all translation keys (useful for debugging)
|
||||
function getAllKeys(obj, prefix) {
|
||||
if (typeof obj === "undefined")
|
||||
obj = translations
|
||||
obj = translations;
|
||||
if (typeof prefix === "undefined")
|
||||
prefix = ""
|
||||
prefix = "";
|
||||
|
||||
var keys = []
|
||||
var keys = [];
|
||||
for (var key in (obj || {})) {
|
||||
const value = obj[key]
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key
|
||||
const value = obj[key];
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||
if (typeof value === "object" && value !== null) {
|
||||
keys = keys.concat(getAllKeys(value, fullKey))
|
||||
keys = keys.concat(getAllKeys(value, fullKey));
|
||||
} else if (typeof value === "string") {
|
||||
keys.push(fullKey)
|
||||
keys.push(fullKey);
|
||||
}
|
||||
}
|
||||
return keys
|
||||
return keys;
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Reload translations (useful for development)
|
||||
function reload() {
|
||||
Logger.d("I18n", "Reloading translations")
|
||||
loadTranslations()
|
||||
Logger.d("I18n", "Reloading translations");
|
||||
loadTranslations();
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Main translation function
|
||||
function tr(key, interpolations) {
|
||||
if (typeof interpolations === "undefined")
|
||||
interpolations = {}
|
||||
interpolations = {};
|
||||
|
||||
if (!isLoaded) {
|
||||
//Logger.d("I18n", "Translations not loaded yet")
|
||||
return key
|
||||
return key;
|
||||
}
|
||||
|
||||
// Navigate nested keys (e.g., "menu.file.open")
|
||||
const keys = key.split(".")
|
||||
const keys = key.split(".");
|
||||
|
||||
// Look-up translation in the active language
|
||||
var value = translations
|
||||
var notFound = false
|
||||
var value = translations;
|
||||
var notFound = false;
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (value && typeof value === "object" && keys[i] in value) {
|
||||
value = value[keys[i]]
|
||||
value = value[keys[i]];
|
||||
} else {
|
||||
Logger.d("I18n", `Translation key "${key}" not found at part "${keys[i]}"`)
|
||||
Logger.d("I18n", `Available keys: ${Object.keys(value || {}).join(", ")}`)
|
||||
notFound = true
|
||||
break
|
||||
Logger.d("I18n", `Translation key "${key}" not found at part "${keys[i]}"`);
|
||||
Logger.d("I18n", `Available keys: ${Object.keys(value || {}).join(", ")}`);
|
||||
notFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to english if not found
|
||||
if (notFound && availableLanguages.includes("en") && langCode !== "en") {
|
||||
value = fallbackTranslations
|
||||
value = fallbackTranslations;
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (value && typeof value === "object" && keys[i] in value) {
|
||||
value = value[keys[i]]
|
||||
value = value[keys[i]];
|
||||
} else {
|
||||
// Indicate this key does not even exists in the english fallback
|
||||
return `## ${key} ##`
|
||||
return `## ${key} ##`;
|
||||
}
|
||||
}
|
||||
|
||||
// Make untranslated string easy to spot
|
||||
value = `<i>${value}</i>`
|
||||
value = `<i>${value}</i>`;
|
||||
} else if (notFound) {
|
||||
// No fallback available
|
||||
return `## ${key} ##`
|
||||
return `## ${key} ##`;
|
||||
}
|
||||
|
||||
if (typeof value !== "string") {
|
||||
Logger.d("I18n", `Translation key "${key}" is not a string`)
|
||||
return key
|
||||
Logger.d("I18n", `Translation key "${key}" is not a string`);
|
||||
return key;
|
||||
}
|
||||
|
||||
// Handle interpolations (e.g., "Hello {name}!")
|
||||
var result = value
|
||||
var result = value;
|
||||
for (var placeholder in interpolations) {
|
||||
const regex = new RegExp(`\\{${placeholder}\\}`, 'g')
|
||||
result = result.replace(regex, interpolations[placeholder])
|
||||
const regex = new RegExp(`\\{${placeholder}\\}`, 'g');
|
||||
result = result.replace(regex, interpolations[placeholder]);
|
||||
}
|
||||
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Plural translation function
|
||||
function trp(key, count, defaultSingular, defaultPlural, interpolations) {
|
||||
if (typeof defaultSingular === "undefined")
|
||||
defaultSingular = ""
|
||||
defaultSingular = "";
|
||||
if (typeof defaultPlural === "undefined")
|
||||
defaultPlural = ""
|
||||
defaultPlural = "";
|
||||
if (typeof interpolations === "undefined")
|
||||
interpolations = {}
|
||||
interpolations = {};
|
||||
|
||||
const pluralKey = count === 1 ? key : `${key}_plural`
|
||||
const defaultValue = count === 1 ? defaultSingular : defaultPlural
|
||||
const pluralKey = count === 1 ? key : `${key}_plural`;
|
||||
const defaultValue = count === 1 ? defaultSingular : defaultPlural;
|
||||
|
||||
// Merge interpolations with count (QML doesn't support spread operator)
|
||||
var finalInterpolations = {
|
||||
"count": count
|
||||
}
|
||||
};
|
||||
for (var prop in interpolations) {
|
||||
finalInterpolations[prop] = interpolations[prop]
|
||||
finalInterpolations[prop] = interpolations[prop];
|
||||
}
|
||||
|
||||
return tr(pluralKey, finalInterpolations)
|
||||
return tr(pluralKey, finalInterpolations);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,15 +26,15 @@ Singleton {
|
||||
signal fontReloaded
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.i("Icons", "Service started")
|
||||
loadFontWithCacheBusting()
|
||||
Logger.i("Icons", "Service started");
|
||||
loadFontWithCacheBusting();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onReloadCompleted() {
|
||||
Logger.d("Icons", "Quickshell reload completed - forcing font reload")
|
||||
reloadFont()
|
||||
Logger.d("Icons", "Quickshell reload completed - forcing font reload");
|
||||
reloadFont();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,20 +42,20 @@ Singleton {
|
||||
function get(iconName) {
|
||||
// Check in aliases first
|
||||
if (aliases[iconName] !== undefined) {
|
||||
iconName = aliases[iconName]
|
||||
iconName = aliases[iconName];
|
||||
}
|
||||
|
||||
// Find the appropriate codepoint
|
||||
return icons[iconName]
|
||||
return icons[iconName];
|
||||
}
|
||||
|
||||
function loadFontWithCacheBusting() {
|
||||
Logger.d("Icons", "Loading font with cache busting")
|
||||
Logger.d("Icons", "Loading font with cache busting");
|
||||
|
||||
// Destroy old loader first
|
||||
if (currentFontLoader) {
|
||||
currentFontLoader.destroy()
|
||||
currentFontLoader = null
|
||||
currentFontLoader.destroy();
|
||||
currentFontLoader = null;
|
||||
}
|
||||
|
||||
// Create new loader with cache-busting URL
|
||||
@@ -64,22 +64,22 @@ Singleton {
|
||||
FontLoader {
|
||||
source: "${cacheBustingPath}"
|
||||
}
|
||||
`, root, "dynamicFontLoader_" + fontVersion)
|
||||
`, root, "dynamicFontLoader_" + fontVersion);
|
||||
|
||||
// Connect to the new loader's status changes
|
||||
currentFontLoader.statusChanged.connect(function () {
|
||||
if (currentFontLoader.status === FontLoader.Ready) {
|
||||
Logger.d("Icons", "Font loaded successfully:", currentFontLoader.name, "(version " + fontVersion + ")")
|
||||
fontReloaded()
|
||||
Logger.d("Icons", "Font loaded successfully:", currentFontLoader.name, "(version " + fontVersion + ")");
|
||||
fontReloaded();
|
||||
} else if (currentFontLoader.status === FontLoader.Error) {
|
||||
Logger.e("Icons", "Font failed to load (version " + fontVersion + ")")
|
||||
Logger.e("Icons", "Font failed to load (version " + fontVersion + ")");
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function reloadFont() {
|
||||
Logger.d("Icons", "Forcing font reload...")
|
||||
fontVersion++
|
||||
loadFontWithCacheBusting()
|
||||
Logger.d("Icons", "Forcing font reload...");
|
||||
fontVersion++;
|
||||
loadFontWithCacheBusting();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,8 +310,8 @@ Singleton {
|
||||
"align-left": "\u{ea09}",
|
||||
"align-left-2": "\u{ff00}",
|
||||
"align-right": "\u{ea0a}",
|
||||
"alpha"//"align-right-2": "\u{feff}",
|
||||
: "\u{f543}",
|
||||
//"align-right-2": "\u{feff}",
|
||||
"alpha": "\u{f543}",
|
||||
"alphabet-arabic": "\u{ff2f}",
|
||||
"alphabet-bangla": "\u{ff2e}",
|
||||
"alphabet-cyrillic": "\u{f1df}",
|
||||
@@ -3143,8 +3143,8 @@ Singleton {
|
||||
"friends": "\u{eab0}",
|
||||
"friends-off": "\u{f136}",
|
||||
"frustum": "\u{fa9f}",
|
||||
"frustum-plus"//"frustum-off": "\u{fa9d}",
|
||||
: "\u{fa9e}",
|
||||
//"frustum-off": "\u{fa9d}",
|
||||
"frustum-plus": "\u{fa9e}",
|
||||
"function": "\u{f225}",
|
||||
"function-filled": "\u{fc2b}",
|
||||
"function-off": "\u{f3f0}",
|
||||
@@ -3403,13 +3403,13 @@ Singleton {
|
||||
"hexagon-letter-x": "\u{f479}",
|
||||
"hexagon-letter-x-filled": "\u{fe30}",
|
||||
"hexagon-letter-y": "\u{f47a}",
|
||||
"hexagon-letter-z"//"hexagon-letter-y-filled": "\u{fe2f}",
|
||||
: "\u{f47b}",
|
||||
"hexagon-minus"//"hexagon-letter-z-filled": "\u{fe2e}",
|
||||
: "\u{fc8f}",
|
||||
//"hexagon-letter-y-filled": "\u{fe2f}",
|
||||
"hexagon-letter-z": "\u{f47b}",
|
||||
//"hexagon-letter-z-filled": "\u{fe2e}",
|
||||
"hexagon-minus": "\u{fc8f}",
|
||||
"hexagon-minus-2": "\u{fc8e}",
|
||||
"hexagon-number-0"//"hexagon-minus-filled": "\u{fe2d}",
|
||||
: "\u{f459}",
|
||||
//"hexagon-minus-filled": "\u{fe2d}",
|
||||
"hexagon-number-0": "\u{f459}",
|
||||
"hexagon-number-0-filled": "\u{f74c}",
|
||||
"hexagon-number-1": "\u{f45a}",
|
||||
"hexagon-number-1-filled": "\u{f74d}",
|
||||
@@ -3432,8 +3432,8 @@ Singleton {
|
||||
"hexagon-off": "\u{ee9c}",
|
||||
"hexagon-plus": "\u{fc45}",
|
||||
"hexagon-plus-2": "\u{fc90}",
|
||||
"hexagonal-prism"//"hexagon-plus-filled": "\u{fe2c}",
|
||||
: "\u{faa5}",
|
||||
//"hexagon-plus-filled": "\u{fe2c}",
|
||||
"hexagonal-prism": "\u{faa5}",
|
||||
"hexagonal-prism-off": "\u{faa3}",
|
||||
"hexagonal-prism-plus": "\u{faa4}",
|
||||
"hexagonal-pyramid": "\u{faa8}",
|
||||
@@ -3463,8 +3463,8 @@ Singleton {
|
||||
"home-eco": "\u{f351}",
|
||||
"home-edit": "\u{f352}",
|
||||
"home-exclamation": "\u{f33c}",
|
||||
"home-hand"//"home-filled": "\u{fe2b}",
|
||||
: "\u{f504}",
|
||||
//"home-filled": "\u{fe2b}",
|
||||
"home-hand": "\u{f504}",
|
||||
"home-heart": "\u{f353}",
|
||||
"home-infinity": "\u{f505}",
|
||||
"home-link": "\u{f354}",
|
||||
@@ -3582,8 +3582,8 @@ Singleton {
|
||||
"ironing-2-filled": "\u{1006e}",
|
||||
"ironing-3": "\u{f2f6}",
|
||||
"ironing-3-filled": "\u{1006d}",
|
||||
"ironing-off"//"ironing-filled": "\u{fe2a}",
|
||||
: "\u{f2f7}",
|
||||
//"ironing-filled": "\u{fe2a}",
|
||||
"ironing-off": "\u{f2f7}",
|
||||
"ironing-steam": "\u{f2f9}",
|
||||
"ironing-steam-filled": "\u{1006c}",
|
||||
"ironing-steam-off": "\u{f2f8}",
|
||||
@@ -3593,8 +3593,8 @@ Singleton {
|
||||
"italic": "\u{eb93}",
|
||||
"jacket": "\u{f661}",
|
||||
"jetpack": "\u{f581}",
|
||||
"jewish-star"//"jetpack-filled": "\u{fe29}",
|
||||
: "\u{f3ff}",
|
||||
//"jetpack-filled": "\u{fe29}",
|
||||
"jewish-star": "\u{f3ff}",
|
||||
"jewish-star-filled": "\u{f67e}",
|
||||
"join-bevel": "\u{ff4c}",
|
||||
"join-round": "\u{ff4b}",
|
||||
@@ -3608,8 +3608,8 @@ Singleton {
|
||||
"kering": "\u{efb8}",
|
||||
"kerning": "\u{efb8}",
|
||||
"key": "\u{eac7}",
|
||||
"key-off"//"key-filled": "\u{fe28}",
|
||||
: "\u{f14b}",
|
||||
//"key-filled": "\u{fe28}",
|
||||
"key-off": "\u{f14b}",
|
||||
"keyboard": "\u{ebd6}",
|
||||
"keyboard-filled": "\u{100a2}",
|
||||
"keyboard-hide": "\u{ec7e}",
|
||||
@@ -3665,20 +3665,20 @@ Singleton {
|
||||
"layers-union": "\u{eacb}",
|
||||
"layout": "\u{eadb}",
|
||||
"layout-2": "\u{eacc}",
|
||||
"layout-align-left"//"layout-2-filled": "\u{fe27}",
|
||||
// "layout-align-bottom": "\u{eacd}",
|
||||
//"layout-2-filled": "\u{fe27}",
|
||||
//"layout-align-bottom": "\u{eacd}",
|
||||
//"layout-align-bottom-filled": "\u{fe26}",
|
||||
// "layout-align-center": "\u{eace}",
|
||||
//"layout-align-center": "\u{eace}",
|
||||
//"layout-align-center-filled": "\u{fe25}",
|
||||
: "\u{eacf}",
|
||||
"layout-align-middle"// "layout-align-left-filled": "\u{fe24}",
|
||||
: "\u{ead0}",
|
||||
"layout-align-right"//"layout-align-middle-filled": "\u{fe23}",
|
||||
: "\u{ead1}",
|
||||
"layout-align-top"//"layout-align-right-filled": "\u{fe22}",
|
||||
: "\u{ead2}",
|
||||
"layout-board"//"layout-align-top-filled": "\u{fe21}",
|
||||
: "\u{ef95}",
|
||||
"layout-align-left": "\u{eacf}",
|
||||
//"layout-align-left-filled": "\u{fe24}",
|
||||
"layout-align-middle": "\u{ead0}",
|
||||
//"layout-align-middle-filled": "\u{fe23}",
|
||||
"layout-align-right": "\u{ead1}",
|
||||
//"layout-align-right-filled": "\u{fe22}",
|
||||
"layout-align-top": "\u{ead2}",
|
||||
//"layout-align-top-filled": "\u{fe21}",
|
||||
"layout-board": "\u{ef95}",
|
||||
"layout-board-filled": "\u{10182}",
|
||||
"layout-board-split": "\u{ef94}",
|
||||
"layout-board-split-filled": "\u{10183}",
|
||||
@@ -3690,8 +3690,8 @@ Singleton {
|
||||
"layout-bottombar-filled": "\u{fc37}",
|
||||
"layout-bottombar-inactive": "\u{fd45}",
|
||||
"layout-cards": "\u{ec13}",
|
||||
"layout-collage"// "layout-cards-filled": "\u{fe20}",
|
||||
: "\u{f389}",
|
||||
//"layout-cards-filled": "\u{fe20}",
|
||||
"layout-collage": "\u{f389}",
|
||||
"layout-columns": "\u{ead4}",
|
||||
"layout-dashboard": "\u{f02c}",
|
||||
"layout-dashboard-filled": "\u{fe1f}",
|
||||
@@ -4172,14 +4172,14 @@ Singleton {
|
||||
"microphone": "\u{eaf0}",
|
||||
"microphone-2": "\u{ef2c}",
|
||||
"microphone-2-off": "\u{f40d}",
|
||||
"microphone-off"//"microphone-filled": "\u{fe0f}",
|
||||
: "\u{ed16}",
|
||||
//"microphone-filled": "\u{fe0f}",
|
||||
"microphone-off": "\u{ed16}",
|
||||
"microscope": "\u{ef64}",
|
||||
"microscope-filled": "\u{10166}",
|
||||
"microscope-off": "\u{f40e}",
|
||||
"microwave": "\u{f248}",
|
||||
"microwave-off"//"microwave-filled": "\u{fe0e}",
|
||||
: "\u{f264}",
|
||||
//"microwave-filled": "\u{fe0e}",
|
||||
"microwave-off": "\u{f264}",
|
||||
"military-award": "\u{f079}",
|
||||
"military-rank": "\u{efcf}",
|
||||
"military-rank-filled": "\u{ff5e}",
|
||||
@@ -4413,18 +4413,18 @@ Singleton {
|
||||
"number-4-small": "\u{fcf9}",
|
||||
"number-40-small": "\u{fffa}",
|
||||
"number-41-small": "\u{fff9}",
|
||||
"number-5"//"number-42-small": "\u{fff8}",
|
||||
// "number-43-small": "\u{fff7}",
|
||||
// "number-44-small": "\u{fff6}",
|
||||
// "number-45-small": "\u{fff5}",
|
||||
// "number-46-small": "\u{fff4}",
|
||||
// "number-47-small": "\u{fff3}",
|
||||
// "number-48-small": "\u{fff2}",
|
||||
// "number-49-small": "\u{fff1}",
|
||||
: "\u{edf5}",
|
||||
//"number-42-small": "\u{fff8}",
|
||||
//"number-43-small": "\u{fff7}",
|
||||
//"number-44-small": "\u{fff6}",
|
||||
//"number-45-small": "\u{fff5}",
|
||||
//"number-46-small": "\u{fff4}",
|
||||
//"number-47-small": "\u{fff3}",
|
||||
//"number-48-small": "\u{fff2}",
|
||||
//"number-49-small": "\u{fff1}",
|
||||
"number-5": "\u{edf5}",
|
||||
"number-5-small": "\u{fcfa}",
|
||||
"number-51-small"// "number-50-small": "\u{fff0}",
|
||||
: "\u{ffef}",
|
||||
//"number-50-small": "\u{fff0}",
|
||||
"number-51-small": "\u{ffef}",
|
||||
"number-52-small": "\u{ffee}",
|
||||
"number-53-small": "\u{ffed}",
|
||||
"number-54-small": "\u{ffec}",
|
||||
@@ -4864,11 +4864,11 @@ Singleton {
|
||||
"quote": "\u{efbe}",
|
||||
"quote-filled": "\u{1009c}",
|
||||
"quote-off": "\u{f188}",
|
||||
"radar"//"quotes": "\u{fb1e}",
|
||||
: "\u{f017}",
|
||||
//"quotes": "\u{fb1e}",
|
||||
"radar": "\u{f017}",
|
||||
"radar-2": "\u{f016}",
|
||||
"radar-off"//"radar-filled": "\u{fe0d}",
|
||||
: "\u{f41f}",
|
||||
//"radar-filled": "\u{fe0d}",
|
||||
"radar-off": "\u{f41f}",
|
||||
"radio": "\u{ef2d}",
|
||||
"radio-off": "\u{f420}",
|
||||
"radioactive": "\u{ecc0}",
|
||||
@@ -4928,12 +4928,12 @@ Singleton {
|
||||
"regex-off": "\u{f421}",
|
||||
"registered": "\u{eb14}",
|
||||
"relation-many-to-many": "\u{ed7f}",
|
||||
"relation-one-to-many"//"relation-many-to-many-filled": "\u{fe0c}",
|
||||
: "\u{ed80}",
|
||||
"relation-one-to-one"//"relation-one-to-many-filled": "\u{fe0b}",
|
||||
: "\u{ed81}",
|
||||
"reload"//"relation-one-to-one-filled": "\u{fe0a}",
|
||||
: "\u{f3ae}",
|
||||
//"relation-many-to-many-filled": "\u{fe0c}",
|
||||
"relation-one-to-many": "\u{ed80}",
|
||||
//"relation-one-to-many-filled": "\u{fe0b}",
|
||||
"relation-one-to-one": "\u{ed81}",
|
||||
//"relation-one-to-one-filled": "\u{fe0a}",
|
||||
"reload": "\u{f3ae}",
|
||||
"reorder": "\u{fc15}",
|
||||
"repeat": "\u{eb72}",
|
||||
"repeat-off": "\u{f18e}",
|
||||
@@ -5085,8 +5085,8 @@ Singleton {
|
||||
"search": "\u{eb1c}",
|
||||
"search-off": "\u{f19c}",
|
||||
"section": "\u{eed5}",
|
||||
"section-sign"//"section-filled": "\u{fe09}",
|
||||
: "\u{f019}",
|
||||
//"section-filled": "\u{fe09}",
|
||||
"section-sign": "\u{f019}",
|
||||
"seeding": "\u{ed51}",
|
||||
"seeding-filled": "\u{10006}",
|
||||
"seeding-off": "\u{f19d}",
|
||||
@@ -5294,8 +5294,8 @@ Singleton {
|
||||
"sort-z-a": "\u{f550}",
|
||||
"sos": "\u{f24a}",
|
||||
"soup": "\u{ef2e}",
|
||||
"soup-off"//"soup-filled": "\u{fe08}",
|
||||
: "\u{f42d}",
|
||||
//"soup-filled": "\u{fe08}",
|
||||
"soup-off": "\u{f42d}",
|
||||
"source-code": "\u{f4a2}",
|
||||
"space": "\u{ec0c}",
|
||||
"space-off": "\u{f1aa}",
|
||||
@@ -5388,22 +5388,22 @@ Singleton {
|
||||
"square-half": "\u{effb}",
|
||||
"square-key": "\u{f638}",
|
||||
"square-letter-a": "\u{f47c}",
|
||||
"square-letter-b"//"square-letter-a-filled": "\u{fe07}",
|
||||
: "\u{f47d}",
|
||||
"square-letter-c"//"square-letter-b-filled": "\u{fe06}",
|
||||
: "\u{f47e}",
|
||||
"square-letter-d"//"square-letter-c-filled": "\u{fe05}",
|
||||
: "\u{f47f}",
|
||||
"square-letter-e"//"square-letter-d-filled": "\u{fe04}",
|
||||
: "\u{f480}",
|
||||
"square-letter-f"//"square-letter-e-filled": "\u{fe03}",
|
||||
: "\u{f481}",
|
||||
"square-letter-g"//"square-letter-f-filled": "\u{fe02}",
|
||||
: "\u{f482}",
|
||||
"square-letter-h"//"square-letter-g-filled": "\u{fe01}",
|
||||
: "\u{f483}",
|
||||
"square-letter-i"//"square-letter-h-filled": "\u{fe00}",
|
||||
: "\u{f484}",
|
||||
//"square-letter-a-filled": "\u{fe07}",
|
||||
"square-letter-b": "\u{f47d}",
|
||||
//"square-letter-b-filled": "\u{fe06}",
|
||||
"square-letter-c": "\u{f47e}",
|
||||
//"square-letter-c-filled": "\u{fe05}",
|
||||
"square-letter-d": "\u{f47f}",
|
||||
//"square-letter-d-filled": "\u{fe04}",
|
||||
"square-letter-e": "\u{f480}",
|
||||
//"square-letter-e-filled": "\u{fe03}",
|
||||
"square-letter-f": "\u{f481}",
|
||||
//"square-letter-f-filled": "\u{fe02}",
|
||||
"square-letter-g": "\u{f482}",
|
||||
//"square-letter-g-filled": "\u{fe01}",
|
||||
"square-letter-h": "\u{f483}",
|
||||
//"square-letter-h-filled": "\u{fe00}",
|
||||
"square-letter-i": "\u{f484}",
|
||||
"square-letter-i-filled": "\u{fdff}",
|
||||
"square-letter-j": "\u{f485}",
|
||||
"square-letter-j-filled": "\u{fdfe}",
|
||||
|
||||
@@ -7,63 +7,63 @@ Singleton {
|
||||
id: root
|
||||
|
||||
function _formatMessage(...args) {
|
||||
var t = Time.getFormattedTimestamp()
|
||||
var t = Time.getFormattedTimestamp();
|
||||
if (args.length > 1) {
|
||||
const maxLength = 14
|
||||
var module = args.shift().substring(0, maxLength).padStart(maxLength, " ")
|
||||
return `\x1b[36m[${t}]\x1b[0m \x1b[35m${module}\x1b[0m ` + args.join(" ")
|
||||
const maxLength = 14;
|
||||
var module = args.shift().substring(0, maxLength).padStart(maxLength, " ");
|
||||
return `\x1b[36m[${t}]\x1b[0m \x1b[35m${module}\x1b[0m ` + args.join(" ");
|
||||
} else {
|
||||
return `[\x1b[36m[${t}]\x1b[0m ` + args.join(" ")
|
||||
return `[\x1b[36m[${t}]\x1b[0m ` + args.join(" ");
|
||||
}
|
||||
}
|
||||
|
||||
function _getStackTrace() {
|
||||
try {
|
||||
throw new Error("Stack trace")
|
||||
throw new Error("Stack trace");
|
||||
} catch (e) {
|
||||
return e.stack
|
||||
return e.stack;
|
||||
}
|
||||
}
|
||||
|
||||
// Debug log (only when Settings.isDebug is true)
|
||||
function d(...args) {
|
||||
if (Settings && Settings.isDebug) {
|
||||
var msg = _formatMessage(...args)
|
||||
console.debug(msg)
|
||||
if (Settings?.isDebug) {
|
||||
var msg = _formatMessage(...args);
|
||||
console.debug(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Info log (always visible)
|
||||
function i(...args) {
|
||||
var msg = _formatMessage(...args)
|
||||
console.info(msg)
|
||||
var msg = _formatMessage(...args);
|
||||
console.info(msg);
|
||||
}
|
||||
|
||||
// Warning log (always visible)
|
||||
function w(...args) {
|
||||
var msg = _formatMessage(...args)
|
||||
console.warn(msg)
|
||||
var msg = _formatMessage(...args);
|
||||
console.warn(msg);
|
||||
}
|
||||
|
||||
// Error log (always visible)
|
||||
function e(...args) {
|
||||
var msg = _formatMessage(...args)
|
||||
console.error(msg)
|
||||
var msg = _formatMessage(...args);
|
||||
console.error(msg);
|
||||
}
|
||||
|
||||
function callStack() {
|
||||
var stack = _getStackTrace()
|
||||
Logger.i("Debug", "--------------------------")
|
||||
Logger.i("Debug", "Current call stack")
|
||||
var stack = _getStackTrace();
|
||||
Logger.i("Debug", "--------------------------");
|
||||
Logger.i("Debug", "Current call stack");
|
||||
// Split the stack into lines and log each one
|
||||
var stackLines = stack.split('\n')
|
||||
var stackLines = stack.split('\n');
|
||||
for (var i = 0; i < stackLines.length; i++) {
|
||||
var line = stackLines[i].trim() // Remove leading/trailing whitespace
|
||||
var line = stackLines[i].trim(); // Remove leading/trailing whitespace
|
||||
if (line.length > 0) {
|
||||
// Only log non-empty lines
|
||||
Logger.i("Debug", `- ${line}`)
|
||||
Logger.i("Debug", `- ${line}`);
|
||||
}
|
||||
}
|
||||
Logger.i("Debug", "--------------------------")
|
||||
Logger.i("Debug", "--------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "../Helpers/QtObj2JS.js" as QtObj2JS
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import "../Helpers/QtObj2JS.js" as QtObj2JS
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -42,30 +42,30 @@ Singleton {
|
||||
// Ensure directories exist before FileView tries to read files
|
||||
Component.onCompleted: {
|
||||
// ensure settings dir exists
|
||||
Quickshell.execDetached(["mkdir", "-p", configDir])
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDir])
|
||||
Quickshell.execDetached(["mkdir", "-p", configDir]);
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDir]);
|
||||
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDirImagesWallpapers])
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDirImagesNotifications])
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDirImagesWallpapers]);
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDirImagesNotifications]);
|
||||
|
||||
// Mark directories as created and trigger file loading
|
||||
directoriesCreated = true
|
||||
directoriesCreated = true;
|
||||
|
||||
// This should only be activated once when the settings structure has changed
|
||||
// Then it should be commented out again, regular users don't need to generate
|
||||
// default settings on every start
|
||||
if (isDebug) {
|
||||
generateDefaultSettings()
|
||||
generateDefaultSettings();
|
||||
}
|
||||
|
||||
// Patch-in the local default, resolved to user's home
|
||||
adapter.general.avatarImage = defaultAvatar
|
||||
adapter.screenRecorder.directory = defaultVideosDirectory
|
||||
adapter.wallpaper.directory = defaultWallpapersDirectory
|
||||
adapter.wallpaper.defaultWallpaper = Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png"
|
||||
adapter.general.avatarImage = defaultAvatar;
|
||||
adapter.screenRecorder.directory = defaultVideosDirectory;
|
||||
adapter.wallpaper.directory = defaultWallpapersDirectory;
|
||||
adapter.wallpaper.defaultWallpaper = Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png";
|
||||
|
||||
// Set the adapter to the settingsFileView to trigger the real settings load
|
||||
settingsFileView.adapter = adapter
|
||||
settingsFileView.adapter = adapter;
|
||||
}
|
||||
|
||||
// Don't write settings to disk immediately
|
||||
@@ -75,7 +75,7 @@ Singleton {
|
||||
running: false
|
||||
interval: 1000
|
||||
onTriggered: {
|
||||
root.saveImmediate()
|
||||
root.saveImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,31 +90,31 @@ Singleton {
|
||||
// Trigger initial load when path changes from empty to actual path
|
||||
onPathChanged: {
|
||||
if (path !== undefined) {
|
||||
reload()
|
||||
reload();
|
||||
}
|
||||
}
|
||||
onLoaded: function () {
|
||||
if (!isLoaded) {
|
||||
Logger.i("Settings", "Settings loaded")
|
||||
Logger.i("Settings", "Settings loaded");
|
||||
|
||||
upgradeSettingsData()
|
||||
validateMonitorConfigurations()
|
||||
isLoaded = true
|
||||
upgradeSettingsData();
|
||||
validateMonitorConfigurations();
|
||||
isLoaded = true;
|
||||
|
||||
// Emit the signal
|
||||
root.settingsLoaded()
|
||||
root.settingsLoaded();
|
||||
|
||||
// Finally, update our local settings version
|
||||
adapter.settingsVersion = settingsVersion
|
||||
adapter.settingsVersion = settingsVersion;
|
||||
}
|
||||
}
|
||||
onLoadFailed: function (error) {
|
||||
if (error.toString().includes("No such file") || error === 2) {
|
||||
// File doesn't exist, create it with default values
|
||||
writeAdapter()
|
||||
writeAdapter();
|
||||
// Also write to fallback if set
|
||||
if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) {
|
||||
settingsFallbackFileView.writeAdapter()
|
||||
settingsFallbackFileView.writeAdapter();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,33 +156,48 @@ Singleton {
|
||||
// Widget configuration for modular bar system
|
||||
property JsonObject widgets
|
||||
widgets: JsonObject {
|
||||
property list<var> left: [{
|
||||
property list<var> left: [
|
||||
{
|
||||
"id": "ControlCenter"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "SystemMonitor"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "ActiveWindow"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "MediaMini"
|
||||
}]
|
||||
property list<var> center: [{
|
||||
}
|
||||
]
|
||||
property list<var> center: [
|
||||
{
|
||||
"id": "Workspace"
|
||||
}]
|
||||
property list<var> right: [{
|
||||
}
|
||||
]
|
||||
property list<var> right: [
|
||||
{
|
||||
"id": "ScreenRecorder"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "Tray"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "NotificationHistory"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "Battery"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "Volume"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "Brightness"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "Clock"
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,41 +309,57 @@ Singleton {
|
||||
property string position: "close_to_bar_button"
|
||||
property JsonObject shortcuts
|
||||
shortcuts: JsonObject {
|
||||
property list<var> left: [{
|
||||
property list<var> left: [
|
||||
{
|
||||
"id": "WiFi"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "Bluetooth"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "ScreenRecorder"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "WallpaperSelector"
|
||||
}]
|
||||
property list<var> right: [{
|
||||
}
|
||||
]
|
||||
property list<var> right: [
|
||||
{
|
||||
"id": "Notifications"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "PowerProfile"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "KeepAwake"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "NightLight"
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
property list<var> cards: [{
|
||||
property list<var> cards: [
|
||||
{
|
||||
"id": "profile-card",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "shortcuts-card",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "audio-card",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "weather-card",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "media-sysmon-card",
|
||||
"enabled": true
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// system monitor
|
||||
@@ -371,25 +402,32 @@ Singleton {
|
||||
property int countdownDuration: 10000
|
||||
property string position: "center"
|
||||
property bool showHeader: true
|
||||
property list<var> powerOptions: [{
|
||||
property list<var> powerOptions: [
|
||||
{
|
||||
"action": "lock",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"action": "suspend",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"action": "hibernate",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"action": "reboot",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"action": "logout",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"action": "shutdown",
|
||||
"enabled": true
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// notifications
|
||||
@@ -493,81 +531,81 @@ Singleton {
|
||||
// Function to preprocess paths by expanding "~" to user's home directory
|
||||
function preprocessPath(path) {
|
||||
if (typeof path !== "string" || path === "") {
|
||||
return path
|
||||
return path;
|
||||
}
|
||||
|
||||
// Expand "~" to user's home directory
|
||||
if (path.startsWith("~/")) {
|
||||
return Quickshell.env("HOME") + path.substring(1)
|
||||
return Quickshell.env("HOME") + path.substring(1);
|
||||
} else if (path === "~") {
|
||||
return Quickshell.env("HOME")
|
||||
return Quickshell.env("HOME");
|
||||
}
|
||||
|
||||
return path
|
||||
return path;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Public function to trigger immediate settings saving
|
||||
function saveImmediate() {
|
||||
settingsFileView.writeAdapter()
|
||||
settingsFileView.writeAdapter();
|
||||
// Write to fallback location if set
|
||||
if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) {
|
||||
settingsFallbackFileView.writeAdapter()
|
||||
settingsFallbackFileView.writeAdapter();
|
||||
}
|
||||
root.settingsSaved() // Emit signal after saving
|
||||
root.settingsSaved(); // Emit signal after saving
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Generate default settings at the root of the repo
|
||||
function generateDefaultSettings() {
|
||||
try {
|
||||
Logger.d("Settings", "Generating settings-default.json")
|
||||
Logger.d("Settings", "Generating settings-default.json");
|
||||
|
||||
// Prepare a clean JSON
|
||||
var plainAdapter = QtObj2JS.qtObjectToPlainObject(adapter)
|
||||
var jsonData = JSON.stringify(plainAdapter, null, 2)
|
||||
var plainAdapter = QtObj2JS.qtObjectToPlainObject(adapter);
|
||||
var jsonData = JSON.stringify(plainAdapter, null, 2);
|
||||
|
||||
var defaultPath = Quickshell.shellDir + "/Assets/settings-default.json"
|
||||
var defaultPath = Quickshell.shellDir + "/Assets/settings-default.json";
|
||||
|
||||
// Encode transfer it has base64 to avoid any escaping issue
|
||||
var base64Data = Qt.btoa(jsonData)
|
||||
Quickshell.execDetached(["sh", "-c", `echo "${base64Data}" | base64 -d > "${defaultPath}"`])
|
||||
var base64Data = Qt.btoa(jsonData);
|
||||
Quickshell.execDetached(["sh", "-c", `echo "${base64Data}" | base64 -d > "${defaultPath}"`]);
|
||||
} catch (error) {
|
||||
Logger.e("Settings", "Failed to generate default settings file: " + error)
|
||||
Logger.e("Settings", "Failed to generate default settings file: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Function to validate monitor configurations
|
||||
function validateMonitorConfigurations() {
|
||||
var availableScreenNames = []
|
||||
var availableScreenNames = [];
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
availableScreenNames.push(Quickshell.screens[i].name)
|
||||
availableScreenNames.push(Quickshell.screens[i].name);
|
||||
}
|
||||
|
||||
Logger.d("Settings", "Available monitors: [" + availableScreenNames.join(", ") + "]")
|
||||
Logger.d("Settings", "Configured bar monitors: [" + adapter.bar.monitors.join(", ") + "]")
|
||||
Logger.d("Settings", "Available monitors: [" + availableScreenNames.join(", ") + "]");
|
||||
Logger.d("Settings", "Configured bar monitors: [" + adapter.bar.monitors.join(", ") + "]");
|
||||
|
||||
// Check bar monitors
|
||||
if (adapter.bar.monitors.length > 0) {
|
||||
var hasValidBarMonitor = false
|
||||
var hasValidBarMonitor = false;
|
||||
for (var j = 0; j < adapter.bar.monitors.length; j++) {
|
||||
if (availableScreenNames.includes(adapter.bar.monitors[j])) {
|
||||
hasValidBarMonitor = true
|
||||
break
|
||||
hasValidBarMonitor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasValidBarMonitor) {
|
||||
Logger.w("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens")
|
||||
adapter.bar.monitors = []
|
||||
} else {
|
||||
Logger.w("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens");
|
||||
adapter.bar.monitors = [];
|
||||
} else
|
||||
|
||||
//Logger.i("Settings", "Found valid bar monitors, keeping configuration")
|
||||
}
|
||||
} else {
|
||||
{}
|
||||
} else
|
||||
|
||||
//Logger.i("Settings", "Bar monitor list is empty, will show on all available screens")
|
||||
}
|
||||
{}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
@@ -576,50 +614,50 @@ Singleton {
|
||||
function upgradeSettingsData() {
|
||||
// Wait for BarWidgetRegistry to be ready
|
||||
if (!BarWidgetRegistry.widgets || Object.keys(BarWidgetRegistry.widgets).length === 0) {
|
||||
Logger.w("Settings", "BarWidgetRegistry not ready, deferring upgrade")
|
||||
Qt.callLater(upgradeSettingsData)
|
||||
return
|
||||
Logger.w("Settings", "BarWidgetRegistry not ready, deferring upgrade");
|
||||
Qt.callLater(upgradeSettingsData);
|
||||
return;
|
||||
}
|
||||
|
||||
const sections = ["left", "center", "right"]
|
||||
const sections = ["left", "center", "right"];
|
||||
|
||||
// -----------------
|
||||
// 1st. convert old widget id to new id
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s]
|
||||
const sectionName = sections[s];
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i]
|
||||
var widget = adapter.bar.widgets[sectionName][i];
|
||||
|
||||
switch (widget.id) {
|
||||
case "DarkModeToggle":
|
||||
widget.id = "DarkMode"
|
||||
break
|
||||
widget.id = "DarkMode";
|
||||
break;
|
||||
case "PowerToggle":
|
||||
widget.id = "SessionMenu"
|
||||
break
|
||||
widget.id = "SessionMenu";
|
||||
break;
|
||||
case "ScreenRecorderIndicator":
|
||||
widget.id = "ScreenRecorder"
|
||||
break
|
||||
widget.id = "ScreenRecorder";
|
||||
break;
|
||||
case "SidePanelToggle":
|
||||
widget.id = "ControlCenter"
|
||||
break
|
||||
widget.id = "ControlCenter";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// 2nd. remove any non existing widget type
|
||||
var removedWidget = false
|
||||
var removedWidget = false;
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s]
|
||||
const widgets = adapter.bar.widgets[sectionName]
|
||||
const sectionName = sections[s];
|
||||
const widgets = adapter.bar.widgets[sectionName];
|
||||
// Iterate backward through the widgets array, so it does not break when removing a widget
|
||||
for (var i = widgets.length - 1; i >= 0; i--) {
|
||||
var widget = widgets[i]
|
||||
var widget = widgets[i];
|
||||
if (!BarWidgetRegistry.hasWidget(widget.id)) {
|
||||
Logger.w(`Settings`, `Deleted invalid widget ${widget.id}`)
|
||||
widgets.splice(i, 1)
|
||||
removedWidget = true
|
||||
Logger.w(`Settings`, `Deleted invalid widget ${widget.id}`);
|
||||
widgets.splice(i, 1);
|
||||
removedWidget = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -627,18 +665,18 @@ Singleton {
|
||||
// -----------------
|
||||
// 3nd. upgrade widget settings
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s]
|
||||
const sectionName = sections[s];
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i]
|
||||
var widget = adapter.bar.widgets[sectionName][i];
|
||||
|
||||
// Check if widget registry supports user settings, if it does not, then there is nothing to do
|
||||
const reg = BarWidgetRegistry.widgetMetadata[widget.id]
|
||||
const reg = BarWidgetRegistry.widgetMetadata[widget.id];
|
||||
if ((reg === undefined) || (reg.allowUserSettings === undefined) || !reg.allowUserSettings) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if (upgradeWidget(widget)) {
|
||||
Logger.d("Settings", `Upgraded ${widget.id} widget:`, JSON.stringify(widget))
|
||||
Logger.d("Settings", `Upgraded ${widget.id} widget:`, JSON.stringify(widget));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -647,14 +685,14 @@ Singleton {
|
||||
// 4th. safety check
|
||||
// if a widget was deleted, ensure we still have a control center
|
||||
if (removedWidget) {
|
||||
var gotControlCenter = false
|
||||
var gotControlCenter = false;
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s]
|
||||
const sectionName = sections[s];
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i]
|
||||
var widget = adapter.bar.widgets[sectionName][i];
|
||||
if (widget.id === "ControlCenter") {
|
||||
gotControlCenter = true
|
||||
break
|
||||
gotControlCenter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,8 +701,8 @@ Singleton {
|
||||
//const obj = JSON.parse('{"id": "ControlCenter"}');
|
||||
adapter.bar.widgets["right"].push(({
|
||||
"id": "ControlCenter"
|
||||
}))
|
||||
Logger.w("Settings", "Added a ControlCenter widget to the right section")
|
||||
}));
|
||||
Logger.w("Settings", "Added a ControlCenter widget to the right section");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,31 +712,31 @@ Singleton {
|
||||
if (adapter.settingsVersion < 21) {
|
||||
// Read raw JSON file to access properties not in adapter schema
|
||||
try {
|
||||
var rawJson = settingsFileView.text()
|
||||
var rawJson = settingsFileView.text();
|
||||
|
||||
if (rawJson) {
|
||||
var parsed = JSON.parse(rawJson)
|
||||
var anyDiscordEnabled = false
|
||||
var parsed = JSON.parse(rawJson);
|
||||
var anyDiscordEnabled = false;
|
||||
|
||||
// Check if any Discord client was enabled
|
||||
const discordClients = ["discord_vesktop", "discord_webcord", "discord_armcord", "discord_equibop", "discord_lightcord", "discord_dorion", "discord_vencord"]
|
||||
const discordClients = ["discord_vesktop", "discord_webcord", "discord_armcord", "discord_equibop", "discord_lightcord", "discord_dorion", "discord_vencord"];
|
||||
|
||||
if (parsed.templates) {
|
||||
for (var i = 0; i < discordClients.length; i++) {
|
||||
if (parsed.templates[discordClients[i]]) {
|
||||
anyDiscordEnabled = true
|
||||
break
|
||||
anyDiscordEnabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set unified discord property
|
||||
adapter.templates.discord = anyDiscordEnabled
|
||||
adapter.templates.discord = anyDiscordEnabled;
|
||||
|
||||
Logger.i("Settings", "Migrated Discord templates to unified 'discord' property (enabled:", anyDiscordEnabled + ")")
|
||||
Logger.i("Settings", "Migrated Discord templates to unified 'discord' property (enabled:", anyDiscordEnabled + ")");
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.w("Settings", "Failed to read raw JSON for Discord migration:", error)
|
||||
Logger.w("Settings", "Failed to read raw JSON for Discord migration:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -708,20 +746,20 @@ Singleton {
|
||||
if (adapter.settingsVersion < 22) {
|
||||
// Read raw JSON file to access properties not in adapter schema
|
||||
try {
|
||||
var rawJson = settingsFileView.text()
|
||||
var rawJson = settingsFileView.text();
|
||||
|
||||
if (rawJson) {
|
||||
var parsed = JSON.parse(rawJson)
|
||||
var parsed = JSON.parse(rawJson);
|
||||
if (parsed.appLauncher && parsed.appLauncher.backgroundOpacity !== undefined) {
|
||||
var oldOpacity = parsed.appLauncher.backgroundOpacity
|
||||
var oldOpacity = parsed.appLauncher.backgroundOpacity;
|
||||
if (adapter.ui) {
|
||||
adapter.ui.panelBackgroundOpacity = oldOpacity
|
||||
Logger.i("Settings", "Migrated appLauncher.backgroundOpacity to ui.panelBackgroundOpacity (value:", oldOpacity + ")")
|
||||
adapter.ui.panelBackgroundOpacity = oldOpacity;
|
||||
Logger.i("Settings", "Migrated appLauncher.backgroundOpacity to ui.panelBackgroundOpacity (value:", oldOpacity + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.w("Settings", "Failed to read raw JSON for migration:", error)
|
||||
Logger.w("Settings", "Failed to read raw JSON for migration:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -732,23 +770,23 @@ Singleton {
|
||||
if (adapter.settingsVersion < 23) {
|
||||
// Read raw JSON file to access dimDesktop property
|
||||
try {
|
||||
var rawJson = settingsFileView.text()
|
||||
var rawJson = settingsFileView.text();
|
||||
|
||||
if (rawJson) {
|
||||
var parsed = JSON.parse(rawJson)
|
||||
var parsed = JSON.parse(rawJson);
|
||||
if (parsed.general && parsed.general.dimDesktop === true) {
|
||||
// Check if dimmerOpacity exists in raw JSON (not adapter default)
|
||||
var dimmerOpacityInJson = parsed.general.dimmerOpacity
|
||||
var dimmerOpacityInJson = parsed.general.dimmerOpacity;
|
||||
|
||||
// If dimmerOpacity wasn't explicitly set in JSON or was 0, set it to 0.8 (80% dimming)
|
||||
if (dimmerOpacityInJson === undefined || dimmerOpacityInJson === 0) {
|
||||
adapter.general.dimmerOpacity = 0.8
|
||||
Logger.i("Settings", "Migrated dimDesktop=true: set dimmerOpacity to 0.8 (80% dimming)")
|
||||
adapter.general.dimmerOpacity = 0.8;
|
||||
Logger.i("Settings", "Migrated dimDesktop=true: set dimmerOpacity to 0.8 (80% dimming)");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.w("Settings", "Failed to read raw JSON for dimDesktop migration:", error)
|
||||
Logger.w("Settings", "Failed to read raw JSON for dimDesktop migration:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -756,35 +794,35 @@ Singleton {
|
||||
// -----------------------------------------------------
|
||||
function upgradeWidget(widget) {
|
||||
// Backup the widget definition before altering
|
||||
const widgetBefore = JSON.stringify(widget)
|
||||
const widgetBefore = JSON.stringify(widget);
|
||||
|
||||
// Get all existing custom settings keys
|
||||
const keys = Object.keys(BarWidgetRegistry.widgetMetadata[widget.id])
|
||||
const keys = Object.keys(BarWidgetRegistry.widgetMetadata[widget.id]);
|
||||
|
||||
// Delete deprecated user settings from the wiget
|
||||
for (const k of Object.keys(widget)) {
|
||||
if (k === "id" || k === "allowUserSettings") {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
if (!keys.includes(k)) {
|
||||
delete widget[k]
|
||||
delete widget[k];
|
||||
}
|
||||
}
|
||||
|
||||
// Inject missing default setting (metaData) from BarWidgetRegistry
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
const k = keys[i]
|
||||
const k = keys[i];
|
||||
if (k === "id" || k === "allowUserSettings") {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if (widget[k] === undefined) {
|
||||
widget[k] = BarWidgetRegistry.widgetMetadata[widget.id][k]
|
||||
widget[k] = BarWidgetRegistry.widgetMetadata[widget.id][k];
|
||||
}
|
||||
}
|
||||
|
||||
// Compare settings, to detect if something has been upgraded
|
||||
const widgetAfter = JSON.stringify(widget)
|
||||
return (widgetAfter !== widgetBefore)
|
||||
const widgetAfter = JSON.stringify(widget);
|
||||
return (widgetAfter !== widgetBefore);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,6 @@ import qs.Services.Power
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
|
||||
/*
|
||||
Preset sizes for font, radii, ?
|
||||
*/
|
||||
|
||||
// Font size
|
||||
readonly property real fontSizeXXS: 8
|
||||
readonly property real fontSizeXS: 9
|
||||
@@ -86,29 +81,27 @@ Singleton {
|
||||
readonly property real barHeight: {
|
||||
switch (Settings.data.bar.density) {
|
||||
case "mini":
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 22 : 20
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 22 : 20;
|
||||
case "compact":
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 27 : 25
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 27 : 25;
|
||||
case "comfortable":
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37;
|
||||
default:
|
||||
|
||||
case "default":
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 33 : 31
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 33 : 31;
|
||||
}
|
||||
}
|
||||
readonly property real capsuleHeight: {
|
||||
switch (Settings.data.bar.density) {
|
||||
case "mini":
|
||||
return Math.round(barHeight * 1.0)
|
||||
return Math.round(barHeight * 1.0);
|
||||
case "compact":
|
||||
return Math.round(barHeight * 0.85)
|
||||
return Math.round(barHeight * 0.85);
|
||||
case "comfortable":
|
||||
return Math.round(barHeight * 0.73)
|
||||
return Math.round(barHeight * 0.73);
|
||||
default:
|
||||
|
||||
case "default":
|
||||
return Math.round(barHeight * 0.82)
|
||||
return Math.round(barHeight * 0.82);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,46 +7,46 @@ Singleton {
|
||||
id: root
|
||||
|
||||
function iconFromName(iconName, fallbackName) {
|
||||
const fallback = fallbackName || "application-x-executable"
|
||||
const fallback = fallbackName || "application-x-executable";
|
||||
try {
|
||||
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
|
||||
const p = Quickshell.iconPath(iconName, fallback)
|
||||
const p = Quickshell.iconPath(iconName, fallback);
|
||||
if (p && p !== "")
|
||||
return p
|
||||
return p;
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e)
|
||||
|
||||
// ignore and fall back
|
||||
}
|
||||
{}
|
||||
try {
|
||||
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : ""
|
||||
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
|
||||
} catch (e2) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve icon path for a DesktopEntries appId - safe on missing entries
|
||||
function iconForAppId(appId, fallbackName) {
|
||||
const fallback = fallbackName || "application-x-executable"
|
||||
const fallback = fallbackName || "application-x-executable";
|
||||
if (!appId)
|
||||
return iconFromName(fallback, fallback)
|
||||
return iconFromName(fallback, fallback);
|
||||
try {
|
||||
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
|
||||
return iconFromName(fallback, fallback)
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId)
|
||||
const name = entry && entry.icon ? entry.icon : ""
|
||||
return iconFromName(name || fallback, fallback)
|
||||
return iconFromName(fallback, fallback);
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId);
|
||||
const name = entry && entry.icon ? entry.icon : "";
|
||||
return iconFromName(name || fallback, fallback);
|
||||
} catch (e) {
|
||||
return iconFromName(fallback, fallback)
|
||||
return iconFromName(fallback, fallback);
|
||||
}
|
||||
}
|
||||
|
||||
// Distro logo helper (absolute path or empty string)
|
||||
function distroLogoPath() {
|
||||
try {
|
||||
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : ""
|
||||
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
|
||||
} catch (e) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
|
||||
Singleton {
|
||||
@@ -12,7 +12,7 @@ Singleton {
|
||||
|
||||
// Returns a Unix Timestamp (in seconds)
|
||||
readonly property int timestamp: {
|
||||
return Math.floor(root.now / 1000)
|
||||
return Math.floor(root.now / 1000);
|
||||
}
|
||||
|
||||
Timer {
|
||||
@@ -22,89 +22,89 @@ Singleton {
|
||||
running: true
|
||||
triggeredOnStart: false
|
||||
onTriggered: {
|
||||
var newTime = new Date()
|
||||
root.now = newTime
|
||||
var newTime = new Date();
|
||||
root.now = newTime;
|
||||
|
||||
// Adjust next interval to sync with the start of the next second
|
||||
var msIntoSecond = newTime.getMilliseconds()
|
||||
var msIntoSecond = newTime.getMilliseconds();
|
||||
if (msIntoSecond > 100) {
|
||||
// If we're more than 100ms into the second, adjust for next time
|
||||
updateTimer.interval = 1000 - msIntoSecond + 10 // +10ms buffer
|
||||
updateTimer.restart()
|
||||
updateTimer.interval = 1000 - msIntoSecond + 10; // +10ms buffer
|
||||
updateTimer.restart();
|
||||
} else {
|
||||
updateTimer.interval = 1000
|
||||
updateTimer.interval = 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Start by syncing to the next second boundary
|
||||
var now = new Date()
|
||||
var msUntilNextSecond = 1000 - now.getMilliseconds()
|
||||
updateTimer.interval = msUntilNextSecond + 10 // +10ms buffer
|
||||
updateTimer.restart()
|
||||
var now = new Date();
|
||||
var msUntilNextSecond = 1000 - now.getMilliseconds();
|
||||
updateTimer.interval = msUntilNextSecond + 10; // +10ms buffer
|
||||
updateTimer.restart();
|
||||
}
|
||||
|
||||
// Formats a Date object into a YYYYMMDD-HHMMSS string.
|
||||
function getFormattedTimestamp(date) {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
date = new Date();
|
||||
}
|
||||
const year = date.getFullYear()
|
||||
const year = date.getFullYear();
|
||||
|
||||
// getMonth() is zero-based, so we add 1
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
return `${year}${month}${day}-${hours}${minutes}${seconds}`
|
||||
return `${year}${month}${day}-${hours}${minutes}${seconds}`;
|
||||
}
|
||||
|
||||
// Format an easy to read approximate duration ex: 4h 32m
|
||||
// Used to display the time remaining on the Battery widget, computer uptime, etc..
|
||||
function formatVagueHumanReadableDuration(totalSeconds) {
|
||||
if (typeof totalSeconds !== 'number' || totalSeconds < 0) {
|
||||
return '0s'
|
||||
return '0s';
|
||||
}
|
||||
|
||||
// Floor the input to handle decimal seconds
|
||||
totalSeconds = Math.floor(totalSeconds)
|
||||
totalSeconds = Math.floor(totalSeconds);
|
||||
|
||||
const days = Math.floor(totalSeconds / 86400)
|
||||
const hours = Math.floor((totalSeconds % 86400) / 3600)
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||
const seconds = totalSeconds % 60
|
||||
const days = Math.floor(totalSeconds / 86400);
|
||||
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
|
||||
const parts = []
|
||||
const parts = [];
|
||||
if (days)
|
||||
parts.push(`${days}d`)
|
||||
parts.push(`${days}d`);
|
||||
if (hours)
|
||||
parts.push(`${hours}h`)
|
||||
parts.push(`${hours}h`);
|
||||
if (minutes)
|
||||
parts.push(`${minutes}m`)
|
||||
parts.push(`${minutes}m`);
|
||||
|
||||
// Only show seconds if no hours and no minutes
|
||||
if (!hours && !minutes) {
|
||||
parts.push(`${seconds}s`)
|
||||
parts.push(`${seconds}s`);
|
||||
}
|
||||
|
||||
return parts.join(' ')
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
// Format a date into
|
||||
function formatRelativeTime(date) {
|
||||
if (!date)
|
||||
return ""
|
||||
const diff = Date.now() - date.getTime()
|
||||
return "";
|
||||
const diff = Date.now() - date.getTime();
|
||||
if (diff < 60000)
|
||||
return "now"
|
||||
return "now";
|
||||
if (diff < 3600000)
|
||||
return `${Math.floor(diff / 60000)}m ago`
|
||||
return `${Math.floor(diff / 60000)}m ago`;
|
||||
if (diff < 86400000)
|
||||
return `${Math.floor(diff / 3600000)}h ago`
|
||||
return `${Math.floor(diff / 86400000)}d ago`
|
||||
return `${Math.floor(diff / 3600000)}h ago`;
|
||||
return `${Math.floor(diff / 86400000)}d ago`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,17 +48,17 @@ Variants {
|
||||
Component.onCompleted: setWallpaperInitial()
|
||||
|
||||
Component.onDestruction: {
|
||||
transitionAnimation.stop()
|
||||
debounceTimer.stop()
|
||||
shaderLoader.active = false
|
||||
currentWallpaper.source = ""
|
||||
nextWallpaper.source = ""
|
||||
transitionAnimation.stop();
|
||||
debounceTimer.stop();
|
||||
shaderLoader.active = false;
|
||||
currentWallpaper.source = "";
|
||||
nextWallpaper.source = "";
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Settings.data.wallpaper
|
||||
function onFillModeChanged() {
|
||||
fillMode = WallpaperService.getFillModeUniform()
|
||||
fillMode = WallpaperService.getFillModeUniform();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ Variants {
|
||||
if (screenName === modelData.name) {
|
||||
// Update wallpaper display
|
||||
// Set wallpaper immediately on startup
|
||||
futureWallpaper = path
|
||||
debounceTimer.restart()
|
||||
futureWallpaper = path;
|
||||
debounceTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,9 +80,9 @@ Variants {
|
||||
function onDisplayScalesChanged() {
|
||||
// Recalculate image sizes without interrupting startup transition
|
||||
if (isStartupTransition) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
recalculateImageSizes()
|
||||
recalculateImageSizes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ Variants {
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
changeWallpaper()
|
||||
changeWallpaper();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,18 +123,18 @@ Variants {
|
||||
sourceSize: undefined
|
||||
onStatusChanged: {
|
||||
if (status === Image.Error) {
|
||||
Logger.w("Current wallpaper failed to load:", source)
|
||||
Logger.w("Current wallpaper failed to load:", source);
|
||||
} else if (status === Image.Ready && !dimensionsCalculated) {
|
||||
dimensionsCalculated = true
|
||||
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight)
|
||||
dimensionsCalculated = true;
|
||||
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight);
|
||||
if (optimalSize !== false) {
|
||||
sourceSize = optimalSize
|
||||
sourceSize = optimalSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
onSourceChanged: {
|
||||
dimensionsCalculated = false
|
||||
sourceSize = undefined
|
||||
dimensionsCalculated = false;
|
||||
sourceSize = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,18 +152,18 @@ Variants {
|
||||
sourceSize: undefined
|
||||
onStatusChanged: {
|
||||
if (status === Image.Error) {
|
||||
Logger.w("Next wallpaper failed to load:", source)
|
||||
Logger.w("Next wallpaper failed to load:", source);
|
||||
} else if (status === Image.Ready && !dimensionsCalculated) {
|
||||
dimensionsCalculated = true
|
||||
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight)
|
||||
dimensionsCalculated = true;
|
||||
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight);
|
||||
if (optimalSize !== false) {
|
||||
sourceSize = optimalSize
|
||||
sourceSize = optimalSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
onSourceChanged: {
|
||||
dimensionsCalculated = false
|
||||
sourceSize = undefined
|
||||
dimensionsCalculated = false;
|
||||
sourceSize = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,15 +176,15 @@ Variants {
|
||||
sourceComponent: {
|
||||
switch (transitionType) {
|
||||
case "wipe":
|
||||
return wipeShaderComponent
|
||||
return wipeShaderComponent;
|
||||
case "disc":
|
||||
return discShaderComponent
|
||||
return discShaderComponent;
|
||||
case "stripes":
|
||||
return stripesShaderComponent
|
||||
return stripesShaderComponent;
|
||||
case "fade":
|
||||
case "none":
|
||||
default:
|
||||
return fadeShaderComponent
|
||||
return fadeShaderComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,64 +307,64 @@ Variants {
|
||||
easing.type: Easing.InOutCubic
|
||||
onFinished: {
|
||||
// Assign new image to current BEFORE clearing to prevent flicker
|
||||
const tempSource = nextWallpaper.source
|
||||
currentWallpaper.source = tempSource
|
||||
transitionProgress = 0.0
|
||||
const tempSource = nextWallpaper.source;
|
||||
currentWallpaper.source = tempSource;
|
||||
transitionProgress = 0.0;
|
||||
|
||||
// Now clear nextWallpaper after currentWallpaper has the new source
|
||||
// Force complete cleanup to free texture memory (~18-25MB per monitor)
|
||||
Qt.callLater(() => {
|
||||
nextWallpaper.source = ""
|
||||
nextWallpaper.sourceSize = undefined
|
||||
nextWallpaper.source = "";
|
||||
nextWallpaper.sourceSize = undefined;
|
||||
Qt.callLater(() => {
|
||||
currentWallpaper.asynchronous = true
|
||||
})
|
||||
})
|
||||
currentWallpaper.asynchronous = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function calculateOptimalWallpaperSize(wpWidth, wpHeight) {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name)
|
||||
const screenWidth = modelData.width * compositorScale
|
||||
const screenHeight = modelData.height * compositorScale
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
const screenWidth = modelData.width * compositorScale;
|
||||
const screenHeight = modelData.height * compositorScale;
|
||||
if (wpWidth <= screenWidth || wpHeight <= screenHeight || wpWidth <= 0 || wpHeight <= 0) {
|
||||
// Do not resize if wallpaper is smaller than one of the screen dimension
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const imageAspectRatio = wpWidth / wpHeight
|
||||
var dim = Qt.size(0, 0)
|
||||
const imageAspectRatio = wpWidth / wpHeight;
|
||||
var dim = Qt.size(0, 0);
|
||||
if (screenWidth >= screenHeight) {
|
||||
const w = Math.min(screenWidth, wpWidth)
|
||||
dim = Qt.size(Math.round(w), Math.round(w / imageAspectRatio))
|
||||
const w = Math.min(screenWidth, wpWidth);
|
||||
dim = Qt.size(Math.round(w), Math.round(w / imageAspectRatio));
|
||||
} else {
|
||||
const h = Math.min(screenHeight, wpHeight)
|
||||
dim = Qt.size(Math.round(h * imageAspectRatio), Math.round(h))
|
||||
const h = Math.min(screenHeight, wpHeight);
|
||||
dim = Qt.size(Math.round(h * imageAspectRatio), Math.round(h));
|
||||
}
|
||||
|
||||
Logger.d("Background", `Wallpaper resized on ${modelData.name} ${screenWidth}x${screenHeight} @ ${compositorScale}x`, "src:", wpWidth, wpHeight, "dst:", dim.width, dim.height)
|
||||
return dim
|
||||
Logger.d("Background", `Wallpaper resized on ${modelData.name} ${screenWidth}x${screenHeight} @ ${compositorScale}x`, "src:", wpWidth, wpHeight, "dst:", dim.width, dim.height);
|
||||
return dim;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function recalculateImageSizes() {
|
||||
// Re-evaluate and apply optimal sourceSize for both images when ready
|
||||
if (currentWallpaper.status === Image.Ready) {
|
||||
const optimal = calculateOptimalWallpaperSize(currentWallpaper.implicitWidth, currentWallpaper.implicitHeight)
|
||||
const optimal = calculateOptimalWallpaperSize(currentWallpaper.implicitWidth, currentWallpaper.implicitHeight);
|
||||
if (optimal !== undefined && optimal !== false) {
|
||||
currentWallpaper.sourceSize = optimal
|
||||
currentWallpaper.sourceSize = optimal;
|
||||
} else {
|
||||
currentWallpaper.sourceSize = undefined
|
||||
currentWallpaper.sourceSize = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextWallpaper.status === Image.Ready) {
|
||||
const optimal2 = calculateOptimalWallpaperSize(nextWallpaper.implicitWidth, nextWallpaper.implicitHeight)
|
||||
const optimal2 = calculateOptimalWallpaperSize(nextWallpaper.implicitWidth, nextWallpaper.implicitHeight);
|
||||
if (optimal2 !== undefined && optimal2 !== false) {
|
||||
nextWallpaper.sourceSize = optimal2
|
||||
nextWallpaper.sourceSize = optimal2;
|
||||
} else {
|
||||
nextWallpaper.sourceSize = undefined
|
||||
nextWallpaper.sourceSize = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,104 +373,104 @@ Variants {
|
||||
function setWallpaperInitial() {
|
||||
// On startup, defer assigning wallpaper until the service cache is ready, retries every tick
|
||||
if (!WallpaperService || !WallpaperService.isInitialized) {
|
||||
Qt.callLater(setWallpaperInitial)
|
||||
return
|
||||
Qt.callLater(setWallpaperInitial);
|
||||
return;
|
||||
}
|
||||
|
||||
const wallpaperPath = WallpaperService.getWallpaper(modelData.name)
|
||||
const wallpaperPath = WallpaperService.getWallpaper(modelData.name);
|
||||
|
||||
futureWallpaper = wallpaperPath
|
||||
performStartupTransition()
|
||||
futureWallpaper = wallpaperPath;
|
||||
performStartupTransition();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function setWallpaperImmediate(source) {
|
||||
transitionAnimation.stop()
|
||||
transitionProgress = 0.0
|
||||
transitionAnimation.stop();
|
||||
transitionProgress = 0.0;
|
||||
|
||||
// Clear nextWallpaper completely to free texture memory
|
||||
nextWallpaper.source = ""
|
||||
nextWallpaper.sourceSize = undefined
|
||||
nextWallpaper.source = "";
|
||||
nextWallpaper.sourceSize = undefined;
|
||||
|
||||
currentWallpaper.source = ""
|
||||
currentWallpaper.source = "";
|
||||
|
||||
Qt.callLater(() => {
|
||||
currentWallpaper.source = source
|
||||
})
|
||||
currentWallpaper.source = source;
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function setWallpaperWithTransition(source) {
|
||||
if (source === currentWallpaper.source) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (transitioning) {
|
||||
// We are interrupting a transition - handle cleanup properly
|
||||
transitionAnimation.stop()
|
||||
transitionProgress = 0
|
||||
transitionAnimation.stop();
|
||||
transitionProgress = 0;
|
||||
|
||||
// Assign nextWallpaper to currentWallpaper BEFORE clearing to prevent flicker
|
||||
const newCurrentSource = nextWallpaper.source
|
||||
currentWallpaper.source = newCurrentSource
|
||||
const newCurrentSource = nextWallpaper.source;
|
||||
currentWallpaper.source = newCurrentSource;
|
||||
|
||||
// Now clear nextWallpaper after current has the new source
|
||||
Qt.callLater(() => {
|
||||
nextWallpaper.source = ""
|
||||
nextWallpaper.source = "";
|
||||
|
||||
// Now set the next wallpaper after a brief delay
|
||||
Qt.callLater(() => {
|
||||
nextWallpaper.source = source
|
||||
currentWallpaper.asynchronous = false
|
||||
transitionAnimation.start()
|
||||
})
|
||||
})
|
||||
return
|
||||
nextWallpaper.source = source;
|
||||
currentWallpaper.asynchronous = false;
|
||||
transitionAnimation.start();
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
nextWallpaper.source = source
|
||||
currentWallpaper.asynchronous = false
|
||||
transitionAnimation.start()
|
||||
nextWallpaper.source = source;
|
||||
currentWallpaper.asynchronous = false;
|
||||
transitionAnimation.start();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Main method that actually trigger the wallpaper change
|
||||
function changeWallpaper() {
|
||||
// Get the transitionType from the settings
|
||||
transitionType = Settings.data.wallpaper.transitionType
|
||||
transitionType = Settings.data.wallpaper.transitionType;
|
||||
|
||||
if (transitionType == "random") {
|
||||
var index = Math.floor(Math.random() * allTransitions.length)
|
||||
transitionType = allTransitions[index]
|
||||
var index = Math.floor(Math.random() * allTransitions.length);
|
||||
transitionType = allTransitions[index];
|
||||
}
|
||||
|
||||
// Ensure the transition type really exists
|
||||
if (transitionType !== "none" && !allTransitions.includes(transitionType)) {
|
||||
transitionType = "fade"
|
||||
transitionType = "fade";
|
||||
}
|
||||
|
||||
//Logger.i("Background", "New wallpaper: ", futureWallpaper, "On:", modelData.name, "Transition:", transitionType)
|
||||
switch (transitionType) {
|
||||
case "none":
|
||||
setWallpaperImmediate(futureWallpaper)
|
||||
break
|
||||
setWallpaperImmediate(futureWallpaper);
|
||||
break;
|
||||
case "wipe":
|
||||
wipeDirection = Math.random() * 4
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
wipeDirection = Math.random() * 4;
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
case "disc":
|
||||
discCenterX = Math.random()
|
||||
discCenterY = Math.random()
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
discCenterX = Math.random();
|
||||
discCenterY = Math.random();
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
case "stripes":
|
||||
stripesCount = Math.round(Math.random() * 20 + 4)
|
||||
stripesAngle = Math.random() * 360
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
stripesCount = Math.round(Math.random() * 20 + 4);
|
||||
stripesAngle = Math.random() * 360;
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
default:
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,46 +478,46 @@ Variants {
|
||||
// Dedicated function for startup animation
|
||||
function performStartupTransition() {
|
||||
// Get the transitionType from the settings
|
||||
transitionType = Settings.data.wallpaper.transitionType
|
||||
transitionType = Settings.data.wallpaper.transitionType;
|
||||
|
||||
if (transitionType == "random") {
|
||||
var index = Math.floor(Math.random() * allTransitions.length)
|
||||
transitionType = allTransitions[index]
|
||||
var index = Math.floor(Math.random() * allTransitions.length);
|
||||
transitionType = allTransitions[index];
|
||||
}
|
||||
|
||||
// Ensure the transition type really exists
|
||||
if (transitionType !== "none" && !allTransitions.includes(transitionType)) {
|
||||
transitionType = "fade"
|
||||
transitionType = "fade";
|
||||
}
|
||||
|
||||
// Apply transitionType so the shader loader picks the correct shader
|
||||
this.transitionType = transitionType
|
||||
this.transitionType = transitionType;
|
||||
|
||||
switch (transitionType) {
|
||||
case "none":
|
||||
setWallpaperImmediate(futureWallpaper)
|
||||
break
|
||||
setWallpaperImmediate(futureWallpaper);
|
||||
break;
|
||||
case "wipe":
|
||||
wipeDirection = Math.random() * 4
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
wipeDirection = Math.random() * 4;
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
case "disc":
|
||||
// Force center origin for elegant startup animation
|
||||
discCenterX = 0.5
|
||||
discCenterY = 0.5
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
discCenterX = 0.5;
|
||||
discCenterY = 0.5;
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
case "stripes":
|
||||
stripesCount = Math.round(Math.random() * 20 + 4)
|
||||
stripesAngle = Math.random() * 360
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
stripesCount = Math.round(Math.random() * 20 + 4);
|
||||
stripesAngle = Math.random() * 360;
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
default:
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
}
|
||||
// Mark startup transition complete
|
||||
isStartupTransition = false
|
||||
isStartupTransition = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
@@ -20,16 +20,16 @@ Loader {
|
||||
|
||||
Component.onCompleted: {
|
||||
if (modelData) {
|
||||
Logger.d("Overview", "Loading overview for Niri on", modelData.name)
|
||||
Logger.d("Overview", "Loading overview for Niri on", modelData.name);
|
||||
}
|
||||
setWallpaperInitial()
|
||||
setWallpaperInitial();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// Clean up resources to prevent memory leak when overviewEnabled is toggled off
|
||||
timerDisableFx.stop()
|
||||
bgImage.layer.enabled = false
|
||||
bgImage.source = ""
|
||||
timerDisableFx.stop();
|
||||
bgImage.layer.enabled = false;
|
||||
bgImage.source = "";
|
||||
}
|
||||
|
||||
// External state management
|
||||
@@ -37,19 +37,19 @@ Loader {
|
||||
target: WallpaperService
|
||||
function onWallpaperChanged(screenName, path) {
|
||||
if (screenName === modelData.name) {
|
||||
wallpaper = path
|
||||
wallpaper = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setWallpaperInitial() {
|
||||
if (!WallpaperService || !WallpaperService.isInitialized) {
|
||||
Qt.callLater(setWallpaperInitial)
|
||||
return
|
||||
Qt.callLater(setWallpaperInitial);
|
||||
return;
|
||||
}
|
||||
const wallpaperPath = WallpaperService.getWallpaper(modelData.name)
|
||||
const wallpaperPath = WallpaperService.getWallpaper(modelData.name);
|
||||
if (wallpaperPath && wallpaperPath !== wallpaper) {
|
||||
wallpaper = wallpaperPath
|
||||
wallpaper = wallpaperPath;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.UPower
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Modules.Notification
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Modules.Notification
|
||||
import qs.Modules.Bar.Extras
|
||||
|
||||
// Bar Component
|
||||
Item {
|
||||
@@ -34,9 +34,9 @@ Item {
|
||||
// Register bar when screen becomes available
|
||||
onScreenChanged: {
|
||||
if (screen && screen.name) {
|
||||
Logger.d("Bar", "Bar screen set to:", screen.name)
|
||||
Logger.d("Bar", " Position:", barPosition, "Floating:", barFloating)
|
||||
BarService.registerBar(screen.name)
|
||||
Logger.d("Bar", "Bar screen set to:", screen.name);
|
||||
Logger.d("Bar", " Position:", barPosition, "Floating:", barFloating);
|
||||
BarService.registerBar(screen.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,12 +46,12 @@ Item {
|
||||
anchors.fill: parent
|
||||
active: {
|
||||
if (root.screen === null || root.screen === undefined) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
var result = monitors.length === 0 || monitors.includes(root.screen.name)
|
||||
return result
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
var result = monitors.length === 0 || monitors.includes(root.screen.name);
|
||||
return result;
|
||||
}
|
||||
|
||||
sourceComponent: Item {
|
||||
@@ -75,73 +75,73 @@ Item {
|
||||
readonly property int topLeftCornerState: {
|
||||
// Floating bar: always simple rounded corners
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
// Top bar: top corners against screen edge = no radius
|
||||
if (barPosition === "top")
|
||||
return -1
|
||||
return -1;
|
||||
// Left bar: top-left against screen edge = no radius
|
||||
if (barPosition === "left")
|
||||
return -1
|
||||
return -1;
|
||||
// Bottom/Right bar with outerCorners: inverted corner
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "right")) {
|
||||
return barIsVertical ? 1 : 2 // horizontal invert for vertical bars, vertical invert for horizontal
|
||||
return barIsVertical ? 1 : 2; // horizontal invert for vertical bars, vertical invert for horizontal
|
||||
}
|
||||
// No outerCorners = square
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int topRightCornerState: {
|
||||
// Floating bar: always simple rounded corners
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
// Top bar: top corners against screen edge = no radius
|
||||
if (barPosition === "top")
|
||||
return -1
|
||||
return -1;
|
||||
// Right bar: top-right against screen edge = no radius
|
||||
if (barPosition === "right")
|
||||
return -1
|
||||
return -1;
|
||||
// Bottom/Left bar with outerCorners: inverted corner
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
// No outerCorners = square
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int bottomLeftCornerState: {
|
||||
// Floating bar: always simple rounded corners
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
// Bottom bar: bottom corners against screen edge = no radius
|
||||
if (barPosition === "bottom")
|
||||
return -1
|
||||
return -1;
|
||||
// Left bar: bottom-left against screen edge = no radius
|
||||
if (barPosition === "left")
|
||||
return -1
|
||||
return -1;
|
||||
// Top/Right bar with outerCorners: inverted corner
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
// No outerCorners = square
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int bottomRightCornerState: {
|
||||
// Floating bar: always simple rounded corners
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
// Bottom bar: bottom corners against screen edge = no radius
|
||||
if (barPosition === "bottom")
|
||||
return -1
|
||||
return -1;
|
||||
// Right bar: bottom-right against screen edge = no radius
|
||||
if (barPosition === "right")
|
||||
return -1
|
||||
return -1;
|
||||
// Top/Left bar with outerCorners: inverted corner
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
// No outerCorners = square
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -151,14 +151,14 @@ Item {
|
||||
preventStealing: true
|
||||
onClicked: function (mouse) {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen)
|
||||
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen);
|
||||
if (Settings.data.controlCenter.position === "close_to_bar_button") {
|
||||
// Will attempt to open the panel next to the bar button if any.
|
||||
controlCenterPanel?.toggle(null, "ControlCenter")
|
||||
controlCenterPanel?.toggle(null, "ControlCenter");
|
||||
} else {
|
||||
controlCenterPanel?.toggle()
|
||||
controlCenterPanel?.toggle();
|
||||
}
|
||||
mouse.accepted = true
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,12 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
|
||||
|
||||
/**
|
||||
* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar
|
||||
*
|
||||
* This is a minimal window that works with the compositor to reserve space,
|
||||
* while the actual bar UI is rendered in NFullScreenWindow.
|
||||
*/
|
||||
* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar
|
||||
*
|
||||
* This is a minimal window that works with the compositor to reserve space,
|
||||
* while the actual bar UI is rendered in NFullScreenWindow.
|
||||
*/
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
@@ -46,11 +45,11 @@ PanelWindow {
|
||||
// Vertical bar: reserve bar height + margin on the anchored edge only
|
||||
if (barFloating) {
|
||||
// For left bar, reserve left margin; for right bar, reserve right margin
|
||||
return Style.barHeight + barMarginH
|
||||
return Style.barHeight + barMarginH;
|
||||
}
|
||||
return Style.barHeight
|
||||
return Style.barHeight;
|
||||
}
|
||||
return 0 // Auto-width when left/right anchors are true
|
||||
return 0; // Auto-width when left/right anchors are true
|
||||
}
|
||||
|
||||
implicitHeight: {
|
||||
@@ -58,17 +57,17 @@ PanelWindow {
|
||||
// Horizontal bar: reserve bar height + margin on the anchored edge only
|
||||
if (barFloating) {
|
||||
// For top bar, reserve top margin; for bottom bar, reserve bottom margin
|
||||
return Style.barHeight + barMarginV
|
||||
return Style.barHeight + barMarginV;
|
||||
}
|
||||
return Style.barHeight
|
||||
return Style.barHeight;
|
||||
}
|
||||
return 0 // Auto-height when top/bottom anchors are true
|
||||
return 0; // Auto-height when top/bottom anchors are true
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("BarExclusionZone", "Created for screen:", screen?.name)
|
||||
Logger.d("BarExclusionZone", " Position:", barPosition, "Exclusive:", exclusive, "Floating:", barFloating)
|
||||
Logger.d("BarExclusionZone", " Anchors - top:", anchors.top, "bottom:", anchors.bottom, "left:", anchors.left, "right:", anchors.right)
|
||||
Logger.d("BarExclusionZone", " Size:", width, "x", height, "implicitWidth:", implicitWidth, "implicitHeight:", implicitHeight)
|
||||
Logger.d("BarExclusionZone", "Created for screen:", screen?.name);
|
||||
Logger.d("BarExclusionZone", " Position:", barPosition, "Exclusive:", exclusive, "Floating:", barFloating);
|
||||
Logger.d("BarExclusionZone", " Anchors - top:", anchors.top, "bottom:", anchors.bottom, "left:", anchors.left, "right:", anchors.right);
|
||||
Logger.d("BarExclusionZone", " Size:", width, "x", height, "implicitWidth:", implicitWidth, "implicitHeight:", implicitHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,19 +92,19 @@ Item {
|
||||
|
||||
function show() {
|
||||
if (pillLoader.item && pillLoader.item.show) {
|
||||
pillLoader.item.show()
|
||||
pillLoader.item.show();
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (pillLoader.item && pillLoader.item.hide) {
|
||||
pillLoader.item.hide()
|
||||
pillLoader.item.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function showDelayed() {
|
||||
if (pillLoader.item && pillLoader.item.showDelayed) {
|
||||
pillLoader.item.showDelayed()
|
||||
pillLoader.item.showDelayed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,18 +45,18 @@ Item {
|
||||
readonly property real iconSize: {
|
||||
switch (root.density) {
|
||||
case "compact":
|
||||
return Math.max(1, Math.round(pillHeight * 0.65))
|
||||
return Math.max(1, Math.round(pillHeight * 0.65));
|
||||
default:
|
||||
return Math.max(1, Math.round(pillHeight * 0.48))
|
||||
return Math.max(1, Math.round(pillHeight * 0.48));
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real textSize: {
|
||||
switch (root.density) {
|
||||
case "compact":
|
||||
return Math.max(1, Math.round(pillHeight * 0.45))
|
||||
return Math.max(1, Math.round(pillHeight * 0.45));
|
||||
default:
|
||||
return Math.max(1, Math.round(pillHeight * 0.33))
|
||||
return Math.max(1, Math.round(pillHeight * 0.33));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ Item {
|
||||
target: root
|
||||
function onTooltipTextChanged() {
|
||||
if (hovered) {
|
||||
TooltipService.updateText(root.tooltipText)
|
||||
TooltipService.updateText(root.tooltipText);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,13 +97,13 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: {
|
||||
// Better text horizontal centering
|
||||
var centerX = (parent.width - width) / 2
|
||||
var offset = oppositeDirection ? Style.marginXS : -Style.marginXS
|
||||
var centerX = (parent.width - width) / 2;
|
||||
var offset = oppositeDirection ? Style.marginXS : -Style.marginXS;
|
||||
if (forceOpen) {
|
||||
// If its force open, the icon disc background is the same color as the bg pill move text slightly
|
||||
offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS
|
||||
offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS;
|
||||
}
|
||||
return centerX + offset
|
||||
return centerX + offset;
|
||||
}
|
||||
text: root.text + root.suffix
|
||||
family: Settings.data.ui.fontFixed
|
||||
@@ -179,11 +179,11 @@ Item {
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
onStarted: {
|
||||
showPill = true
|
||||
showPill = true;
|
||||
}
|
||||
onStopped: {
|
||||
delayedHideAnim.start()
|
||||
root.shown()
|
||||
delayedHideAnim.start();
|
||||
root.shown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ Item {
|
||||
}
|
||||
ScriptAction {
|
||||
script: if (shouldAnimateHide) {
|
||||
hideAnim.start()
|
||||
hideAnim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,9 +220,9 @@ Item {
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
onStopped: {
|
||||
showPill = false
|
||||
shouldAnimateHide = false
|
||||
root.hidden()
|
||||
showPill = false;
|
||||
shouldAnimateHide = false;
|
||||
root.hidden();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ Item {
|
||||
interval: Style.pillDelay
|
||||
onTriggered: {
|
||||
if (!showPill) {
|
||||
showAnim.start()
|
||||
showAnim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,31 +241,31 @@ Item {
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onEntered: {
|
||||
hovered = true
|
||||
root.entered()
|
||||
TooltipService.show(Screen, pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong)
|
||||
hovered = true;
|
||||
root.entered();
|
||||
TooltipService.show(Screen, pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong);
|
||||
if (forceClose) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!forceOpen) {
|
||||
showDelayed()
|
||||
showDelayed();
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
hovered = false
|
||||
root.exited()
|
||||
hovered = false;
|
||||
root.exited();
|
||||
if (!forceOpen && !forceClose) {
|
||||
hide()
|
||||
hide();
|
||||
}
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
}
|
||||
onClicked: function (mouse) {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.clicked()
|
||||
root.clicked();
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
root.rightClicked()
|
||||
root.rightClicked();
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
root.middleClicked()
|
||||
root.middleClicked();
|
||||
}
|
||||
}
|
||||
onWheel: wheel => root.wheel(wheel.angleDelta.y)
|
||||
@@ -273,43 +273,43 @@ Item {
|
||||
|
||||
function show() {
|
||||
if (!showPill) {
|
||||
shouldAnimateHide = autoHide
|
||||
showAnim.start()
|
||||
shouldAnimateHide = autoHide;
|
||||
showAnim.start();
|
||||
} else {
|
||||
hideAnim.stop()
|
||||
delayedHideAnim.restart()
|
||||
hideAnim.stop();
|
||||
delayedHideAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (forceOpen) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (showPill) {
|
||||
hideAnim.start()
|
||||
hideAnim.start();
|
||||
}
|
||||
showTimer.stop()
|
||||
showTimer.stop();
|
||||
}
|
||||
|
||||
function showDelayed() {
|
||||
if (!showPill) {
|
||||
shouldAnimateHide = autoHide
|
||||
showTimer.start()
|
||||
shouldAnimateHide = autoHide;
|
||||
showTimer.start();
|
||||
} else {
|
||||
hideAnim.stop()
|
||||
delayedHideAnim.restart()
|
||||
hideAnim.stop();
|
||||
delayedHideAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
onForceOpenChanged: {
|
||||
if (forceOpen) {
|
||||
// Immediately lock open without animations
|
||||
showAnim.stop()
|
||||
hideAnim.stop()
|
||||
delayedHideAnim.stop()
|
||||
showPill = true
|
||||
showAnim.stop();
|
||||
hideAnim.stop();
|
||||
delayedHideAnim.stop();
|
||||
showPill = true;
|
||||
} else {
|
||||
hide()
|
||||
hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,18 +55,18 @@ Item {
|
||||
readonly property real iconSize: {
|
||||
switch (root.density) {
|
||||
case "compact":
|
||||
return Math.max(1, Math.round(pillHeight * 0.65))
|
||||
return Math.max(1, Math.round(pillHeight * 0.65));
|
||||
default:
|
||||
return Math.max(1, Math.round(pillHeight * 0.48))
|
||||
return Math.max(1, Math.round(pillHeight * 0.48));
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real textSize: {
|
||||
switch (root.density) {
|
||||
case "compact":
|
||||
return Math.max(1, Math.round(pillHeight * 0.38))
|
||||
return Math.max(1, Math.round(pillHeight * 0.38));
|
||||
default:
|
||||
return Math.max(1, Math.round(pillHeight * 0.33))
|
||||
return Math.max(1, Math.round(pillHeight * 0.33));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ Item {
|
||||
target: root
|
||||
function onTooltipTextChanged() {
|
||||
if (hovered) {
|
||||
TooltipService.updateText(root.tooltipText)
|
||||
TooltipService.updateText(root.tooltipText);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,11 +123,11 @@ Item {
|
||||
visible: revealed
|
||||
|
||||
function getVerticalCenterOffset() {
|
||||
var offset = openDownward ? Math.round(pillPaddingVertical * 0.75) : -Math.round(pillPaddingVertical * 0.75)
|
||||
var offset = openDownward ? Math.round(pillPaddingVertical * 0.75) : -Math.round(pillPaddingVertical * 0.75);
|
||||
if (forceOpen) {
|
||||
offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS
|
||||
offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS;
|
||||
}
|
||||
return offset
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
Behavior on width {
|
||||
@@ -212,11 +212,11 @@ Item {
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
onStarted: {
|
||||
showPill = true
|
||||
showPill = true;
|
||||
}
|
||||
onStopped: {
|
||||
delayedHideAnim.start()
|
||||
root.shown()
|
||||
delayedHideAnim.start();
|
||||
root.shown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ Item {
|
||||
}
|
||||
ScriptAction {
|
||||
script: if (shouldAnimateHide) {
|
||||
hideAnim.start()
|
||||
hideAnim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,9 +261,9 @@ Item {
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
onStopped: {
|
||||
showPill = false
|
||||
shouldAnimateHide = false
|
||||
root.hidden()
|
||||
showPill = false;
|
||||
shouldAnimateHide = false;
|
||||
root.hidden();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ Item {
|
||||
interval: Style.pillDelay
|
||||
onTriggered: {
|
||||
if (!showPill) {
|
||||
showAnim.start()
|
||||
showAnim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,31 +282,31 @@ Item {
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onEntered: {
|
||||
hovered = true
|
||||
root.entered()
|
||||
TooltipService.show(Screen, pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong)
|
||||
hovered = true;
|
||||
root.entered();
|
||||
TooltipService.show(Screen, pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong);
|
||||
if (forceClose) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!forceOpen) {
|
||||
showDelayed()
|
||||
showDelayed();
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
hovered = false
|
||||
root.exited()
|
||||
hovered = false;
|
||||
root.exited();
|
||||
if (!forceOpen && !forceClose) {
|
||||
hide()
|
||||
hide();
|
||||
}
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
}
|
||||
onClicked: function (mouse) {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.clicked()
|
||||
root.clicked();
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
root.rightClicked()
|
||||
root.rightClicked();
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
root.middleClicked()
|
||||
root.middleClicked();
|
||||
}
|
||||
}
|
||||
onWheel: wheel => root.wheel(wheel.angleDelta.y)
|
||||
@@ -314,43 +314,43 @@ Item {
|
||||
|
||||
function show() {
|
||||
if (!showPill) {
|
||||
shouldAnimateHide = autoHide
|
||||
showAnim.start()
|
||||
shouldAnimateHide = autoHide;
|
||||
showAnim.start();
|
||||
} else {
|
||||
hideAnim.stop()
|
||||
delayedHideAnim.restart()
|
||||
hideAnim.stop();
|
||||
delayedHideAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (forceOpen) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (showPill) {
|
||||
hideAnim.start()
|
||||
hideAnim.start();
|
||||
}
|
||||
showTimer.stop()
|
||||
showTimer.stop();
|
||||
}
|
||||
|
||||
function showDelayed() {
|
||||
if (!showPill) {
|
||||
shouldAnimateHide = autoHide
|
||||
showTimer.start()
|
||||
shouldAnimateHide = autoHide;
|
||||
showTimer.start();
|
||||
} else {
|
||||
hideAnim.stop()
|
||||
delayedHideAnim.restart()
|
||||
hideAnim.stop();
|
||||
delayedHideAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
onForceOpenChanged: {
|
||||
if (forceOpen) {
|
||||
// Immediately lock open without animations
|
||||
showAnim.stop()
|
||||
hideAnim.stop()
|
||||
delayedHideAnim.stop()
|
||||
showPill = true
|
||||
showAnim.stop();
|
||||
hideAnim.stop();
|
||||
delayedHideAnim.stop();
|
||||
showPill = true;
|
||||
} else {
|
||||
hide()
|
||||
hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Services.UI
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
visible: loader.item ? ((loader.item.opacity > 0.0) || (loader.item.hasOwnProperty("hideMode") && loader.item.hideMode === "transparent")) : false
|
||||
|
||||
function getImplicitSize(item, prop) {
|
||||
return (item && item.visible) ? Math.round(item[prop]) : 0
|
||||
return (item && item.visible) ? Math.round(item[prop]) : 0;
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -36,42 +36,41 @@ Item {
|
||||
|
||||
onLoaded: {
|
||||
if (!item)
|
||||
return
|
||||
|
||||
Logger.d("BarWidgetLoader", "Loading widget", widgetId, "on screen:", widgetScreen.name)
|
||||
return;
|
||||
Logger.d("BarWidgetLoader", "Loading widget", widgetId, "on screen:", widgetScreen.name);
|
||||
|
||||
// Apply properties to loaded widget
|
||||
for (var prop in widgetProps) {
|
||||
if (item.hasOwnProperty(prop)) {
|
||||
item[prop] = widgetProps[prop]
|
||||
item[prop] = widgetProps[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// Set screen property
|
||||
if (item.hasOwnProperty("screen")) {
|
||||
item.screen = widgetScreen
|
||||
item.screen = widgetScreen;
|
||||
}
|
||||
|
||||
// Set scaling property
|
||||
if (item.hasOwnProperty("scaling")) {
|
||||
item.scaling = Qt.binding(function () {
|
||||
return root.scaling
|
||||
})
|
||||
return root.scaling;
|
||||
});
|
||||
}
|
||||
|
||||
// Register this widget instance with BarService
|
||||
BarService.registerWidget(widgetScreen.name, section, widgetId, sectionIndex, item)
|
||||
BarService.registerWidget(widgetScreen.name, section, widgetId, sectionIndex, item);
|
||||
|
||||
// Call custom onLoaded if it exists
|
||||
if (item.hasOwnProperty("onLoaded")) {
|
||||
item.onLoaded()
|
||||
item.onLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// Unregister when destroyed
|
||||
if (widgetScreen && section) {
|
||||
BarService.unregisterWidget(widgetScreen.name, section, widgetId, sectionIndex)
|
||||
BarService.unregisterWidget(widgetScreen.name, section, widgetId, sectionIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +78,7 @@ Item {
|
||||
// Error handling
|
||||
Component.onCompleted: {
|
||||
if (!BarWidgetRegistry.hasWidget(widgetId)) {
|
||||
Logger.w("BarWidgetLoader", "Widget not found in registry:", widgetId)
|
||||
Logger.w("BarWidgetLoader", "Widget not found in registry:", widgetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,20 +25,20 @@ PopupWindow {
|
||||
// Compute if current tray item is pinned
|
||||
readonly property bool isPinned: {
|
||||
if (!trayItem || widgetSection === "" || widgetIndex < 0)
|
||||
return false
|
||||
var widgets = Settings.data.bar.widgets[widgetSection]
|
||||
return false;
|
||||
var widgets = Settings.data.bar.widgets[widgetSection];
|
||||
if (!widgets || widgetIndex >= widgets.length)
|
||||
return false
|
||||
var widgetSettings = widgets[widgetIndex]
|
||||
return false;
|
||||
var widgetSettings = widgets[widgetIndex];
|
||||
if (!widgetSettings || widgetSettings.id !== "Tray")
|
||||
return false
|
||||
var pinnedList = widgetSettings.pinned || []
|
||||
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || ""
|
||||
return false;
|
||||
var pinnedList = widgetSettings.pinned || [];
|
||||
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || "";
|
||||
for (var i = 0; i < pinnedList.length; i++) {
|
||||
if (pinnedList[i] === itemName)
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property int menuWidth: 220
|
||||
@@ -53,47 +53,47 @@ PopupWindow {
|
||||
anchor.rect.x: anchorX
|
||||
anchor.rect.y: {
|
||||
if (isSubMenu) {
|
||||
const offsetY = Settings.data.bar.position === "bottom" ? -10 : 10
|
||||
return anchorY + offsetY
|
||||
const offsetY = Settings.data.bar.position === "bottom" ? -10 : 10;
|
||||
return anchorY + offsetY;
|
||||
}
|
||||
return anchorY + Settings.data.bar.position === "bottom" ? -implicitHeight : Style.barHeight
|
||||
return anchorY + Settings.data.bar.position === "bottom" ? -implicitHeight : Style.barHeight;
|
||||
}
|
||||
|
||||
function showAt(item, x, y) {
|
||||
if (!item) {
|
||||
Logger.w("TrayMenu", "anchorItem is undefined, won't show menu.")
|
||||
return
|
||||
Logger.w("TrayMenu", "anchorItem is undefined, won't show menu.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opener.children || opener.children.values.length === 0) {
|
||||
//Logger.w("TrayMenu", "Menu not ready, delaying show")
|
||||
Qt.callLater(() => showAt(item, x, y))
|
||||
return
|
||||
Qt.callLater(() => showAt(item, x, y));
|
||||
return;
|
||||
}
|
||||
|
||||
anchorItem = item
|
||||
anchorX = x
|
||||
anchorY = y
|
||||
anchorItem = item;
|
||||
anchorX = x;
|
||||
anchorY = y;
|
||||
|
||||
visible = true
|
||||
forceActiveFocus()
|
||||
visible = true;
|
||||
forceActiveFocus();
|
||||
|
||||
// Force update after showing.
|
||||
Qt.callLater(() => {
|
||||
root.anchor.updateAnchor()
|
||||
})
|
||||
root.anchor.updateAnchor();
|
||||
});
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
visible = false
|
||||
visible = false;
|
||||
|
||||
// Clean up all submenus recursively
|
||||
for (var i = 0; i < columnLayout.children.length; i++) {
|
||||
const child = columnLayout.children[i]
|
||||
const child = columnLayout.children[i];
|
||||
if (child?.subMenu) {
|
||||
child.subMenu.hideMenu()
|
||||
child.subMenu.destroy()
|
||||
child.subMenu = null
|
||||
child.subMenu.hideMenu();
|
||||
child.subMenu.destroy();
|
||||
child.subMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,11 +166,11 @@ PopupWindow {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: {
|
||||
if (modelData?.isSeparator) {
|
||||
return 8
|
||||
return 8;
|
||||
} else {
|
||||
// Calculate based on text content
|
||||
const textHeight = text.contentHeight || (Style.fontSizeS * 1.2)
|
||||
return Math.max(28, textHeight + (Style.marginS * 2))
|
||||
const textHeight = text.contentHeight || (Style.fontSizeS * 1.2);
|
||||
return Math.max(28, textHeight + (Style.marginS * 2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,31 +237,31 @@ PopupWindow {
|
||||
// Click on items with children toggles submenu
|
||||
if (entry.subMenu) {
|
||||
// Close existing submenu
|
||||
entry.subMenu.hideMenu()
|
||||
entry.subMenu.destroy()
|
||||
entry.subMenu = null
|
||||
entry.subMenu.hideMenu();
|
||||
entry.subMenu.destroy();
|
||||
entry.subMenu = null;
|
||||
} else {
|
||||
// Close any other open submenus first
|
||||
for (var i = 0; i < columnLayout.children.length; i++) {
|
||||
const sibling = columnLayout.children[i]
|
||||
const sibling = columnLayout.children[i];
|
||||
if (sibling !== entry && sibling.subMenu) {
|
||||
sibling.subMenu.hideMenu()
|
||||
sibling.subMenu.destroy()
|
||||
sibling.subMenu = null
|
||||
sibling.subMenu.hideMenu();
|
||||
sibling.subMenu.destroy();
|
||||
sibling.subMenu = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine submenu opening direction
|
||||
let openLeft = false
|
||||
const barPosition = Settings.data.bar.position
|
||||
const globalPos = entry.mapToItem(null, 0, 0)
|
||||
let openLeft = false;
|
||||
const barPosition = Settings.data.bar.position;
|
||||
const globalPos = entry.mapToItem(null, 0, 0);
|
||||
|
||||
if (barPosition === "right") {
|
||||
openLeft = true
|
||||
openLeft = true;
|
||||
} else if (barPosition === "left") {
|
||||
openLeft = false
|
||||
openLeft = false;
|
||||
} else {
|
||||
openLeft = (root.widgetSection === "right")
|
||||
openLeft = (root.widgetSection === "right");
|
||||
}
|
||||
|
||||
// Open new submenu
|
||||
@@ -269,30 +269,30 @@ PopupWindow {
|
||||
"menu": modelData,
|
||||
"isSubMenu": true,
|
||||
"screen": root.screen
|
||||
})
|
||||
});
|
||||
|
||||
if (entry.subMenu) {
|
||||
const overlap = 60
|
||||
entry.subMenu.anchorItem = entry
|
||||
entry.subMenu.anchorX = openLeft ? -overlap : overlap
|
||||
entry.subMenu.anchorY = 0
|
||||
entry.subMenu.visible = true
|
||||
const overlap = 60;
|
||||
entry.subMenu.anchorItem = entry;
|
||||
entry.subMenu.anchorX = openLeft ? -overlap : overlap;
|
||||
entry.subMenu.anchorY = 0;
|
||||
entry.subMenu.visible = true;
|
||||
// Force anchor update with new position
|
||||
Qt.callLater(() => {
|
||||
entry.subMenu.anchor.updateAnchor()
|
||||
})
|
||||
entry.subMenu.anchor.updateAnchor();
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Click on regular items triggers them
|
||||
modelData.triggered()
|
||||
root.hideMenu()
|
||||
modelData.triggered();
|
||||
root.hideMenu();
|
||||
|
||||
// Close the drawer if it's open
|
||||
if (root.screen) {
|
||||
const panel = PanelService.getPanel("trayDrawerPanel", root.screen)
|
||||
const panel = PanelService.getPanel("trayDrawerPanel", root.screen);
|
||||
if (panel && panel.visible) {
|
||||
panel.close()
|
||||
panel.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,8 +303,8 @@ PopupWindow {
|
||||
|
||||
Component.onDestruction: {
|
||||
if (subMenu) {
|
||||
subMenu.destroy()
|
||||
subMenu = null
|
||||
subMenu.destroy();
|
||||
subMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -351,9 +351,9 @@ PopupWindow {
|
||||
|
||||
onClicked: {
|
||||
if (root.isPinned) {
|
||||
root.removeFromPinned()
|
||||
root.removeFromPinned();
|
||||
} else {
|
||||
root.addToPinned()
|
||||
root.addToPinned();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,72 +363,72 @@ PopupWindow {
|
||||
|
||||
function addToPinned() {
|
||||
if (!trayItem || widgetSection === "" || widgetIndex < 0) {
|
||||
Logger.w("TrayMenu", "Cannot pin: missing tray item or widget info")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot pin: missing tray item or widget info");
|
||||
return;
|
||||
}
|
||||
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || ""
|
||||
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || "";
|
||||
if (!itemName) {
|
||||
Logger.w("TrayMenu", "Cannot pin: tray item has no name")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot pin: tray item has no name");
|
||||
return;
|
||||
}
|
||||
var widgets = Settings.data.bar.widgets[widgetSection]
|
||||
var widgets = Settings.data.bar.widgets[widgetSection];
|
||||
if (!widgets || widgetIndex >= widgets.length) {
|
||||
Logger.w("TrayMenu", "Cannot pin: invalid widget index")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot pin: invalid widget index");
|
||||
return;
|
||||
}
|
||||
var widgetSettings = widgets[widgetIndex]
|
||||
var widgetSettings = widgets[widgetIndex];
|
||||
if (!widgetSettings || widgetSettings.id !== "Tray") {
|
||||
Logger.w("TrayMenu", "Cannot pin: widget is not a Tray widget")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot pin: widget is not a Tray widget");
|
||||
return;
|
||||
}
|
||||
var pinnedList = widgetSettings.pinned || []
|
||||
var newPinned = pinnedList.slice()
|
||||
newPinned.push(itemName)
|
||||
var newSettings = Object.assign({}, widgetSettings)
|
||||
newSettings.pinned = newPinned
|
||||
widgets[widgetIndex] = newSettings
|
||||
Settings.data.bar.widgets[widgetSection] = widgets
|
||||
Settings.saveImmediate()
|
||||
var pinnedList = widgetSettings.pinned || [];
|
||||
var newPinned = pinnedList.slice();
|
||||
newPinned.push(itemName);
|
||||
var newSettings = Object.assign({}, widgetSettings);
|
||||
newSettings.pinned = newPinned;
|
||||
widgets[widgetIndex] = newSettings;
|
||||
Settings.data.bar.widgets[widgetSection] = widgets;
|
||||
Settings.saveImmediate();
|
||||
|
||||
// Close drawer when pinning (drawer needs to resize)
|
||||
if (screen) {
|
||||
const panel = PanelService.getPanel("trayDrawerPanel", screen)
|
||||
const panel = PanelService.getPanel("trayDrawerPanel", screen);
|
||||
if (panel)
|
||||
panel.close()
|
||||
panel.close();
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromPinned() {
|
||||
if (!trayItem || widgetSection === "" || widgetIndex < 0) {
|
||||
Logger.w("TrayMenu", "Cannot unpin: missing tray item or widget info")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot unpin: missing tray item or widget info");
|
||||
return;
|
||||
}
|
||||
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || ""
|
||||
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || "";
|
||||
if (!itemName) {
|
||||
Logger.w("TrayMenu", "Cannot unpin: tray item has no name")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot unpin: tray item has no name");
|
||||
return;
|
||||
}
|
||||
var widgets = Settings.data.bar.widgets[widgetSection]
|
||||
var widgets = Settings.data.bar.widgets[widgetSection];
|
||||
if (!widgets || widgetIndex >= widgets.length) {
|
||||
Logger.w("TrayMenu", "Cannot unpin: invalid widget index")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot unpin: invalid widget index");
|
||||
return;
|
||||
}
|
||||
var widgetSettings = widgets[widgetIndex]
|
||||
var widgetSettings = widgets[widgetIndex];
|
||||
if (!widgetSettings || widgetSettings.id !== "Tray") {
|
||||
Logger.w("TrayMenu", "Cannot unpin: widget is not a Tray widget")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot unpin: widget is not a Tray widget");
|
||||
return;
|
||||
}
|
||||
var pinnedList = widgetSettings.pinned || []
|
||||
var newPinned = []
|
||||
var pinnedList = widgetSettings.pinned || [];
|
||||
var newPinned = [];
|
||||
for (var i = 0; i < pinnedList.length; i++) {
|
||||
if (pinnedList[i] !== itemName) {
|
||||
newPinned.push(pinnedList[i])
|
||||
newPinned.push(pinnedList[i]);
|
||||
}
|
||||
}
|
||||
var newSettings = Object.assign({}, widgetSettings)
|
||||
newSettings.pinned = newPinned
|
||||
widgets[widgetIndex] = newSettings
|
||||
Settings.data.bar.widgets[widgetSection] = widgets
|
||||
Settings.saveImmediate()
|
||||
var newSettings = Object.assign({}, widgetSettings);
|
||||
newSettings.pinned = newPinned;
|
||||
widgets[widgetIndex] = newSettings;
|
||||
Settings.data.bar.widgets[widgetSection] = widgets;
|
||||
Settings.saveImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Widget settings - matching MediaMini pattern
|
||||
@@ -73,60 +73,60 @@ Item {
|
||||
}
|
||||
|
||||
function calculatedVerticalDimension() {
|
||||
return Math.round((Style.baseWidgetSize - 5) * scaling)
|
||||
return Math.round((Style.baseWidgetSize - 5) * scaling);
|
||||
}
|
||||
|
||||
function calculateContentWidth() {
|
||||
// Calculate the actual content width based on visible elements
|
||||
var contentWidth = 0
|
||||
var margins = Style.marginS * scaling * 2 // Left and right margins
|
||||
var contentWidth = 0;
|
||||
var margins = Style.marginS * scaling * 2; // Left and right margins
|
||||
|
||||
// Icon width (if visible)
|
||||
if (showIcon) {
|
||||
contentWidth += 18 * scaling
|
||||
contentWidth += Style.marginS * scaling // Spacing after icon
|
||||
contentWidth += 18 * scaling;
|
||||
contentWidth += Style.marginS * scaling; // Spacing after icon
|
||||
}
|
||||
|
||||
// Text width (use the measured width)
|
||||
contentWidth += fullTitleMetrics.contentWidth
|
||||
contentWidth += fullTitleMetrics.contentWidth;
|
||||
|
||||
// Additional small margin for text
|
||||
contentWidth += Style.marginXXS * 2
|
||||
contentWidth += Style.marginXXS * 2;
|
||||
|
||||
// Add container margins
|
||||
contentWidth += margins
|
||||
contentWidth += margins;
|
||||
|
||||
return Math.ceil(contentWidth)
|
||||
return Math.ceil(contentWidth);
|
||||
}
|
||||
|
||||
// Dynamic width: adapt to content but respect maximum width setting
|
||||
readonly property real dynamicWidth: {
|
||||
// If using fixed width mode, always use maxWidth
|
||||
if (useFixedWidth) {
|
||||
return maxWidth
|
||||
return maxWidth;
|
||||
}
|
||||
// Otherwise, adapt to content
|
||||
if (!hasFocusedWindow) {
|
||||
return Math.min(calculateContentWidth(), maxWidth)
|
||||
return Math.min(calculateContentWidth(), maxWidth);
|
||||
}
|
||||
// Use content width but don't exceed user-set maximum width
|
||||
return Math.min(calculateContentWidth(), maxWidth)
|
||||
return Math.min(calculateContentWidth(), maxWidth);
|
||||
}
|
||||
|
||||
function getAppIcon() {
|
||||
try {
|
||||
// Try CompositorService first
|
||||
const focusedWindow = CompositorService.getFocusedWindow()
|
||||
const focusedWindow = CompositorService.getFocusedWindow();
|
||||
if (focusedWindow && focusedWindow.appId) {
|
||||
try {
|
||||
const idValue = focusedWindow.appId
|
||||
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue)
|
||||
const iconResult = ThemeIcons.iconForAppId(normalizedId.toLowerCase())
|
||||
const idValue = focusedWindow.appId;
|
||||
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue);
|
||||
const iconResult = ThemeIcons.iconForAppId(normalizedId.toLowerCase());
|
||||
if (iconResult && iconResult !== "") {
|
||||
return iconResult
|
||||
return iconResult;
|
||||
}
|
||||
} catch (iconError) {
|
||||
Logger.w("ActiveWindow", "Error getting icon from CompositorService:", iconError)
|
||||
Logger.w("ActiveWindow", "Error getting icon from CompositorService:", iconError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,25 +134,25 @@ Item {
|
||||
// Fallback to ToplevelManager
|
||||
if (ToplevelManager && ToplevelManager.activeToplevel) {
|
||||
try {
|
||||
const activeToplevel = ToplevelManager.activeToplevel
|
||||
const activeToplevel = ToplevelManager.activeToplevel;
|
||||
if (activeToplevel.appId) {
|
||||
const idValue2 = activeToplevel.appId
|
||||
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2)
|
||||
const iconResult2 = ThemeIcons.iconForAppId(normalizedId2.toLowerCase())
|
||||
const idValue2 = activeToplevel.appId;
|
||||
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2);
|
||||
const iconResult2 = ThemeIcons.iconForAppId(normalizedId2.toLowerCase());
|
||||
if (iconResult2 && iconResult2 !== "") {
|
||||
return iconResult2
|
||||
return iconResult2;
|
||||
}
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
Logger.w("ActiveWindow", "Error getting icon from ToplevelManager:", fallbackError)
|
||||
Logger.w("ActiveWindow", "Error getting icon from ToplevelManager:", fallbackError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ThemeIcons.iconFromName(fallbackIcon)
|
||||
return ThemeIcons.iconFromName(fallbackIcon);
|
||||
} catch (e) {
|
||||
Logger.w("ActiveWindow", "Error in getAppIcon:", e)
|
||||
return ThemeIcons.iconFromName(fallbackIcon)
|
||||
Logger.w("ActiveWindow", "Error in getAppIcon:", e);
|
||||
return ThemeIcons.iconFromName(fallbackIcon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,10 +228,10 @@ Item {
|
||||
id: titleContainer
|
||||
Layout.preferredWidth: {
|
||||
// Calculate available width based on other elements
|
||||
var iconWidth = (showIcon && windowIcon.visible ? (18 + Style.marginS) : 0)
|
||||
var totalMargins = Style.marginXXS * 2
|
||||
var availableWidth = mainContainer.width - iconWidth - totalMargins
|
||||
return Math.max(20, availableWidth)
|
||||
var iconWidth = (showIcon && windowIcon.visible ? (18 + Style.marginS) : 0);
|
||||
var totalMargins = Style.marginXXS * 2;
|
||||
var availableWidth = mainContainer.width - iconWidth - totalMargins;
|
||||
return Math.max(20, availableWidth);
|
||||
}
|
||||
Layout.maximumWidth: Layout.preferredWidth
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
@@ -252,8 +252,8 @@ Item {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (scrollingMode === "always" && titleContainer.needsScrolling) {
|
||||
titleContainer.isScrolling = true
|
||||
titleContainer.isResetting = false
|
||||
titleContainer.isScrolling = true;
|
||||
titleContainer.isResetting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,29 +261,29 @@ Item {
|
||||
// Update scrolling state based on mode
|
||||
property var updateScrollingState: function () {
|
||||
if (scrollingMode === "never") {
|
||||
isScrolling = false
|
||||
isResetting = false
|
||||
isScrolling = false;
|
||||
isResetting = false;
|
||||
} else if (scrollingMode === "always") {
|
||||
if (needsScrolling) {
|
||||
if (mouseArea.containsMouse) {
|
||||
isScrolling = false
|
||||
isResetting = true
|
||||
isScrolling = false;
|
||||
isResetting = true;
|
||||
} else {
|
||||
scrollStartTimer.restart()
|
||||
scrollStartTimer.restart();
|
||||
}
|
||||
} else {
|
||||
scrollStartTimer.stop()
|
||||
isScrolling = false
|
||||
isResetting = false
|
||||
scrollStartTimer.stop();
|
||||
isScrolling = false;
|
||||
isResetting = false;
|
||||
}
|
||||
} else if (scrollingMode === "hover") {
|
||||
if (mouseArea.containsMouse && needsScrolling) {
|
||||
isScrolling = true
|
||||
isResetting = false
|
||||
isScrolling = true;
|
||||
isResetting = false;
|
||||
} else {
|
||||
isScrolling = false
|
||||
isScrolling = false;
|
||||
if (needsScrolling) {
|
||||
isResetting = true
|
||||
isResetting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,7 +296,7 @@ Item {
|
||||
Connections {
|
||||
target: mouseArea
|
||||
function onContainsMouseChanged() {
|
||||
titleContainer.updateScrollingState()
|
||||
titleContainer.updateScrollingState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,10 +322,10 @@ Item {
|
||||
color: Color.mOnSurface
|
||||
onTextChanged: {
|
||||
if (root.scrollingMode === "always") {
|
||||
titleContainer.isScrolling = false
|
||||
titleContainer.isResetting = false
|
||||
scrollContainer.scrollX = 0
|
||||
scrollStartTimer.restart()
|
||||
titleContainer.isScrolling = false;
|
||||
titleContainer.isResetting = false;
|
||||
scrollContainer.scrollX = 0;
|
||||
scrollStartTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,7 +349,7 @@ Item {
|
||||
duration: 300
|
||||
easing.type: Easing.OutQuad
|
||||
onFinished: {
|
||||
titleContainer.isResetting = false
|
||||
titleContainer.isResetting = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,11 +412,11 @@ Item {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onEntered: {
|
||||
if ((windowTitle !== "") && isVerticalBar || (scrollingMode === "never")) {
|
||||
TooltipService.show(Screen, root, windowTitle, BarService.getTooltipDirection())
|
||||
TooltipService.show(Screen, root, windowTitle, BarService.getTooltipDirection());
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,18 +426,18 @@ Item {
|
||||
target: CompositorService
|
||||
function onActiveWindowChanged() {
|
||||
try {
|
||||
windowIcon.source = Qt.binding(getAppIcon)
|
||||
windowIconVertical.source = Qt.binding(getAppIcon)
|
||||
windowIcon.source = Qt.binding(getAppIcon);
|
||||
windowIconVertical.source = Qt.binding(getAppIcon);
|
||||
} catch (e) {
|
||||
Logger.w("ActiveWindow", "Error in onActiveWindowChanged:", e)
|
||||
Logger.w("ActiveWindow", "Error in onActiveWindowChanged:", e);
|
||||
}
|
||||
}
|
||||
function onWindowListChanged() {
|
||||
try {
|
||||
windowIcon.source = Qt.binding(getAppIcon)
|
||||
windowIconVertical.source = Qt.binding(getAppIcon)
|
||||
windowIcon.source = Qt.binding(getAppIcon);
|
||||
windowIconVertical.source = Qt.binding(getAppIcon);
|
||||
} catch (e) {
|
||||
Logger.w("ActiveWindow", "Error in onWindowListChanged:", e)
|
||||
Logger.w("ActiveWindow", "Error in onWindowListChanged:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.Media
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Widgets.AudioSpectrum
|
||||
|
||||
@@ -22,12 +22,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Resolve settings: try user settings or defaults from BarWidgetRegistry
|
||||
@@ -38,16 +38,16 @@ Item {
|
||||
readonly property color fillColor: {
|
||||
switch (colorName) {
|
||||
case "primary":
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
case "secondary":
|
||||
return Color.mSecondary
|
||||
return Color.mSecondary;
|
||||
case "tertiary":
|
||||
return Color.mTertiary
|
||||
return Color.mTertiary;
|
||||
case "error":
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
case "onSurface":
|
||||
default:
|
||||
return Color.mOnSurface
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,13 +92,13 @@ Item {
|
||||
sourceComponent: {
|
||||
switch (currentVisualizerType) {
|
||||
case "linear":
|
||||
return linearComponent
|
||||
return linearComponent;
|
||||
case "mirrored":
|
||||
return mirroredComponent
|
||||
return mirroredComponent;
|
||||
case "wave":
|
||||
return waveComponent
|
||||
return waveComponent;
|
||||
default:
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,13 +112,13 @@ Item {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
onClicked: mouse => {
|
||||
const types = ["linear", "mirrored", "wave"]
|
||||
const currentIndex = types.indexOf(currentVisualizerType)
|
||||
const nextIndex = (currentIndex + 1) % types.length
|
||||
const newType = types[nextIndex]
|
||||
const types = ["linear", "mirrored", "wave"];
|
||||
const currentIndex = types.indexOf(currentVisualizerType);
|
||||
const nextIndex = (currentIndex + 1) % types.length;
|
||||
const newType = types[nextIndex];
|
||||
|
||||
// Update settings directly, maybe this should be a widget setting...
|
||||
Settings.data.audio.visualizerType = newType
|
||||
Settings.data.audio.visualizerType = newType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.Hardware
|
||||
import qs.Widgets
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -22,12 +22,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -53,13 +53,13 @@ Item {
|
||||
function maybeNotify(percent, charging) {
|
||||
// Only notify once we are a below threshold
|
||||
if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) {
|
||||
root.hasNotifiedLowBattery = true
|
||||
root.hasNotifiedLowBattery = true;
|
||||
ToastService.showWarning(I18n.tr("toast.battery.low"), I18n.tr("toast.battery.low-desc", {
|
||||
"percent": Math.round(percent)
|
||||
}))
|
||||
}));
|
||||
} else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) {
|
||||
// Reset when charging starts or when battery recovers 5% above threshold
|
||||
root.hasNotifiedLowBattery = false
|
||||
root.hasNotifiedLowBattery = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,20 +67,20 @@ Item {
|
||||
Connections {
|
||||
target: UPower.displayDevice
|
||||
function onPercentageChanged() {
|
||||
var currentPercent = UPower.displayDevice.percentage * 100
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging
|
||||
root.maybeNotify(currentPercent, isCharging)
|
||||
var currentPercent = UPower.displayDevice.percentage * 100;
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
|
||||
root.maybeNotify(currentPercent, isCharging);
|
||||
}
|
||||
|
||||
function onStateChanged() {
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
|
||||
// Reset notification flag when charging starts
|
||||
if (isCharging) {
|
||||
root.hasNotifiedLowBattery = false
|
||||
root.hasNotifiedLowBattery = false;
|
||||
}
|
||||
// Also re-evaluate maybeNotify, as state might have changed
|
||||
var currentPercent = UPower.displayDevice.percentage * 100
|
||||
root.maybeNotify(currentPercent, isCharging)
|
||||
var currentPercent = UPower.displayDevice.percentage * 100;
|
||||
root.maybeNotify(currentPercent, isCharging);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,49 +97,49 @@ Item {
|
||||
forceClose: displayMode === "alwaysHide" || !isReady || (!testMode && !battery.isLaptopBattery)
|
||||
onClicked: PanelService.getPanel("batteryPanel", screen)?.toggle(this)
|
||||
tooltipText: {
|
||||
let lines = []
|
||||
let lines = [];
|
||||
if (testMode) {
|
||||
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(12345)}.`)
|
||||
return lines.join("\n")
|
||||
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(12345)}.`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
if (!isReady || !battery.isLaptopBattery) {
|
||||
return I18n.tr("battery.no-battery-detected")
|
||||
return I18n.tr("battery.no-battery-detected");
|
||||
}
|
||||
if (battery.timeToEmpty > 0) {
|
||||
lines.push(I18n.tr("battery.time-left", {
|
||||
"time": Time.formatVagueHumanReadableDuration(battery.timeToEmpty)
|
||||
}))
|
||||
}));
|
||||
}
|
||||
if (battery.timeToFull > 0) {
|
||||
lines.push(I18n.tr("battery.time-until-full", {
|
||||
"time": Time.formatVagueHumanReadableDuration(battery.timeToFull)
|
||||
}))
|
||||
}));
|
||||
}
|
||||
if (battery.changeRate !== undefined) {
|
||||
const rate = battery.changeRate
|
||||
const rate = battery.changeRate;
|
||||
if (rate > 0) {
|
||||
lines.push(charging ? I18n.tr("battery.charging-rate", {
|
||||
"rate": rate.toFixed(2)
|
||||
}) : I18n.tr("battery.discharging-rate", {
|
||||
"rate": rate.toFixed(2)
|
||||
}))
|
||||
}));
|
||||
} else if (rate < 0) {
|
||||
lines.push(I18n.tr("battery.discharging-rate", {
|
||||
"rate": Math.abs(rate).toFixed(2)
|
||||
}))
|
||||
}));
|
||||
} else {
|
||||
// Rate is 0 - check if plugged in (charging state) or idle
|
||||
lines.push(charging ? I18n.tr("battery.plugged-in") : I18n.tr("battery.idle"))
|
||||
lines.push(charging ? I18n.tr("battery.plugged-in") : I18n.tr("battery.idle"));
|
||||
}
|
||||
} else {
|
||||
lines.push(charging ? I18n.tr("battery.charging") : I18n.tr("battery.discharging"))
|
||||
lines.push(charging ? I18n.tr("battery.charging") : I18n.tr("battery.discharging"));
|
||||
}
|
||||
if (battery.healthPercentage !== undefined && battery.healthPercentage > 0) {
|
||||
lines.push(I18n.tr("battery.health", {
|
||||
"percent": Math.round(battery.healthPercentage)
|
||||
}))
|
||||
}));
|
||||
}
|
||||
return lines.join("\n")
|
||||
return lines.join("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -41,16 +41,16 @@ Item {
|
||||
icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
|
||||
text: {
|
||||
if (BluetoothService.connectedDevices && BluetoothService.connectedDevices.length > 0) {
|
||||
const firstDevice = BluetoothService.connectedDevices[0]
|
||||
return firstDevice.name || firstDevice.deviceName
|
||||
const firstDevice = BluetoothService.connectedDevices[0];
|
||||
return firstDevice.name || firstDevice.deviceName;
|
||||
}
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
suffix: {
|
||||
if (BluetoothService.connectedDevices && BluetoothService.connectedDevices.length > 1) {
|
||||
return ` + ${BluetoothService.connectedDevices.length - 1}`
|
||||
return ` + ${BluetoothService.connectedDevices.length - 1}`;
|
||||
}
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
autoHide: false
|
||||
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
|
||||
@@ -59,9 +59,9 @@ Item {
|
||||
onRightClicked: BluetoothService.setBluetoothEnabled(!BluetoothService.enabled)
|
||||
tooltipText: {
|
||||
if (pill.text !== "") {
|
||||
return pill.text
|
||||
return pill.text;
|
||||
}
|
||||
return I18n.tr("tooltips.bluetooth-devices")
|
||||
return I18n.tr("tooltips.bluetooth-devices");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -40,13 +40,13 @@ Item {
|
||||
visible: getMonitor() !== null
|
||||
|
||||
function getMonitor() {
|
||||
return BrightnessService.getMonitorForScreen(screen) || null
|
||||
return BrightnessService.getMonitorForScreen(screen) || null;
|
||||
}
|
||||
|
||||
function getIcon() {
|
||||
var monitor = getMonitor()
|
||||
var brightness = monitor ? monitor.brightness : 0
|
||||
return brightness <= 0.5 ? "brightness-low" : "brightness-high"
|
||||
var monitor = getMonitor();
|
||||
var brightness = monitor ? monitor.brightness : 0;
|
||||
return brightness <= 0.5 ? "brightness-low" : "brightness-high";
|
||||
}
|
||||
|
||||
// Connection used to open the pill when brightness changes
|
||||
@@ -57,12 +57,12 @@ Item {
|
||||
// Ignore if this is the first time we receive an update.
|
||||
// Most likely service just kicked off.
|
||||
if (!firstBrightnessReceived) {
|
||||
firstBrightnessReceived = true
|
||||
return
|
||||
firstBrightnessReceived = true;
|
||||
return;
|
||||
}
|
||||
|
||||
pill.show()
|
||||
hideTimerAfterChange.restart()
|
||||
pill.show();
|
||||
hideTimerAfterChange.restart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,42 +82,42 @@ Item {
|
||||
icon: getIcon()
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: {
|
||||
var monitor = getMonitor()
|
||||
return monitor ? Math.round(monitor.brightness * 100) : ""
|
||||
var monitor = getMonitor();
|
||||
return monitor ? Math.round(monitor.brightness * 100) : "";
|
||||
}
|
||||
suffix: text.length > 0 ? "%" : "-"
|
||||
forceOpen: displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide"
|
||||
tooltipText: {
|
||||
var monitor = getMonitor()
|
||||
var monitor = getMonitor();
|
||||
if (!monitor)
|
||||
return ""
|
||||
return "";
|
||||
return I18n.tr("tooltips.brightness-at", {
|
||||
"brightness": Math.round(monitor.brightness * 100)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onWheel: function (angle) {
|
||||
var monitor = getMonitor()
|
||||
var monitor = getMonitor();
|
||||
if (!monitor)
|
||||
return
|
||||
return;
|
||||
if (angle > 0) {
|
||||
monitor.increaseBrightness()
|
||||
monitor.increaseBrightness();
|
||||
} else if (angle < 0) {
|
||||
monitor.decreaseBrightness()
|
||||
monitor.decreaseBrightness();
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||
settingsPanel.open()
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display;
|
||||
settingsPanel.open();
|
||||
}
|
||||
|
||||
onRightClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||
settingsPanel.open()
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display;
|
||||
settingsPanel.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ Rectangle {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
@@ -70,9 +70,9 @@ Rectangle {
|
||||
Binding on pointSize {
|
||||
value: {
|
||||
if (repeater.model.length == 1) {
|
||||
return Style.fontSizeS * scaling
|
||||
return Style.fontSizeS * scaling;
|
||||
} else {
|
||||
return (index == 0) ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling
|
||||
return (index == 0) ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,15 +122,15 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
if (!PanelService.getPanel("calendarPanel", screen)?.active) {
|
||||
TooltipService.show(Screen, root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection())
|
||||
TooltipService.show(Screen, root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection());
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
}
|
||||
onClicked: {
|
||||
TooltipService.hide()
|
||||
PanelService.getPanel("calendarPanel", screen)?.toggle(this)
|
||||
TooltipService.hide();
|
||||
PanelService.getPanel("calendarPanel", screen)?.toggle(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick.Effects
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services.UI
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
NIconButton {
|
||||
id: root
|
||||
@@ -21,12 +21,12 @@ NIconButton {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon
|
||||
@@ -34,8 +34,8 @@ NIconButton {
|
||||
readonly property string customIconPath: widgetSettings.customIconPath || ""
|
||||
readonly property bool colorizeDistroLogo: {
|
||||
if (widgetSettings.colorizeDistroLogo !== undefined)
|
||||
return widgetSettings.colorizeDistroLogo
|
||||
return widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false
|
||||
return widgetSettings.colorizeDistroLogo;
|
||||
return widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false;
|
||||
}
|
||||
|
||||
// If we have a custom path or distro logo, don't use the theme icon.
|
||||
@@ -51,12 +51,12 @@ NIconButton {
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: useDistroLogo ? Color.mHover : Color.transparent
|
||||
onClicked: {
|
||||
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen)
|
||||
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen);
|
||||
if (Settings.data.controlCenter.position === "close_to_bar_button") {
|
||||
// Willopen the panel next to the bar button.
|
||||
controlCenterPanel?.toggle(this)
|
||||
controlCenterPanel?.toggle(this);
|
||||
} else {
|
||||
controlCenterPanel?.toggle()
|
||||
controlCenterPanel?.toggle();
|
||||
}
|
||||
}
|
||||
onRightClicked: PanelService.getPanel("settingsPanel", screen)?.toggle()
|
||||
@@ -69,10 +69,10 @@ NIconButton {
|
||||
height: width
|
||||
source: {
|
||||
if (customIconPath !== "")
|
||||
return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath
|
||||
return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath;
|
||||
if (useDistroLogo)
|
||||
return HostService.osLogo
|
||||
return ""
|
||||
return HostService.osLogo;
|
||||
return "";
|
||||
}
|
||||
visible: source !== ""
|
||||
smooth: true
|
||||
|
||||
@@ -3,10 +3,10 @@ import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Modules.Panels.Settings
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Modules.Panels.Settings
|
||||
import qs.Modules.Bar.Extras
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -22,12 +22,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -63,31 +63,31 @@ Item {
|
||||
autoHide: false
|
||||
forceOpen: _dynamicText !== ""
|
||||
tooltipText: {
|
||||
var tooltipLines = []
|
||||
var tooltipLines = [];
|
||||
|
||||
if (hasExec) {
|
||||
if (leftClickExec !== "") {
|
||||
tooltipLines.push(`Left click: ${leftClickExec}.`)
|
||||
tooltipLines.push(`Left click: ${leftClickExec}.`);
|
||||
}
|
||||
if (rightClickExec !== "") {
|
||||
tooltipLines.push(`Right click: ${rightClickExec}.`)
|
||||
tooltipLines.push(`Right click: ${rightClickExec}.`);
|
||||
}
|
||||
if (middleClickExec !== "") {
|
||||
tooltipLines.push(`Middle click: ${middleClickExec}.`)
|
||||
tooltipLines.push(`Middle click: ${middleClickExec}.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (_dynamicTooltip !== "") {
|
||||
if (tooltipLines.length > 0) {
|
||||
tooltipLines.push("")
|
||||
tooltipLines.push("");
|
||||
}
|
||||
tooltipLines.push(_dynamicTooltip)
|
||||
tooltipLines.push(_dynamicTooltip);
|
||||
}
|
||||
|
||||
if (tooltipLines.length === 0) {
|
||||
return "Custom button, configure in settings."
|
||||
return "Custom button, configure in settings.";
|
||||
} else {
|
||||
return tooltipLines.join("\n")
|
||||
return tooltipLines.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,121 +135,121 @@ Item {
|
||||
stderr: StdioCollector {}
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (textStream) {
|
||||
Logger.w("CustomButton", `Streaming text command exited (code: ${exitCode}), restarting...`)
|
||||
return
|
||||
Logger.w("CustomButton", `Streaming text command exited (code: ${exitCode}), restarting...`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseDynamicContent(content) {
|
||||
var contentStr = String(content || "").trim()
|
||||
var contentStr = String(content || "").trim();
|
||||
|
||||
if (parseJson) {
|
||||
var lineToParse = contentStr
|
||||
var lineToParse = contentStr;
|
||||
|
||||
if (!textStream && contentStr.includes('\n')) {
|
||||
const lines = contentStr.split('\n').filter(line => line.trim() !== '')
|
||||
const lines = contentStr.split('\n').filter(line => line.trim() !== '');
|
||||
if (lines.length > 0) {
|
||||
lineToParse = lines[lines.length - 1]
|
||||
lineToParse = lines[lines.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(lineToParse)
|
||||
const text = parsed.text || ""
|
||||
const icon = parsed.icon || ""
|
||||
const tooltip = parsed.tooltip || ""
|
||||
const parsed = JSON.parse(lineToParse);
|
||||
const text = parsed.text || "";
|
||||
const icon = parsed.icon || "";
|
||||
const tooltip = parsed.tooltip || "";
|
||||
|
||||
if (checkCollapse(text)) {
|
||||
_dynamicText = ""
|
||||
_dynamicIcon = ""
|
||||
_dynamicTooltip = ""
|
||||
return
|
||||
_dynamicText = "";
|
||||
_dynamicIcon = "";
|
||||
_dynamicTooltip = "";
|
||||
return;
|
||||
}
|
||||
|
||||
_dynamicText = text
|
||||
_dynamicIcon = icon
|
||||
_dynamicTooltip = tooltip
|
||||
return
|
||||
_dynamicText = text;
|
||||
_dynamicIcon = icon;
|
||||
_dynamicTooltip = tooltip;
|
||||
return;
|
||||
} catch (e) {
|
||||
Logger.w("CustomButton", `Failed to parse JSON. Content: "${lineToParse}"`)
|
||||
Logger.w("CustomButton", `Failed to parse JSON. Content: "${lineToParse}"`);
|
||||
}
|
||||
}
|
||||
|
||||
if (checkCollapse(contentStr)) {
|
||||
_dynamicText = ""
|
||||
_dynamicIcon = ""
|
||||
_dynamicTooltip = ""
|
||||
return
|
||||
_dynamicText = "";
|
||||
_dynamicIcon = "";
|
||||
_dynamicTooltip = "";
|
||||
return;
|
||||
}
|
||||
|
||||
_dynamicText = contentStr
|
||||
_dynamicIcon = ""
|
||||
_dynamicTooltip = ""
|
||||
_dynamicText = contentStr;
|
||||
_dynamicIcon = "";
|
||||
_dynamicTooltip = "";
|
||||
}
|
||||
|
||||
function checkCollapse(text) {
|
||||
if (!textCollapse || textCollapse.length === 0) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
if (textCollapse.startsWith("/") && textCollapse.endsWith("/") && textCollapse.length > 1) {
|
||||
// Treat as regex
|
||||
var pattern = textCollapse.substring(1, textCollapse.length - 1)
|
||||
var pattern = textCollapse.substring(1, textCollapse.length - 1);
|
||||
try {
|
||||
var regex = new RegExp(pattern)
|
||||
return regex.test(text)
|
||||
var regex = new RegExp(pattern);
|
||||
return regex.test(text);
|
||||
} catch (e) {
|
||||
Logger.w("CustomButton", `Invalid regex for textCollapse: ${textCollapse} - ${e.message}`)
|
||||
return (textCollapse === text) // Fallback to exact match on invalid regex
|
||||
Logger.w("CustomButton", `Invalid regex for textCollapse: ${textCollapse} - ${e.message}`);
|
||||
return (textCollapse === text); // Fallback to exact match on invalid regex
|
||||
}
|
||||
} else {
|
||||
// Treat as plain string
|
||||
return (textCollapse === text)
|
||||
return (textCollapse === text);
|
||||
}
|
||||
}
|
||||
|
||||
function onClicked() {
|
||||
if (leftClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", leftClickExec])
|
||||
Logger.i("CustomButton", `Executing command: ${leftClickExec}`)
|
||||
Quickshell.execDetached(["sh", "-c", leftClickExec]);
|
||||
Logger.i("CustomButton", `Executing command: ${leftClickExec}`);
|
||||
} else if (!hasExec && !leftClickUpdateText) {
|
||||
// No script was defined, open settings
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Bar
|
||||
settingsPanel.open()
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Bar;
|
||||
settingsPanel.open();
|
||||
}
|
||||
if (!textStream && leftClickUpdateText) {
|
||||
runTextCommand()
|
||||
runTextCommand();
|
||||
}
|
||||
}
|
||||
|
||||
function onRightClicked() {
|
||||
if (rightClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", rightClickExec])
|
||||
Logger.i("CustomButton", `Executing command: ${rightClickExec}`)
|
||||
Quickshell.execDetached(["sh", "-c", rightClickExec]);
|
||||
Logger.i("CustomButton", `Executing command: ${rightClickExec}`);
|
||||
}
|
||||
if (!textStream && rightClickUpdateText) {
|
||||
runTextCommand()
|
||||
runTextCommand();
|
||||
}
|
||||
}
|
||||
|
||||
function onMiddleClicked() {
|
||||
if (middleClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", middleClickExec])
|
||||
Logger.i("CustomButton", `Executing command: ${middleClickExec}`)
|
||||
Quickshell.execDetached(["sh", "-c", middleClickExec]);
|
||||
Logger.i("CustomButton", `Executing command: ${middleClickExec}`);
|
||||
}
|
||||
if (!textStream && middleClickUpdateText) {
|
||||
runTextCommand()
|
||||
runTextCommand();
|
||||
}
|
||||
}
|
||||
|
||||
function runTextCommand() {
|
||||
if (!textCommand || textCommand.length === 0)
|
||||
return
|
||||
return;
|
||||
if (textProc.running)
|
||||
return
|
||||
textProc.command = ["sh", "-lc", textCommand]
|
||||
textProc.running = true
|
||||
return;
|
||||
textProc.command = ["sh", "-lc", textCommand];
|
||||
textProc.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
NIconButton {
|
||||
id: root
|
||||
|
||||
@@ -2,10 +2,10 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.Power
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Modules.Bar.Extras
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -21,12 +21,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -47,20 +47,20 @@ Item {
|
||||
forceOpen: IdleInhibitorService.timeout !== null
|
||||
forceClose: IdleInhibitorService.timeout == null
|
||||
onWheel: function (delta) {
|
||||
var sign = delta > 0 ? 1 : -1
|
||||
var sign = delta > 0 ? 1 : -1;
|
||||
// the offset makes scrolling down feel symmetrical to scrolling up
|
||||
var timeout = IdleInhibitorService.timeout - (delta < 0 ? 60 : 0)
|
||||
var timeout = IdleInhibitorService.timeout - (delta < 0 ? 60 : 0);
|
||||
if (timeout == null || timeout < 600) {
|
||||
delta = 60 // <= 10m, increment at 1m interval
|
||||
delta = 60; // <= 10m, increment at 1m interval
|
||||
} else if (timeout >= 600 && timeout < 1800) {
|
||||
delta = 300 // >= 10m, increment at 5m interval
|
||||
delta = 300; // >= 10m, increment at 5m interval
|
||||
} else if (timeout >= 1800 && timeout < 3600) {
|
||||
delta = 600 // >= 30m, increment at 10m interval
|
||||
delta = 600; // >= 30m, increment at 10m interval
|
||||
} else if (timeout >= 3600) {
|
||||
delta = 1800 // > 1h, increment at 30m interval
|
||||
delta = 1800; // > 1h, increment at 30m interval
|
||||
}
|
||||
|
||||
IdleInhibitorService.changeTimeout(delta * sign)
|
||||
IdleInhibitorService.changeTimeout(delta * sign);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.Keyboard
|
||||
import qs.Widgets
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.Keyboard
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -23,12 +23,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode
|
||||
@@ -53,9 +53,9 @@ Item {
|
||||
})
|
||||
forceOpen: root.displayMode === "forceOpen"
|
||||
forceClose: root.displayMode === "alwaysHide"
|
||||
onClicked: {
|
||||
onClicked:
|
||||
|
||||
// You could open keyboard settings here if needed.
|
||||
}
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ Rectangle {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
|
||||
@@ -2,11 +2,11 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Widgets.AudioSpectrum
|
||||
import qs.Commons
|
||||
import qs.Services.Media
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Widgets.AudioSpectrum
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -23,12 +23,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right")
|
||||
@@ -49,18 +49,18 @@ Item {
|
||||
readonly property string placeholderText: I18n.tr("bar.widget-settings.media-mini.no-active-player")
|
||||
|
||||
readonly property string tooltipText: {
|
||||
var title = getTitle()
|
||||
var controls = ""
|
||||
var title = getTitle();
|
||||
var controls = "";
|
||||
if (MediaService.canGoNext) {
|
||||
controls += "Right click for next.\n"
|
||||
controls += "Right click for next.\n";
|
||||
}
|
||||
if (MediaService.canGoPrevious) {
|
||||
controls += "Middle click for previous."
|
||||
controls += "Middle click for previous.";
|
||||
}
|
||||
if (controls !== "") {
|
||||
return title + "\n\n" + controls
|
||||
return title + "\n\n" + controls;
|
||||
}
|
||||
return title
|
||||
return title;
|
||||
}
|
||||
|
||||
// Hide conditions
|
||||
@@ -94,57 +94,57 @@ Item {
|
||||
}
|
||||
|
||||
function getTitle() {
|
||||
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
|
||||
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "");
|
||||
}
|
||||
|
||||
function calculatedVerticalDimension() {
|
||||
return Math.round((Style.baseWidgetSize - 5) * scaling)
|
||||
return Math.round((Style.baseWidgetSize - 5) * scaling);
|
||||
}
|
||||
|
||||
function calculateContentWidth() {
|
||||
// Calculate the actual content width based on visible elements
|
||||
var contentWidth = 0
|
||||
var margins = Style.marginS * scaling * 2 // Left and right margins
|
||||
var contentWidth = 0;
|
||||
var margins = Style.marginS * scaling * 2; // Left and right margins
|
||||
|
||||
// Icon or album art width
|
||||
if (!hasActivePlayer || !showAlbumArt) {
|
||||
// Icon width
|
||||
contentWidth += Math.round(18 * scaling)
|
||||
contentWidth += Math.round(18 * scaling);
|
||||
} else if (showAlbumArt && hasActivePlayer) {
|
||||
// Album art width
|
||||
contentWidth += 21 * scaling
|
||||
contentWidth += 21 * scaling;
|
||||
}
|
||||
|
||||
// Spacing between icon/art and text; only if there is text
|
||||
if (fullTitleMetrics.contentWidth > 0) {
|
||||
contentWidth += Style.marginS * scaling
|
||||
contentWidth += Style.marginS * scaling;
|
||||
|
||||
// Text width (use the measured width)
|
||||
contentWidth += fullTitleMetrics.contentWidth
|
||||
contentWidth += fullTitleMetrics.contentWidth;
|
||||
|
||||
// Additional small margin for text
|
||||
contentWidth += Style.marginXXS * 2
|
||||
contentWidth += Style.marginXXS * 2;
|
||||
}
|
||||
|
||||
// Add container margins
|
||||
contentWidth += margins
|
||||
contentWidth += margins;
|
||||
|
||||
return Math.ceil(contentWidth)
|
||||
return Math.ceil(contentWidth);
|
||||
}
|
||||
|
||||
// Dynamic width: adapt to content but respect maximum width setting
|
||||
readonly property real dynamicWidth: {
|
||||
// If using fixed width mode, always use maxWidth
|
||||
if (useFixedWidth) {
|
||||
return maxWidth
|
||||
return maxWidth;
|
||||
}
|
||||
// Otherwise, adapt to content
|
||||
if (!hasActivePlayer) {
|
||||
// Keep compact when no active player
|
||||
return calculateContentWidth()
|
||||
return calculateContentWidth();
|
||||
}
|
||||
// Use content width but don't exceed user-set maximum width
|
||||
return Math.min(calculateContentWidth(), maxWidth)
|
||||
return Math.min(calculateContentWidth(), maxWidth);
|
||||
}
|
||||
|
||||
// A hidden text element to safely measure the full title width
|
||||
@@ -279,11 +279,11 @@ Item {
|
||||
id: titleContainer
|
||||
Layout.preferredWidth: {
|
||||
// Calculate available width based on other elements in the row
|
||||
var iconWidth = (windowIcon.visible ? (18 * scaling + Style.marginS * scaling) : 0)
|
||||
var albumArtWidth = (hasActivePlayer && showAlbumArt ? (21 * scaling + Style.marginS * scaling) : 0)
|
||||
var totalMargins = Style.marginXXS * 2
|
||||
var availableWidth = mainContainer.width - iconWidth - albumArtWidth - totalMargins
|
||||
return Math.max(20, availableWidth)
|
||||
var iconWidth = (windowIcon.visible ? (18 * scaling + Style.marginS * scaling) : 0);
|
||||
var albumArtWidth = (hasActivePlayer && showAlbumArt ? (21 * scaling + Style.marginS * scaling) : 0);
|
||||
var totalMargins = Style.marginXXS * 2;
|
||||
var availableWidth = mainContainer.width - iconWidth - albumArtWidth - totalMargins;
|
||||
return Math.max(20, availableWidth);
|
||||
}
|
||||
Layout.maximumWidth: Layout.preferredWidth
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
@@ -304,8 +304,8 @@ Item {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (scrollingMode === "always" && titleContainer.needsScrolling) {
|
||||
titleContainer.isScrolling = true
|
||||
titleContainer.isResetting = false
|
||||
titleContainer.isScrolling = true;
|
||||
titleContainer.isResetting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,48 +313,48 @@ Item {
|
||||
// Update scrolling state based on mode
|
||||
property var updateScrollingState: function () {
|
||||
if (scrollingMode === "never") {
|
||||
isScrolling = false
|
||||
isResetting = false
|
||||
isScrolling = false;
|
||||
isResetting = false;
|
||||
} else if (scrollingMode === "always") {
|
||||
if (needsScrolling) {
|
||||
if (mouseArea.containsMouse) {
|
||||
isScrolling = false
|
||||
isResetting = true
|
||||
isScrolling = false;
|
||||
isResetting = true;
|
||||
} else {
|
||||
scrollStartTimer.restart()
|
||||
scrollStartTimer.restart();
|
||||
}
|
||||
} else {
|
||||
scrollStartTimer.stop()
|
||||
isScrolling = false
|
||||
isResetting = false
|
||||
scrollStartTimer.stop();
|
||||
isScrolling = false;
|
||||
isResetting = false;
|
||||
}
|
||||
} else if (scrollingMode === "hover") {
|
||||
if (mouseArea.containsMouse && needsScrolling) {
|
||||
isScrolling = true
|
||||
isResetting = false
|
||||
isScrolling = true;
|
||||
isResetting = false;
|
||||
} else {
|
||||
isScrolling = false
|
||||
isScrolling = false;
|
||||
if (needsScrolling) {
|
||||
isResetting = true
|
||||
isResetting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onWidthChanged: {
|
||||
containerWidth = width
|
||||
updateScrollingState()
|
||||
containerWidth = width;
|
||||
updateScrollingState();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
containerWidth = width
|
||||
updateScrollingState()
|
||||
containerWidth = width;
|
||||
updateScrollingState();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: mouseArea
|
||||
function onContainsMouseChanged() {
|
||||
titleContainer.updateScrollingState()
|
||||
titleContainer.updateScrollingState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,11 +380,11 @@ Item {
|
||||
horizontalAlignment: hasActivePlayer ? Text.AlignLeft : Text.AlignHCenter
|
||||
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
|
||||
onTextChanged: {
|
||||
titleContainer.isScrolling = false
|
||||
titleContainer.isResetting = false
|
||||
scrollContainer.scrollX = 0
|
||||
titleContainer.isScrolling = false;
|
||||
titleContainer.isResetting = false;
|
||||
scrollContainer.scrollX = 0;
|
||||
if (titleContainer.needsScrolling) {
|
||||
scrollStartTimer.restart()
|
||||
scrollStartTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,7 +408,7 @@ Item {
|
||||
duration: 300
|
||||
easing.type: Easing.OutQuad
|
||||
onFinished: {
|
||||
titleContainer.isResetting = false
|
||||
titleContainer.isResetting = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,28 +462,28 @@ Item {
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
if (!hasActivePlayer || !MediaService.currentPlayer || !MediaService.canPlay) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
MediaService.playPause()
|
||||
MediaService.playPause();
|
||||
} else if (mouse.button == Qt.RightButton) {
|
||||
MediaService.next()
|
||||
TooltipService.hide()
|
||||
MediaService.next();
|
||||
TooltipService.hide();
|
||||
} else if (mouse.button == Qt.MiddleButton) {
|
||||
MediaService.previous()
|
||||
TooltipService.hide()
|
||||
MediaService.previous();
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
|
||||
onEntered: {
|
||||
var textToShow = hasActivePlayer ? tooltipText : placeholderText
|
||||
var textToShow = hasActivePlayer ? tooltipText : placeholderText;
|
||||
if ((textToShow !== "") && isVerticalBar || (scrollingMode === "never")) {
|
||||
TooltipService.show(Screen, root, textToShow, BarService.getTooltipDirection())
|
||||
TooltipService.show(Screen, root, textToShow, BarService.getTooltipDirection());
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -48,13 +48,13 @@ Item {
|
||||
// Logger.i("Bar:Microphone", "onInputVolumeChanged")
|
||||
if (!firstInputVolumeReceived) {
|
||||
// Ignore the first volume change
|
||||
firstInputVolumeReceived = true
|
||||
firstInputVolumeReceived = true;
|
||||
} else {
|
||||
// If a tooltip is visible while we show the pill
|
||||
// hide it so it doesn't overlap the volume slider.
|
||||
TooltipService.hide()
|
||||
pill.show()
|
||||
externalHideTimer.restart()
|
||||
TooltipService.hide();
|
||||
pill.show();
|
||||
externalHideTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,11 +66,11 @@ Item {
|
||||
// Logger.i("Bar:Microphone", "onInputMutedChanged")
|
||||
if (!firstInputVolumeReceived) {
|
||||
// Ignore the first mute change
|
||||
firstInputVolumeReceived = true
|
||||
firstInputVolumeReceived = true;
|
||||
} else {
|
||||
TooltipService.hide()
|
||||
pill.show()
|
||||
externalHideTimer.restart()
|
||||
TooltipService.hide();
|
||||
pill.show();
|
||||
externalHideTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ Item {
|
||||
running: false
|
||||
interval: 1500
|
||||
onTriggered: {
|
||||
pill.hide()
|
||||
pill.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,25 +101,25 @@ Item {
|
||||
|
||||
onWheel: function (delta) {
|
||||
// As soon as we start scrolling to adjust volume, hide the tooltip
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
|
||||
wheelAccumulator += delta
|
||||
wheelAccumulator += delta;
|
||||
if (wheelAccumulator >= 120) {
|
||||
wheelAccumulator = 0
|
||||
AudioService.setInputVolume(AudioService.inputVolume + AudioService.stepVolume)
|
||||
wheelAccumulator = 0;
|
||||
AudioService.setInputVolume(AudioService.inputVolume + AudioService.stepVolume);
|
||||
} else if (wheelAccumulator <= -120) {
|
||||
wheelAccumulator = 0
|
||||
AudioService.setInputVolume(AudioService.inputVolume - AudioService.stepVolume)
|
||||
wheelAccumulator = 0;
|
||||
AudioService.setInputVolume(AudioService.inputVolume - AudioService.stepVolume);
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
PanelService.getPanel("audioPanel", screen)?.toggle(this)
|
||||
PanelService.getPanel("audioPanel", screen)?.toggle(this);
|
||||
}
|
||||
onRightClicked: {
|
||||
AudioService.setInputMuted(!AudioService.inputMuted)
|
||||
AudioService.setInputMuted(!AudioService.inputMuted);
|
||||
}
|
||||
onMiddleClicked: {
|
||||
Quickshell.execDetached(["pwvucontrol"])
|
||||
Quickshell.execDetached(["pwvucontrol"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
@@ -28,24 +28,24 @@ NIconButton {
|
||||
onClicked: {
|
||||
// Check if wlsunset is available before enabling night light
|
||||
if (!ProgramCheckerService.wlsunsetAvailable) {
|
||||
ToastService.showWarning(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.not-installed"))
|
||||
return
|
||||
ToastService.showWarning(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.not-installed"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Settings.data.nightLight.enabled) {
|
||||
Settings.data.nightLight.enabled = true
|
||||
Settings.data.nightLight.forced = false
|
||||
Settings.data.nightLight.enabled = true;
|
||||
Settings.data.nightLight.forced = false;
|
||||
} else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) {
|
||||
Settings.data.nightLight.forced = true
|
||||
Settings.data.nightLight.forced = true;
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false
|
||||
Settings.data.nightLight.forced = false
|
||||
Settings.data.nightLight.enabled = false;
|
||||
Settings.data.nightLight.forced = false;
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||
settingsPanel.open()
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display;
|
||||
settingsPanel.open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
@@ -26,6 +26,6 @@ NIconButton {
|
||||
tooltipText: PowerProfileService.noctaliaPerformanceMode ? I18n.tr("tooltips.noctalia-performance-enabled") : I18n.tr("tooltips.noctalia-performance-disabled")
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
onClicked: {
|
||||
PowerProfileService.toggleNoctaliaPerformance()
|
||||
PowerProfileService.toggleNoctaliaPerformance();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
NIconButton {
|
||||
@@ -22,27 +22,27 @@ NIconButton {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge !== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge
|
||||
readonly property bool hideWhenZero: (widgetSettings.hideWhenZero !== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero
|
||||
|
||||
function computeUnreadCount() {
|
||||
var since = NotificationService.lastSeenTs
|
||||
var count = 0
|
||||
var model = NotificationService.historyList
|
||||
var since = NotificationService.lastSeenTs;
|
||||
var count = 0;
|
||||
var model = NotificationService.historyList;
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
var item = model.get(i)
|
||||
var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp
|
||||
var item = model.get(i);
|
||||
var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp;
|
||||
if (ts > since)
|
||||
count++
|
||||
count++;
|
||||
}
|
||||
return count
|
||||
return count;
|
||||
}
|
||||
|
||||
baseSize: Style.capsuleHeight
|
||||
@@ -57,8 +57,8 @@ NIconButton {
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
onClicked: {
|
||||
var panel = PanelService.getPanel("notificationHistoryPanel", screen)
|
||||
panel?.toggle(this)
|
||||
var panel = PanelService.getPanel("notificationHistoryPanel", screen);
|
||||
panel?.toggle(this);
|
||||
}
|
||||
|
||||
onRightClicked: NotificationService.doNotDisturb = !NotificationService.doNotDisturb
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.Media
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
// Screen Recording Indicator
|
||||
@@ -24,10 +24,10 @@ NIconButton {
|
||||
|
||||
function handleClick() {
|
||||
if (!ScreenRecorderService.isAvailable) {
|
||||
ToastService.showError(I18n.tr("toast.recording.not-installed"), I18n.tr("toast.recording.not-installed-desc"))
|
||||
return
|
||||
ToastService.showError(I18n.tr("toast.recording.not-installed"), I18n.tr("toast.recording.not-installed-desc"));
|
||||
return;
|
||||
}
|
||||
ScreenRecorderService.toggleRecording()
|
||||
ScreenRecorderService.toggleRecording();
|
||||
}
|
||||
|
||||
onClicked: handleClick()
|
||||
|
||||
@@ -19,12 +19,12 @@ NIconButton {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string colorName: widgetSettings.colorName !== undefined ? widgetSettings.colorName : widgetMetadata.colorName
|
||||
@@ -32,16 +32,16 @@ NIconButton {
|
||||
readonly property color iconColor: {
|
||||
switch (colorName) {
|
||||
case "primary":
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
case "secondary":
|
||||
return Color.mSecondary
|
||||
return Color.mSecondary;
|
||||
case "tertiary":
|
||||
return Color.mTertiary
|
||||
return Color.mTertiary;
|
||||
case "error":
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
case "onSurface":
|
||||
default:
|
||||
return Color.mOnSurface
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Use settings or defaults from BarWidgetRegistry
|
||||
|
||||
@@ -3,8 +3,8 @@ import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Panels.Settings
|
||||
import qs.Services.UI
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
@@ -21,12 +21,12 @@ Rectangle {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
@@ -43,8 +43,8 @@ Rectangle {
|
||||
|
||||
readonly property real iconSize: textSize * 1.4
|
||||
readonly property real textSize: {
|
||||
var base = isVertical ? width * 0.82 : height
|
||||
return Math.max(1, (density === "compact") ? base * 0.43 : base * 0.33)
|
||||
var base = isVertical ? width * 0.82 : height;
|
||||
return Math.max(1, (density === "compact") ? base * 0.43 : base * 0.33);
|
||||
}
|
||||
|
||||
// Match Workspace widget pill sizing: base ratio depends on bar density
|
||||
@@ -178,11 +178,11 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
|
||||
onLoaded: {
|
||||
item.warning = Qt.binding(() => cpuWarning)
|
||||
item.critical = Qt.binding(() => cpuCritical)
|
||||
item.indicatorWidth = Qt.binding(() => cpuUsageContainer.width)
|
||||
item.warningColor = Qt.binding(() => root.warningColor)
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor)
|
||||
item.warning = Qt.binding(() => cpuWarning);
|
||||
item.critical = Qt.binding(() => cpuCritical);
|
||||
item.indicatorWidth = Qt.binding(() => cpuUsageContainer.width);
|
||||
item.warningColor = Qt.binding(() => root.warningColor);
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,11 +214,11 @@ Rectangle {
|
||||
|
||||
NText {
|
||||
text: {
|
||||
let usage = Math.round(SystemStatService.cpuUsage)
|
||||
let usage = Math.round(SystemStatService.cpuUsage);
|
||||
if (usage < 100) {
|
||||
return `${usage}%`
|
||||
return `${usage}%`;
|
||||
} else {
|
||||
return usage
|
||||
return usage;
|
||||
}
|
||||
}
|
||||
family: Settings.data.ui.fontFixed
|
||||
@@ -252,11 +252,11 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
|
||||
onLoaded: {
|
||||
item.warning = Qt.binding(() => tempWarning)
|
||||
item.critical = Qt.binding(() => tempCritical)
|
||||
item.indicatorWidth = Qt.binding(() => cpuTempContainer.width)
|
||||
item.warningColor = Qt.binding(() => root.warningColor)
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor)
|
||||
item.warning = Qt.binding(() => tempWarning);
|
||||
item.critical = Qt.binding(() => tempCritical);
|
||||
item.indicatorWidth = Qt.binding(() => cpuTempContainer.width);
|
||||
item.warningColor = Qt.binding(() => root.warningColor);
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,11 +319,11 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
|
||||
onLoaded: {
|
||||
item.warning = Qt.binding(() => memWarning)
|
||||
item.critical = Qt.binding(() => memCritical)
|
||||
item.indicatorWidth = Qt.binding(() => memoryContainer.width)
|
||||
item.warningColor = Qt.binding(() => root.warningColor)
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor)
|
||||
item.warning = Qt.binding(() => memWarning);
|
||||
item.critical = Qt.binding(() => memCritical);
|
||||
item.indicatorWidth = Qt.binding(() => memoryContainer.width);
|
||||
item.warningColor = Qt.binding(() => root.warningColor);
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,11 +472,11 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
|
||||
onLoaded: {
|
||||
item.warning = Qt.binding(() => diskWarning)
|
||||
item.critical = Qt.binding(() => diskCritical)
|
||||
item.indicatorWidth = Qt.binding(() => diskContainer.width)
|
||||
item.warningColor = Qt.binding(() => root.warningColor)
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor)
|
||||
item.warning = Qt.binding(() => diskWarning);
|
||||
item.critical = Qt.binding(() => diskCritical);
|
||||
item.indicatorWidth = Qt.binding(() => diskContainer.width);
|
||||
item.warningColor = Qt.binding(() => root.warningColor);
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
@@ -27,12 +27,12 @@ Rectangle {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
property bool hasWindow: false
|
||||
@@ -42,35 +42,35 @@ Rectangle {
|
||||
|
||||
function updateHasWindow() {
|
||||
try {
|
||||
var total = CompositorService.windows.count || 0
|
||||
var total = CompositorService.windows.count || 0;
|
||||
var activeIds = CompositorService.getActiveWorkspaces().map(function (ws) {
|
||||
return ws.id
|
||||
})
|
||||
var found = false
|
||||
return ws.id;
|
||||
});
|
||||
var found = false;
|
||||
for (var i = 0; i < total; i++) {
|
||||
var w = CompositorService.windows.get(i)
|
||||
var w = CompositorService.windows.get(i);
|
||||
if (!w)
|
||||
continue
|
||||
var passOutput = (!onlySameOutput) || (w.output == screen.name)
|
||||
var passWorkspace = (!onlyActiveWorkspaces) || (activeIds.includes(w.workspaceId))
|
||||
continue;
|
||||
var passOutput = (!onlySameOutput) || (w.output == screen.name);
|
||||
var passWorkspace = (!onlyActiveWorkspaces) || (activeIds.includes(w.workspaceId));
|
||||
if (passOutput && passWorkspace) {
|
||||
found = true
|
||||
break
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
hasWindow = found
|
||||
hasWindow = found;
|
||||
} catch (e) {
|
||||
hasWindow = false
|
||||
hasWindow = false;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CompositorService
|
||||
function onWindowListChanged() {
|
||||
updateHasWindow()
|
||||
updateHasWindow();
|
||||
}
|
||||
function onWorkspaceChanged() {
|
||||
updateHasWindow()
|
||||
updateHasWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ Rectangle {
|
||||
property ShellScreen screen: root.screen
|
||||
|
||||
visible: (!onlySameOutput || modelData.output == screen.name) && (!onlyActiveWorkspaces || CompositorService.getActiveWorkspaces().map(function (ws) {
|
||||
return ws.id
|
||||
return ws.id;
|
||||
}).includes(modelData.workspaceId))
|
||||
|
||||
Layout.preferredWidth: root.itemSize
|
||||
@@ -125,7 +125,6 @@ Rectangle {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
IconImage {
|
||||
|
||||
id: appIcon
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
@@ -144,10 +143,10 @@ Rectangle {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: iconBackground
|
||||
anchors.bottomMargin: -2
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
id: iconBackground
|
||||
width: 4
|
||||
height: 4
|
||||
color: modelData.isFocused ? Color.mPrimary : Color.transparent
|
||||
@@ -163,19 +162,18 @@ Rectangle {
|
||||
|
||||
onPressed: function (mouse) {
|
||||
if (!taskbarItem.modelData)
|
||||
return
|
||||
|
||||
return;
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
try {
|
||||
CompositorService.focusWindow(taskbarItem.modelData)
|
||||
CompositorService.focusWindow(taskbarItem.modelData);
|
||||
} catch (error) {
|
||||
Logger.e("Taskbar", "Failed to activate toplevel: " + error)
|
||||
Logger.e("Taskbar", "Failed to activate toplevel: " + error);
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
try {
|
||||
CompositorService.closeWindow(taskbarItem.modelData)
|
||||
CompositorService.closeWindow(taskbarItem.modelData);
|
||||
} catch (error) {
|
||||
Logger.e("Taskbar", "Failed to close toplevel: " + error)
|
||||
Logger.e("Taskbar", "Failed to close toplevel: " + error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
|
||||
readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied
|
||||
@@ -49,40 +49,39 @@ Item {
|
||||
property bool wheelCooldown: false
|
||||
|
||||
function refreshWorkspaces() {
|
||||
localWorkspaces.clear()
|
||||
localWorkspaces.clear();
|
||||
if (!screen)
|
||||
return
|
||||
|
||||
const screenName = screen.name.toLowerCase()
|
||||
return;
|
||||
const screenName = screen.name.toLowerCase();
|
||||
|
||||
for (var i = 0; i < CompositorService.workspaces.count; i++) {
|
||||
const ws = CompositorService.workspaces.get(i)
|
||||
const ws = CompositorService.workspaces.get(i);
|
||||
|
||||
if (ws.output.toLowerCase() !== screenName)
|
||||
continue
|
||||
continue;
|
||||
if (hideUnoccupied && !ws.isOccupied && !ws.isFocused)
|
||||
continue
|
||||
continue;
|
||||
|
||||
// Copy all properties from ws and add windows
|
||||
var workspaceData = Object.assign({}, ws)
|
||||
workspaceData.windows = CompositorService.getWindowsForWorkspace(ws.id)
|
||||
var workspaceData = Object.assign({}, ws);
|
||||
workspaceData.windows = CompositorService.getWindowsForWorkspace(ws.id);
|
||||
|
||||
localWorkspaces.append(workspaceData)
|
||||
localWorkspaces.append(workspaceData);
|
||||
}
|
||||
updateWorkspaceFocus()
|
||||
updateWorkspaceFocus();
|
||||
}
|
||||
|
||||
function triggerUnifiedWave() {
|
||||
effectColor = Color.mPrimary
|
||||
masterAnimation.restart()
|
||||
effectColor = Color.mPrimary;
|
||||
masterAnimation.restart();
|
||||
}
|
||||
|
||||
function updateWorkspaceFocus() {
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
const ws = localWorkspaces.get(i)
|
||||
const ws = localWorkspaces.get(i);
|
||||
if (ws.isFocused === true) {
|
||||
root.triggerUnifiedWave()
|
||||
break
|
||||
root.triggerUnifiedWave();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,27 +89,27 @@ Item {
|
||||
function getFocusedLocalIndex() {
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
if (localWorkspaces.get(i).isFocused === true)
|
||||
return i
|
||||
return i;
|
||||
}
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
function switchByOffset(offset) {
|
||||
if (localWorkspaces.count === 0)
|
||||
return
|
||||
var current = getFocusedLocalIndex()
|
||||
return;
|
||||
var current = getFocusedLocalIndex();
|
||||
if (current < 0)
|
||||
current = 0
|
||||
var next = (current + offset) % localWorkspaces.count
|
||||
current = 0;
|
||||
var next = (current + offset) % localWorkspaces.count;
|
||||
if (next < 0)
|
||||
next = localWorkspaces.count - 1
|
||||
const ws = localWorkspaces.get(next)
|
||||
next = localWorkspaces.count - 1;
|
||||
const ws = localWorkspaces.get(next);
|
||||
if (ws && ws.idx !== undefined)
|
||||
CompositorService.switchToWorkspace(ws)
|
||||
CompositorService.switchToWorkspace(ws);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
refreshWorkspaces()
|
||||
refreshWorkspaces();
|
||||
}
|
||||
|
||||
onScreenChanged: refreshWorkspaces()
|
||||
@@ -123,11 +122,11 @@ Item {
|
||||
target: CompositorService
|
||||
|
||||
function onWorkspacesChanged() {
|
||||
refreshWorkspaces()
|
||||
refreshWorkspaces();
|
||||
}
|
||||
|
||||
function onWindowListChanged() {
|
||||
refreshWorkspaces()
|
||||
refreshWorkspaces();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,8 +163,8 @@ Item {
|
||||
interval: 150
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.wheelCooldown = false
|
||||
root.wheelAccumulatedDelta = 0
|
||||
root.wheelCooldown = false;
|
||||
root.wheelAccumulatedDelta = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,24 +175,24 @@ Item {
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
onWheel: function (event) {
|
||||
if (root.wheelCooldown)
|
||||
return
|
||||
return;
|
||||
// Prefer vertical delta, fall back to horizontal if needed
|
||||
var dy = event.angleDelta.y
|
||||
var dx = event.angleDelta.x
|
||||
var useDy = Math.abs(dy) >= Math.abs(dx)
|
||||
var delta = useDy ? dy : dx
|
||||
var dy = event.angleDelta.y;
|
||||
var dx = event.angleDelta.x;
|
||||
var useDy = Math.abs(dy) >= Math.abs(dx);
|
||||
var delta = useDy ? dy : dx;
|
||||
// One notch is typically 120
|
||||
root.wheelAccumulatedDelta += delta
|
||||
var step = 120
|
||||
root.wheelAccumulatedDelta += delta;
|
||||
var step = 120;
|
||||
if (Math.abs(root.wheelAccumulatedDelta) >= step) {
|
||||
var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1
|
||||
var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1;
|
||||
// For vertical layout, natural mapping: wheel up -> previous, down -> next (already handled by sign)
|
||||
// For horizontal layout, same mapping using vertical wheel
|
||||
root.switchByOffset(direction)
|
||||
root.wheelCooldown = true
|
||||
wheelDebounce.restart()
|
||||
root.wheelAccumulatedDelta = 0
|
||||
event.accepted = true
|
||||
root.switchByOffset(direction);
|
||||
root.wheelCooldown = true;
|
||||
wheelDebounce.restart();
|
||||
root.wheelAccumulatedDelta = 0;
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,7 +220,7 @@ Item {
|
||||
enabled: !hasWindows
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
CompositorService.switchToWorkspace(workspaceModel)
|
||||
CompositorService.switchToWorkspace(workspaceModel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,22 +296,22 @@ Item {
|
||||
|
||||
onPressed: function (mouse) {
|
||||
if (!model) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
CompositorService.focusWindow(model)
|
||||
CompositorService.focusWindow(model);
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
CompositorService.closeWindow(model)
|
||||
CompositorService.closeWindow(model);
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
taskbarItem.itemHovered = true
|
||||
TooltipService.show(Screen, taskbarItem, model.title || model.appId || "Unknown app.", BarService.getTooltipDirection())
|
||||
taskbarItem.itemHovered = true;
|
||||
TooltipService.show(Screen, taskbarItem, model.title || model.appId || "Unknown app.", BarService.getTooltipDirection());
|
||||
}
|
||||
onExited: {
|
||||
taskbarItem.itemHovered = false
|
||||
TooltipService.hide()
|
||||
taskbarItem.itemHovered = false;
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,13 +341,13 @@ Item {
|
||||
|
||||
color: {
|
||||
if (workspaceModel.isFocused)
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
if (workspaceModel.isUrgent)
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
if (hasWindows)
|
||||
return Color.mSecondary
|
||||
return Color.mSecondary;
|
||||
|
||||
return Qt.alpha(Color.mOutline, 0.3)
|
||||
return Qt.alpha(Color.mOutline, 0.3);
|
||||
}
|
||||
|
||||
scale: workspaceModel.isActive ? 1.0 : 0.9
|
||||
@@ -390,9 +389,9 @@ Item {
|
||||
|
||||
text: {
|
||||
if (root.labelMode === "name" && workspaceModel.name && workspaceModel.name.length > 0) {
|
||||
return workspaceModel.name.substring(0, root.characterCount)
|
||||
return workspaceModel.name.substring(0, root.characterCount);
|
||||
} else {
|
||||
return workspaceModel.idx.toString()
|
||||
return workspaceModel.idx.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,24 +405,24 @@ Item {
|
||||
|
||||
color: {
|
||||
if (workspaceModel.isFocused)
|
||||
return Color.mOnPrimary
|
||||
return Color.mOnPrimary;
|
||||
if (workspaceModel.isUrgent)
|
||||
return Color.mOnError
|
||||
return Color.mOnError;
|
||||
if (hasWindows)
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnSecondary;
|
||||
|
||||
return Color.mOnSurface
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
|
||||
opacity: {
|
||||
if (workspaceModel.isFocused)
|
||||
return 1.0
|
||||
return 1.0;
|
||||
if (workspaceModel.isUrgent)
|
||||
return 0.9
|
||||
return 0.9;
|
||||
if (hasWindows)
|
||||
return 0.8
|
||||
return 0.8;
|
||||
|
||||
return 0.6
|
||||
return 0.6;
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Widgets
|
||||
@@ -21,8 +21,8 @@ Rectangle {
|
||||
// Get shared tray menu window from PanelService (reactive to trigger changes)
|
||||
readonly property var trayMenuWindow: {
|
||||
// Reference trigger to force re-evaluation
|
||||
var _ = trayMenuUpdateTrigger
|
||||
return PanelService.getTrayMenuWindow(screen)
|
||||
var _ = trayMenuUpdateTrigger;
|
||||
return PanelService.getTrayMenuWindow(screen);
|
||||
}
|
||||
|
||||
readonly property var trayMenu: trayMenuWindow ? trayMenuWindow.trayMenuLoader : null
|
||||
@@ -31,7 +31,7 @@ Rectangle {
|
||||
target: PanelService
|
||||
function onTrayMenuWindowRegistered(registeredScreen) {
|
||||
if (registeredScreen === screen) {
|
||||
root.trayMenuUpdateTrigger++
|
||||
root.trayMenuUpdateTrigger++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,12 +45,12 @@ Rectangle {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
@@ -76,149 +76,149 @@ Rectangle {
|
||||
}
|
||||
|
||||
function _performFilteredItemsUpdate() {
|
||||
let newItems = []
|
||||
let newItems = [];
|
||||
if (SystemTray.items && SystemTray.items.values) {
|
||||
const trayItems = SystemTray.items.values
|
||||
const trayItems = SystemTray.items.values;
|
||||
for (var i = 0; i < trayItems.length; i++) {
|
||||
const item = trayItems[i]
|
||||
const item = trayItems[i];
|
||||
if (!item) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
const title = item.tooltipTitle || item.name || item.id || ""
|
||||
const title = item.tooltipTitle || item.name || item.id || "";
|
||||
|
||||
// Check if blacklisted
|
||||
let isBlacklisted = false
|
||||
let isBlacklisted = false;
|
||||
if (root.blacklist && root.blacklist.length > 0) {
|
||||
for (var j = 0; j < root.blacklist.length; j++) {
|
||||
const rule = root.blacklist[j]
|
||||
const rule = root.blacklist[j];
|
||||
if (wildCardMatch(title, rule)) {
|
||||
isBlacklisted = true
|
||||
break
|
||||
isBlacklisted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isBlacklisted) {
|
||||
newItems.push(item)
|
||||
newItems.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If drawer is disabled, show all items inline
|
||||
if (!root.drawerEnabled) {
|
||||
filteredItems = newItems
|
||||
dropdownItems = []
|
||||
filteredItems = newItems;
|
||||
dropdownItems = [];
|
||||
} else {
|
||||
// Build inline (pinned) and drawer (unpinned) lists
|
||||
// If pinned list is empty, all items go to drawer (none inline)
|
||||
// If pinned list has items, pinned items are inline, rest go to drawer
|
||||
if (pinned && pinned.length > 0) {
|
||||
let pinnedItems = []
|
||||
let pinnedItems = [];
|
||||
for (var k = 0; k < newItems.length; k++) {
|
||||
const item2 = newItems[k]
|
||||
const title2 = item2.tooltipTitle || item2.name || item2.id || ""
|
||||
const item2 = newItems[k];
|
||||
const title2 = item2.tooltipTitle || item2.name || item2.id || "";
|
||||
for (var m = 0; m < pinned.length; m++) {
|
||||
const rule2 = pinned[m]
|
||||
const rule2 = pinned[m];
|
||||
if (wildCardMatch(title2, rule2)) {
|
||||
pinnedItems.push(item2)
|
||||
break
|
||||
pinnedItems.push(item2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
filteredItems = pinnedItems
|
||||
filteredItems = pinnedItems;
|
||||
|
||||
// Unpinned items go to drawer
|
||||
let unpinnedItems = []
|
||||
let unpinnedItems = [];
|
||||
for (var v = 0; v < newItems.length; v++) {
|
||||
const cand = newItems[v]
|
||||
let isPinned = false
|
||||
const cand = newItems[v];
|
||||
let isPinned = false;
|
||||
for (var f = 0; f < filteredItems.length; f++) {
|
||||
if (filteredItems[f] === cand) {
|
||||
isPinned = true
|
||||
break
|
||||
isPinned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isPinned)
|
||||
unpinnedItems.push(cand)
|
||||
unpinnedItems.push(cand);
|
||||
}
|
||||
dropdownItems = unpinnedItems
|
||||
dropdownItems = unpinnedItems;
|
||||
} else {
|
||||
// No pinned items: all items go to drawer (none inline)
|
||||
filteredItems = []
|
||||
dropdownItems = newItems
|
||||
filteredItems = [];
|
||||
dropdownItems = newItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateFilteredItems() {
|
||||
updateDebounceTimer.restart()
|
||||
updateDebounceTimer.restart();
|
||||
}
|
||||
|
||||
function wildCardMatch(str, rule) {
|
||||
if (!str || !rule) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
//Logger.d("Tray", "wildCardMatch - Input str:", str, "rule:", rule)
|
||||
|
||||
// Escape all special regex characters in the rule
|
||||
let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
// Convert '*' to '.*' for wildcard matching
|
||||
let pattern = escapedRule.replace(/\\\*/g, '.*')
|
||||
let pattern = escapedRule.replace(/\\\*/g, '.*');
|
||||
// Add ^ and $ to match the entire string
|
||||
pattern = '^' + pattern + '$'
|
||||
pattern = '^' + pattern + '$';
|
||||
|
||||
//Logger.d("Tray", "wildCardMatch - Generated pattern:", pattern)
|
||||
try {
|
||||
const regex = new RegExp(pattern, 'i')
|
||||
const regex = new RegExp(pattern, 'i');
|
||||
// 'i' for case-insensitive
|
||||
//Logger.d("Tray", "wildCardMatch - Regex test result:", regex.test(str))
|
||||
return regex.test(str)
|
||||
return regex.test(str);
|
||||
} catch (e) {
|
||||
Logger.w("Tray", "Invalid regex pattern for wildcard match:", rule, e.message)
|
||||
return false // If regex is invalid, it won't match
|
||||
Logger.w("Tray", "Invalid regex pattern for wildcard match:", rule, e.message);
|
||||
return false; // If regex is invalid, it won't match
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDrawer(button) {
|
||||
TooltipService.hideImmediately()
|
||||
TooltipService.hideImmediately();
|
||||
|
||||
// Close the tray menu if it's open
|
||||
if (trayMenuWindow && trayMenuWindow.visible) {
|
||||
trayMenuWindow.close()
|
||||
trayMenuWindow.close();
|
||||
}
|
||||
|
||||
const panel = PanelService.getPanel("trayDrawerPanel", root.screen)
|
||||
const panel = PanelService.getPanel("trayDrawerPanel", root.screen);
|
||||
if (panel) {
|
||||
panel.widgetSection = root.section
|
||||
panel.widgetIndex = root.sectionWidgetIndex
|
||||
panel.toggle(this)
|
||||
panel.widgetSection = root.section;
|
||||
panel.widgetIndex = root.sectionWidgetIndex;
|
||||
panel.toggle(this);
|
||||
}
|
||||
}
|
||||
|
||||
function onLoaded() {
|
||||
// When the widget is fully initialized with its props set the screen for the trayMenu
|
||||
if (trayMenu && trayMenu.item) {
|
||||
trayMenu.item.screen = screen
|
||||
trayMenu.item.screen = screen;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SystemTray.items
|
||||
function onValuesChanged() {
|
||||
root.updateFilteredItems()
|
||||
root.updateFilteredItems();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Settings
|
||||
function onSettingsSaved() {
|
||||
root.updateFilteredItems()
|
||||
root.updateFilteredItems();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.updateFilteredItems() // Initial update
|
||||
root.updateFilteredItems(); // Initial update
|
||||
}
|
||||
|
||||
visible: filteredItems.length > 0 || dropdownItems.length > 0
|
||||
@@ -248,14 +248,14 @@ Rectangle {
|
||||
icon: {
|
||||
switch (barPosition) {
|
||||
case "bottom":
|
||||
return "caret-up"
|
||||
return "caret-up";
|
||||
case "left":
|
||||
return "caret-right"
|
||||
return "caret-right";
|
||||
case "right":
|
||||
return "caret-left"
|
||||
return "caret-left";
|
||||
case "top":
|
||||
default:
|
||||
return "caret-down"
|
||||
return "caret-down";
|
||||
}
|
||||
}
|
||||
onClicked: toggleDrawer(this)
|
||||
@@ -283,20 +283,20 @@ Rectangle {
|
||||
property bool menuJustOpened: false
|
||||
|
||||
source: {
|
||||
let icon = modelData?.icon || ""
|
||||
let icon = modelData?.icon || "";
|
||||
if (!icon) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
|
||||
// Process icon path
|
||||
if (icon.includes("?path=")) {
|
||||
const chunks = icon.split("?path=")
|
||||
const name = chunks[0]
|
||||
const path = chunks[1]
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1)
|
||||
return `file://${path}/${fileName}`
|
||||
const chunks = icon.split("?path=");
|
||||
const name = chunks[0];
|
||||
const path = chunks[1];
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
return icon
|
||||
return icon;
|
||||
}
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
|
||||
@@ -315,71 +315,71 @@ Rectangle {
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
if (!modelData) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
// Close any open menu first
|
||||
if (trayMenuWindow) {
|
||||
trayMenuWindow.close()
|
||||
trayMenuWindow.close();
|
||||
}
|
||||
|
||||
if (!modelData.onlyMenu) {
|
||||
modelData.activate()
|
||||
modelData.activate();
|
||||
}
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
// Close the menu if it was visible
|
||||
if (trayMenuWindow && trayMenuWindow.visible) {
|
||||
trayMenuWindow.close()
|
||||
return
|
||||
trayMenuWindow.close();
|
||||
return;
|
||||
}
|
||||
modelData.secondaryActivate && modelData.secondaryActivate()
|
||||
modelData.secondaryActivate && modelData.secondaryActivate();
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
TooltipService.hideImmediately()
|
||||
TooltipService.hideImmediately();
|
||||
|
||||
// Close the menu if it was visible
|
||||
if (trayMenuWindow && trayMenuWindow.visible) {
|
||||
trayMenuWindow.close()
|
||||
return
|
||||
trayMenuWindow.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any opened panel
|
||||
if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) {
|
||||
PanelService.openedPanel.close()
|
||||
PanelService.openedPanel.close();
|
||||
}
|
||||
|
||||
if (modelData.hasMenu && modelData.menu && trayMenuWindow && trayMenu && trayMenu.item) {
|
||||
trayMenuWindow.open()
|
||||
trayMenuWindow.open();
|
||||
|
||||
// Position menu based on bar position
|
||||
let menuX, menuY
|
||||
let menuX, menuY;
|
||||
if (barPosition === "left") {
|
||||
// For left bar: position menu to the right of the bar
|
||||
menuX = width + Style.marginM
|
||||
menuY = 0
|
||||
menuX = width + Style.marginM;
|
||||
menuY = 0;
|
||||
} else if (barPosition === "right") {
|
||||
// For right bar: position menu to the left of the bar
|
||||
menuX = -trayMenu.item.width - Style.marginM
|
||||
menuY = 0
|
||||
menuX = -trayMenu.item.width - Style.marginM;
|
||||
menuY = 0;
|
||||
} else {
|
||||
// For horizontal bars: center horizontally and position below
|
||||
menuX = (width / 2) - (trayMenu.item.width / 2)
|
||||
menuY = (barPosition === "top") ? Style.barHeight : -Style.barHeight
|
||||
menuX = (width / 2) - (trayMenu.item.width / 2);
|
||||
menuY = (barPosition === "top") ? Style.barHeight : -Style.barHeight;
|
||||
}
|
||||
trayMenu.item.trayItem = modelData
|
||||
trayMenu.item.widgetSection = root.section
|
||||
trayMenu.item.widgetIndex = root.sectionWidgetIndex
|
||||
trayMenu.item.showAt(parent, menuX, menuY)
|
||||
trayMenu.item.trayItem = modelData;
|
||||
trayMenu.item.widgetSection = root.section;
|
||||
trayMenu.item.widgetIndex = root.sectionWidgetIndex;
|
||||
trayMenu.item.showAt(parent, menuX, menuY);
|
||||
} else {
|
||||
Logger.d("Tray", "No menu available for", modelData.id, "or trayMenu not set")
|
||||
Logger.d("Tray", "No menu available for", modelData.id, "or trayMenu not set");
|
||||
}
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
if (trayMenuWindow) {
|
||||
trayMenuWindow.close()
|
||||
trayMenuWindow.close();
|
||||
}
|
||||
TooltipService.show(Screen, trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection())
|
||||
TooltipService.show(Screen, trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection());
|
||||
}
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
@@ -403,14 +403,14 @@ Rectangle {
|
||||
icon: {
|
||||
switch (barPosition) {
|
||||
case "bottom":
|
||||
return "caret-up"
|
||||
return "caret-up";
|
||||
case "left":
|
||||
return "caret-right"
|
||||
return "caret-right";
|
||||
case "right":
|
||||
return "caret-left"
|
||||
return "caret-left";
|
||||
case "top":
|
||||
default:
|
||||
return "caret-down"
|
||||
return "caret-down";
|
||||
}
|
||||
}
|
||||
onClicked: toggleDrawer(this)
|
||||
|
||||
@@ -5,8 +5,8 @@ import Quickshell.Services.Pipewire
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Modules.Panels.Settings
|
||||
import qs.Services.UI
|
||||
import qs.Services.Media
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
@@ -23,12 +23,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -48,12 +48,12 @@ Item {
|
||||
// Logger.i("Bar:Volume", "onVolumeChanged")
|
||||
if (!firstVolumeReceived) {
|
||||
// Ignore the first volume change
|
||||
firstVolumeReceived = true
|
||||
firstVolumeReceived = true;
|
||||
} else {
|
||||
// Hide any tooltip while the pill is visible / being updated
|
||||
TooltipService.hide()
|
||||
pill.show()
|
||||
externalHideTimer.restart()
|
||||
TooltipService.hide();
|
||||
pill.show();
|
||||
externalHideTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ Item {
|
||||
running: false
|
||||
interval: 1500
|
||||
onTriggered: {
|
||||
pill.hide()
|
||||
pill.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,25 +84,25 @@ Item {
|
||||
|
||||
onWheel: function (delta) {
|
||||
// Hide tooltip as soon as the user starts scrolling to adjust volume
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
|
||||
wheelAccumulator += delta
|
||||
wheelAccumulator += delta;
|
||||
if (wheelAccumulator >= 120) {
|
||||
wheelAccumulator = 0
|
||||
AudioService.increaseVolume()
|
||||
wheelAccumulator = 0;
|
||||
AudioService.increaseVolume();
|
||||
} else if (wheelAccumulator <= -120) {
|
||||
wheelAccumulator = 0
|
||||
AudioService.decreaseVolume()
|
||||
wheelAccumulator = 0;
|
||||
AudioService.decreaseVolume();
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
PanelService.getPanel("audioPanel", screen)?.toggle(this)
|
||||
PanelService.getPanel("audioPanel", screen)?.toggle(this);
|
||||
}
|
||||
onRightClicked: {
|
||||
AudioService.setOutputMuted(!AudioService.muted)
|
||||
AudioService.setOutputMuted(!AudioService.muted);
|
||||
}
|
||||
onMiddleClicked: {
|
||||
Quickshell.execDetached(["sh", "-c", "pwvucontrol || pavucontrol"])
|
||||
Quickshell.execDetached(["sh", "-c", "pwvucontrol || pavucontrol"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ NIconButton {
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
onClicked: {
|
||||
var wallpaperPanel = PanelService.getPanel("wallpaperPanel", screen)
|
||||
var wallpaperPanel = PanelService.getPanel("wallpaperPanel", screen);
|
||||
if (Settings.data.wallpaper.panelPosition === "follow_bar") {
|
||||
wallpaperPanel?.toggle(this)
|
||||
wallpaperPanel?.toggle(this);
|
||||
} else {
|
||||
wallpaperPanel?.toggle()
|
||||
wallpaperPanel?.toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.Networking
|
||||
import qs.Services.UI
|
||||
import qs.Modules.Bar.Extras
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -19,12 +19,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -41,37 +41,37 @@ Item {
|
||||
icon: {
|
||||
try {
|
||||
if (NetworkService.ethernetConnected) {
|
||||
return NetworkService.internetConnectivity ? "ethernet" : "ethernet-off"
|
||||
return NetworkService.internetConnectivity ? "ethernet" : "ethernet-off";
|
||||
}
|
||||
let connected = false
|
||||
let signalStrength = 0
|
||||
let connected = false;
|
||||
let signalStrength = 0;
|
||||
for (const net in NetworkService.networks) {
|
||||
if (NetworkService.networks[net].connected) {
|
||||
connected = true
|
||||
signalStrength = NetworkService.networks[net].signal
|
||||
break
|
||||
connected = true;
|
||||
signalStrength = NetworkService.networks[net].signal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return connected ? NetworkService.signalIcon(signalStrength, true) : "wifi-off"
|
||||
return connected ? NetworkService.signalIcon(signalStrength, true) : "wifi-off";
|
||||
} catch (error) {
|
||||
Logger.e("Wi-Fi", "Error getting icon:", error)
|
||||
return "wifi-off"
|
||||
Logger.e("Wi-Fi", "Error getting icon:", error);
|
||||
return "wifi-off";
|
||||
}
|
||||
}
|
||||
text: {
|
||||
try {
|
||||
if (NetworkService.ethernetConnected) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
for (const net in NetworkService.networks) {
|
||||
if (NetworkService.networks[net].connected) {
|
||||
return net
|
||||
return net;
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return "";
|
||||
} catch (error) {
|
||||
Logger.e("Wi-Fi", "Error getting ssid:", error)
|
||||
return "error"
|
||||
Logger.e("Wi-Fi", "Error getting ssid:", error);
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
autoHide: false
|
||||
@@ -81,9 +81,9 @@ Item {
|
||||
onRightClicked: NetworkService.setWifiEnabled(!Settings.data.network.wifiEnabled)
|
||||
tooltipText: {
|
||||
if (pill.text !== "") {
|
||||
return pill.text
|
||||
return pill.text;
|
||||
}
|
||||
return I18n.tr("tooltips.manage-wifi")
|
||||
return I18n.tr("tooltips.manage-wifi");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
@@ -24,23 +24,23 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
|
||||
readonly property bool density: Settings.data.bar.density
|
||||
readonly property real baseDimensionRatio: {
|
||||
const b = (density === "compact") ? 0.85 : 0.65
|
||||
const b = (density === "compact") ? 0.85 : 0.65;
|
||||
if (widgetSettings.labelMode === "none") {
|
||||
return b * 0.75
|
||||
return b * 0.75;
|
||||
}
|
||||
return b
|
||||
return b;
|
||||
}
|
||||
|
||||
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
|
||||
@@ -68,76 +68,76 @@ Item {
|
||||
implicitHeight: isVertical ? computeHeight() : Style.barHeight
|
||||
|
||||
function getWorkspaceWidth(ws) {
|
||||
const d = Style.capsuleHeight * root.baseDimensionRatio
|
||||
const factor = ws.isActive ? 2.2 : 1
|
||||
const d = Style.capsuleHeight * root.baseDimensionRatio;
|
||||
const factor = ws.isActive ? 2.2 : 1;
|
||||
|
||||
// For name mode, calculate width based on actual text content
|
||||
if (labelMode === "name" && ws.name && ws.name.length > 0) {
|
||||
const displayText = ws.name.substring(0, characterCount)
|
||||
const textWidth = displayText.length * (d * 0.4) // Approximate width per character
|
||||
const padding = d * 0.6 // Padding on both sides
|
||||
return Math.max(d * factor, textWidth + padding)
|
||||
const displayText = ws.name.substring(0, characterCount);
|
||||
const textWidth = displayText.length * (d * 0.4); // Approximate width per character
|
||||
const padding = d * 0.6; // Padding on both sides
|
||||
return Math.max(d * factor, textWidth + padding);
|
||||
}
|
||||
|
||||
return d * factor
|
||||
return d * factor;
|
||||
}
|
||||
|
||||
function getWorkspaceHeight(ws) {
|
||||
const d = Style.capsuleHeight * root.baseDimensionRatio
|
||||
const factor = ws.isActive ? 2.2 : 1
|
||||
return d * factor
|
||||
const d = Style.capsuleHeight * root.baseDimensionRatio;
|
||||
const factor = ws.isActive ? 2.2 : 1;
|
||||
return d * factor;
|
||||
}
|
||||
|
||||
function computeWidth() {
|
||||
let total = 0
|
||||
let total = 0;
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
const ws = localWorkspaces.get(i)
|
||||
total += getWorkspaceWidth(ws)
|
||||
const ws = localWorkspaces.get(i);
|
||||
total += getWorkspaceWidth(ws);
|
||||
}
|
||||
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
|
||||
total += horizontalPadding * 2
|
||||
return Math.round(total)
|
||||
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills;
|
||||
total += horizontalPadding * 2;
|
||||
return Math.round(total);
|
||||
}
|
||||
|
||||
function computeHeight() {
|
||||
let total = 0
|
||||
let total = 0;
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
const ws = localWorkspaces.get(i)
|
||||
total += getWorkspaceHeight(ws)
|
||||
const ws = localWorkspaces.get(i);
|
||||
total += getWorkspaceHeight(ws);
|
||||
}
|
||||
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
|
||||
total += horizontalPadding * 2
|
||||
return Math.round(total)
|
||||
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills;
|
||||
total += horizontalPadding * 2;
|
||||
return Math.round(total);
|
||||
}
|
||||
|
||||
function getFocusedLocalIndex() {
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
if (localWorkspaces.get(i).isFocused === true)
|
||||
return i
|
||||
return i;
|
||||
}
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
function switchByOffset(offset) {
|
||||
if (localWorkspaces.count === 0)
|
||||
return
|
||||
var current = getFocusedLocalIndex()
|
||||
return;
|
||||
var current = getFocusedLocalIndex();
|
||||
if (current < 0)
|
||||
current = 0
|
||||
var next = (current + offset) % localWorkspaces.count
|
||||
current = 0;
|
||||
var next = (current + offset) % localWorkspaces.count;
|
||||
if (next < 0)
|
||||
next = localWorkspaces.count - 1
|
||||
const ws = localWorkspaces.get(next)
|
||||
next = localWorkspaces.count - 1;
|
||||
const ws = localWorkspaces.get(next);
|
||||
if (ws && ws.idx !== undefined)
|
||||
CompositorService.switchToWorkspace(ws)
|
||||
CompositorService.switchToWorkspace(ws);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
refreshWorkspaces()
|
||||
refreshWorkspaces();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
root.isDestroying = true
|
||||
root.isDestroying = true;
|
||||
}
|
||||
|
||||
onScreenChanged: refreshWorkspaces()
|
||||
@@ -146,40 +146,40 @@ Item {
|
||||
Connections {
|
||||
target: CompositorService
|
||||
function onWorkspacesChanged() {
|
||||
refreshWorkspaces()
|
||||
refreshWorkspaces();
|
||||
}
|
||||
}
|
||||
|
||||
function refreshWorkspaces() {
|
||||
localWorkspaces.clear()
|
||||
localWorkspaces.clear();
|
||||
if (screen !== null) {
|
||||
for (var i = 0; i < CompositorService.workspaces.count; i++) {
|
||||
const ws = CompositorService.workspaces.get(i)
|
||||
const ws = CompositorService.workspaces.get(i);
|
||||
if (ws.output.toLowerCase() === screen.name.toLowerCase()) {
|
||||
if (hideUnoccupied && !ws.isOccupied && !ws.isFocused) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
localWorkspaces.append(ws)
|
||||
localWorkspaces.append(ws);
|
||||
}
|
||||
}
|
||||
}
|
||||
workspaceRepeaterHorizontal.model = localWorkspaces
|
||||
workspaceRepeaterVertical.model = localWorkspaces
|
||||
updateWorkspaceFocus()
|
||||
workspaceRepeaterHorizontal.model = localWorkspaces;
|
||||
workspaceRepeaterVertical.model = localWorkspaces;
|
||||
updateWorkspaceFocus();
|
||||
}
|
||||
|
||||
function triggerUnifiedWave() {
|
||||
effectColor = Color.mPrimary
|
||||
masterAnimation.restart()
|
||||
effectColor = Color.mPrimary;
|
||||
masterAnimation.restart();
|
||||
}
|
||||
|
||||
function updateWorkspaceFocus() {
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
const ws = localWorkspaces.get(i)
|
||||
const ws = localWorkspaces.get(i);
|
||||
if (ws.isFocused === true) {
|
||||
root.triggerUnifiedWave()
|
||||
root.workspaceChanged(ws.id, Color.mPrimary)
|
||||
break
|
||||
root.triggerUnifiedWave();
|
||||
root.workspaceChanged(ws.id, Color.mPrimary);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,8 +228,8 @@ Item {
|
||||
interval: 150
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.wheelCooldown = false
|
||||
root.wheelAccumulatedDelta = 0
|
||||
root.wheelCooldown = false;
|
||||
root.wheelAccumulatedDelta = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,24 +240,24 @@ Item {
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
onWheel: function (event) {
|
||||
if (root.wheelCooldown)
|
||||
return
|
||||
return;
|
||||
// Prefer vertical delta, fall back to horizontal if needed
|
||||
var dy = event.angleDelta.y
|
||||
var dx = event.angleDelta.x
|
||||
var useDy = Math.abs(dy) >= Math.abs(dx)
|
||||
var delta = useDy ? dy : dx
|
||||
var dy = event.angleDelta.y;
|
||||
var dx = event.angleDelta.x;
|
||||
var useDy = Math.abs(dy) >= Math.abs(dx);
|
||||
var delta = useDy ? dy : dx;
|
||||
// One notch is typically 120
|
||||
root.wheelAccumulatedDelta += delta
|
||||
var step = 120
|
||||
root.wheelAccumulatedDelta += delta;
|
||||
var step = 120;
|
||||
if (Math.abs(root.wheelAccumulatedDelta) >= step) {
|
||||
var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1
|
||||
var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1;
|
||||
// For vertical layout, natural mapping: wheel up -> previous, down -> next (already handled by sign)
|
||||
// For horizontal layout, same mapping using vertical wheel
|
||||
root.switchByOffset(direction)
|
||||
root.wheelCooldown = true
|
||||
wheelDebounce.restart()
|
||||
root.wheelAccumulatedDelta = 0
|
||||
event.accepted = true
|
||||
root.switchByOffset(direction);
|
||||
root.wheelCooldown = true;
|
||||
wheelDebounce.restart();
|
||||
root.wheelAccumulatedDelta = 0;
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,9 +290,9 @@ Item {
|
||||
y: (pill.height - height) / 2 + (height - contentHeight) / 2
|
||||
text: {
|
||||
if (labelMode === "name" && model.name && model.name.length > 0) {
|
||||
return model.name.substring(0, characterCount)
|
||||
return model.name.substring(0, characterCount);
|
||||
} else {
|
||||
return model.idx.toString()
|
||||
return model.idx.toString();
|
||||
}
|
||||
}
|
||||
family: Settings.data.ui.fontFixed
|
||||
@@ -303,13 +303,13 @@ Item {
|
||||
wrapMode: Text.Wrap
|
||||
color: {
|
||||
if (model.isFocused)
|
||||
return Color.mOnPrimary
|
||||
return Color.mOnPrimary;
|
||||
if (model.isUrgent)
|
||||
return Color.mOnError
|
||||
return Color.mOnError;
|
||||
if (model.isOccupied)
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnSecondary;
|
||||
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnSecondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,13 +318,13 @@ Item {
|
||||
radius: width * 0.5
|
||||
color: {
|
||||
if (model.isFocused)
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
if (model.isUrgent)
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
if (model.isOccupied)
|
||||
return Color.mSecondary
|
||||
return Color.mSecondary;
|
||||
|
||||
return Qt.alpha(Color.mSecondary, 0.3)
|
||||
return Qt.alpha(Color.mSecondary, 0.3);
|
||||
}
|
||||
scale: model.isActive ? 1.0 : 0.9
|
||||
z: 0
|
||||
@@ -334,7 +334,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
CompositorService.switchToWorkspace(model)
|
||||
CompositorService.switchToWorkspace(model);
|
||||
}
|
||||
hoverEnabled: true
|
||||
}
|
||||
@@ -435,9 +435,9 @@ Item {
|
||||
y: (pillVertical.height - height) / 2 + (height - contentHeight) / 2
|
||||
text: {
|
||||
if (labelMode === "name" && model.name && model.name.length > 0) {
|
||||
return model.name.substring(0, characterCount)
|
||||
return model.name.substring(0, characterCount);
|
||||
} else {
|
||||
return model.idx.toString()
|
||||
return model.idx.toString();
|
||||
}
|
||||
}
|
||||
family: Settings.data.ui.fontFixed
|
||||
@@ -448,13 +448,13 @@ Item {
|
||||
wrapMode: Text.Wrap
|
||||
color: {
|
||||
if (model.isFocused)
|
||||
return Color.mOnPrimary
|
||||
return Color.mOnPrimary;
|
||||
if (model.isUrgent)
|
||||
return Color.mOnError
|
||||
return Color.mOnError;
|
||||
if (model.isOccupied)
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnSecondary;
|
||||
|
||||
return Color.mOnSurface
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -463,13 +463,13 @@ Item {
|
||||
radius: width * 0.5
|
||||
color: {
|
||||
if (model.isFocused)
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
if (model.isUrgent)
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
if (model.isOccupied)
|
||||
return Color.mSecondary
|
||||
return Color.mSecondary;
|
||||
|
||||
return Color.mOutline
|
||||
return Color.mOutline;
|
||||
}
|
||||
scale: model.isActive ? 1.0 : 0.9
|
||||
z: 0
|
||||
@@ -479,7 +479,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
CompositorService.switchToWorkspace(model)
|
||||
CompositorService.switchToWorkspace(model);
|
||||
}
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
@@ -26,7 +26,7 @@ Loader {
|
||||
target: BarService
|
||||
function onBarReadyChanged(screenName) {
|
||||
if (screenName === modelData.name) {
|
||||
barIsReady = true
|
||||
barIsReady = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ Loader {
|
||||
Connections {
|
||||
target: ToplevelManager ? ToplevelManager.toplevels : null
|
||||
function onValuesChanged() {
|
||||
updateDockApps()
|
||||
updateDockApps();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,17 +43,17 @@ Loader {
|
||||
Connections {
|
||||
target: Settings.data.dock
|
||||
function onPinnedAppsChanged() {
|
||||
updateDockApps()
|
||||
updateDockApps();
|
||||
}
|
||||
function onOnlySameOutputChanged() {
|
||||
updateDockApps()
|
||||
updateDockApps();
|
||||
}
|
||||
}
|
||||
|
||||
// Initial update when component is ready
|
||||
Component.onCompleted: {
|
||||
if (ToplevelManager) {
|
||||
updateDockApps()
|
||||
updateDockApps();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,33 +93,33 @@ Loader {
|
||||
// Function to close any open context menu
|
||||
function closeAllContextMenus() {
|
||||
if (currentContextMenu && currentContextMenu.visible) {
|
||||
currentContextMenu.hide()
|
||||
currentContextMenu.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update the combined dock apps model
|
||||
function updateDockApps() {
|
||||
const runningApps = ToplevelManager ? (ToplevelManager.toplevels.values || []) : []
|
||||
const pinnedApps = Settings.data.dock.pinnedApps || []
|
||||
const combined = []
|
||||
const processedAppIds = new Set()
|
||||
const runningApps = ToplevelManager ? (ToplevelManager.toplevels.values || []) : [];
|
||||
const pinnedApps = Settings.data.dock.pinnedApps || [];
|
||||
const combined = [];
|
||||
const processedAppIds = new Set();
|
||||
|
||||
// Strategy: Maintain app positions as much as possible
|
||||
// 1. First pass: Add all running apps (both pinned and non-pinned) in their current order
|
||||
runningApps.forEach(toplevel => {
|
||||
if (toplevel && toplevel.appId && !(Settings.data.dock.onlySameOutput && toplevel.screens && !toplevel.screens.includes(modelData))) {
|
||||
const isPinned = pinnedApps.includes(toplevel.appId)
|
||||
const appType = isPinned ? "pinned-running" : "running"
|
||||
const isPinned = pinnedApps.includes(toplevel.appId);
|
||||
const appType = isPinned ? "pinned-running" : "running";
|
||||
|
||||
combined.push({
|
||||
"type": appType,
|
||||
"toplevel": toplevel,
|
||||
"appId": toplevel.appId,
|
||||
"title": toplevel.title
|
||||
})
|
||||
processedAppIds.add(toplevel.appId)
|
||||
});
|
||||
processedAppIds.add(toplevel.appId);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 2. Second pass: Add non-running pinned apps at the end
|
||||
pinnedApps.forEach(pinnedAppId => {
|
||||
@@ -130,11 +130,11 @@ Loader {
|
||||
"toplevel": null,
|
||||
"appId": pinnedAppId,
|
||||
"title": pinnedAppId
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
dockApps = combined
|
||||
dockApps = combined;
|
||||
}
|
||||
|
||||
// Timer to unload dock after hide animation completes
|
||||
@@ -143,7 +143,7 @@ Loader {
|
||||
interval: hideAnimationDuration + 50 // Add small buffer
|
||||
onTriggered: {
|
||||
if (hidden && autoHide) {
|
||||
dockLoaded = false
|
||||
dockLoaded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,14 +155,14 @@ Loader {
|
||||
onTriggered: {
|
||||
// Force menuHovered to false if no menu is current or visible
|
||||
if (!root.currentContextMenu || !root.currentContextMenu.visible) {
|
||||
menuHovered = false
|
||||
menuHovered = false;
|
||||
}
|
||||
if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) {
|
||||
hidden = true
|
||||
unloadTimer.restart() // Start unload timer when hiding
|
||||
hidden = true;
|
||||
unloadTimer.restart(); // Start unload timer when hiding
|
||||
} else if (autoHide && !dockHovered && !peekHovered) {
|
||||
// Restart timer if menu is closing (handles race condition)
|
||||
restart()
|
||||
restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,9 +173,9 @@ Loader {
|
||||
interval: showDelay
|
||||
onTriggered: {
|
||||
if (autoHide) {
|
||||
dockLoaded = true // Load dock immediately
|
||||
hidden = false // Then trigger show animation
|
||||
unloadTimer.stop() // Cancel any pending unload
|
||||
dockLoaded = true; // Load dock immediately
|
||||
hidden = false; // Then trigger show animation
|
||||
unloadTimer.stop(); // Cancel any pending unload
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,14 +183,14 @@ Loader {
|
||||
// Watch for autoHide setting changes
|
||||
onAutoHideChanged: {
|
||||
if (!autoHide) {
|
||||
hidden = false
|
||||
dockLoaded = true
|
||||
hideTimer.stop()
|
||||
showTimer.stop()
|
||||
unloadTimer.stop()
|
||||
hidden = false;
|
||||
dockLoaded = true;
|
||||
hideTimer.stop();
|
||||
showTimer.stop();
|
||||
unloadTimer.stop();
|
||||
} else {
|
||||
hidden = true
|
||||
unloadTimer.restart() // Schedule unload after animation
|
||||
hidden = true;
|
||||
unloadTimer.restart(); // Schedule unload after animation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,16 +218,16 @@ Loader {
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: {
|
||||
peekHovered = true
|
||||
peekHovered = true;
|
||||
if (hidden) {
|
||||
showTimer.start()
|
||||
showTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
peekHovered = false
|
||||
peekHovered = false;
|
||||
if (!hidden && !dockHovered && !anyAppHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
hideTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,9 +260,9 @@ Loader {
|
||||
margins.bottom: {
|
||||
switch (Settings.data.bar.position) {
|
||||
case "bottom":
|
||||
return (Style.barHeight + Style.marginM) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL + floatingMargin : floatingMargin)
|
||||
return (Style.barHeight + Style.marginM) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL + floatingMargin : floatingMargin);
|
||||
default:
|
||||
return floatingMargin
|
||||
return floatingMargin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,24 +313,24 @@ Loader {
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: {
|
||||
dockHovered = true
|
||||
dockHovered = true;
|
||||
if (autoHide) {
|
||||
showTimer.stop()
|
||||
hideTimer.stop()
|
||||
unloadTimer.stop() // Cancel unload if hovering
|
||||
showTimer.stop();
|
||||
hideTimer.stop();
|
||||
unloadTimer.stop(); // Cancel unload if hovering
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
dockHovered = false
|
||||
dockHovered = false;
|
||||
if (autoHide && !anyAppHovered && !peekHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
hideTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
// Close any open context menu when clicking on the dock background
|
||||
closeAllContextMenus()
|
||||
closeAllContextMenus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,8 +342,8 @@ Loader {
|
||||
|
||||
function getAppIcon(appData): string {
|
||||
if (!appData || !appData.appId)
|
||||
return ""
|
||||
return ThemeIcons.iconForAppId(appData.appId?.toLowerCase())
|
||||
return "";
|
||||
return ThemeIcons.iconForAppId(appData.appId?.toLowerCase());
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -371,7 +371,7 @@ Loader {
|
||||
Connections {
|
||||
target: modelData?.toplevel
|
||||
function onClosed() {
|
||||
Qt.callLater(root.updateDockApps)
|
||||
Qt.callLater(root.updateDockApps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,9 +452,9 @@ Loader {
|
||||
onHoveredChanged: {
|
||||
// Only update menuHovered if this menu is current and visible
|
||||
if (root.currentContextMenu === contextMenu && contextMenu.visible) {
|
||||
menuHovered = hovered
|
||||
menuHovered = hovered;
|
||||
} else {
|
||||
menuHovered = false
|
||||
menuHovered = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,26 +462,26 @@ Loader {
|
||||
target: contextMenu
|
||||
function onRequestClose() {
|
||||
// Clear current menu immediately to prevent hover updates
|
||||
root.currentContextMenu = null
|
||||
hideTimer.stop()
|
||||
contextMenu.hide()
|
||||
menuHovered = false
|
||||
anyAppHovered = false
|
||||
root.currentContextMenu = null;
|
||||
hideTimer.stop();
|
||||
contextMenu.hide();
|
||||
menuHovered = false;
|
||||
anyAppHovered = false;
|
||||
}
|
||||
}
|
||||
onAppClosed: root.updateDockApps // Force immediate dock update when app is closed
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
root.currentContextMenu = contextMenu
|
||||
anyAppHovered = false
|
||||
root.currentContextMenu = contextMenu;
|
||||
anyAppHovered = false;
|
||||
} else if (root.currentContextMenu === contextMenu) {
|
||||
root.currentContextMenu = null
|
||||
hideTimer.stop()
|
||||
menuHovered = false
|
||||
anyAppHovered = false
|
||||
root.currentContextMenu = null;
|
||||
hideTimer.stop();
|
||||
menuHovered = false;
|
||||
anyAppHovered = false;
|
||||
// Restart hide timer after menu closes
|
||||
if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
hideTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,28 +496,28 @@ Loader {
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||
|
||||
onEntered: {
|
||||
anyAppHovered = true
|
||||
const appName = appButton.appTitle || appButton.appId || "Unknown"
|
||||
const tooltipText = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
|
||||
anyAppHovered = true;
|
||||
const appName = appButton.appTitle || appButton.appId || "Unknown";
|
||||
const tooltipText = appName.length > 40 ? appName.substring(0, 37) + "..." : appName;
|
||||
if (!contextMenu.visible) {
|
||||
TooltipService.show(Screen, appButton, tooltipText, "top")
|
||||
TooltipService.show(Screen, appButton, tooltipText, "top");
|
||||
}
|
||||
if (autoHide) {
|
||||
showTimer.stop()
|
||||
hideTimer.stop()
|
||||
unloadTimer.stop() // Cancel unload if hovering app
|
||||
showTimer.stop();
|
||||
hideTimer.stop();
|
||||
unloadTimer.stop(); // Cancel unload if hovering app
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
anyAppHovered = false
|
||||
TooltipService.hide()
|
||||
anyAppHovered = false;
|
||||
TooltipService.hide();
|
||||
// Clear menuHovered if no current menu or menu not visible
|
||||
if (!root.currentContextMenu || !root.currentContextMenu.visible) {
|
||||
menuHovered = false
|
||||
menuHovered = false;
|
||||
}
|
||||
if (autoHide && !dockHovered && !peekHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
hideTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,33 +525,33 @@ Loader {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
// If right-clicking on the same app with an open context menu, close it
|
||||
if (root.currentContextMenu === contextMenu && contextMenu.visible) {
|
||||
root.closeAllContextMenus()
|
||||
return
|
||||
root.closeAllContextMenus();
|
||||
return;
|
||||
}
|
||||
// Close any other existing context menu first
|
||||
root.closeAllContextMenus()
|
||||
root.closeAllContextMenus();
|
||||
// Hide tooltip when showing context menu
|
||||
TooltipService.hideImmediately()
|
||||
contextMenu.show(appButton, modelData.toplevel || modelData)
|
||||
return
|
||||
TooltipService.hideImmediately();
|
||||
contextMenu.show(appButton, modelData.toplevel || modelData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any existing context menu for non-right-click actions
|
||||
root.closeAllContextMenus()
|
||||
root.closeAllContextMenus();
|
||||
|
||||
// Check if toplevel is still valid (not a stale reference)
|
||||
const isValidToplevel = modelData?.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(modelData.toplevel)
|
||||
const isValidToplevel = modelData?.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(modelData.toplevel);
|
||||
|
||||
if (mouse.button === Qt.MiddleButton && isValidToplevel && modelData.toplevel.close) {
|
||||
modelData.toplevel.close()
|
||||
Qt.callLater(root.updateDockApps) // Force immediate dock update
|
||||
modelData.toplevel.close();
|
||||
Qt.callLater(root.updateDockApps); // Force immediate dock update
|
||||
} else if (mouse.button === Qt.LeftButton) {
|
||||
if (isValidToplevel && modelData.toplevel.activate) {
|
||||
// Running app - activate it
|
||||
modelData.toplevel.activate()
|
||||
modelData.toplevel.activate();
|
||||
} else if (modelData?.appId) {
|
||||
// Pinned app not running - launch it
|
||||
Quickshell.execDetached(["gtk-launch", modelData.appId])
|
||||
Quickshell.execDetached(["gtk-launch", modelData.appId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,21 +31,21 @@ PopupWindow {
|
||||
|
||||
function initItems() {
|
||||
// Is this a running app?
|
||||
const isRunning = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel)
|
||||
const isRunning = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel);
|
||||
|
||||
// Is this a pinned app?
|
||||
const isPinned = root.toplevel && root.isAppPinned(root.toplevel.appId)
|
||||
const isPinned = root.toplevel && root.isAppPinned(root.toplevel.appId);
|
||||
|
||||
var next = []
|
||||
var next = [];
|
||||
if (isRunning) {
|
||||
// Focus item
|
||||
next.push({
|
||||
"icon": "eye",
|
||||
"text": I18n.tr("dock.menu.focus"),
|
||||
"action": function () {
|
||||
handleFocus()
|
||||
handleFocus();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Pin/Unpin item
|
||||
@@ -53,9 +53,9 @@ PopupWindow {
|
||||
"icon": !isPinned ? "pin" : "unpin",
|
||||
"text": !isPinned ? I18n.tr("dock.menu.pin") : I18n.tr("dock.menu.unpin"),
|
||||
"action": function () {
|
||||
handlePin()
|
||||
handlePin();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (isRunning) {
|
||||
// Close item
|
||||
@@ -63,55 +63,54 @@ PopupWindow {
|
||||
"icon": "close",
|
||||
"text": I18n.tr("dock.menu.close"),
|
||||
"action": function () {
|
||||
handleClose()
|
||||
handleClose();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Create a menu entry for each app-specific action definied in its .desktop file
|
||||
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) {
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId)
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId);
|
||||
if (entry != null) {
|
||||
entry.actions.forEach(function (action) {
|
||||
next.push({
|
||||
"icon": "",
|
||||
"text": action.name,
|
||||
"action": function () {
|
||||
action.execute()
|
||||
action.execute();
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
root.items = next
|
||||
root.items = next;
|
||||
}
|
||||
|
||||
// Helper functions for pin/unpin functionality
|
||||
function isAppPinned(appId) {
|
||||
if (!appId)
|
||||
return false
|
||||
const pinnedApps = Settings.data.dock.pinnedApps || []
|
||||
return pinnedApps.includes(appId)
|
||||
return false;
|
||||
const pinnedApps = Settings.data.dock.pinnedApps || [];
|
||||
return pinnedApps.includes(appId);
|
||||
}
|
||||
|
||||
function toggleAppPin(appId) {
|
||||
if (!appId)
|
||||
return
|
||||
|
||||
let pinnedApps = (Settings.data.dock.pinnedApps || []).slice() // Create a copy
|
||||
const isPinned = pinnedApps.includes(appId)
|
||||
return;
|
||||
let pinnedApps = (Settings.data.dock.pinnedApps || []).slice(); // Create a copy
|
||||
const isPinned = pinnedApps.includes(appId);
|
||||
|
||||
if (isPinned) {
|
||||
// Unpin: remove from array
|
||||
pinnedApps = pinnedApps.filter(id => id !== appId)
|
||||
pinnedApps = pinnedApps.filter(id => id !== appId);
|
||||
} else {
|
||||
// Pin: add to array
|
||||
pinnedApps.push(appId)
|
||||
pinnedApps.push(appId);
|
||||
}
|
||||
|
||||
// Update the settings
|
||||
Settings.data.dock.pinnedApps = pinnedApps
|
||||
Settings.data.dock.pinnedApps = pinnedApps;
|
||||
}
|
||||
|
||||
anchor.item: anchorItem
|
||||
@@ -120,62 +119,62 @@ PopupWindow {
|
||||
|
||||
function show(item, toplevelData) {
|
||||
if (!item) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
anchorItem = item
|
||||
toplevel = toplevelData
|
||||
initItems()
|
||||
visible = true
|
||||
canAutoClose = false
|
||||
gracePeriodTimer.restart()
|
||||
anchorItem = item;
|
||||
toplevel = toplevelData;
|
||||
initItems();
|
||||
visible = true;
|
||||
canAutoClose = false;
|
||||
gracePeriodTimer.restart();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
visible = false
|
||||
root.items.length = 0
|
||||
visible = false;
|
||||
root.items.length = 0;
|
||||
}
|
||||
|
||||
// Helper function to determine which menu item is under the mouse
|
||||
function getHoveredItem(mouseY) {
|
||||
const itemHeight = 32
|
||||
const startY = Style.marginM
|
||||
const relativeY = mouseY - startY
|
||||
const itemHeight = 32;
|
||||
const startY = Style.marginM;
|
||||
const relativeY = mouseY - startY;
|
||||
|
||||
if (relativeY < 0)
|
||||
return -1
|
||||
return -1;
|
||||
|
||||
const itemIndex = Math.floor(relativeY / itemHeight)
|
||||
return itemIndex >= 0 && itemIndex < root.items.length ? itemIndex : -1
|
||||
const itemIndex = Math.floor(relativeY / itemHeight);
|
||||
return itemIndex >= 0 && itemIndex < root.items.length ? itemIndex : -1;
|
||||
}
|
||||
|
||||
function handleFocus() {
|
||||
if (root.toplevel?.activate) {
|
||||
root.toplevel.activate()
|
||||
root.toplevel.activate();
|
||||
}
|
||||
root.requestClose()
|
||||
root.requestClose();
|
||||
}
|
||||
|
||||
function handlePin() {
|
||||
if (root.toplevel?.appId) {
|
||||
root.toggleAppPin(root.toplevel.appId)
|
||||
root.toggleAppPin(root.toplevel.appId);
|
||||
}
|
||||
root.requestClose()
|
||||
root.requestClose();
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
// Check if toplevel is still valid before trying to close it
|
||||
const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel)
|
||||
const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel);
|
||||
|
||||
if (isValidToplevel && root.toplevel.close) {
|
||||
root.toplevel.close()
|
||||
root.toplevel.close();
|
||||
// Trigger immediate dock update callback if provided
|
||||
if (root.onAppClosed && typeof root.onAppClosed === "function") {
|
||||
Qt.callLater(root.onAppClosed)
|
||||
Qt.callLater(root.onAppClosed);
|
||||
}
|
||||
}
|
||||
root.hide()
|
||||
root.requestClose()
|
||||
root.hide();
|
||||
root.requestClose();
|
||||
}
|
||||
|
||||
// Short delay to ignore spurious events
|
||||
@@ -184,9 +183,9 @@ PopupWindow {
|
||||
interval: 1500
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.canAutoClose = true
|
||||
root.canAutoClose = true;
|
||||
if (!menuMouseArea.containsMouse) {
|
||||
closeTimer.start()
|
||||
closeTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +196,7 @@ PopupWindow {
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: {
|
||||
root.hide()
|
||||
root.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,25 +215,25 @@ PopupWindow {
|
||||
cursorShape: root.hoveredItem >= 0 ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
|
||||
onEntered: {
|
||||
closeTimer.stop()
|
||||
closeTimer.stop();
|
||||
}
|
||||
|
||||
onExited: {
|
||||
root.hoveredItem = -1
|
||||
root.hoveredItem = -1;
|
||||
if (root.canAutoClose) {
|
||||
// Only close if grace period has passed
|
||||
closeTimer.start()
|
||||
closeTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
root.hoveredItem = root.getHoveredItem(mouse.y)
|
||||
root.hoveredItem = root.getHoveredItem(mouse.y);
|
||||
}
|
||||
|
||||
onClicked: mouse => {
|
||||
const clickedItem = root.getHoveredItem(mouse.y)
|
||||
const clickedItem = root.getHoveredItem(mouse.y);
|
||||
if (clickedItem >= 0) {
|
||||
root.items[clickedItem].action.call()
|
||||
root.items[clickedItem].action.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,24 +18,24 @@ Scope {
|
||||
|
||||
onCurrentTextChanged: {
|
||||
if (currentText !== "") {
|
||||
showFailure = false
|
||||
errorMessage = ""
|
||||
showFailure = false;
|
||||
errorMessage = "";
|
||||
}
|
||||
}
|
||||
|
||||
function tryUnlock() {
|
||||
if (!pamAvailable) {
|
||||
errorMessage = "PAM not available"
|
||||
showFailure = true
|
||||
return
|
||||
errorMessage = "PAM not available";
|
||||
showFailure = true;
|
||||
return;
|
||||
}
|
||||
|
||||
root.unlockInProgress = true
|
||||
errorMessage = ""
|
||||
showFailure = false
|
||||
root.unlockInProgress = true;
|
||||
errorMessage = "";
|
||||
showFailure = false;
|
||||
|
||||
Logger.i("LockContext", "Starting PAM authentication for user:", pam.user)
|
||||
pam.start()
|
||||
Logger.i("LockContext", "Starting PAM authentication for user:", pam.user);
|
||||
pam.start();
|
||||
}
|
||||
|
||||
PamContext {
|
||||
@@ -44,48 +44,48 @@ Scope {
|
||||
user: HostService.username
|
||||
|
||||
onPamMessage: {
|
||||
Logger.i("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired)
|
||||
Logger.i("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired);
|
||||
|
||||
if (messageIsError) {
|
||||
errorMessage = message
|
||||
errorMessage = message;
|
||||
} else {
|
||||
infoMessage = message
|
||||
infoMessage = message;
|
||||
}
|
||||
|
||||
if (responseRequired) {
|
||||
Logger.i("LockContext", "Responding to PAM with password")
|
||||
respond(root.currentText)
|
||||
Logger.i("LockContext", "Responding to PAM with password");
|
||||
respond(root.currentText);
|
||||
}
|
||||
}
|
||||
|
||||
onResponseRequiredChanged: {
|
||||
Logger.i("LockContext", "Response required changed:", responseRequired)
|
||||
Logger.i("LockContext", "Response required changed:", responseRequired);
|
||||
if (responseRequired && root.unlockInProgress) {
|
||||
Logger.i("LockContext", "Automatically responding to PAM")
|
||||
respond(root.currentText)
|
||||
Logger.i("LockContext", "Automatically responding to PAM");
|
||||
respond(root.currentText);
|
||||
}
|
||||
}
|
||||
|
||||
onCompleted: result => {
|
||||
Logger.i("LockContext", "PAM completed with result:", result)
|
||||
Logger.i("LockContext", "PAM completed with result:", result);
|
||||
if (result === PamResult.Success) {
|
||||
Logger.i("LockContext", "Authentication successful")
|
||||
root.unlocked()
|
||||
Logger.i("LockContext", "Authentication successful");
|
||||
root.unlocked();
|
||||
} else {
|
||||
Logger.i("LockContext", "Authentication failed")
|
||||
errorMessage = "Authentication failed"
|
||||
showFailure = true
|
||||
root.failed()
|
||||
Logger.i("LockContext", "Authentication failed");
|
||||
errorMessage = "Authentication failed";
|
||||
showFailure = true;
|
||||
root.failed();
|
||||
}
|
||||
root.unlockInProgress = false
|
||||
root.unlockInProgress = false;
|
||||
}
|
||||
|
||||
onError: {
|
||||
Logger.i("LockContext", "PAM error:", error, "message:", message)
|
||||
errorMessage = message || "Authentication error"
|
||||
showFailure = true
|
||||
root.unlockInProgress = false
|
||||
root.failed()
|
||||
Logger.i("LockContext", "PAM error:", error, "message:", message);
|
||||
errorMessage = message || "Authentication error";
|
||||
showFailure = true;
|
||||
root.unlockInProgress = false;
|
||||
root.failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Pam
|
||||
import Quickshell.Services.UPower
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Keyboard
|
||||
import qs.Services.Location
|
||||
import qs.Services.Media
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Widgets.AudioSpectrum
|
||||
|
||||
@@ -31,14 +31,14 @@ Loader {
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
lockScreen.active = false
|
||||
lockScreen.active = false;
|
||||
// Reset the deprecation flag when unlocking
|
||||
lockScreen.triggeredViaDeprecatedCall = false
|
||||
lockScreen.triggeredViaDeprecatedCall = false;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleUnloadAfterUnlock() {
|
||||
unloadAfterUnlockTimer.start()
|
||||
unloadAfterUnlockTimer.start();
|
||||
}
|
||||
|
||||
sourceComponent: Component {
|
||||
@@ -48,12 +48,12 @@ Loader {
|
||||
LockContext {
|
||||
id: lockContext
|
||||
onUnlocked: {
|
||||
lockSession.locked = false
|
||||
lockScreen.scheduleUnloadAfterUnlock()
|
||||
lockContext.currentText = ""
|
||||
lockSession.locked = false;
|
||||
lockScreen.scheduleUnloadAfterUnlock();
|
||||
lockContext.currentText = "";
|
||||
}
|
||||
onFailed: {
|
||||
lockContext.currentText = ""
|
||||
lockContext.currentText = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,21 +130,20 @@ Loader {
|
||||
smooth: false
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const ctx = getContext("2d");
|
||||
if (!ctx)
|
||||
return
|
||||
return;
|
||||
ctx.reset();
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.fillStyle = parent.cornerColor;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.fillStyle = parent.cornerColor
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
ctx.globalCompositeOperation = "destination-out"
|
||||
ctx.fillStyle = "#ffffff"
|
||||
ctx.beginPath()
|
||||
ctx.arc(width, height, parent.cornerRadius, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
ctx.globalCompositeOperation = "destination-out";
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.beginPath();
|
||||
ctx.arc(width, height, parent.cornerRadius, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
@@ -164,21 +163,20 @@ Loader {
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const ctx = getContext("2d");
|
||||
if (!ctx)
|
||||
return
|
||||
return;
|
||||
ctx.reset();
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.fillStyle = parent.cornerColor;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.fillStyle = parent.cornerColor
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
ctx.globalCompositeOperation = "destination-out"
|
||||
ctx.fillStyle = "#ffffff"
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, height, parent.cornerRadius, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
ctx.globalCompositeOperation = "destination-out";
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, height, parent.cornerRadius, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
@@ -198,21 +196,20 @@ Loader {
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const ctx = getContext("2d");
|
||||
if (!ctx)
|
||||
return
|
||||
return;
|
||||
ctx.reset();
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.fillStyle = parent.cornerColor;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.fillStyle = parent.cornerColor
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
ctx.globalCompositeOperation = "destination-out"
|
||||
ctx.fillStyle = "#ffffff"
|
||||
ctx.beginPath()
|
||||
ctx.arc(width, 0, parent.cornerRadius, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
ctx.globalCompositeOperation = "destination-out";
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.beginPath();
|
||||
ctx.arc(width, 0, parent.cornerRadius, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
@@ -232,21 +229,20 @@ Loader {
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const ctx = getContext("2d");
|
||||
if (!ctx)
|
||||
return
|
||||
return;
|
||||
ctx.reset();
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.fillStyle = parent.cornerColor;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.fillStyle = parent.cornerColor
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
ctx.globalCompositeOperation = "destination-out"
|
||||
ctx.fillStyle = "#ffffff"
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, 0, parent.cornerRadius, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
ctx.globalCompositeOperation = "destination-out";
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, parent.cornerRadius, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
@@ -347,7 +343,7 @@ Loader {
|
||||
// Date below
|
||||
NText {
|
||||
text: {
|
||||
var lang = I18n.locale.name.split("_")[0]
|
||||
var lang = I18n.locale.name.split("_")[0];
|
||||
var formats = {
|
||||
"de": "dddd, d. MMMM",
|
||||
"es": "dddd, d 'de' MMMM",
|
||||
@@ -356,8 +352,8 @@ Loader {
|
||||
"zh": "yyyy年M月d日 dddd",
|
||||
"uk": "dddd, d MMMM",
|
||||
"tr": "dddd, d MMMM"
|
||||
}
|
||||
return I18n.locale.toString(Time.now, formats[lang] || "dddd, MMMM d")
|
||||
};
|
||||
return I18n.locale.toString(Time.now, formats[lang] || "dddd, MMMM d");
|
||||
}
|
||||
pointSize: Style.fontSizeXL
|
||||
font.weight: Font.Medium
|
||||
@@ -487,15 +483,15 @@ Loader {
|
||||
// Compact status indicators container (compact mode only)
|
||||
Rectangle {
|
||||
width: {
|
||||
var hasBattery = UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent
|
||||
var hasKeyboard = keyboardLayout.currentLayout !== "Unknown"
|
||||
var hasBattery = UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent;
|
||||
var hasKeyboard = keyboardLayout.currentLayout !== "Unknown";
|
||||
|
||||
if (hasBattery && hasKeyboard) {
|
||||
return 200
|
||||
return 200;
|
||||
} else if (hasBattery || hasKeyboard) {
|
||||
return 120
|
||||
return 120;
|
||||
} else {
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
height: 40
|
||||
@@ -708,14 +704,14 @@ Loader {
|
||||
|
||||
NText {
|
||||
text: {
|
||||
var temp = LocationService.data.weather.current_weather.temperature
|
||||
var suffix = "C"
|
||||
var temp = LocationService.data.weather.current_weather.temperature;
|
||||
var suffix = "C";
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
temp = LocationService.celsiusToFahrenheit(temp)
|
||||
suffix = "F"
|
||||
temp = LocationService.celsiusToFahrenheit(temp);
|
||||
suffix = "F";
|
||||
}
|
||||
temp = Math.round(temp)
|
||||
return temp + "°" + suffix
|
||||
temp = Math.round(temp);
|
||||
return temp + "°" + suffix;
|
||||
}
|
||||
pointSize: Style.fontSizeXL
|
||||
font.weight: Style.fontWeightBold
|
||||
@@ -724,14 +720,14 @@ Loader {
|
||||
|
||||
NText {
|
||||
text: {
|
||||
var wind = LocationService.data.weather.current_weather.windspeed
|
||||
var unit = "km/h"
|
||||
var wind = LocationService.data.weather.current_weather.windspeed;
|
||||
var unit = "km/h";
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
wind = wind * 0.621371 // Convert km/h to mph
|
||||
unit = "mph"
|
||||
wind = wind * 0.621371; // Convert km/h to mph
|
||||
unit = "mph";
|
||||
}
|
||||
wind = Math.round(wind)
|
||||
return wind + " " + unit
|
||||
wind = Math.round(wind);
|
||||
return wind + " " + unit;
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
@@ -773,8 +769,8 @@ Loader {
|
||||
|
||||
NText {
|
||||
text: {
|
||||
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"))
|
||||
return I18n.locale.toString(weatherDate, "ddd")
|
||||
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"));
|
||||
return I18n.locale.toString(weatherDate, "ddd");
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
@@ -791,15 +787,15 @@ Loader {
|
||||
|
||||
NText {
|
||||
text: {
|
||||
var max = LocationService.data.weather.daily.temperature_2m_max[index]
|
||||
var min = LocationService.data.weather.daily.temperature_2m_min[index]
|
||||
var max = LocationService.data.weather.daily.temperature_2m_max[index];
|
||||
var min = LocationService.data.weather.daily.temperature_2m_min[index];
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
max = LocationService.celsiusToFahrenheit(max)
|
||||
min = LocationService.celsiusToFahrenheit(min)
|
||||
max = LocationService.celsiusToFahrenheit(max);
|
||||
min = LocationService.celsiusToFahrenheit(min);
|
||||
}
|
||||
max = Math.round(max)
|
||||
min = Math.round(min)
|
||||
return max + "°/" + min + "°"
|
||||
max = Math.round(max);
|
||||
min = Math.round(min);
|
||||
return max + "°/" + min + "°";
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightMedium
|
||||
@@ -916,7 +912,7 @@ Loader {
|
||||
|
||||
Keys.onPressed: function (event) {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
lockContext.tryUnlock()
|
||||
lockContext.tryUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.UI
|
||||
|
||||
// ------------------------------
|
||||
// MainScreen for each screen (manages bar + all panels)
|
||||
@@ -16,10 +16,10 @@ Variants {
|
||||
|
||||
property bool shouldBeActive: {
|
||||
if (!modelData || !modelData.name) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
Logger.d("Shell", "MainScreen activated for", modelData?.name)
|
||||
return true
|
||||
Logger.d("Shell", "MainScreen activated for", modelData?.name);
|
||||
return true;
|
||||
}
|
||||
|
||||
property bool windowLoaded: false
|
||||
@@ -33,7 +33,7 @@ Variants {
|
||||
|
||||
onLoaded: {
|
||||
// Signal that window is loaded so exclusion zone can be created
|
||||
parent.windowLoaded = true
|
||||
parent.windowLoaded = true;
|
||||
}
|
||||
|
||||
sourceComponent: MainScreen {
|
||||
@@ -45,11 +45,11 @@ Variants {
|
||||
Loader {
|
||||
active: {
|
||||
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
|
||||
return false
|
||||
return false;
|
||||
|
||||
// Check if bar is configured for this screen
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
return monitors.length === 0 || monitors.includes(modelData?.name)
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
return monitors.length === 0 || monitors.includes(modelData?.name);
|
||||
}
|
||||
asynchronous: false
|
||||
|
||||
@@ -58,7 +58,7 @@ Variants {
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
Logger.d("Shell", "BarContentWindow created for", modelData?.name)
|
||||
Logger.d("Shell", "BarContentWindow created for", modelData?.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,11 +67,11 @@ Variants {
|
||||
Loader {
|
||||
active: {
|
||||
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
|
||||
return false
|
||||
return false;
|
||||
|
||||
// Check if bar is configured for this screen
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
return monitors.length === 0 || monitors.includes(modelData?.name)
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
return monitors.length === 0 || monitors.includes(modelData?.name);
|
||||
}
|
||||
asynchronous: false
|
||||
|
||||
@@ -80,7 +80,7 @@ Variants {
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
Logger.d("Shell", "BarExclusionZone created for", modelData?.name)
|
||||
Logger.d("Shell", "BarExclusionZone created for", modelData?.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ Variants {
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
Logger.d("Shell", "TrayMenuWindow created for", modelData?.name)
|
||||
Logger.d("Shell", "TrayMenuWindow created for", modelData?.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,16 @@ import QtQuick.Shapes
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
|
||||
/**
|
||||
* AllBackgrounds - Unified Shape container for all bar and panel backgrounds
|
||||
*
|
||||
* Unified shadow system. This component contains a single Shape
|
||||
* with multiple ShapePath children (one for bar, one for each panel type).
|
||||
*
|
||||
* Benefits:
|
||||
* - Single GPU-accelerated rendering pass for all backgrounds
|
||||
* - Unified shadow system (one MultiEffect for everything)
|
||||
*/
|
||||
* AllBackgrounds - Unified Shape container for all bar and panel backgrounds
|
||||
*
|
||||
* Unified shadow system. This component contains a single Shape
|
||||
* with multiple ShapePath children (one for bar, one for each panel type).
|
||||
*
|
||||
* Benefits:
|
||||
* - Single GPU-accelerated rendering pass for all backgrounds
|
||||
* - Unified shadow system (one MultiEffect for everything)
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@@ -46,15 +45,14 @@ Item {
|
||||
enabled: false // Disable mouse input on the Shape itself
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("AllBackgrounds", "AllBackgrounds initialized")
|
||||
Logger.d("AllBackgrounds", " bar:", root.bar)
|
||||
Logger.d("AllBackgrounds", " windowRoot:", root.windowRoot)
|
||||
Logger.d("AllBackgrounds", "AllBackgrounds initialized");
|
||||
Logger.d("AllBackgrounds", " bar:", root.bar);
|
||||
Logger.d("AllBackgrounds", " windowRoot:", root.windowRoot);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Bar
|
||||
*/
|
||||
* Bar
|
||||
*/
|
||||
BarBackground {
|
||||
bar: root.bar
|
||||
shapeContainer: backgroundsShape
|
||||
@@ -62,10 +60,9 @@ Item {
|
||||
backgroundColor: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Panels
|
||||
*/
|
||||
* Panels
|
||||
*/
|
||||
|
||||
// Audio
|
||||
PanelBackground {
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Modules.MainScreen.Backgrounds
|
||||
|
||||
import qs.Services.UI
|
||||
|
||||
/**
|
||||
* BarBackground - ShapePath component for rendering the bar background
|
||||
*
|
||||
* Unified shadow system. This component is a ShapePath that will be
|
||||
* a child of the unified AllBackgrounds Shape container.
|
||||
*
|
||||
* Uses 4-state per-corner system for flexible corner rendering:
|
||||
* - State -1: No radius (flat/square corner)
|
||||
* - State 0: Normal (inner curve)
|
||||
* - State 1: Horizontal inversion (outer curve on X-axis)
|
||||
* - State 2: Vertical inversion (outer curve on Y-axis)
|
||||
*/
|
||||
* BarBackground - ShapePath component for rendering the bar background
|
||||
*
|
||||
* Unified shadow system. This component is a ShapePath that will be
|
||||
* a child of the unified AllBackgrounds Shape container.
|
||||
*
|
||||
* Uses 4-state per-corner system for flexible corner rendering:
|
||||
* - State -1: No radius (flat/square corner)
|
||||
* - State 0: Normal (inner curve)
|
||||
* - State 1: Horizontal inversion (outer curve on X-axis)
|
||||
* - State 2: Vertical inversion (outer curve on Y-axis)
|
||||
*/
|
||||
ShapePath {
|
||||
id: root
|
||||
|
||||
@@ -35,15 +34,15 @@ ShapePath {
|
||||
readonly property bool shouldShow: {
|
||||
// Check global bar visibility
|
||||
if (!BarService.isVisible)
|
||||
return false
|
||||
return false;
|
||||
|
||||
// Check screen-specific configuration
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
var screenName = windowRoot?.screen?.name || ""
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
var screenName = windowRoot?.screen?.name || "";
|
||||
|
||||
// If no monitors specified, show on all screens
|
||||
// If monitors specified, only show if this screen is in the list
|
||||
return monitors.length === 0 || monitors.includes(screenName)
|
||||
return monitors.length === 0 || monitors.includes(screenName);
|
||||
}
|
||||
|
||||
// Corner radius (from Style)
|
||||
@@ -65,9 +64,9 @@ ShapePath {
|
||||
function getCornerRadius(cornerState) {
|
||||
// State -1 = no radius (flat corner)
|
||||
if (cornerState === -1)
|
||||
return 0
|
||||
return 0;
|
||||
// All other states use effectiveRadius
|
||||
return effectiveRadius
|
||||
return effectiveRadius;
|
||||
}
|
||||
|
||||
// Per-corner multipliers and radii based on bar's corner states (handle null bar)
|
||||
|
||||
@@ -3,22 +3,21 @@ import QtQuick.Shapes
|
||||
import qs.Commons
|
||||
import qs.Modules.MainScreen.Backgrounds
|
||||
|
||||
|
||||
/**
|
||||
* PanelBackground - ShapePath component for rendering a single background
|
||||
*
|
||||
* Unified shadow system. This component is a ShapePath that will
|
||||
* be a child of the unified AllBackgrounds Shape container.
|
||||
*
|
||||
* Reads positioning and geometry from PanelPlaceholder (via panel.panelItem).
|
||||
* The actual panel content lives in a separate SmartPanelWindow.
|
||||
*
|
||||
* Uses 4-state per-corner system for flexible corner rendering:
|
||||
* - State -1: No radius (flat/square corner)
|
||||
* - State 0: Normal (inner curve)
|
||||
* - State 1: Horizontal inversion (outer curve on X-axis)
|
||||
* - State 2: Vertical inversion (outer curve on Y-axis)
|
||||
*/
|
||||
* PanelBackground - ShapePath component for rendering a single background
|
||||
*
|
||||
* Unified shadow system. This component is a ShapePath that will
|
||||
* be a child of the unified AllBackgrounds Shape container.
|
||||
*
|
||||
* Reads positioning and geometry from PanelPlaceholder (via panel.panelItem).
|
||||
* The actual panel content lives in a separate SmartPanelWindow.
|
||||
*
|
||||
* Uses 4-state per-corner system for flexible corner rendering:
|
||||
* - State -1: No radius (flat/square corner)
|
||||
* - State 0: Normal (inner curve)
|
||||
* - State 1: Horizontal inversion (outer curve on X-axis)
|
||||
* - State 2: Vertical inversion (outer curve on Y-axis)
|
||||
*/
|
||||
ShapePath {
|
||||
id: root
|
||||
|
||||
@@ -51,9 +50,9 @@ ShapePath {
|
||||
function getCornerRadius(cornerState) {
|
||||
// State -1 = no radius (flat corner)
|
||||
if (cornerState === -1)
|
||||
return 0
|
||||
return 0;
|
||||
// All other states use effectiveRadius
|
||||
return effectiveRadius
|
||||
return effectiveRadius;
|
||||
}
|
||||
|
||||
// Per-corner multipliers and radii based on panelBg's corner states
|
||||
|
||||
@@ -4,78 +4,71 @@ import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import Quickshell
|
||||
|
||||
|
||||
/**
|
||||
* ShapeCornerHelper - Utility singleton for shape corner calculations
|
||||
*
|
||||
* Uses 4-state per-corner system for flexible corner rendering:
|
||||
* - State -1: No radius (flat/square corner)
|
||||
* - State 0: Normal (inner curve)
|
||||
* - State 1: Horizontal inversion (outer curve on X-axis)
|
||||
* - State 2: Vertical inversion (outer curve on Y-axis)
|
||||
*
|
||||
* The key technique: Using PathArc direction control (Clockwise vs Counterclockwise)
|
||||
* combined with multipliers to create both inner and outer corner curves.
|
||||
*/
|
||||
* ShapeCornerHelper - Utility singleton for shape corner calculations
|
||||
*
|
||||
* Uses 4-state per-corner system for flexible corner rendering:
|
||||
* - State -1: No radius (flat/square corner)
|
||||
* - State 0: Normal (inner curve)
|
||||
* - State 1: Horizontal inversion (outer curve on X-axis)
|
||||
* - State 2: Vertical inversion (outer curve on Y-axis)
|
||||
*
|
||||
* The key technique: Using PathArc direction control (Clockwise vs Counterclockwise)
|
||||
* combined with multipliers to create both inner and outer corner curves.
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
|
||||
/**
|
||||
* Get X-axis multiplier for a corner state
|
||||
* State 1 (horizontal invert) returns -1, others return 1
|
||||
*/
|
||||
* Get X-axis multiplier for a corner state
|
||||
* State 1 (horizontal invert) returns -1, others return 1
|
||||
*/
|
||||
function getMultX(cornerState) {
|
||||
return cornerState === 1 ? -1 : 1
|
||||
return cornerState === 1 ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Y-axis multiplier for a corner state
|
||||
* State 2 (vertical invert) returns -1, others return 1
|
||||
*/
|
||||
* Get Y-axis multiplier for a corner state
|
||||
* State 2 (vertical invert) returns -1, others return 1
|
||||
*/
|
||||
function getMultY(cornerState) {
|
||||
return cornerState === 2 ? -1 : 1
|
||||
return cornerState === 2 ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get PathArc direction for a corner based on its multipliers
|
||||
* Uses XOR logic: if X inverted differs from Y inverted, use Counterclockwise
|
||||
* This creates the outer curve effect for inverted corners
|
||||
*/
|
||||
* Get PathArc direction for a corner based on its multipliers
|
||||
* Uses XOR logic: if X inverted differs from Y inverted, use Counterclockwise
|
||||
* This creates the outer curve effect for inverted corners
|
||||
*/
|
||||
function getArcDirection(multX, multY) {
|
||||
return ((multX < 0) !== (multY < 0)) ? PathArc.Counterclockwise : PathArc.Clockwise
|
||||
return ((multX < 0) !== (multY < 0)) ? PathArc.Counterclockwise : PathArc.Clockwise;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience function to get arc direction directly from corner state
|
||||
*/
|
||||
* Convenience function to get arc direction directly from corner state
|
||||
*/
|
||||
function getArcDirectionFromState(cornerState) {
|
||||
const multX = getMultX(cornerState)
|
||||
const multY = getMultY(cornerState)
|
||||
return getArcDirection(multX, multY)
|
||||
const multX = getMultX(cornerState);
|
||||
const multY = getMultY(cornerState);
|
||||
return getArcDirection(multX, multY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the "flattening" radius when shape dimensions are too small
|
||||
* Prevents visual artifacts when radius exceeds dimensions
|
||||
*/
|
||||
* Get the "flattening" radius when shape dimensions are too small
|
||||
* Prevents visual artifacts when radius exceeds dimensions
|
||||
*/
|
||||
function getFlattenedRadius(dimension, requestedRadius) {
|
||||
if (dimension < requestedRadius * 2) {
|
||||
return dimension / 2
|
||||
return dimension / 2;
|
||||
}
|
||||
return requestedRadius
|
||||
return requestedRadius;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a shape should use flattened corners
|
||||
* Returns true if width or height is too small for the requested radius
|
||||
*/
|
||||
* Check if a shape should use flattened corners
|
||||
* Returns true if width or height is too small for the requested radius
|
||||
*/
|
||||
function shouldFlatten(width, height, radius) {
|
||||
return width < radius * 2 || height < radius * 2
|
||||
return width < radius * 2 || height < radius * 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,18 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Modules.Bar
|
||||
|
||||
import qs.Services.UI
|
||||
|
||||
/**
|
||||
* BarContentWindow - Separate transparent PanelWindow for bar content
|
||||
*
|
||||
* This window contains only the bar widgets (content), while the background
|
||||
* is rendered in MainScreen's unified Shape system. This separation prevents
|
||||
* fullscreen redraws when bar widgets redraw.
|
||||
*
|
||||
* This component should be instantiated once per screen by AllScreens.qml
|
||||
*/
|
||||
* BarContentWindow - Separate transparent PanelWindow for bar content
|
||||
*
|
||||
* This window contains only the bar widgets (content), while the background
|
||||
* is rendered in MainScreen's unified Shape system. This separation prevents
|
||||
* fullscreen redraws when bar widgets redraw.
|
||||
*
|
||||
* This component should be instantiated once per screen by AllScreens.qml
|
||||
*/
|
||||
PanelWindow {
|
||||
id: barWindow
|
||||
|
||||
@@ -22,7 +21,7 @@ PanelWindow {
|
||||
color: Color.transparent // Transparent - background is in MainScreen below
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("BarContentWindow", "Bar content window created for screen:", barWindow.screen?.name)
|
||||
Logger.d("BarContentWindow", "Bar content window created for screen:", barWindow.screen?.name);
|
||||
}
|
||||
|
||||
// Wayland layer configuration
|
||||
|
||||
@@ -3,13 +3,12 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
|
||||
|
||||
/**
|
||||
* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar
|
||||
*
|
||||
* This is a minimal window that works with the compositor to reserve space,
|
||||
* while the actual bar UI is rendered in MainScreen.
|
||||
*/
|
||||
* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar
|
||||
*
|
||||
* This is a minimal window that works with the compositor to reserve space,
|
||||
* while the actual bar UI is rendered in MainScreen.
|
||||
*/
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
@@ -46,11 +45,11 @@ PanelWindow {
|
||||
// Vertical bar: reserve bar height + margin on the anchored edge only
|
||||
if (barFloating) {
|
||||
// For left bar, reserve left margin; for right bar, reserve right margin
|
||||
return Style.barHeight + barMarginH
|
||||
return Style.barHeight + barMarginH;
|
||||
}
|
||||
return Style.barHeight
|
||||
return Style.barHeight;
|
||||
}
|
||||
return 0 // Auto-width when left/right anchors are true
|
||||
return 0; // Auto-width when left/right anchors are true
|
||||
}
|
||||
|
||||
implicitHeight: {
|
||||
@@ -58,17 +57,17 @@ PanelWindow {
|
||||
// Horizontal bar: reserve bar height + margin on the anchored edge only
|
||||
if (barFloating) {
|
||||
// For top bar, reserve top margin; for bottom bar, reserve bottom margin
|
||||
return Style.barHeight + barMarginV
|
||||
return Style.barHeight + barMarginV;
|
||||
}
|
||||
return Style.barHeight
|
||||
return Style.barHeight;
|
||||
}
|
||||
return 0 // Auto-height when top/bottom anchors are true
|
||||
return 0; // Auto-height when top/bottom anchors are true
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("BarExclusionZone", "Created for screen:", screen?.name)
|
||||
Logger.d("BarExclusionZone", " Position:", barPosition, "Exclusive:", exclusive, "Floating:", barFloating)
|
||||
Logger.d("BarExclusionZone", " Anchors - top:", anchors.top, "bottom:", anchors.bottom, "left:", anchors.left, "right:", anchors.right)
|
||||
Logger.d("BarExclusionZone", " Size:", width, "x", height, "implicitWidth:", implicitWidth, "implicitHeight:", implicitHeight)
|
||||
Logger.d("BarExclusionZone", "Created for screen:", screen?.name);
|
||||
Logger.d("BarExclusionZone", " Position:", barPosition, "Exclusive:", exclusive, "Floating:", barFloating);
|
||||
Logger.d("BarExclusionZone", " Anchors - top:", anchors.top, "bottom:", anchors.bottom, "left:", anchors.left, "right:", anchors.right);
|
||||
Logger.d("BarExclusionZone", " Size:", width, "x", height, "implicitWidth:", implicitWidth, "implicitHeight:", implicitHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,9 @@ import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import "Backgrounds" as Backgrounds
|
||||
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import "Backgrounds" as Backgrounds
|
||||
|
||||
// All panels
|
||||
import qs.Modules.Bar
|
||||
@@ -23,11 +22,11 @@ import qs.Modules.Panels.SetupWizard
|
||||
import qs.Modules.Panels.Tray
|
||||
import qs.Modules.Panels.Wallpaper
|
||||
import qs.Modules.Panels.WiFi
|
||||
|
||||
import qs.Services.UI
|
||||
|
||||
/**
|
||||
* MainScreen - Single PanelWindow per screen that manages all panels and the bar
|
||||
*/
|
||||
* MainScreen - Single PanelWindow per screen that manages all panels and the bar
|
||||
*/
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
@@ -62,7 +61,7 @@ PanelWindow {
|
||||
readonly property var wifiPanelPlaceholder: wifiPanel.panelPlaceholder
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("MainScreen", "Initialized for screen:", screen?.name, "- Dimensions:", screen?.width, "x", screen?.height, "- Position:", screen?.x, ",", screen?.y)
|
||||
Logger.d("MainScreen", "Initialized for screen:", screen?.name, "- Dimensions:", screen?.width, "x", screen?.height, "- Position:", screen?.x, ",", screen?.y);
|
||||
}
|
||||
|
||||
// Wayland
|
||||
@@ -85,9 +84,9 @@ PanelWindow {
|
||||
|
||||
color: {
|
||||
if (dimmerOpacity > 0 && isPanelOpen && !isPanelClosing) {
|
||||
return Qt.alpha(Color.mShadow, dimmerOpacity)
|
||||
return Qt.alpha(Color.mShadow, dimmerOpacity);
|
||||
}
|
||||
return Color.transparent
|
||||
return Color.transparent;
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
@@ -101,15 +100,15 @@ PanelWindow {
|
||||
readonly property bool barShouldShow: {
|
||||
// Check global bar visibility
|
||||
if (!BarService.isVisible)
|
||||
return false
|
||||
return false;
|
||||
|
||||
// Check screen-specific configuration
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
var screenName = screen?.name || ""
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
var screenName = screen?.name || "";
|
||||
|
||||
// If no monitors specified, show on all screens
|
||||
// If monitors specified, only show if this screen is in the list
|
||||
return monitors.length === 0 || monitors.includes(screenName)
|
||||
return monitors.length === 0 || monitors.includes(screenName);
|
||||
}
|
||||
|
||||
// Make everything click-through except bar
|
||||
@@ -166,8 +165,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "audioPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(audioPanel)
|
||||
objectName = "audioPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(audioPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,8 +176,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "batteryPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(batteryPanel)
|
||||
objectName = "batteryPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(batteryPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,8 +187,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "bluetoothPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(bluetoothPanel)
|
||||
objectName = "bluetoothPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(bluetoothPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,8 +198,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "controlCenterPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(controlCenterPanel)
|
||||
objectName = "controlCenterPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(controlCenterPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,8 +209,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "calendarPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(calendarPanel)
|
||||
objectName = "calendarPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(calendarPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,8 +220,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "launcherPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(launcherPanel)
|
||||
objectName = "launcherPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(launcherPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,8 +231,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "notificationHistoryPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(notificationHistoryPanel)
|
||||
objectName = "notificationHistoryPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(notificationHistoryPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,8 +242,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "sessionMenuPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(sessionMenuPanel)
|
||||
objectName = "sessionMenuPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(sessionMenuPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,8 +253,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "settingsPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(settingsPanel)
|
||||
objectName = "settingsPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(settingsPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,8 +264,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "setupWizardPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(setupWizardPanel)
|
||||
objectName = "setupWizardPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(setupWizardPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,8 +275,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "trayDrawerPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(trayDrawerPanel)
|
||||
objectName = "trayDrawerPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(trayDrawerPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,8 +286,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "wallpaperPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(wallpaperPanel)
|
||||
objectName = "wallpaperPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(wallpaperPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,8 +297,8 @@ PanelWindow {
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "wifiPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(wifiPanel)
|
||||
objectName = "wifiPanel-" + (screen?.name || "unknown");
|
||||
PanelService.registerPanel(wifiPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,94 +325,93 @@ PanelWindow {
|
||||
// Use screen dimensions directly
|
||||
x: {
|
||||
if (barPosition === "right")
|
||||
return screen.width - Style.barHeight - barMarginH - attachmentOverlap // Extend left towards panels
|
||||
return barMarginH
|
||||
return screen.width - Style.barHeight - barMarginH - attachmentOverlap; // Extend left towards panels
|
||||
return barMarginH;
|
||||
}
|
||||
y: {
|
||||
if (barPosition === "bottom")
|
||||
return screen.height - Style.barHeight - barMarginV - attachmentOverlap
|
||||
return barMarginV
|
||||
return screen.height - Style.barHeight - barMarginV - attachmentOverlap;
|
||||
return barMarginV;
|
||||
}
|
||||
width: {
|
||||
if (barIsVertical) {
|
||||
return Style.barHeight + attachmentOverlap
|
||||
return Style.barHeight + attachmentOverlap;
|
||||
}
|
||||
return screen.width - barMarginH * 2
|
||||
return screen.width - barMarginH * 2;
|
||||
}
|
||||
height: {
|
||||
if (barIsVertical) {
|
||||
return screen.height - barMarginV * 2
|
||||
return screen.height - barMarginV * 2;
|
||||
}
|
||||
return Style.barHeight + attachmentOverlap
|
||||
return Style.barHeight + attachmentOverlap;
|
||||
}
|
||||
|
||||
// Corner states (same as Bar.qml)
|
||||
readonly property int topLeftCornerState: {
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
if (barPosition === "top")
|
||||
return -1
|
||||
return -1;
|
||||
if (barPosition === "left")
|
||||
return -1
|
||||
return -1;
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "right")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int topRightCornerState: {
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
if (barPosition === "top")
|
||||
return -1
|
||||
return -1;
|
||||
if (barPosition === "right")
|
||||
return -1
|
||||
return -1;
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int bottomLeftCornerState: {
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
if (barPosition === "bottom")
|
||||
return -1
|
||||
return -1;
|
||||
if (barPosition === "left")
|
||||
return -1
|
||||
return -1;
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int bottomRightCornerState: {
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
if (barPosition === "bottom")
|
||||
return -1
|
||||
return -1;
|
||||
if (barPosition === "right")
|
||||
return -1
|
||||
return -1;
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("MainScreen", "===== Bar placeholder loaded =====")
|
||||
Logger.d("MainScreen", " Screen:", screen?.name, "Size:", screen?.width, "x", screen?.height)
|
||||
Logger.d("MainScreen", " Bar position:", barPosition, "| isVertical:", barIsVertical)
|
||||
Logger.d("MainScreen", " Bar dimensions: x=" + x, "y=" + y, "width=" + width, "height=" + height)
|
||||
Logger.d("MainScreen", " Style.barHeight =", Style.barHeight)
|
||||
Logger.d("MainScreen", " Margins: H=" + barMarginH, "V=" + barMarginV, "| Floating:", barFloating)
|
||||
Logger.d("MainScreen", "===== Bar placeholder loaded =====");
|
||||
Logger.d("MainScreen", " Screen:", screen?.name, "Size:", screen?.width, "x", screen?.height);
|
||||
Logger.d("MainScreen", " Bar position:", barPosition, "| isVertical:", barIsVertical);
|
||||
Logger.d("MainScreen", " Bar dimensions: x=" + x, "y=" + y, "width=" + width, "height=" + height);
|
||||
Logger.d("MainScreen", " Style.barHeight =", Style.barHeight);
|
||||
Logger.d("MainScreen", " Margins: H=" + barMarginH, "V=" + barMarginV, "| Floating:", barFloating);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Screen Corners
|
||||
*/
|
||||
* Screen Corners
|
||||
*/
|
||||
ScreenCorners {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,13 @@ import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
|
||||
/**
|
||||
* PanelPlaceholder - Lightweight positioning logic for panel backgrounds
|
||||
*
|
||||
* This component stays in MainScreen and provides geometry for PanelBackground rendering.
|
||||
* It contains only positioning calculations and animations, no visual content.
|
||||
* The actual panel content lives in a separate SmartPanelWindow.
|
||||
*/
|
||||
* PanelPlaceholder - Lightweight positioning logic for panel backgrounds
|
||||
*
|
||||
* This component stays in MainScreen and provides geometry for PanelBackground rendering.
|
||||
* It contains only positioning calculations and animations, no visual content.
|
||||
* The actual panel content lives in a separate SmartPanelWindow.
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@@ -71,13 +70,13 @@ Item {
|
||||
readonly property bool allowAttach: Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar
|
||||
readonly property bool allowAttachToBar: {
|
||||
if (!(Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar) || Settings.data.bar.backgroundOpacity < 1.0) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
// A panel can only be attached to a bar if there is a bar on that screen
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
var result = monitors.length === 0 || monitors.includes(root.screen?.name || "")
|
||||
return result
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
var result = monitors.length === 0 || monitors.includes(root.screen?.name || "");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Effective anchor properties (depend on allowAttach)
|
||||
@@ -97,17 +96,17 @@ Item {
|
||||
|
||||
function onUiScaleRatioChanged() {
|
||||
if (root.isPanelVisible) {
|
||||
root.setPosition()
|
||||
root.setPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public function to update content size from SmartPanelWindow
|
||||
function updateContentSize(w, h) {
|
||||
contentPreferredWidth = w
|
||||
contentPreferredHeight = h
|
||||
contentPreferredWidth = w;
|
||||
contentPreferredHeight = h;
|
||||
if (isPanelVisible) {
|
||||
setPosition()
|
||||
setPosition();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,45 +114,45 @@ Item {
|
||||
function setPosition() {
|
||||
// Don't calculate position if parent dimensions aren't available yet
|
||||
if (!root.width || !root.height) {
|
||||
Logger.d("PanelPlaceholder", "Skipping setPosition - dimensions not ready:", root.width, "x", root.height, panelName)
|
||||
Qt.callLater(setPosition)
|
||||
return
|
||||
Logger.d("PanelPlaceholder", "Skipping setPosition - dimensions not ready:", root.width, "x", root.height, panelName);
|
||||
Qt.callLater(setPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate panel dimensions first (needed for positioning)
|
||||
var w
|
||||
var w;
|
||||
// Priority 1: Content-driven size (dynamic)
|
||||
if (contentPreferredWidth > 0) {
|
||||
w = contentPreferredWidth
|
||||
w = contentPreferredWidth;
|
||||
} // Priority 2: Ratio-based size
|
||||
else if (root.preferredWidthRatio !== undefined) {
|
||||
w = Math.round(Math.max(root.width * root.preferredWidthRatio, root.preferredWidth))
|
||||
w = Math.round(Math.max(root.width * root.preferredWidthRatio, root.preferredWidth));
|
||||
} // Priority 3: Static preferred width
|
||||
else {
|
||||
w = root.preferredWidth
|
||||
w = root.preferredWidth;
|
||||
}
|
||||
var panelWidth = Math.min(w, root.width - Style.marginL * 2)
|
||||
var panelWidth = Math.min(w, root.width - Style.marginL * 2);
|
||||
|
||||
var h
|
||||
var h;
|
||||
// Priority 1: Content-driven size (dynamic)
|
||||
if (contentPreferredHeight > 0) {
|
||||
h = contentPreferredHeight
|
||||
h = contentPreferredHeight;
|
||||
} // Priority 2: Ratio-based size
|
||||
else if (root.preferredHeightRatio !== undefined) {
|
||||
h = Math.round(Math.max(root.height * root.preferredHeightRatio, root.preferredHeight))
|
||||
h = Math.round(Math.max(root.height * root.preferredHeightRatio, root.preferredHeight));
|
||||
} // Priority 3: Static preferred height
|
||||
else {
|
||||
h = root.preferredHeight
|
||||
h = root.preferredHeight;
|
||||
}
|
||||
var panelHeight = Math.min(h, root.height - Style.barHeight - Style.marginL * 2)
|
||||
var panelHeight = Math.min(h, root.height - Style.barHeight - Style.marginL * 2);
|
||||
|
||||
// Update panelBackground target size (will be animated)
|
||||
panelBackground.targetWidth = panelWidth
|
||||
panelBackground.targetHeight = panelHeight
|
||||
panelBackground.targetWidth = panelWidth;
|
||||
panelBackground.targetHeight = panelHeight;
|
||||
|
||||
// Calculate position
|
||||
var calculatedX
|
||||
var calculatedY
|
||||
var calculatedX;
|
||||
var calculatedY;
|
||||
|
||||
// ===== X POSITIONING =====
|
||||
if (root.useButtonPosition && root.width > 0 && panelWidth > 0) {
|
||||
@@ -162,111 +161,111 @@ Item {
|
||||
if (allowAttach) {
|
||||
// Attached panels: align with bar edge (left or right side)
|
||||
if (root.barPosition === "left") {
|
||||
var leftBarEdge = root.barMarginH + Style.barHeight
|
||||
calculatedX = leftBarEdge
|
||||
var leftBarEdge = root.barMarginH + Style.barHeight;
|
||||
calculatedX = leftBarEdge;
|
||||
} else {
|
||||
// right
|
||||
var rightBarEdge = root.width - root.barMarginH - Style.barHeight
|
||||
calculatedX = rightBarEdge - panelWidth
|
||||
var rightBarEdge = root.width - root.barMarginH - Style.barHeight;
|
||||
calculatedX = rightBarEdge - panelWidth;
|
||||
}
|
||||
} else {
|
||||
// Detached panels: center on button X position
|
||||
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2
|
||||
var minX = Style.marginL
|
||||
var maxX = root.width - panelWidth - Style.marginL
|
||||
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2;
|
||||
var minX = Style.marginL;
|
||||
var maxX = root.width - panelWidth - Style.marginL;
|
||||
|
||||
// Account for vertical bar taking up space
|
||||
if (root.barPosition === "left") {
|
||||
minX = root.barMarginH + Style.barHeight + Style.marginL
|
||||
minX = root.barMarginH + Style.barHeight + Style.marginL;
|
||||
} else if (root.barPosition === "right") {
|
||||
maxX = root.width - root.barMarginH - Style.barHeight - panelWidth - Style.marginL
|
||||
maxX = root.width - root.barMarginH - Style.barHeight - panelWidth - Style.marginL;
|
||||
}
|
||||
|
||||
panelX = Math.max(minX, Math.min(panelX, maxX))
|
||||
calculatedX = panelX
|
||||
panelX = Math.max(minX, Math.min(panelX, maxX));
|
||||
calculatedX = panelX;
|
||||
}
|
||||
} else {
|
||||
// For horizontal bars, center panel on button X position
|
||||
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2
|
||||
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2;
|
||||
if (allowAttach) {
|
||||
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0
|
||||
var barLeftEdge = root.barMarginH + cornerInset
|
||||
var barRightEdge = root.width - root.barMarginH - cornerInset
|
||||
panelX = Math.max(barLeftEdge, Math.min(panelX, barRightEdge - panelWidth))
|
||||
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0;
|
||||
var barLeftEdge = root.barMarginH + cornerInset;
|
||||
var barRightEdge = root.width - root.barMarginH - cornerInset;
|
||||
panelX = Math.max(barLeftEdge, Math.min(panelX, barRightEdge - panelWidth));
|
||||
} else {
|
||||
panelX = Math.max(Style.marginL, Math.min(panelX, root.width - panelWidth - Style.marginL))
|
||||
panelX = Math.max(Style.marginL, Math.min(panelX, root.width - panelWidth - Style.marginL));
|
||||
}
|
||||
calculatedX = panelX
|
||||
calculatedX = panelX;
|
||||
}
|
||||
} else {
|
||||
// Standard anchor positioning
|
||||
if (root.panelAnchorHorizontalCenter) {
|
||||
if (root.barIsVertical) {
|
||||
if (root.barPosition === "left") {
|
||||
var availableStart = root.barMarginH + Style.barHeight
|
||||
var availableWidth = root.width - availableStart
|
||||
calculatedX = availableStart + (availableWidth - panelWidth) / 2
|
||||
var availableStart = root.barMarginH + Style.barHeight;
|
||||
var availableWidth = root.width - availableStart;
|
||||
calculatedX = availableStart + (availableWidth - panelWidth) / 2;
|
||||
} else if (root.barPosition === "right") {
|
||||
var availableWidth = root.width - root.barMarginH - Style.barHeight
|
||||
calculatedX = (availableWidth - panelWidth) / 2
|
||||
var availableWidth = root.width - root.barMarginH - Style.barHeight;
|
||||
calculatedX = (availableWidth - panelWidth) / 2;
|
||||
} else {
|
||||
calculatedX = (root.width - panelWidth) / 2
|
||||
calculatedX = (root.width - panelWidth) / 2;
|
||||
}
|
||||
} else {
|
||||
calculatedX = (root.width - panelWidth) / 2
|
||||
calculatedX = (root.width - panelWidth) / 2;
|
||||
}
|
||||
} else if (root.effectivePanelAnchorRight) {
|
||||
if (allowAttach && root.barIsVertical && root.barPosition === "right") {
|
||||
var rightBarEdge = root.width - root.barMarginH - Style.barHeight
|
||||
calculatedX = rightBarEdge - panelWidth
|
||||
var rightBarEdge = root.width - root.barMarginH - Style.barHeight;
|
||||
calculatedX = rightBarEdge - panelWidth;
|
||||
} else if (allowAttach) {
|
||||
// Account for corner inset when bar is floating, horizontal, AND panel is on same edge as bar
|
||||
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom)
|
||||
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom);
|
||||
if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) {
|
||||
var rightCornerInset = Style.radiusL * 2
|
||||
calculatedX = root.width - root.barMarginH - rightCornerInset - panelWidth
|
||||
var rightCornerInset = Style.radiusL * 2;
|
||||
calculatedX = root.width - root.barMarginH - rightCornerInset - panelWidth;
|
||||
} else {
|
||||
calculatedX = root.width - panelWidth
|
||||
calculatedX = root.width - panelWidth;
|
||||
}
|
||||
} else {
|
||||
calculatedX = root.width - panelWidth - Style.marginL
|
||||
calculatedX = root.width - panelWidth - Style.marginL;
|
||||
}
|
||||
} else if (root.effectivePanelAnchorLeft) {
|
||||
if (allowAttach && root.barIsVertical && root.barPosition === "left") {
|
||||
var leftBarEdge = root.barMarginH + Style.barHeight
|
||||
calculatedX = leftBarEdge
|
||||
var leftBarEdge = root.barMarginH + Style.barHeight;
|
||||
calculatedX = leftBarEdge;
|
||||
} else if (allowAttach) {
|
||||
// Account for corner inset when bar is floating, horizontal, AND panel is on same edge as bar
|
||||
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom)
|
||||
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom);
|
||||
if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) {
|
||||
var leftCornerInset = Style.radiusL * 2
|
||||
calculatedX = root.barMarginH + leftCornerInset
|
||||
var leftCornerInset = Style.radiusL * 2;
|
||||
calculatedX = root.barMarginH + leftCornerInset;
|
||||
} else {
|
||||
calculatedX = 0
|
||||
calculatedX = 0;
|
||||
}
|
||||
} else {
|
||||
calculatedX = Style.marginL
|
||||
calculatedX = Style.marginL;
|
||||
}
|
||||
} else {
|
||||
// No explicit anchor: default to centering on bar
|
||||
if (root.barIsVertical) {
|
||||
if (root.barPosition === "left") {
|
||||
var availableStart = root.barMarginH + Style.barHeight
|
||||
var availableWidth = root.width - availableStart - Style.marginL
|
||||
calculatedX = availableStart + (availableWidth - panelWidth) / 2
|
||||
var availableStart = root.barMarginH + Style.barHeight;
|
||||
var availableWidth = root.width - availableStart - Style.marginL;
|
||||
calculatedX = availableStart + (availableWidth - panelWidth) / 2;
|
||||
} else {
|
||||
var availableWidth = root.width - root.barMarginH - Style.barHeight - Style.marginL
|
||||
calculatedX = Style.marginL + (availableWidth - panelWidth) / 2
|
||||
var availableWidth = root.width - root.barMarginH - Style.barHeight - Style.marginL;
|
||||
calculatedX = Style.marginL + (availableWidth - panelWidth) / 2;
|
||||
}
|
||||
} else {
|
||||
if (allowAttach) {
|
||||
var cornerInset = Style.radiusL + (root.barFloating ? Style.radiusL : 0)
|
||||
var barLeftEdge = root.barMarginH + cornerInset
|
||||
var barRightEdge = root.width - root.barMarginH - cornerInset
|
||||
var centeredX = (root.width - panelWidth) / 2
|
||||
calculatedX = Math.max(barLeftEdge, Math.min(centeredX, barRightEdge - panelWidth))
|
||||
var cornerInset = Style.radiusL + (root.barFloating ? Style.radiusL : 0);
|
||||
var barLeftEdge = root.barMarginH + cornerInset;
|
||||
var barRightEdge = root.width - root.barMarginH - cornerInset;
|
||||
var centeredX = (root.width - panelWidth) / 2;
|
||||
calculatedX = Math.max(barLeftEdge, Math.min(centeredX, barRightEdge - panelWidth));
|
||||
} else {
|
||||
calculatedX = (root.width - panelWidth) / 2
|
||||
calculatedX = (root.width - panelWidth) / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,76 +273,76 @@ Item {
|
||||
|
||||
// Edge snapping for X
|
||||
if (allowAttach && !root.barFloating && root.width > 0 && panelWidth > 0) {
|
||||
var leftEdgePos = root.barMarginH
|
||||
var leftEdgePos = root.barMarginH;
|
||||
if (root.barPosition === "left") {
|
||||
leftEdgePos = root.barMarginH + Style.barHeight
|
||||
leftEdgePos = root.barMarginH + Style.barHeight;
|
||||
}
|
||||
|
||||
var rightEdgePos = root.width - root.barMarginH - panelWidth
|
||||
var rightEdgePos = root.width - root.barMarginH - panelWidth;
|
||||
if (root.barPosition === "right") {
|
||||
rightEdgePos = root.width - root.barMarginH - Style.barHeight - panelWidth
|
||||
rightEdgePos = root.width - root.barMarginH - Style.barHeight - panelWidth;
|
||||
}
|
||||
|
||||
// Only snap to left edge if panel is actually meant to be at left
|
||||
var shouldSnapToLeft = root.effectivePanelAnchorLeft || (!root.hasExplicitHorizontalAnchor && root.barPosition === "left")
|
||||
var shouldSnapToLeft = root.effectivePanelAnchorLeft || (!root.hasExplicitHorizontalAnchor && root.barPosition === "left");
|
||||
// Only snap to right edge if panel is actually meant to be at right
|
||||
var shouldSnapToRight = root.effectivePanelAnchorRight || (!root.hasExplicitHorizontalAnchor && root.barPosition === "right")
|
||||
var shouldSnapToRight = root.effectivePanelAnchorRight || (!root.hasExplicitHorizontalAnchor && root.barPosition === "right");
|
||||
|
||||
if (shouldSnapToLeft && Math.abs(calculatedX - leftEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedX = leftEdgePos
|
||||
calculatedX = leftEdgePos;
|
||||
} else if (shouldSnapToRight && Math.abs(calculatedX - rightEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedX = rightEdgePos
|
||||
calculatedX = rightEdgePos;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Y POSITIONING =====
|
||||
if (root.useButtonPosition && root.height > 0 && panelHeight > 0) {
|
||||
if (root.barPosition === "top") {
|
||||
var topBarEdge = root.barMarginV + Style.barHeight
|
||||
var topBarEdge = root.barMarginV + Style.barHeight;
|
||||
if (allowAttach) {
|
||||
calculatedY = topBarEdge
|
||||
calculatedY = topBarEdge;
|
||||
} else {
|
||||
calculatedY = topBarEdge + Style.marginM
|
||||
calculatedY = topBarEdge + Style.marginM;
|
||||
}
|
||||
} else if (root.barPosition === "bottom") {
|
||||
var bottomBarEdge = root.height - root.barMarginV - Style.barHeight
|
||||
var bottomBarEdge = root.height - root.barMarginV - Style.barHeight;
|
||||
if (allowAttach) {
|
||||
calculatedY = bottomBarEdge - panelHeight
|
||||
calculatedY = bottomBarEdge - panelHeight;
|
||||
} else {
|
||||
calculatedY = bottomBarEdge - panelHeight - Style.marginM
|
||||
calculatedY = bottomBarEdge - panelHeight - Style.marginM;
|
||||
}
|
||||
} else if (root.barIsVertical) {
|
||||
var panelY = root.buttonPosition.y + root.buttonHeight / 2 - panelHeight / 2
|
||||
var extraPadding = (allowAttach && root.barFloating) ? Style.radiusL : 0
|
||||
var panelY = root.buttonPosition.y + root.buttonHeight / 2 - panelHeight / 2;
|
||||
var extraPadding = (allowAttach && root.barFloating) ? Style.radiusL : 0;
|
||||
if (allowAttach) {
|
||||
var cornerInset = extraPadding + (root.barFloating ? Style.radiusL : 0)
|
||||
var barTopEdge = root.barMarginV + cornerInset
|
||||
var barBottomEdge = root.height - root.barMarginV - cornerInset
|
||||
panelY = Math.max(barTopEdge, Math.min(panelY, barBottomEdge - panelHeight))
|
||||
var cornerInset = extraPadding + (root.barFloating ? Style.radiusL : 0);
|
||||
var barTopEdge = root.barMarginV + cornerInset;
|
||||
var barBottomEdge = root.height - root.barMarginV - cornerInset;
|
||||
panelY = Math.max(barTopEdge, Math.min(panelY, barBottomEdge - panelHeight));
|
||||
} else {
|
||||
panelY = Math.max(Style.marginL + extraPadding, Math.min(panelY, root.height - panelHeight - Style.marginL - extraPadding))
|
||||
panelY = Math.max(Style.marginL + extraPadding, Math.min(panelY, root.height - panelHeight - Style.marginL - extraPadding));
|
||||
}
|
||||
calculatedY = panelY
|
||||
calculatedY = panelY;
|
||||
}
|
||||
} else {
|
||||
// Standard anchor positioning
|
||||
var barOffset = 0
|
||||
var barOffset = 0;
|
||||
if (!allowAttach) {
|
||||
if (root.barPosition === "top") {
|
||||
barOffset = root.barMarginV + Style.barHeight + Style.marginM
|
||||
barOffset = root.barMarginV + Style.barHeight + Style.marginM;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
barOffset = root.barMarginV + Style.barHeight + Style.marginM
|
||||
barOffset = root.barMarginV + Style.barHeight + Style.marginM;
|
||||
}
|
||||
} else {
|
||||
if (root.effectivePanelAnchorTop && root.barPosition === "top") {
|
||||
calculatedY = root.barMarginV + Style.barHeight
|
||||
calculatedY = root.barMarginV + Style.barHeight;
|
||||
} else if (root.effectivePanelAnchorBottom && root.barPosition === "bottom") {
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
||||
} else if (!root.hasExplicitVerticalAnchor) {
|
||||
if (root.barPosition === "top") {
|
||||
calculatedY = root.barMarginV + Style.barHeight
|
||||
calculatedY = root.barMarginV + Style.barHeight;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,57 +351,57 @@ Item {
|
||||
if (root.panelAnchorVerticalCenter) {
|
||||
if (!root.barIsVertical) {
|
||||
if (root.barPosition === "top") {
|
||||
var availableStart = root.barMarginV + Style.barHeight
|
||||
var availableHeight = root.height - availableStart
|
||||
calculatedY = availableStart + (availableHeight - panelHeight) / 2
|
||||
var availableStart = root.barMarginV + Style.barHeight;
|
||||
var availableHeight = root.height - availableStart;
|
||||
calculatedY = availableStart + (availableHeight - panelHeight) / 2;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
var availableHeight = root.height - root.barMarginV - Style.barHeight
|
||||
calculatedY = (availableHeight - panelHeight) / 2
|
||||
var availableHeight = root.height - root.barMarginV - Style.barHeight;
|
||||
calculatedY = (availableHeight - panelHeight) / 2;
|
||||
} else {
|
||||
calculatedY = (root.height - panelHeight) / 2
|
||||
calculatedY = (root.height - panelHeight) / 2;
|
||||
}
|
||||
} else {
|
||||
calculatedY = (root.height - panelHeight) / 2
|
||||
calculatedY = (root.height - panelHeight) / 2;
|
||||
}
|
||||
} else if (root.effectivePanelAnchorTop) {
|
||||
if (allowAttach) {
|
||||
calculatedY = 0
|
||||
calculatedY = 0;
|
||||
} else {
|
||||
var topBarOffset = (root.barPosition === "top") ? barOffset : 0
|
||||
calculatedY = topBarOffset + Style.marginL
|
||||
var topBarOffset = (root.barPosition === "top") ? barOffset : 0;
|
||||
calculatedY = topBarOffset + Style.marginL;
|
||||
}
|
||||
} else if (root.effectivePanelAnchorBottom) {
|
||||
if (allowAttach) {
|
||||
calculatedY = root.height - panelHeight
|
||||
calculatedY = root.height - panelHeight;
|
||||
} else {
|
||||
var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0
|
||||
calculatedY = root.height - panelHeight - bottomBarOffset - Style.marginL
|
||||
var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0;
|
||||
calculatedY = root.height - panelHeight - bottomBarOffset - Style.marginL;
|
||||
}
|
||||
} else {
|
||||
if (root.barIsVertical) {
|
||||
if (allowAttach) {
|
||||
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0
|
||||
var barTopEdge = root.barMarginV + cornerInset
|
||||
var barBottomEdge = root.height - root.barMarginV - cornerInset
|
||||
var centeredY = (root.height - panelHeight) / 2
|
||||
calculatedY = Math.max(barTopEdge, Math.min(centeredY, barBottomEdge - panelHeight))
|
||||
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0;
|
||||
var barTopEdge = root.barMarginV + cornerInset;
|
||||
var barBottomEdge = root.height - root.barMarginV - cornerInset;
|
||||
var centeredY = (root.height - panelHeight) / 2;
|
||||
calculatedY = Math.max(barTopEdge, Math.min(centeredY, barBottomEdge - panelHeight));
|
||||
} else {
|
||||
calculatedY = (root.height - panelHeight) / 2
|
||||
calculatedY = (root.height - panelHeight) / 2;
|
||||
}
|
||||
} else {
|
||||
if (allowAttach && !root.barIsVertical) {
|
||||
if (root.barPosition === "top") {
|
||||
calculatedY = root.barMarginV + Style.barHeight
|
||||
calculatedY = root.barMarginV + Style.barHeight;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
||||
}
|
||||
} else {
|
||||
if (root.barPosition === "top") {
|
||||
calculatedY = barOffset + Style.marginL
|
||||
calculatedY = barOffset + Style.marginL;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
calculatedY = Style.marginL
|
||||
calculatedY = Style.marginL;
|
||||
} else {
|
||||
calculatedY = Style.marginL
|
||||
calculatedY = Style.marginL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -412,34 +411,34 @@ Item {
|
||||
|
||||
// Edge snapping for Y
|
||||
if (allowAttach && !root.barFloating && root.height > 0 && panelHeight > 0) {
|
||||
var topEdgePos = root.barMarginV
|
||||
var topEdgePos = root.barMarginV;
|
||||
if (root.barPosition === "top") {
|
||||
topEdgePos = root.barMarginV + Style.barHeight
|
||||
topEdgePos = root.barMarginV + Style.barHeight;
|
||||
}
|
||||
|
||||
var bottomEdgePos = root.height - root.barMarginV - panelHeight
|
||||
var bottomEdgePos = root.height - root.barMarginV - panelHeight;
|
||||
if (root.barPosition === "bottom") {
|
||||
bottomEdgePos = root.height - root.barMarginV - Style.barHeight - panelHeight
|
||||
bottomEdgePos = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
||||
}
|
||||
|
||||
// Only snap to top edge if panel is actually meant to be at top
|
||||
var shouldSnapToTop = root.effectivePanelAnchorTop || (!root.hasExplicitVerticalAnchor && root.barPosition === "top")
|
||||
var shouldSnapToTop = root.effectivePanelAnchorTop || (!root.hasExplicitVerticalAnchor && root.barPosition === "top");
|
||||
// Only snap to bottom edge if panel is actually meant to be at bottom
|
||||
var shouldSnapToBottom = root.effectivePanelAnchorBottom || (!root.hasExplicitVerticalAnchor && root.barPosition === "bottom")
|
||||
var shouldSnapToBottom = root.effectivePanelAnchorBottom || (!root.hasExplicitVerticalAnchor && root.barPosition === "bottom");
|
||||
|
||||
if (shouldSnapToTop && Math.abs(calculatedY - topEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedY = topEdgePos
|
||||
calculatedY = topEdgePos;
|
||||
} else if (shouldSnapToBottom && Math.abs(calculatedY - bottomEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedY = bottomEdgePos
|
||||
calculatedY = bottomEdgePos;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply calculated positions (set targets for animation)
|
||||
panelBackground.targetX = calculatedX
|
||||
panelBackground.targetY = calculatedY
|
||||
panelBackground.targetX = calculatedX;
|
||||
panelBackground.targetY = calculatedY;
|
||||
|
||||
Logger.d("PanelPlaceholder", "Position calculated:", calculatedX, calculatedY, panelName)
|
||||
Logger.d("PanelPlaceholder", " Panel size:", panelWidth, "x", panelHeight)
|
||||
Logger.d("PanelPlaceholder", "Position calculated:", calculatedX, calculatedY, panelName);
|
||||
Logger.d("PanelPlaceholder", " Panel size:", panelWidth, "x", panelHeight);
|
||||
}
|
||||
|
||||
// The panel background geometry item
|
||||
@@ -469,35 +468,35 @@ Item {
|
||||
// Animation direction determination (using target position to avoid binding loops)
|
||||
readonly property bool willTouchTopBar: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (!allowAttachToBar || root.barPosition !== "top" || root.barIsVertical)
|
||||
return false
|
||||
var targetTopBarY = root.barMarginV + Style.barHeight
|
||||
return Math.abs(panelBackground.targetY - targetTopBarY) <= 1
|
||||
return false;
|
||||
var targetTopBarY = root.barMarginV + Style.barHeight;
|
||||
return Math.abs(panelBackground.targetY - targetTopBarY) <= 1;
|
||||
}
|
||||
readonly property bool willTouchBottomBar: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (!allowAttachToBar || root.barPosition !== "bottom" || root.barIsVertical)
|
||||
return false
|
||||
var targetBottomBarY = root.height - root.barMarginV - Style.barHeight - panelBackground.targetHeight
|
||||
return Math.abs(panelBackground.targetY - targetBottomBarY) <= 1
|
||||
return false;
|
||||
var targetBottomBarY = root.height - root.barMarginV - Style.barHeight - panelBackground.targetHeight;
|
||||
return Math.abs(panelBackground.targetY - targetBottomBarY) <= 1;
|
||||
}
|
||||
readonly property bool willTouchLeftBar: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (!allowAttachToBar || root.barPosition !== "left" || !root.barIsVertical)
|
||||
return false
|
||||
var targetLeftBarX = root.barMarginH + Style.barHeight
|
||||
return Math.abs(panelBackground.targetX - targetLeftBarX) <= 1
|
||||
return false;
|
||||
var targetLeftBarX = root.barMarginH + Style.barHeight;
|
||||
return Math.abs(panelBackground.targetX - targetLeftBarX) <= 1;
|
||||
}
|
||||
readonly property bool willTouchRightBar: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (!allowAttachToBar || root.barPosition !== "right" || !root.barIsVertical)
|
||||
return false
|
||||
var targetRightBarX = root.width - root.barMarginH - Style.barHeight - panelBackground.targetWidth
|
||||
return Math.abs(panelBackground.targetX - targetRightBarX) <= 1
|
||||
return false;
|
||||
var targetRightBarX = root.width - root.barMarginH - Style.barHeight - panelBackground.targetWidth;
|
||||
return Math.abs(panelBackground.targetX - targetRightBarX) <= 1;
|
||||
}
|
||||
readonly property bool willTouchTopEdge: isPanelVisible && allowAttach && panelBackground.targetY <= 1
|
||||
readonly property bool willTouchBottomEdge: isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1)
|
||||
@@ -506,59 +505,59 @@ Item {
|
||||
|
||||
readonly property bool isActuallyAttachedToAnyEdge: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return willTouchTopBar || willTouchBottomBar || willTouchLeftBar || willTouchRightBar || willTouchTopEdge || willTouchBottomEdge || willTouchLeftEdge || willTouchRightEdge
|
||||
return false;
|
||||
return willTouchTopBar || willTouchBottomBar || willTouchLeftBar || willTouchRightBar || willTouchTopEdge || willTouchBottomEdge || willTouchLeftEdge || willTouchRightEdge;
|
||||
}
|
||||
|
||||
readonly property bool animateFromTop: {
|
||||
if (!isPanelVisible)
|
||||
return true
|
||||
return true;
|
||||
if (willTouchTopBar)
|
||||
return true
|
||||
return true;
|
||||
if (willTouchTopEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar)
|
||||
return true
|
||||
return true;
|
||||
if (!isActuallyAttachedToAnyEdge)
|
||||
return true
|
||||
return false
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
readonly property bool animateFromBottom: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchBottomBar)
|
||||
return true
|
||||
return true;
|
||||
if (willTouchBottomEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar)
|
||||
return true
|
||||
return false
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
readonly property bool animateFromLeft: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchTopBar || willTouchBottomBar)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchLeftBar)
|
||||
return true
|
||||
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1
|
||||
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1)
|
||||
return true;
|
||||
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1;
|
||||
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1);
|
||||
if (touchingTopEdge || touchingBottomEdge)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchLeftEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar)
|
||||
return true
|
||||
return false
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
readonly property bool animateFromRight: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchTopBar || willTouchBottomBar)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchRightBar)
|
||||
return true
|
||||
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1
|
||||
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1)
|
||||
return true;
|
||||
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1;
|
||||
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1);
|
||||
if (touchingTopEdge || touchingBottomEdge)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchRightEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar)
|
||||
return true
|
||||
return false
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property bool shouldAnimateWidth: !shouldAnimateHeight && (animateFromLeft || animateFromRight)
|
||||
@@ -567,17 +566,17 @@ Item {
|
||||
// Current animated width/height
|
||||
readonly property real currentWidth: {
|
||||
if (isClosing && opacityFadeComplete && shouldAnimateWidth)
|
||||
return 0
|
||||
return 0;
|
||||
if (isClosing || isPanelVisible)
|
||||
return targetWidth
|
||||
return 0
|
||||
return targetWidth;
|
||||
return 0;
|
||||
}
|
||||
readonly property real currentHeight: {
|
||||
if (isClosing && opacityFadeComplete && shouldAnimateHeight)
|
||||
return 0
|
||||
return 0;
|
||||
if (isClosing || isPanelVisible)
|
||||
return targetHeight
|
||||
return 0
|
||||
return targetHeight;
|
||||
return 0;
|
||||
}
|
||||
|
||||
width: currentWidth
|
||||
@@ -586,28 +585,28 @@ Item {
|
||||
x: {
|
||||
if (animateFromRight) {
|
||||
if (isPanelVisible || isClosing) {
|
||||
var targetRightEdge = targetX + targetWidth
|
||||
return targetRightEdge - width
|
||||
var targetRightEdge = targetX + targetWidth;
|
||||
return targetRightEdge - width;
|
||||
}
|
||||
}
|
||||
return targetX
|
||||
return targetX;
|
||||
}
|
||||
y: {
|
||||
if (animateFromBottom) {
|
||||
if (isPanelVisible || isClosing) {
|
||||
var targetBottomEdge = targetY + targetHeight
|
||||
return targetBottomEdge - height
|
||||
var targetBottomEdge = targetY + targetHeight;
|
||||
return targetBottomEdge - height;
|
||||
}
|
||||
}
|
||||
return targetY
|
||||
return targetY;
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: {
|
||||
if (!panelBackground.shouldAnimateWidth)
|
||||
return 0
|
||||
return root.isClosing ? Style.animationFast : Style.animationNormal
|
||||
return 0;
|
||||
return root.isClosing ? Style.animationFast : Style.animationNormal;
|
||||
}
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: panelBackground.bezierCurve
|
||||
@@ -618,8 +617,8 @@ Item {
|
||||
NumberAnimation {
|
||||
duration: {
|
||||
if (!panelBackground.shouldAnimateHeight)
|
||||
return 0
|
||||
return root.isClosing ? Style.animationFast : Style.animationNormal
|
||||
return 0;
|
||||
return root.isClosing ? Style.animationFast : Style.animationNormal;
|
||||
}
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: panelBackground.bezierCurve
|
||||
@@ -628,75 +627,75 @@ Item {
|
||||
|
||||
// Corner states for PanelBackground to read
|
||||
property int topLeftCornerState: {
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft))
|
||||
var barTouchInverted = touchingTopBar || touchingLeftBar
|
||||
var edgeInverted = allowAttach && (touchingLeftEdge || touchingTopEdge)
|
||||
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top")
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft));
|
||||
var barTouchInverted = touchingTopBar || touchingLeftBar;
|
||||
var edgeInverted = allowAttach && (touchingLeftEdge || touchingTopEdge);
|
||||
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top");
|
||||
|
||||
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
||||
if (touchingLeftEdge && touchingTopEdge)
|
||||
return 0
|
||||
return 0;
|
||||
if (touchingLeftEdge)
|
||||
return 2
|
||||
return 2;
|
||||
if (touchingTopEdge)
|
||||
return 1
|
||||
return root.barIsVertical ? 2 : 1
|
||||
return 1;
|
||||
return root.barIsVertical ? 2 : 1;
|
||||
}
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
property int topRightCornerState: {
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight))
|
||||
var barTouchInverted = touchingTopBar || touchingRightBar
|
||||
var edgeInverted = allowAttach && (touchingRightEdge || touchingTopEdge)
|
||||
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top")
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight));
|
||||
var barTouchInverted = touchingTopBar || touchingRightBar;
|
||||
var edgeInverted = allowAttach && (touchingRightEdge || touchingTopEdge);
|
||||
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top");
|
||||
|
||||
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
||||
if (touchingRightEdge && touchingTopEdge)
|
||||
return 0
|
||||
return 0;
|
||||
if (touchingRightEdge)
|
||||
return 2
|
||||
return 2;
|
||||
if (touchingTopEdge)
|
||||
return 1
|
||||
return root.barIsVertical ? 2 : 1
|
||||
return 1;
|
||||
return root.barIsVertical ? 2 : 1;
|
||||
}
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
property int bottomLeftCornerState: {
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft))
|
||||
var barTouchInverted = touchingBottomBar || touchingLeftBar
|
||||
var edgeInverted = allowAttach && (touchingLeftEdge || touchingBottomEdge)
|
||||
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom")
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft));
|
||||
var barTouchInverted = touchingBottomBar || touchingLeftBar;
|
||||
var edgeInverted = allowAttach && (touchingLeftEdge || touchingBottomEdge);
|
||||
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom");
|
||||
|
||||
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
||||
if (touchingLeftEdge && touchingBottomEdge)
|
||||
return 0
|
||||
return 0;
|
||||
if (touchingLeftEdge)
|
||||
return 2
|
||||
return 2;
|
||||
if (touchingBottomEdge)
|
||||
return 1
|
||||
return root.barIsVertical ? 2 : 1
|
||||
return 1;
|
||||
return root.barIsVertical ? 2 : 1;
|
||||
}
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
property int bottomRightCornerState: {
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight))
|
||||
var barTouchInverted = touchingBottomBar || touchingRightBar
|
||||
var edgeInverted = allowAttach && (touchingRightEdge || touchingBottomEdge)
|
||||
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom")
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight));
|
||||
var barTouchInverted = touchingBottomBar || touchingRightBar;
|
||||
var edgeInverted = allowAttach && (touchingRightEdge || touchingBottomEdge);
|
||||
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom");
|
||||
|
||||
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
||||
if (touchingRightEdge && touchingBottomEdge)
|
||||
return 0
|
||||
return 0;
|
||||
if (touchingRightEdge)
|
||||
return 2
|
||||
return 2;
|
||||
if (touchingBottomEdge)
|
||||
return 1
|
||||
return root.barIsVertical ? 2 : 1
|
||||
return 1;
|
||||
return root.barIsVertical ? 2 : 1;
|
||||
}
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@ import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import qs.Commons
|
||||
|
||||
|
||||
/**
|
||||
* ScreenCorners - Shape component for rendering screen corners
|
||||
*
|
||||
* Renders concave corners at the screen edges to create a rounded screen effect.
|
||||
* Self-contained Shape component (no shadows).
|
||||
*/
|
||||
* ScreenCorners - Shape component for rendering screen corners
|
||||
*
|
||||
* Renders concave corners at the screen edges to create a rounded screen effect.
|
||||
* Self-contained Shape component (no shadows).
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
|
||||
@@ -3,15 +3,14 @@ import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
|
||||
/**
|
||||
* SmartPanel - Wrapper that creates placeholder + content window
|
||||
*
|
||||
* This component is a thin wrapper that maintains backward compatibility
|
||||
* while splitting panel rendering into:
|
||||
* 1. PanelPlaceholder (in MainScreen, for background rendering)
|
||||
* 2. SmartPanelWindow (separate window, for content)
|
||||
*/
|
||||
* SmartPanel - Wrapper that creates placeholder + content window
|
||||
*
|
||||
* This component is a thin wrapper that maintains backward compatibility
|
||||
* while splitting panel rendering into:
|
||||
* 1. PanelPlaceholder (in MainScreen, for background rendering)
|
||||
* 2. SmartPanelWindow (separate window, for content)
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@@ -59,60 +58,74 @@ Item {
|
||||
|
||||
// Keyboard event handlers - these can be overridden by panel implementations
|
||||
// Note: SmartPanelWindow directly calls these functions via panelWrapper reference
|
||||
function onEscapePressed() {}
|
||||
function onTabPressed() {}
|
||||
function onBackTabPressed() {}
|
||||
function onUpPressed() {}
|
||||
function onDownPressed() {}
|
||||
function onLeftPressed() {}
|
||||
function onRightPressed() {}
|
||||
function onReturnPressed() {}
|
||||
function onHomePressed() {}
|
||||
function onEndPressed() {}
|
||||
function onPageUpPressed() {}
|
||||
function onPageDownPressed() {}
|
||||
function onCtrlJPressed() {}
|
||||
function onCtrlKPressed() {}
|
||||
function onEscapePressed() {
|
||||
}
|
||||
function onTabPressed() {
|
||||
}
|
||||
function onBackTabPressed() {
|
||||
}
|
||||
function onUpPressed() {
|
||||
}
|
||||
function onDownPressed() {
|
||||
}
|
||||
function onLeftPressed() {
|
||||
}
|
||||
function onRightPressed() {
|
||||
}
|
||||
function onReturnPressed() {
|
||||
}
|
||||
function onHomePressed() {
|
||||
}
|
||||
function onEndPressed() {
|
||||
}
|
||||
function onPageUpPressed() {
|
||||
}
|
||||
function onPageDownPressed() {
|
||||
}
|
||||
function onCtrlJPressed() {
|
||||
}
|
||||
function onCtrlKPressed() {
|
||||
}
|
||||
|
||||
// Public control functions
|
||||
function toggle(buttonItem, buttonName) {
|
||||
// Ensure window is created before toggling
|
||||
if (!root.windowActive) {
|
||||
root.windowActive = true
|
||||
root.windowActive = true;
|
||||
Qt.callLater(function () {
|
||||
if (windowLoader.item) {
|
||||
windowLoader.item.toggle(buttonItem, buttonName)
|
||||
windowLoader.item.toggle(buttonItem, buttonName);
|
||||
}
|
||||
})
|
||||
});
|
||||
} else if (windowLoader.item) {
|
||||
windowLoader.item.toggle(buttonItem, buttonName)
|
||||
windowLoader.item.toggle(buttonItem, buttonName);
|
||||
}
|
||||
}
|
||||
|
||||
function open(buttonItem, buttonName) {
|
||||
// Ensure window is created before opening
|
||||
if (!root.windowActive) {
|
||||
root.windowActive = true
|
||||
root.windowActive = true;
|
||||
Qt.callLater(function () {
|
||||
if (windowLoader.item) {
|
||||
windowLoader.item.open(buttonItem, buttonName)
|
||||
windowLoader.item.open(buttonItem, buttonName);
|
||||
}
|
||||
})
|
||||
});
|
||||
} else if (windowLoader.item) {
|
||||
windowLoader.item.open(buttonItem, buttonName)
|
||||
windowLoader.item.open(buttonItem, buttonName);
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (windowLoader.item) {
|
||||
windowLoader.item.close()
|
||||
windowLoader.item.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Expose setPosition for panels that need to recalculate on settings changes
|
||||
function setPosition() {
|
||||
if (panelPlaceholder) {
|
||||
panelPlaceholder.setPosition()
|
||||
panelPlaceholder.setPosition();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,11 +170,11 @@ Item {
|
||||
// Forward signals
|
||||
onPanelOpened: root.opened()
|
||||
onPanelClosed: {
|
||||
root.closed()
|
||||
root.closed();
|
||||
// Destroy the window after close animation completes
|
||||
Qt.callLater(function () {
|
||||
root.windowActive = false
|
||||
})
|
||||
root.windowActive = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,9 +185,9 @@ Item {
|
||||
// Use Qt.callLater to ensure objectName is set by parent before registering
|
||||
Qt.callLater(function () {
|
||||
if (!objectName) {
|
||||
Logger.w("SmartPanel", "Panel created without objectName - PanelService registration may fail")
|
||||
Logger.w("SmartPanel", "Panel created without objectName - PanelService registration may fail");
|
||||
}
|
||||
PanelService.registerPanel(root)
|
||||
})
|
||||
PanelService.registerPanel(root);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@ import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
|
||||
|
||||
/**
|
||||
* SmartPanelWindow - Separate window for panel content
|
||||
*
|
||||
* This component runs in its own window, separate from MainScreen.
|
||||
* It follows the PanelPlaceholder for positioning and contains the actual panel content.
|
||||
*/
|
||||
* SmartPanelWindow - Separate window for panel content
|
||||
*
|
||||
* This component runs in its own window, separate from MainScreen.
|
||||
* It follows the PanelPlaceholder for positioning and contains the actual panel content.
|
||||
*/
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
@@ -63,13 +62,13 @@ PanelWindow {
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (!root.isPanelOpen) {
|
||||
return WlrKeyboardFocus.None
|
||||
return WlrKeyboardFocus.None;
|
||||
}
|
||||
if (CompositorService.isHyprland) {
|
||||
// Exclusive focus on hyprland is too restrictive.
|
||||
return WlrKeyboardFocus.OnDemand
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
} else {
|
||||
return root.exclusiveKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand
|
||||
return root.exclusiveKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,95 +90,95 @@ PanelWindow {
|
||||
|
||||
// Sync state to placeholder
|
||||
onIsPanelVisibleChanged: {
|
||||
placeholder.isPanelVisible = isPanelVisible
|
||||
placeholder.isPanelVisible = isPanelVisible;
|
||||
}
|
||||
onIsClosingChanged: {
|
||||
placeholder.isClosing = isClosing
|
||||
placeholder.isClosing = isClosing;
|
||||
}
|
||||
onOpacityFadeCompleteChanged: {
|
||||
placeholder.opacityFadeComplete = opacityFadeComplete
|
||||
placeholder.opacityFadeComplete = opacityFadeComplete;
|
||||
}
|
||||
|
||||
// Panel control functions
|
||||
function toggle(buttonItem, buttonName) {
|
||||
if (!isPanelOpen) {
|
||||
open(buttonItem, buttonName)
|
||||
open(buttonItem, buttonName);
|
||||
} else {
|
||||
close()
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
function open(buttonItem, buttonName) {
|
||||
if (!buttonItem && buttonName) {
|
||||
buttonItem = BarService.lookupWidget(buttonName, placeholder.screen.name)
|
||||
buttonItem = BarService.lookupWidget(buttonName, placeholder.screen.name);
|
||||
}
|
||||
|
||||
if (buttonItem) {
|
||||
placeholder.buttonItem = buttonItem
|
||||
placeholder.buttonItem = buttonItem;
|
||||
// Map button position to screen coordinates
|
||||
var buttonPos = buttonItem.mapToItem(null, 0, 0)
|
||||
placeholder.buttonPosition = Qt.point(buttonPos.x, buttonPos.y)
|
||||
placeholder.buttonWidth = buttonItem.width
|
||||
placeholder.buttonHeight = buttonItem.height
|
||||
placeholder.useButtonPosition = true
|
||||
var buttonPos = buttonItem.mapToItem(null, 0, 0);
|
||||
placeholder.buttonPosition = Qt.point(buttonPos.x, buttonPos.y);
|
||||
placeholder.buttonWidth = buttonItem.width;
|
||||
placeholder.buttonHeight = buttonItem.height;
|
||||
placeholder.useButtonPosition = true;
|
||||
} else {
|
||||
// No button provided: reset button position mode
|
||||
placeholder.buttonItem = null
|
||||
placeholder.useButtonPosition = false
|
||||
placeholder.buttonItem = null;
|
||||
placeholder.useButtonPosition = false;
|
||||
}
|
||||
|
||||
// Set isPanelOpen to trigger content loading
|
||||
isPanelOpen = true
|
||||
isPanelOpen = true;
|
||||
|
||||
// Notify PanelService
|
||||
PanelService.willOpenPanel(root)
|
||||
PanelService.willOpenPanel(root);
|
||||
}
|
||||
|
||||
function close() {
|
||||
// Start close sequence: fade opacity first
|
||||
isClosing = true
|
||||
sizeAnimationComplete = false
|
||||
closeFinalized = false
|
||||
isClosing = true;
|
||||
sizeAnimationComplete = false;
|
||||
closeFinalized = false;
|
||||
|
||||
// Stop the open animation timer if it's still running
|
||||
opacityTrigger.stop()
|
||||
openWatchdogActive = false
|
||||
openWatchdogTimer.stop()
|
||||
opacityTrigger.stop();
|
||||
openWatchdogActive = false;
|
||||
openWatchdogTimer.stop();
|
||||
|
||||
// Start close watchdog timer
|
||||
closeWatchdogActive = true
|
||||
closeWatchdogTimer.restart()
|
||||
closeWatchdogActive = true;
|
||||
closeWatchdogTimer.restart();
|
||||
|
||||
// If opacity is already 0, skip directly to size animation
|
||||
if (contentWrapper.opacity === 0.0) {
|
||||
opacityFadeComplete = true
|
||||
opacityFadeComplete = true;
|
||||
} else {
|
||||
opacityFadeComplete = false
|
||||
opacityFadeComplete = false;
|
||||
}
|
||||
|
||||
Logger.d("SmartPanelWindow", "Closing panel", placeholder.panelName)
|
||||
Logger.d("SmartPanelWindow", "Closing panel", placeholder.panelName);
|
||||
}
|
||||
|
||||
function finalizeClose() {
|
||||
// Prevent double-finalization
|
||||
if (root.closeFinalized) {
|
||||
Logger.w("SmartPanelWindow", "finalizeClose called but already finalized - ignoring", placeholder.panelName)
|
||||
return
|
||||
Logger.w("SmartPanelWindow", "finalizeClose called but already finalized - ignoring", placeholder.panelName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Complete the close sequence after animations finish
|
||||
root.closeFinalized = true
|
||||
root.closeWatchdogActive = false
|
||||
closeWatchdogTimer.stop()
|
||||
root.closeFinalized = true;
|
||||
root.closeWatchdogActive = false;
|
||||
closeWatchdogTimer.stop();
|
||||
|
||||
root.isPanelVisible = false
|
||||
root.isPanelOpen = false
|
||||
root.isClosing = false
|
||||
root.opacityFadeComplete = false
|
||||
PanelService.closedPanel(root)
|
||||
panelClosed()
|
||||
root.isPanelVisible = false;
|
||||
root.isPanelOpen = false;
|
||||
root.isClosing = false;
|
||||
root.opacityFadeComplete = false;
|
||||
PanelService.closedPanel(root);
|
||||
panelClosed();
|
||||
|
||||
Logger.d("SmartPanelWindow", "Panel close finalized", placeholder.panelName)
|
||||
Logger.d("SmartPanelWindow", "Panel close finalized", placeholder.panelName);
|
||||
}
|
||||
|
||||
// Fullscreen container for click-to-close and content
|
||||
@@ -189,59 +188,59 @@ PanelWindow {
|
||||
|
||||
// Handle keyboard events directly via Keys handler
|
||||
Keys.onPressed: event => {
|
||||
Logger.d("SmartPanelWindow", "Key pressed:", event.key, "for panel:", placeholder.panelName)
|
||||
Logger.d("SmartPanelWindow", "Key pressed:", event.key, "for panel:", placeholder.panelName);
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
panelWrapper.onEscapePressed()
|
||||
panelWrapper.onEscapePressed();
|
||||
if (closeWithEscape) {
|
||||
root.close()
|
||||
event.accepted = true
|
||||
root.close();
|
||||
event.accepted = true;
|
||||
}
|
||||
} else if (panelWrapper) {
|
||||
if (event.key === Qt.Key_Up && panelWrapper.onUpPressed) {
|
||||
panelWrapper.onUpPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onUpPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down && panelWrapper.onDownPressed) {
|
||||
panelWrapper.onDownPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onDownPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Left && panelWrapper.onLeftPressed) {
|
||||
panelWrapper.onLeftPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onLeftPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Right && panelWrapper.onRightPressed) {
|
||||
panelWrapper.onRightPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onRightPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Tab && panelWrapper.onTabPressed) {
|
||||
panelWrapper.onTabPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onTabPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Backtab && panelWrapper.onBackTabPressed) {
|
||||
panelWrapper.onBackTabPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onBackTabPressed();
|
||||
event.accepted = true;
|
||||
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && panelWrapper.onReturnPressed) {
|
||||
panelWrapper.onReturnPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onReturnPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Home && panelWrapper.onHomePressed) {
|
||||
panelWrapper.onHomePressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onHomePressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_End && panelWrapper.onEndPressed) {
|
||||
panelWrapper.onEndPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onEndPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_PageUp && panelWrapper.onPageUpPressed) {
|
||||
panelWrapper.onPageUpPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onPageUpPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_PageDown && panelWrapper.onPageDownPressed) {
|
||||
panelWrapper.onPageDownPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onPageDownPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_J && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlJPressed) {
|
||||
panelWrapper.onCtrlJPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onCtrlJPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_K && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlKPressed) {
|
||||
panelWrapper.onCtrlKPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onCtrlKPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_N && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlNPressed) {
|
||||
panelWrapper.onCtrlNPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onCtrlNPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_P && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlPPressed) {
|
||||
panelWrapper.onCtrlPPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onCtrlPPressed();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,8 +251,8 @@ PanelWindow {
|
||||
enabled: root.isPanelOpen && !root.isClosing
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
root.close()
|
||||
mouse.accepted = true
|
||||
root.close();
|
||||
mouse.accepted = true;
|
||||
}
|
||||
z: 0
|
||||
}
|
||||
@@ -271,10 +270,10 @@ PanelWindow {
|
||||
// Opacity animation
|
||||
opacity: {
|
||||
if (isClosing)
|
||||
return 0.0
|
||||
return 0.0;
|
||||
if (isPanelVisible && sizeAnimationComplete)
|
||||
return 1.0
|
||||
return 0.0
|
||||
return 1.0;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -287,33 +286,33 @@ PanelWindow {
|
||||
// Safety: Zero-duration animation handling
|
||||
if (!running && duration === 0) {
|
||||
if (root.isClosing && contentWrapper.opacity === 0.0) {
|
||||
root.opacityFadeComplete = true
|
||||
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight
|
||||
root.opacityFadeComplete = true;
|
||||
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight;
|
||||
if (shouldFinalizeNow) {
|
||||
Logger.d("SmartPanelWindow", "Zero-duration opacity + no size animation - finalizing", placeholder.panelName)
|
||||
Qt.callLater(root.finalizeClose)
|
||||
Logger.d("SmartPanelWindow", "Zero-duration opacity + no size animation - finalizing", placeholder.panelName);
|
||||
Qt.callLater(root.finalizeClose);
|
||||
}
|
||||
} else if (root.isPanelVisible && contentWrapper.opacity === 1.0) {
|
||||
root.openWatchdogActive = false
|
||||
openWatchdogTimer.stop()
|
||||
root.openWatchdogActive = false;
|
||||
openWatchdogTimer.stop();
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// When opacity fade completes during close, trigger size animation
|
||||
if (!running && root.isClosing && contentWrapper.opacity === 0.0) {
|
||||
root.opacityFadeComplete = true
|
||||
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight
|
||||
root.opacityFadeComplete = true;
|
||||
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight;
|
||||
if (shouldFinalizeNow) {
|
||||
Logger.d("SmartPanelWindow", "No animation - finalizing immediately", placeholder.panelName)
|
||||
Qt.callLater(root.finalizeClose)
|
||||
Logger.d("SmartPanelWindow", "No animation - finalizing immediately", placeholder.panelName);
|
||||
Qt.callLater(root.finalizeClose);
|
||||
} else {
|
||||
Logger.d("SmartPanelWindow", "Animation will run - waiting for size animation", placeholder.panelName)
|
||||
Logger.d("SmartPanelWindow", "Animation will run - waiting for size animation", placeholder.panelName);
|
||||
}
|
||||
} // When opacity fade completes during open, stop watchdog
|
||||
else if (!running && root.isPanelVisible && contentWrapper.opacity === 1.0) {
|
||||
root.openWatchdogActive = false
|
||||
openWatchdogTimer.stop()
|
||||
root.openWatchdogActive = false;
|
||||
openWatchdogTimer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -330,31 +329,31 @@ PanelWindow {
|
||||
onLoaded: {
|
||||
// Capture initial content-driven size if available
|
||||
if (contentLoader.item) {
|
||||
var hasWidthProp = contentLoader.item.hasOwnProperty('contentPreferredWidth')
|
||||
var hasHeightProp = contentLoader.item.hasOwnProperty('contentPreferredHeight')
|
||||
var hasWidthProp = contentLoader.item.hasOwnProperty('contentPreferredWidth');
|
||||
var hasHeightProp = contentLoader.item.hasOwnProperty('contentPreferredHeight');
|
||||
|
||||
if (hasWidthProp || hasHeightProp) {
|
||||
var initialWidth = hasWidthProp ? contentLoader.item.contentPreferredWidth : 0
|
||||
var initialHeight = hasHeightProp ? contentLoader.item.contentPreferredHeight : 0
|
||||
placeholder.updateContentSize(initialWidth, initialHeight)
|
||||
Logger.d("SmartPanelWindow", "Initial content size:", initialWidth, "x", initialHeight, placeholder.panelName)
|
||||
var initialWidth = hasWidthProp ? contentLoader.item.contentPreferredWidth : 0;
|
||||
var initialHeight = hasHeightProp ? contentLoader.item.contentPreferredHeight : 0;
|
||||
placeholder.updateContentSize(initialWidth, initialHeight);
|
||||
Logger.d("SmartPanelWindow", "Initial content size:", initialWidth, "x", initialHeight, placeholder.panelName);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate position in placeholder
|
||||
placeholder.setPosition()
|
||||
placeholder.setPosition();
|
||||
|
||||
// Make panel visible on the next frame
|
||||
Qt.callLater(function () {
|
||||
root.isPanelVisible = true
|
||||
opacityTrigger.start()
|
||||
root.isPanelVisible = true;
|
||||
opacityTrigger.start();
|
||||
|
||||
// Start open watchdog timer
|
||||
root.openWatchdogActive = true
|
||||
openWatchdogTimer.start()
|
||||
root.openWatchdogActive = true;
|
||||
openWatchdogTimer.start();
|
||||
|
||||
panelOpened()
|
||||
})
|
||||
panelOpened();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,7 +362,7 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
mouse.accepted = true // Eat the click to prevent propagation to background
|
||||
mouse.accepted = true; // Eat the click to prevent propagation to background
|
||||
}
|
||||
z: -1 // Behind content but above background click-to-close
|
||||
}
|
||||
@@ -375,13 +374,13 @@ PanelWindow {
|
||||
|
||||
function onContentPreferredWidthChanged() {
|
||||
if (root.isPanelOpen && root.isPanelVisible && contentLoader.item) {
|
||||
placeholder.updateContentSize(contentLoader.item.contentPreferredWidth, placeholder.contentPreferredHeight)
|
||||
placeholder.updateContentSize(contentLoader.item.contentPreferredWidth, placeholder.contentPreferredHeight);
|
||||
}
|
||||
}
|
||||
|
||||
function onContentPreferredHeightChanged() {
|
||||
if (root.isPanelOpen && root.isPanelVisible && contentLoader.item) {
|
||||
placeholder.updateContentSize(placeholder.contentPreferredWidth, contentLoader.item.contentPreferredHeight)
|
||||
placeholder.updateContentSize(placeholder.contentPreferredWidth, contentLoader.item.contentPreferredHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,7 +394,7 @@ PanelWindow {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.isPanelVisible) {
|
||||
root.sizeAnimationComplete = true
|
||||
root.sizeAnimationComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,11 +406,11 @@ PanelWindow {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.openWatchdogActive) {
|
||||
Logger.w("SmartPanelWindow", "Open watchdog timeout - forcing panel visible state", placeholder.panelName)
|
||||
root.openWatchdogActive = false
|
||||
Logger.w("SmartPanelWindow", "Open watchdog timeout - forcing panel visible state", placeholder.panelName);
|
||||
root.openWatchdogActive = false;
|
||||
if (root.isPanelOpen && !root.isPanelVisible) {
|
||||
root.isPanelVisible = true
|
||||
root.sizeAnimationComplete = true
|
||||
root.isPanelVisible = true;
|
||||
root.sizeAnimationComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,8 +423,8 @@ PanelWindow {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.closeWatchdogActive && !root.closeFinalized) {
|
||||
Logger.w("SmartPanelWindow", "Close watchdog timeout - forcing panel close", placeholder.panelName)
|
||||
Qt.callLater(root.finalizeClose)
|
||||
Logger.w("SmartPanelWindow", "Close watchdog timeout - forcing panel close", placeholder.panelName);
|
||||
Qt.callLater(root.finalizeClose);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -437,14 +436,14 @@ PanelWindow {
|
||||
function onWidthChanged() {
|
||||
// When width shrinks to 0 during close and we're animating width, finalize
|
||||
if (root.isClosing && placeholder.panelItem.width === 0 && placeholder.panelItem.shouldAnimateWidth) {
|
||||
Qt.callLater(root.finalizeClose)
|
||||
Qt.callLater(root.finalizeClose);
|
||||
}
|
||||
}
|
||||
|
||||
function onHeightChanged() {
|
||||
// When height shrinks to 0 during close and we're animating height, finalize
|
||||
if (root.isClosing && placeholder.panelItem.height === 0 && placeholder.panelItem.shouldAnimateHeight) {
|
||||
Qt.callLater(root.finalizeClose)
|
||||
Qt.callLater(root.finalizeClose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.UI
|
||||
|
||||
// Separate window for TrayMenu context menus
|
||||
// This is a top-level PanelWindow (sibling to MainScreen, not nested inside it)
|
||||
@@ -30,18 +30,18 @@ PanelWindow {
|
||||
|
||||
// Register with PanelService so panels can find this window
|
||||
Component.onCompleted: {
|
||||
objectName = "trayMenuWindow-" + (screen?.name || "unknown")
|
||||
PanelService.registerTrayMenuWindow(screen, root)
|
||||
objectName = "trayMenuWindow-" + (screen?.name || "unknown");
|
||||
PanelService.registerTrayMenuWindow(screen, root);
|
||||
}
|
||||
|
||||
function open() {
|
||||
visible = true
|
||||
visible = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible = false
|
||||
visible = false;
|
||||
if (trayMenu.item) {
|
||||
trayMenu.item.hideMenu()
|
||||
trayMenu.item.hideMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ PanelWindow {
|
||||
source: Quickshell.shellDir + "/Modules/Bar/Extras/TrayMenu.qml"
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
item.screen = root.screen
|
||||
item.screen = root.screen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
@@ -35,7 +35,7 @@ Variants {
|
||||
target: notificationModel
|
||||
function onCountChanged() {
|
||||
if (notificationModel.count === 0 && root.active) {
|
||||
delayTimer.restart()
|
||||
delayTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,30 +66,30 @@ Variants {
|
||||
// Calculate bar offsets for each edge separately
|
||||
readonly property int barOffsetTop: {
|
||||
if (barPos !== "top")
|
||||
return 0
|
||||
const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0
|
||||
return Style.barHeight + floatMarginV
|
||||
return 0;
|
||||
const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0;
|
||||
return Style.barHeight + floatMarginV;
|
||||
}
|
||||
|
||||
readonly property int barOffsetBottom: {
|
||||
if (barPos !== "bottom")
|
||||
return 0
|
||||
const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0
|
||||
return Style.barHeight + floatMarginV
|
||||
return 0;
|
||||
const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0;
|
||||
return Style.barHeight + floatMarginV;
|
||||
}
|
||||
|
||||
readonly property int barOffsetLeft: {
|
||||
if (barPos !== "left")
|
||||
return 0
|
||||
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
||||
return floatMarginH
|
||||
return 0;
|
||||
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0;
|
||||
return floatMarginH;
|
||||
}
|
||||
|
||||
readonly property int barOffsetRight: {
|
||||
if (barPos !== "right")
|
||||
return 0
|
||||
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
||||
return floatMarginH
|
||||
return 0;
|
||||
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0;
|
||||
return floatMarginH;
|
||||
}
|
||||
|
||||
// Anchoring
|
||||
@@ -111,31 +111,31 @@ Variants {
|
||||
|
||||
Component.onCompleted: {
|
||||
animateConnection = function (notificationId) {
|
||||
var delegate = null
|
||||
var delegate = null;
|
||||
if (notificationRepeater) {
|
||||
for (var i = 0; i < notificationRepeater.count; i++) {
|
||||
var item = notificationRepeater.itemAt(i)
|
||||
var item = notificationRepeater.itemAt(i);
|
||||
if (item?.notificationId === notificationId) {
|
||||
delegate = item
|
||||
break
|
||||
delegate = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (delegate?.animateOut) {
|
||||
delegate.animateOut()
|
||||
delegate.animateOut();
|
||||
} else {
|
||||
NotificationService.dismissActiveNotification(notificationId)
|
||||
NotificationService.dismissActiveNotification(notificationId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NotificationService.animateAndRemove.connect(animateConnection)
|
||||
NotificationService.animateAndRemove.connect(animateConnection);
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (animateConnection) {
|
||||
NotificationService.animateAndRemove.disconnect(animateConnection)
|
||||
animateConnection = null
|
||||
NotificationService.animateAndRemove.disconnect(animateConnection);
|
||||
animateConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,8 +223,8 @@ Variants {
|
||||
width: parent.availableWidth * model.progress
|
||||
|
||||
color: {
|
||||
var baseColor = model.urgency === 2 ? Color.mError : model.urgency === 0 ? Color.mOnSurface : Color.mPrimary
|
||||
return Qt.alpha(baseColor, Settings.data.notifications.backgroundOpacity || 1.0)
|
||||
var baseColor = model.urgency === 2 ? Color.mError : model.urgency === 0 ? Color.mOnSurface : Color.mPrimary;
|
||||
return Qt.alpha(baseColor, Settings.data.notifications.backgroundOpacity || 1.0);
|
||||
}
|
||||
|
||||
antialiasing: true
|
||||
@@ -257,10 +257,10 @@ Variants {
|
||||
// Hover handling
|
||||
onHoverCountChanged: {
|
||||
if (hoverCount > 0) {
|
||||
resumeTimer.stop()
|
||||
NotificationService.pauseTimeout(notificationId)
|
||||
resumeTimer.stop();
|
||||
NotificationService.pauseTimeout(notificationId);
|
||||
} else {
|
||||
resumeTimer.start()
|
||||
resumeTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ Variants {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (hoverCount === 0) {
|
||||
NotificationService.resumeTimeout(notificationId)
|
||||
NotificationService.resumeTimeout(notificationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,30 +284,30 @@ Variants {
|
||||
onExited: parent.hoverCount--
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
animateOut()
|
||||
animateOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animation setup
|
||||
function triggerEntryAnimation() {
|
||||
animInDelayTimer.stop()
|
||||
removalTimer.stop()
|
||||
resumeTimer.stop()
|
||||
isRemoving = false
|
||||
hoverCount = 0
|
||||
animInDelayTimer.stop();
|
||||
removalTimer.stop();
|
||||
resumeTimer.stop();
|
||||
isRemoving = false;
|
||||
hoverCount = 0;
|
||||
if (Settings.data.general.animationDisabled) {
|
||||
slideOffset = 0
|
||||
scaleValue = 1.0
|
||||
opacityValue = 1.0
|
||||
return
|
||||
slideOffset = 0;
|
||||
scaleValue = 1.0;
|
||||
opacityValue = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
slideOffset = slideInOffset
|
||||
scaleValue = 0.8
|
||||
opacityValue = 0.0
|
||||
animInDelayTimer.interval = animationDelay
|
||||
animInDelayTimer.start()
|
||||
slideOffset = slideInOffset;
|
||||
scaleValue = 0.8;
|
||||
opacityValue = 0.0;
|
||||
animInDelayTimer.interval = animationDelay;
|
||||
animInDelayTimer.start();
|
||||
}
|
||||
|
||||
Component.onCompleted: triggerEntryAnimation()
|
||||
@@ -320,23 +320,23 @@ Variants {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (card.isRemoving)
|
||||
return
|
||||
slideOffset = 0
|
||||
scaleValue = 1.0
|
||||
opacityValue = 1.0
|
||||
return;
|
||||
slideOffset = 0;
|
||||
scaleValue = 1.0;
|
||||
opacityValue = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
function animateOut() {
|
||||
if (isRemoving)
|
||||
return
|
||||
animInDelayTimer.stop()
|
||||
resumeTimer.stop()
|
||||
isRemoving = true
|
||||
return;
|
||||
animInDelayTimer.stop();
|
||||
resumeTimer.stop();
|
||||
isRemoving = true;
|
||||
if (!Settings.data.general.animationDisabled) {
|
||||
slideOffset = slideOutOffset
|
||||
scaleValue = 0.8
|
||||
opacityValue = 0.0
|
||||
slideOffset = slideOutOffset;
|
||||
scaleValue = 0.8;
|
||||
opacityValue = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,13 +345,13 @@ Variants {
|
||||
interval: Style.animationSlow
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
NotificationService.dismissActiveNotification(notificationId)
|
||||
NotificationService.dismissActiveNotification(notificationId);
|
||||
}
|
||||
}
|
||||
|
||||
onIsRemovingChanged: {
|
||||
if (isRemoving) {
|
||||
removalTimer.start()
|
||||
removalTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,9 +478,9 @@ Variants {
|
||||
property string parentNotificationId: notificationId
|
||||
property var parsedActions: {
|
||||
try {
|
||||
return model.actionsJson ? JSON.parse(model.actionsJson) : []
|
||||
return model.actionsJson ? JSON.parse(model.actionsJson) : [];
|
||||
} catch (e) {
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
}
|
||||
visible: parsedActions.length > 0
|
||||
@@ -495,11 +495,11 @@ Variants {
|
||||
onExited: card.hoverCount--
|
||||
|
||||
text: {
|
||||
var actionText = actionData.text || "Open"
|
||||
var actionText = actionData.text || "Open";
|
||||
if (actionText.includes(",")) {
|
||||
return actionText.split(",")[1] || actionText
|
||||
return actionText.split(",")[1] || actionText;
|
||||
}
|
||||
return actionText
|
||||
return actionText;
|
||||
}
|
||||
fontSize: Style.fontSizeS
|
||||
backgroundColor: Color.mPrimary
|
||||
@@ -508,7 +508,7 @@ Variants {
|
||||
outlined: false
|
||||
implicitHeight: 24
|
||||
onClicked: {
|
||||
NotificationService.invokeAction(parent.parentNotificationId, actionData.identifier)
|
||||
NotificationService.invokeAction(parent.parentNotificationId, actionData.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,8 +528,8 @@ Variants {
|
||||
anchors.rightMargin: Style.marginXL
|
||||
|
||||
onClicked: {
|
||||
NotificationService.removeFromHistory(model.id)
|
||||
animateOut()
|
||||
NotificationService.removeFromHistory(model.id);
|
||||
animateOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.Pipewire
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Media
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
// Unified OSD component that displays volume, input volume, and brightness changes
|
||||
Variants {
|
||||
@@ -52,54 +52,54 @@ Variants {
|
||||
switch (currentOSDType) {
|
||||
case "volume":
|
||||
if (isMuted)
|
||||
return "volume-mute"
|
||||
return "volume-mute";
|
||||
if (currentVolume <= Number.EPSILON)
|
||||
return "volume-zero"
|
||||
return currentVolume <= 0.5 ? "volume-low" : "volume-high"
|
||||
return "volume-zero";
|
||||
return currentVolume <= 0.5 ? "volume-low" : "volume-high";
|
||||
case "inputVolume":
|
||||
return isInputMuted ? "microphone-off" : "microphone"
|
||||
return isInputMuted ? "microphone-off" : "microphone";
|
||||
case "brightness":
|
||||
return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high"
|
||||
return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high";
|
||||
default:
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentValue() {
|
||||
switch (currentOSDType) {
|
||||
case "volume":
|
||||
return isMuted ? 0 : currentVolume
|
||||
return isMuted ? 0 : currentVolume;
|
||||
case "inputVolume":
|
||||
return isInputMuted ? 0 : currentInputVolume
|
||||
return isInputMuted ? 0 : currentInputVolume;
|
||||
case "brightness":
|
||||
return currentBrightness
|
||||
return currentBrightness;
|
||||
default:
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function getMaxValue() {
|
||||
if (currentOSDType === "volume" || currentOSDType === "inputVolume") {
|
||||
return Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
|
||||
return Settings.data.audio.volumeOverdrive ? 1.5 : 1.0;
|
||||
}
|
||||
return 1.0
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
function getDisplayPercentage() {
|
||||
const value = getCurrentValue()
|
||||
const max = getMaxValue()
|
||||
const pct = Math.round(Math.min(max, value) * 100)
|
||||
return pct + "%"
|
||||
const value = getCurrentValue();
|
||||
const max = getMaxValue();
|
||||
const pct = Math.round(Math.min(max, value) * 100);
|
||||
return pct + "%";
|
||||
}
|
||||
|
||||
function getProgressColor() {
|
||||
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted)
|
||||
return isMutedState ? Color.mError : Color.mPrimary
|
||||
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted);
|
||||
return isMutedState ? Color.mError : Color.mPrimary;
|
||||
}
|
||||
|
||||
function getIconColor() {
|
||||
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted)
|
||||
return isMutedState ? Color.mError : Color.mOnSurface
|
||||
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted);
|
||||
return isMutedState ? Color.mError : Color.mOnSurface;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -108,35 +108,35 @@ Variants {
|
||||
function initializeAudioValues() {
|
||||
// Initialize output volume
|
||||
if (AudioService.sink?.ready && AudioService.sink?.audio && lastKnownVolume < 0) {
|
||||
const vol = AudioService.volume
|
||||
const vol = AudioService.volume;
|
||||
if (vol !== undefined && !isNaN(vol)) {
|
||||
lastKnownVolume = vol
|
||||
volumeInitialized = true
|
||||
muteInitialized = true
|
||||
lastKnownVolume = vol;
|
||||
volumeInitialized = true;
|
||||
muteInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize input volume
|
||||
if (AudioService.hasInput && AudioService.source?.ready && AudioService.source?.audio && lastKnownInputVolume < 0) {
|
||||
const inputVol = AudioService.inputVolume
|
||||
const inputVol = AudioService.inputVolume;
|
||||
if (inputVol !== undefined && !isNaN(inputVol)) {
|
||||
lastKnownInputVolume = inputVol
|
||||
inputInitialized = true
|
||||
lastKnownInputVolume = inputVol;
|
||||
inputInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetOutputInit() {
|
||||
lastKnownVolume = -1
|
||||
volumeInitialized = false
|
||||
muteInitialized = false
|
||||
Qt.callLater(initializeAudioValues)
|
||||
lastKnownVolume = -1;
|
||||
volumeInitialized = false;
|
||||
muteInitialized = false;
|
||||
Qt.callLater(initializeAudioValues);
|
||||
}
|
||||
|
||||
function resetInputInit() {
|
||||
lastKnownInputVolume = -1
|
||||
inputInitialized = false
|
||||
Qt.callLater(initializeAudioValues)
|
||||
lastKnownInputVolume = -1;
|
||||
inputInitialized = false;
|
||||
Qt.callLater(initializeAudioValues);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -144,48 +144,48 @@ Variants {
|
||||
// ============================================================================
|
||||
function connectBrightnessMonitors() {
|
||||
for (var i = 0; i < BrightnessService.monitors.length; i++) {
|
||||
const monitor = BrightnessService.monitors[i]
|
||||
monitor.brightnessUpdated.disconnect(onBrightnessChanged)
|
||||
monitor.brightnessUpdated.connect(onBrightnessChanged)
|
||||
const monitor = BrightnessService.monitors[i];
|
||||
monitor.brightnessUpdated.disconnect(onBrightnessChanged);
|
||||
monitor.brightnessUpdated.connect(onBrightnessChanged);
|
||||
}
|
||||
}
|
||||
|
||||
function onBrightnessChanged(newBrightness) {
|
||||
lastUpdatedBrightness = newBrightness
|
||||
lastUpdatedBrightness = newBrightness;
|
||||
|
||||
if (!brightnessInitialized) {
|
||||
brightnessInitialized = true
|
||||
return
|
||||
brightnessInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
showOSD("brightness")
|
||||
showOSD("brightness");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OSD Display Control
|
||||
// ============================================================================
|
||||
function showOSD(type) {
|
||||
currentOSDType = type
|
||||
currentOSDType = type;
|
||||
|
||||
if (!root.active) {
|
||||
root.active = true
|
||||
root.active = true;
|
||||
}
|
||||
|
||||
if (root.item) {
|
||||
root.item.showOSD()
|
||||
root.item.showOSD();
|
||||
} else {
|
||||
Qt.callLater(() => {
|
||||
if (root.item)
|
||||
root.item.showOSD()
|
||||
})
|
||||
root.item.showOSD();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function hideOSD() {
|
||||
if (root.item?.osdItem) {
|
||||
root.item.osdItem.hideImmediately()
|
||||
root.item.osdItem.hideImmediately();
|
||||
} else if (root.active) {
|
||||
root.active = false
|
||||
root.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,15 +199,15 @@ Variants {
|
||||
|
||||
function onReadyChanged() {
|
||||
if (Pipewire.ready)
|
||||
Qt.callLater(initializeAudioValues)
|
||||
Qt.callLater(initializeAudioValues);
|
||||
}
|
||||
|
||||
function onDefaultAudioSinkChanged() {
|
||||
resetOutputInit()
|
||||
resetOutputInit();
|
||||
}
|
||||
|
||||
function onDefaultAudioSourceChanged() {
|
||||
resetInputInit()
|
||||
resetInputInit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,72 +217,68 @@ Variants {
|
||||
|
||||
function onSinkChanged() {
|
||||
if (AudioService.sink?.ready && AudioService.sink?.audio) {
|
||||
resetOutputInit()
|
||||
resetOutputInit();
|
||||
}
|
||||
}
|
||||
|
||||
function onSourceChanged() {
|
||||
if (AudioService.hasInput && AudioService.source?.ready && AudioService.source?.audio) {
|
||||
resetInputInit()
|
||||
resetInputInit();
|
||||
}
|
||||
}
|
||||
|
||||
function onVolumeChanged() {
|
||||
if (lastKnownVolume < 0) {
|
||||
initializeAudioValues()
|
||||
initializeAudioValues();
|
||||
if (lastKnownVolume < 0)
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!volumeInitialized)
|
||||
return
|
||||
|
||||
return;
|
||||
if (Math.abs(AudioService.volume - lastKnownVolume) > 0.001) {
|
||||
lastKnownVolume = AudioService.volume
|
||||
showOSD("volume")
|
||||
lastKnownVolume = AudioService.volume;
|
||||
showOSD("volume");
|
||||
}
|
||||
}
|
||||
|
||||
function onMutedChanged() {
|
||||
if (lastKnownVolume < 0) {
|
||||
initializeAudioValues()
|
||||
initializeAudioValues();
|
||||
if (lastKnownVolume < 0)
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!muteInitialized)
|
||||
return
|
||||
showOSD("volume")
|
||||
return;
|
||||
showOSD("volume");
|
||||
}
|
||||
|
||||
function onInputVolumeChanged() {
|
||||
if (!AudioService.hasInput)
|
||||
return
|
||||
|
||||
return;
|
||||
if (lastKnownInputVolume < 0) {
|
||||
initializeAudioValues()
|
||||
initializeAudioValues();
|
||||
if (lastKnownInputVolume < 0)
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!inputInitialized)
|
||||
return
|
||||
|
||||
return;
|
||||
if (Math.abs(AudioService.inputVolume - lastKnownInputVolume) > 0.001) {
|
||||
lastKnownInputVolume = AudioService.inputVolume
|
||||
showOSD("inputVolume")
|
||||
lastKnownInputVolume = AudioService.inputVolume;
|
||||
showOSD("inputVolume");
|
||||
}
|
||||
}
|
||||
|
||||
function onInputMutedChanged() {
|
||||
if (!AudioService.hasInput)
|
||||
return
|
||||
|
||||
return;
|
||||
if (lastKnownInputVolume < 0) {
|
||||
initializeAudioValues()
|
||||
initializeAudioValues();
|
||||
if (lastKnownInputVolume < 0)
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!inputInitialized)
|
||||
return
|
||||
showOSD("inputVolume")
|
||||
return;
|
||||
showOSD("inputVolume");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +286,7 @@ Variants {
|
||||
Connections {
|
||||
target: BrightnessService
|
||||
function onMonitorsChanged() {
|
||||
connectBrightnessMonitors()
|
||||
connectBrightnessMonitors();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,9 +297,9 @@ Variants {
|
||||
running: true
|
||||
onTriggered: {
|
||||
if (Pipewire.ready)
|
||||
initializeAudioValues()
|
||||
muteInitialized = true
|
||||
connectBrightnessMonitors()
|
||||
initializeAudioValues();
|
||||
muteInitialized = true;
|
||||
connectBrightnessMonitors();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,22 +310,21 @@ Variants {
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (!Pipewire.ready)
|
||||
return
|
||||
|
||||
const needsOutputInit = lastKnownVolume < 0
|
||||
const needsInputInit = AudioService.hasInput && lastKnownInputVolume < 0
|
||||
return;
|
||||
const needsOutputInit = lastKnownVolume < 0;
|
||||
const needsInputInit = AudioService.hasInput && lastKnownInputVolume < 0;
|
||||
|
||||
if (needsOutputInit || needsInputInit) {
|
||||
initializeAudioValues()
|
||||
initializeAudioValues();
|
||||
|
||||
// Stop timer if both are initialized
|
||||
const outputDone = lastKnownVolume >= 0
|
||||
const inputDone = !AudioService.hasInput || lastKnownInputVolume >= 0
|
||||
const outputDone = lastKnownVolume >= 0;
|
||||
const inputDone = !AudioService.hasInput || lastKnownInputVolume >= 0;
|
||||
if (outputDone && inputDone) {
|
||||
running = false
|
||||
running = false;
|
||||
}
|
||||
} else {
|
||||
running = false
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,8 +350,8 @@ Variants {
|
||||
readonly property int vWidth: Math.round(80 * Style.uiScaleRatio)
|
||||
readonly property int vHeight: Math.round(280 * Style.uiScaleRatio)
|
||||
readonly property int barThickness: {
|
||||
const base = Math.max(8, Math.round(8 * Style.uiScaleRatio))
|
||||
return base % 2 === 0 ? base : base + 1
|
||||
const base = Math.max(8, Math.round(8 * Style.uiScaleRatio));
|
||||
return base % 2 === 0 ? base : base + 1;
|
||||
}
|
||||
|
||||
anchors.top: isTop
|
||||
@@ -366,15 +361,15 @@ Variants {
|
||||
|
||||
function calculateMargin(isAnchored, position) {
|
||||
if (!isAnchored)
|
||||
return 0
|
||||
return 0;
|
||||
|
||||
let base = Style.marginM
|
||||
let base = Style.marginM;
|
||||
if (Settings.data.bar.position === position) {
|
||||
const isVertical = position === "top" || position === "bottom"
|
||||
const floatExtra = Settings.data.bar.floating ? (isVertical ? Settings.data.bar.marginVertical : Settings.data.bar.marginHorizontal) * Style.marginXL : 0
|
||||
return Style.barHeight + base + floatExtra
|
||||
const isVertical = position === "top" || position === "bottom";
|
||||
const floatExtra = Settings.data.bar.floating ? (isVertical ? Settings.data.bar.marginVertical : Settings.data.bar.marginHorizontal) * Style.marginXL : 0;
|
||||
return Style.barHeight + base + floatExtra;
|
||||
}
|
||||
return base
|
||||
return base;
|
||||
}
|
||||
|
||||
margins.top: calculateMargin(anchors.top, "top")
|
||||
@@ -422,9 +417,9 @@ Variants {
|
||||
id: visibilityTimer
|
||||
interval: Style.animationNormal + 50
|
||||
onTriggered: {
|
||||
osdItem.visible = false
|
||||
root.currentOSDType = ""
|
||||
root.active = false
|
||||
osdItem.visible = false;
|
||||
root.currentOSDType = "";
|
||||
root.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,8 +431,8 @@ Variants {
|
||||
color: Qt.alpha(Color.mSurface, Settings.data.osd.backgroundOpacity || 1.0)
|
||||
border.color: Qt.alpha(Color.mOutline, Settings.data.osd.backgroundOpacity || 1.0)
|
||||
border.width: {
|
||||
const bw = Math.max(2, Style.borderM)
|
||||
return bw % 2 === 0 ? bw : bw + 1
|
||||
const bw = Math.max(2, Style.borderM);
|
||||
return bw % 2 === 0 ? bw : bw + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,39 +603,39 @@ Variants {
|
||||
}
|
||||
|
||||
function show() {
|
||||
hideTimer.stop()
|
||||
visibilityTimer.stop()
|
||||
osdItem.visible = true
|
||||
hideTimer.stop();
|
||||
visibilityTimer.stop();
|
||||
osdItem.visible = true;
|
||||
|
||||
Qt.callLater(() => {
|
||||
osdItem.opacity = 1
|
||||
osdItem.scale = 1.0
|
||||
})
|
||||
osdItem.opacity = 1;
|
||||
osdItem.scale = 1.0;
|
||||
});
|
||||
|
||||
hideTimer.start()
|
||||
hideTimer.start();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
hideTimer.stop()
|
||||
visibilityTimer.stop()
|
||||
osdItem.opacity = 0
|
||||
osdItem.scale = 0.85
|
||||
visibilityTimer.start()
|
||||
hideTimer.stop();
|
||||
visibilityTimer.stop();
|
||||
osdItem.opacity = 0;
|
||||
osdItem.scale = 0.85;
|
||||
visibilityTimer.start();
|
||||
}
|
||||
|
||||
function hideImmediately() {
|
||||
hideTimer.stop()
|
||||
visibilityTimer.stop()
|
||||
osdItem.opacity = 0
|
||||
osdItem.scale = 0.85
|
||||
osdItem.visible = false
|
||||
root.currentOSDType = ""
|
||||
root.active = false
|
||||
hideTimer.stop();
|
||||
visibilityTimer.stop();
|
||||
osdItem.opacity = 0;
|
||||
osdItem.scale = 0.85;
|
||||
osdItem.visible = false;
|
||||
root.currentOSDType = "";
|
||||
root.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
function showOSD() {
|
||||
osdItem.show()
|
||||
osdItem.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.Media
|
||||
import qs.Widgets
|
||||
|
||||
SmartPanel {
|
||||
id: root
|
||||
@@ -25,7 +25,7 @@ SmartPanel {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localOutputVolumeChanging) {
|
||||
localOutputVolume = AudioService.volume
|
||||
localOutputVolume = AudioService.volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ SmartPanel {
|
||||
target: AudioService.source?.audio ? AudioService.source?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localInputVolumeChanging) {
|
||||
localInputVolume = AudioService.inputVolume
|
||||
localInputVolume = AudioService.inputVolume;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,10 +46,10 @@ SmartPanel {
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localOutputVolume)
|
||||
AudioService.setVolume(localOutputVolume);
|
||||
}
|
||||
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
|
||||
AudioService.setInputVolume(localInputVolume)
|
||||
AudioService.setInputVolume(localInputVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.output-muted")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
AudioService.setOutputMuted(!AudioService.muted)
|
||||
AudioService.setOutputMuted(!AudioService.muted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.input-muted")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
AudioService.setInputMuted(!AudioService.inputMuted)
|
||||
AudioService.setInputMuted(!AudioService.inputMuted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.close")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
root.close()
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,8 +179,8 @@ SmartPanel {
|
||||
text: modelData.description
|
||||
checked: AudioService.sink?.id === modelData.id
|
||||
onClicked: {
|
||||
AudioService.setAudioSink(modelData)
|
||||
localOutputVolume = AudioService.volume
|
||||
AudioService.setAudioSink(modelData);
|
||||
localOutputVolume = AudioService.volume;
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.Hardware
|
||||
import qs.Widgets
|
||||
import qs.Modules.MainScreen
|
||||
|
||||
SmartPanel {
|
||||
id: root
|
||||
@@ -14,21 +14,25 @@ SmartPanel {
|
||||
property var optionsModel: []
|
||||
|
||||
function updateOptionsModel() {
|
||||
let newOptions = [{
|
||||
"id": BatteryService.ChargingMode.Full,
|
||||
"label": "battery.panel.full"
|
||||
}, {
|
||||
"id": BatteryService.ChargingMode.Balanced,
|
||||
"label": "battery.panel.balanced"
|
||||
}, {
|
||||
"id": BatteryService.ChargingMode.Lifespan,
|
||||
"label": "battery.panel.lifespan"
|
||||
}]
|
||||
root.optionsModel = newOptions
|
||||
let newOptions = [
|
||||
{
|
||||
"id": BatteryService.ChargingMode.Full,
|
||||
"label": "battery.panel.full"
|
||||
},
|
||||
{
|
||||
"id": BatteryService.ChargingMode.Balanced,
|
||||
"label": "battery.panel.balanced"
|
||||
},
|
||||
{
|
||||
"id": BatteryService.ChargingMode.Lifespan,
|
||||
"label": "battery.panel.lifespan"
|
||||
}
|
||||
];
|
||||
root.optionsModel = newOptions;
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
updateOptionsModel()
|
||||
updateOptionsModel();
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
@@ -49,7 +53,7 @@ SmartPanel {
|
||||
})
|
||||
checked: BatteryService.chargingMode === modelData.id
|
||||
onClicked: {
|
||||
BatteryService.setChargingMode(modelData.id)
|
||||
BatteryService.setChargingMode(modelData.id);
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
@@ -131,7 +135,7 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.close")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
root.close()
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import Quickshell.Wayland
|
||||
@@ -13,9 +13,7 @@ NBox {
|
||||
|
||||
property string label: ""
|
||||
property string tooltipText: ""
|
||||
property var model: {
|
||||
|
||||
}
|
||||
property var model: {}
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: column.implicitHeight + Style.marginM * 2
|
||||
@@ -52,10 +50,10 @@ NBox {
|
||||
|
||||
function getContentColor(defaultColor = Color.mOnSurface) {
|
||||
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
if (modelData.blocked)
|
||||
return Color.mError
|
||||
return defaultColor
|
||||
return Color.mError;
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -154,33 +152,33 @@ NBox {
|
||||
fontWeight: Style.fontWeightMedium
|
||||
backgroundColor: {
|
||||
if (device.canDisconnect && !isBusy) {
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
}
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
}
|
||||
tooltipText: root.tooltipText
|
||||
text: {
|
||||
if (modelData.pairing) {
|
||||
return I18n.tr("bluetooth.panel.pairing")
|
||||
return I18n.tr("bluetooth.panel.pairing");
|
||||
}
|
||||
if (modelData.blocked) {
|
||||
return I18n.tr("bluetooth.panel.blocked")
|
||||
return I18n.tr("bluetooth.panel.blocked");
|
||||
}
|
||||
if (modelData.connected) {
|
||||
return I18n.tr("bluetooth.panel.disconnect")
|
||||
return I18n.tr("bluetooth.panel.disconnect");
|
||||
}
|
||||
return I18n.tr("bluetooth.panel.connect")
|
||||
return I18n.tr("bluetooth.panel.connect");
|
||||
}
|
||||
icon: (isBusy ? "busy" : null)
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
BluetoothService.disconnectDevice(modelData)
|
||||
BluetoothService.disconnectDevice(modelData);
|
||||
} else {
|
||||
BluetoothService.connectDeviceWithTrust(modelData)
|
||||
BluetoothService.connectDeviceWithTrust(modelData);
|
||||
}
|
||||
}
|
||||
onRightClicked: {
|
||||
BluetoothService.forgetDevice(modelData)
|
||||
BluetoothService.forgetDevice(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.Networking
|
||||
import qs.Widgets
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.Networking
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
SmartPanel {
|
||||
id: root
|
||||
@@ -65,7 +65,7 @@ SmartPanel {
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
if (BluetoothService.adapter) {
|
||||
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering
|
||||
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.close")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
root.close()
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,9 +143,9 @@ SmartPanel {
|
||||
label: I18n.tr("bluetooth.panel.connected-devices")
|
||||
property var items: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return []
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && dev.connected)
|
||||
return BluetoothService.sortDevices(filtered)
|
||||
return [];
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && dev.connected);
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
model: items
|
||||
visible: items.length > 0
|
||||
@@ -158,9 +158,9 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.connect-disconnect-devices")
|
||||
property var items: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return []
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted))
|
||||
return BluetoothService.sortDevices(filtered)
|
||||
return [];
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted));
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
model: items
|
||||
visible: items.length > 0
|
||||
@@ -172,9 +172,9 @@ SmartPanel {
|
||||
label: I18n.tr("bluetooth.panel.available-devices")
|
||||
property var items: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return []
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.paired && !dev.trusted)
|
||||
return BluetoothService.sortDevices(filtered)
|
||||
return [];
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.paired && !dev.trusted);
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
model: items
|
||||
visible: items.length > 0
|
||||
@@ -187,13 +187,13 @@ SmartPanel {
|
||||
Layout.preferredHeight: columnScanning.implicitHeight + Style.marginM * 2
|
||||
visible: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
var availableCount = Bluetooth.devices.values.filter(dev => {
|
||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0)
|
||||
}).length
|
||||
return (availableCount === 0)
|
||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
||||
}).length;
|
||||
return (availableCount === 0);
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
@@ -21,22 +21,22 @@ SmartPanel {
|
||||
|
||||
// Helper function to calculate ISO week number
|
||||
function getISOWeekNumber(date) {
|
||||
const target = new Date(date.valueOf())
|
||||
const dayNr = (date.getDay() + 6) % 7
|
||||
target.setDate(target.getDate() - dayNr + 3)
|
||||
const firstThursday = new Date(target.getFullYear(), 0, 4)
|
||||
const diff = target - firstThursday
|
||||
const oneWeek = 1000 * 60 * 60 * 24 * 7
|
||||
const weekNumber = 1 + Math.round(diff / oneWeek)
|
||||
return weekNumber
|
||||
const target = new Date(date.valueOf());
|
||||
const dayNr = (date.getDay() + 6) % 7;
|
||||
target.setDate(target.getDate() - dayNr + 3);
|
||||
const firstThursday = new Date(target.getFullYear(), 0, 4);
|
||||
const diff = target - firstThursday;
|
||||
const oneWeek = 1000 * 60 * 60 * 24 * 7;
|
||||
const weekNumber = 1 + Math.round(diff / oneWeek);
|
||||
return weekNumber;
|
||||
}
|
||||
|
||||
// Helper function to check if an event is all-day
|
||||
function isAllDayEvent(event) {
|
||||
const duration = event.end - event.start
|
||||
const startDate = new Date(event.start * 1000)
|
||||
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0
|
||||
return duration === 86400 && isAtMidnight
|
||||
const duration = event.end - event.start;
|
||||
const startDate = new Date(event.start * 1000);
|
||||
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0;
|
||||
return duration === 86400 && isAtMidnight;
|
||||
}
|
||||
|
||||
panelContent: Item {
|
||||
@@ -50,12 +50,12 @@ SmartPanel {
|
||||
|
||||
property real calendarGridHeight: {
|
||||
// Calculate number of weeks in the calendar grid
|
||||
const numWeeks = grid.daysModel ? Math.ceil(grid.daysModel.length / 7) : 5
|
||||
const numWeeks = grid.daysModel ? Math.ceil(grid.daysModel.length / 7) : 5;
|
||||
|
||||
// Calendar grid height (dynamic based on number of weeks)
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
|
||||
|
||||
return numWeeks * rowHeight
|
||||
return numWeeks * rowHeight;
|
||||
}
|
||||
ColumnLayout {
|
||||
id: content
|
||||
@@ -69,17 +69,17 @@ SmartPanel {
|
||||
readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null)
|
||||
|
||||
function checkIsCurrentMonth() {
|
||||
return (now.getMonth() === grid.month) && (now.getFullYear() === grid.year)
|
||||
return (now.getMonth() === grid.month) && (now.getFullYear() === grid.year);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
isCurrentMonth = checkIsCurrentMonth()
|
||||
isCurrentMonth = checkIsCurrentMonth();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Time
|
||||
function onNowChanged() {
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth()
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ SmartPanel {
|
||||
target: I18n
|
||||
function onLanguageChanged() {
|
||||
// Force update of day names when language changes
|
||||
grid.month = grid.month
|
||||
grid.month = grid.month;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,11 +180,11 @@ SmartPanel {
|
||||
NText {
|
||||
text: {
|
||||
if (!Settings.data.location.weatherEnabled)
|
||||
return ""
|
||||
return "";
|
||||
if (!content.weatherReady)
|
||||
return I18n.tr("calendar.weather.loading")
|
||||
const chunks = Settings.data.location.name.split(",")
|
||||
return chunks[0]
|
||||
return I18n.tr("calendar.weather.loading");
|
||||
const chunks = Settings.data.location.name.split(",");
|
||||
return chunks[0];
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightMedium
|
||||
@@ -227,11 +227,11 @@ SmartPanel {
|
||||
id: calendar
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: {
|
||||
const navigationHeight = Style.baseWidgetSize // Navigation buttons row
|
||||
const dayNamesHeight = Style.baseWidgetSize * 0.6 // Day names header row
|
||||
const innerMargins = Style.marginM * 2 // Top and bottom margins inside NBox
|
||||
const innerSpacing = Style.marginS * 2 // Spacing between nav, dayNames, and grid (2 gaps)
|
||||
return navigationHeight + dayNamesHeight + calendarGridHeight + innerMargins + innerSpacing
|
||||
const navigationHeight = Style.baseWidgetSize; // Navigation buttons row
|
||||
const dayNamesHeight = Style.baseWidgetSize * 0.6; // Day names header row
|
||||
const innerMargins = Style.marginM * 2; // Top and bottom margins inside NBox
|
||||
const innerSpacing = Style.marginS * 2; // Spacing between nav, dayNames, and grid (2 gaps)
|
||||
return navigationHeight + dayNamesHeight + calendarGridHeight + innerMargins + innerSpacing;
|
||||
}
|
||||
|
||||
Behavior on Layout.preferredWidth {
|
||||
@@ -257,46 +257,46 @@ SmartPanel {
|
||||
NIconButton {
|
||||
icon: "chevron-left"
|
||||
onClicked: {
|
||||
let newDate = new Date(grid.year, grid.month - 1, 1)
|
||||
grid.year = newDate.getFullYear()
|
||||
grid.month = newDate.getMonth()
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth()
|
||||
const now = new Date()
|
||||
const monthStart = new Date(grid.year, grid.month, 1)
|
||||
const monthEnd = new Date(grid.year, grid.month + 1, 0)
|
||||
let newDate = new Date(grid.year, grid.month - 1, 1);
|
||||
grid.year = newDate.getFullYear();
|
||||
grid.month = newDate.getMonth();
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth();
|
||||
const now = new Date();
|
||||
const monthStart = new Date(grid.year, grid.month, 1);
|
||||
const monthEnd = new Date(grid.year, grid.month + 1, 0);
|
||||
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)))
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)))
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
|
||||
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30)
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "calendar"
|
||||
onClicked: {
|
||||
grid.month = now.getMonth()
|
||||
grid.year = now.getFullYear()
|
||||
content.isCurrentMonth = true
|
||||
CalendarService.loadEvents()
|
||||
grid.month = now.getMonth();
|
||||
grid.year = now.getFullYear();
|
||||
content.isCurrentMonth = true;
|
||||
CalendarService.loadEvents();
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "chevron-right"
|
||||
onClicked: {
|
||||
let newDate = new Date(grid.year, grid.month + 1, 1)
|
||||
grid.year = newDate.getFullYear()
|
||||
grid.month = newDate.getMonth()
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth()
|
||||
const now = new Date()
|
||||
const monthStart = new Date(grid.year, grid.month, 1)
|
||||
const monthEnd = new Date(grid.year, grid.month + 1, 0)
|
||||
let newDate = new Date(grid.year, grid.month + 1, 1);
|
||||
grid.year = newDate.getFullYear();
|
||||
grid.month = newDate.getMonth();
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth();
|
||||
const now = new Date();
|
||||
const monthStart = new Date(grid.year, grid.month, 1);
|
||||
const monthEnd = new Date(grid.year, grid.month + 1, 0);
|
||||
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)))
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)))
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
|
||||
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30)
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,9 +324,9 @@ SmartPanel {
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
let dayIndex = (content.firstDayOfWeek + index) % 7
|
||||
const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat)
|
||||
return dayName.substring(0, 2).toUpperCase()
|
||||
let dayIndex = (content.firstDayOfWeek + index) % 7;
|
||||
const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat);
|
||||
return dayName.substring(0, 2).toUpperCase();
|
||||
}
|
||||
color: Color.mPrimary
|
||||
pointSize: Style.fontSizeS
|
||||
@@ -345,55 +345,55 @@ SmartPanel {
|
||||
// Helper function to check if a date has events
|
||||
function hasEventsOnDate(year, month, day) {
|
||||
if (!CalendarService.available || CalendarService.events.length === 0)
|
||||
return false
|
||||
return false;
|
||||
|
||||
const targetDate = new Date(year, month, day)
|
||||
const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000
|
||||
const targetEnd = targetStart + 86400 // +24 hours
|
||||
const targetDate = new Date(year, month, day);
|
||||
const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000;
|
||||
const targetEnd = targetStart + 86400; // +24 hours
|
||||
|
||||
return CalendarService.events.some(event => {
|
||||
// Check if event starts or overlaps with this day
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd)
|
||||
})
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to get events for a specific date
|
||||
function getEventsForDate(year, month, day) {
|
||||
if (!CalendarService.available || CalendarService.events.length === 0)
|
||||
return []
|
||||
return [];
|
||||
|
||||
const targetDate = new Date(year, month, day)
|
||||
const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000)
|
||||
const targetEnd = targetStart + 86400 // +24 hours
|
||||
const targetDate = new Date(year, month, day);
|
||||
const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000);
|
||||
const targetEnd = targetStart + 86400; // +24 hours
|
||||
|
||||
return CalendarService.events.filter(event => {
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd)
|
||||
})
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to check if an event is multi-day
|
||||
function isMultiDayEvent(event) {
|
||||
if (isAllDayEvent(event)) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
const startDate = new Date(event.start * 1000)
|
||||
const endDate = new Date(event.end * 1000)
|
||||
const startDate = new Date(event.start * 1000);
|
||||
const endDate = new Date(event.end * 1000);
|
||||
|
||||
const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
|
||||
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate())
|
||||
const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
|
||||
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
return startDateOnly.getTime() !== endDateOnly.getTime()
|
||||
return startDateOnly.getTime() !== endDateOnly.getTime();
|
||||
}
|
||||
|
||||
// Helper function to get color for a specific event
|
||||
function getEventColor(event, isToday) {
|
||||
if (isMultiDayEvent(event)) {
|
||||
return isToday ? Color.mOnSecondary : Color.mTertiary
|
||||
return isToday ? Color.mOnSecondary : Color.mTertiary;
|
||||
} else if (isAllDayEvent(event)) {
|
||||
return isToday ? Color.mOnSecondary : Color.mSecondary
|
||||
return isToday ? Color.mOnSecondary : Color.mSecondary;
|
||||
} else {
|
||||
return isToday ? Color.mOnSecondary : Color.mPrimary
|
||||
return isToday ? Color.mOnSecondary : Color.mPrimary;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,9 +402,9 @@ SmartPanel {
|
||||
visible: Settings.data.location.showWeekNumberInCalendar
|
||||
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
|
||||
Layout.preferredHeight: {
|
||||
const numWeeks = weekNumbers ? weekNumbers.length : 5
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS
|
||||
return numWeeks * rowHeight
|
||||
const numWeeks = weekNumbers ? weekNumbers.length : 5;
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
|
||||
return numWeeks * rowHeight;
|
||||
}
|
||||
spacing: Style.marginXXS
|
||||
|
||||
@@ -417,33 +417,33 @@ SmartPanel {
|
||||
|
||||
property var weekNumbers: {
|
||||
if (!grid.daysModel || grid.daysModel.length === 0)
|
||||
return []
|
||||
return [];
|
||||
|
||||
const weeks = []
|
||||
const numWeeks = Math.ceil(grid.daysModel.length / 7)
|
||||
const weeks = [];
|
||||
const numWeeks = Math.ceil(grid.daysModel.length / 7);
|
||||
|
||||
for (var i = 0; i < numWeeks; i++) {
|
||||
const dayIndex = i * 7
|
||||
const dayIndex = i * 7;
|
||||
if (dayIndex < grid.daysModel.length) {
|
||||
const weekDay = grid.daysModel[dayIndex]
|
||||
const date = new Date(weekDay.year, weekDay.month, weekDay.day)
|
||||
const weekDay = grid.daysModel[dayIndex];
|
||||
const date = new Date(weekDay.year, weekDay.month, weekDay.day);
|
||||
|
||||
// Get Thursday of this week for ISO week calculation
|
||||
const firstDayOfWeek = content.firstDayOfWeek
|
||||
let thursday = new Date(date)
|
||||
const firstDayOfWeek = content.firstDayOfWeek;
|
||||
let thursday = new Date(date);
|
||||
if (firstDayOfWeek === 0) {
|
||||
thursday.setDate(date.getDate() + 4)
|
||||
thursday.setDate(date.getDate() + 4);
|
||||
} else if (firstDayOfWeek === 1) {
|
||||
thursday.setDate(date.getDate() + 3)
|
||||
thursday.setDate(date.getDate() + 3);
|
||||
} else {
|
||||
let daysToThursday = (4 - firstDayOfWeek + 7) % 7
|
||||
thursday.setDate(date.getDate() + daysToThursday)
|
||||
let daysToThursday = (4 - firstDayOfWeek + 7) % 7;
|
||||
thursday.setDate(date.getDate() + daysToThursday);
|
||||
}
|
||||
|
||||
weeks.push(root.getISOWeekNumber(thursday))
|
||||
weeks.push(root.getISOWeekNumber(thursday));
|
||||
}
|
||||
}
|
||||
return weeks
|
||||
return weeks;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
@@ -466,9 +466,9 @@ SmartPanel {
|
||||
id: grid
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: {
|
||||
const numWeeks = daysModel ? Math.ceil(daysModel.length / 7) : 5
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS
|
||||
return numWeeks * rowHeight
|
||||
const numWeeks = daysModel ? Math.ceil(daysModel.length / 7) : 5;
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
|
||||
return numWeeks * rowHeight;
|
||||
}
|
||||
columns: 7
|
||||
columnSpacing: Style.marginXXS
|
||||
@@ -486,51 +486,51 @@ SmartPanel {
|
||||
|
||||
// Calculate days to display
|
||||
property var daysModel: {
|
||||
const firstOfMonth = new Date(year, month, 1)
|
||||
const lastOfMonth = new Date(year, month + 1, 0)
|
||||
const daysInMonth = lastOfMonth.getDate()
|
||||
const firstOfMonth = new Date(year, month, 1);
|
||||
const lastOfMonth = new Date(year, month + 1, 0);
|
||||
const daysInMonth = lastOfMonth.getDate();
|
||||
|
||||
// Get first day of week (0 = Sunday, 1 = Monday, etc.)
|
||||
const firstDayOfWeek = content.firstDayOfWeek
|
||||
const firstOfMonthDayOfWeek = firstOfMonth.getDay()
|
||||
const firstDayOfWeek = content.firstDayOfWeek;
|
||||
const firstOfMonthDayOfWeek = firstOfMonth.getDay();
|
||||
|
||||
// Calculate days before first of month
|
||||
let daysBefore = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7
|
||||
let daysBefore = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7;
|
||||
|
||||
// Calculate days after last of month to complete the week
|
||||
const lastOfMonthDayOfWeek = lastOfMonth.getDay()
|
||||
const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7
|
||||
const lastOfMonthDayOfWeek = lastOfMonth.getDay();
|
||||
const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7;
|
||||
|
||||
// Build array of day objects
|
||||
const days = []
|
||||
const today = new Date()
|
||||
const days = [];
|
||||
const today = new Date();
|
||||
|
||||
// Previous month days
|
||||
const prevMonth = new Date(year, month, 0)
|
||||
const prevMonthDays = prevMonth.getDate()
|
||||
const prevMonth = new Date(year, month, 0);
|
||||
const prevMonthDays = prevMonth.getDate();
|
||||
for (var i = daysBefore - 1; i >= 0; i--) {
|
||||
const day = prevMonthDays - i
|
||||
const date = new Date(year, month - 1, day)
|
||||
const day = prevMonthDays - i;
|
||||
const date = new Date(year, month - 1, day);
|
||||
days.push({
|
||||
"day": day,
|
||||
"month": month - 1,
|
||||
"year": month === 0 ? year - 1 : year,
|
||||
"today": false,
|
||||
"currentMonth": false
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Current month days
|
||||
for (var day = 1; day <= daysInMonth; day++) {
|
||||
const date = new Date(year, month, day)
|
||||
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate()
|
||||
const date = new Date(year, month, day);
|
||||
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
|
||||
days.push({
|
||||
"day": day,
|
||||
"month": month,
|
||||
"year": year,
|
||||
"today": isToday,
|
||||
"currentMonth": true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Next month days (only if needed to complete the week)
|
||||
@@ -541,10 +541,10 @@ SmartPanel {
|
||||
"year": month === 11 ? year + 1 : year,
|
||||
"today": false,
|
||||
"currentMonth": false
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return days
|
||||
return days;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
@@ -566,10 +566,10 @@ SmartPanel {
|
||||
text: modelData.day
|
||||
color: {
|
||||
if (modelData.today)
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnSecondary;
|
||||
if (modelData.currentMonth)
|
||||
return Color.mOnSurface
|
||||
return Color.mOnSurfaceVariant
|
||||
return Color.mOnSurface;
|
||||
return Color.mOnSurfaceVariant;
|
||||
}
|
||||
opacity: modelData.currentMonth ? 1.0 : 0.4
|
||||
pointSize: Style.fontSizeM
|
||||
@@ -602,35 +602,35 @@ SmartPanel {
|
||||
enabled: Settings.data.location.showCalendarEvents
|
||||
|
||||
onEntered: {
|
||||
const events = parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day)
|
||||
const events = parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day);
|
||||
if (events.length > 0) {
|
||||
const summaries = events.map(event => {
|
||||
if (isAllDayEvent(event)) {
|
||||
return event.summary
|
||||
return event.summary;
|
||||
} else {
|
||||
// Always format with '0' padding to ensure proper horizontal alignment
|
||||
const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm"
|
||||
const start = new Date(event.start * 1000)
|
||||
const startFormatted = I18n.locale.toString(start, timeFormat)
|
||||
const end = new Date(event.end * 1000)
|
||||
const endFormatted = I18n.locale.toString(end, timeFormat)
|
||||
return `${startFormatted}-${endFormatted} ${event.summary}`
|
||||
const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm";
|
||||
const start = new Date(event.start * 1000);
|
||||
const startFormatted = I18n.locale.toString(start, timeFormat);
|
||||
const end = new Date(event.end * 1000);
|
||||
const endFormatted = I18n.locale.toString(end, timeFormat);
|
||||
return `${startFormatted}-${endFormatted} ${event.summary}`;
|
||||
}
|
||||
}).join('\n')
|
||||
TooltipService.show(screen, parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed)
|
||||
}).join('\n');
|
||||
TooltipService.show(screen, parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`
|
||||
const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`;
|
||||
if (ProgramCheckerService.gnomeCalendarAvailable) {
|
||||
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes])
|
||||
root.close()
|
||||
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes]);
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ NBox {
|
||||
property bool localInputVolumeChanging: false
|
||||
|
||||
Component.onCompleted: {
|
||||
var vol = AudioService.volume
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0
|
||||
var inputVol = AudioService.inputVolume
|
||||
localInputVolume = (inputVol !== undefined && !isNaN(inputVol)) ? inputVol : 0
|
||||
var vol = AudioService.volume;
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
var inputVol = AudioService.inputVolume;
|
||||
localInputVolume = (inputVol !== undefined && !isNaN(inputVol)) ? inputVol : 0;
|
||||
}
|
||||
|
||||
// Timer to debounce volume changes
|
||||
@@ -29,10 +29,10 @@ NBox {
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localOutputVolume)
|
||||
AudioService.setVolume(localOutputVolume);
|
||||
}
|
||||
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
|
||||
AudioService.setInputVolume(localInputVolume)
|
||||
AudioService.setInputVolume(localInputVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,8 @@ NBox {
|
||||
target: AudioService
|
||||
function onVolumeChanged() {
|
||||
if (!localOutputVolumeChanging) {
|
||||
var vol = AudioService.volume
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0
|
||||
var vol = AudioService.volume;
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,8 +52,8 @@ NBox {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localOutputVolumeChanging) {
|
||||
var vol = AudioService.volume
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0
|
||||
var vol = AudioService.volume;
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,8 @@ NBox {
|
||||
target: AudioService
|
||||
function onInputVolumeChanged() {
|
||||
if (!localInputVolumeChanging) {
|
||||
var vol = AudioService.inputVolume
|
||||
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0
|
||||
var vol = AudioService.inputVolume;
|
||||
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,8 +72,8 @@ NBox {
|
||||
target: AudioService.source?.audio ? AudioService.source?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localInputVolumeChanging) {
|
||||
var vol = AudioService.inputVolume
|
||||
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0
|
||||
var vol = AudioService.inputVolume;
|
||||
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,7 @@ NBox {
|
||||
colorFgHover: Color.mOnHover
|
||||
onClicked: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = !AudioService.muted
|
||||
AudioService.sink.audio.muted = !AudioService.muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Media
|
||||
@@ -22,7 +22,7 @@ NBox {
|
||||
target: WallpaperService
|
||||
function onWallpaperChanged(screenName, path) {
|
||||
if (screenName === screen.name) {
|
||||
wallpaper = path
|
||||
wallpaper = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,8 +48,8 @@ NBox {
|
||||
|
||||
// Background image that covers everything
|
||||
Image {
|
||||
readonly property int dim: Math.round(256 * Style.uiScaleRatio)
|
||||
id: bgImage
|
||||
readonly property int dim: Math.round(256 * Style.uiScaleRatio)
|
||||
anchors.fill: parent
|
||||
source: MediaService.trackArtUrl || wallpaper
|
||||
sourceSize: Qt.size(dim, dim)
|
||||
@@ -81,13 +81,13 @@ NBox {
|
||||
sourceComponent: {
|
||||
switch (Settings.data.audio.visualizerType) {
|
||||
case "linear":
|
||||
return linearComponent
|
||||
return linearComponent;
|
||||
case "mirrored":
|
||||
return mirroredComponent
|
||||
return mirroredComponent;
|
||||
case "wave":
|
||||
return waveComponent
|
||||
return waveComponent;
|
||||
default:
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,8 +164,8 @@ NBox {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
var menuItems = []
|
||||
var players = MediaService.getAvailablePlayers()
|
||||
var menuItems = [];
|
||||
var players = MediaService.getAvailablePlayers();
|
||||
for (var i = 0; i < players.length; i++) {
|
||||
menuItems.push({
|
||||
"label": players[i].identity,
|
||||
@@ -173,10 +173,10 @@ NBox {
|
||||
"icon": "disc",
|
||||
"enabled": true,
|
||||
"visible": true
|
||||
})
|
||||
});
|
||||
}
|
||||
playerContextMenu.model = menuItems
|
||||
playerContextMenu.openAtItem(playerSelectorButton, playerSelectorButton.width - playerContextMenu.width, playerSelectorButton.height)
|
||||
playerContextMenu.model = menuItems;
|
||||
playerContextMenu.openAtItem(playerSelectorButton, playerSelectorButton.width - playerContextMenu.width, playerSelectorButton.height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,9 +187,9 @@ NBox {
|
||||
verticalPolicy: ScrollBar.AlwaysOff
|
||||
|
||||
onTriggered: function (action) {
|
||||
var index = parseInt(action)
|
||||
var index = parseInt(action);
|
||||
if (!isNaN(index)) {
|
||||
MediaService.switchToPlayer(index)
|
||||
MediaService.switchToPlayer(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,11 +276,11 @@ NBox {
|
||||
property real seekEpsilon: 0.01
|
||||
property real progressRatio: {
|
||||
if (!MediaService.currentPlayer || MediaService.trackLength <= 0)
|
||||
return 0
|
||||
const r = MediaService.currentPosition / MediaService.trackLength
|
||||
return 0;
|
||||
const r = MediaService.currentPosition / MediaService.trackLength;
|
||||
if (isNaN(r) || !isFinite(r))
|
||||
return 0
|
||||
return Math.max(0, Math.min(1, r))
|
||||
return 0;
|
||||
return Math.max(0, Math.min(1, r));
|
||||
}
|
||||
property real effectiveRatio: (MediaService.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio
|
||||
|
||||
@@ -290,10 +290,10 @@ NBox {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) {
|
||||
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio))
|
||||
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio));
|
||||
if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
|
||||
MediaService.seekByRatio(next)
|
||||
progressWrapper.lastSentSeekRatio = next
|
||||
MediaService.seekByRatio(next);
|
||||
progressWrapper.lastSentSeekRatio = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,21 +310,21 @@ NBox {
|
||||
heightRatio: 0.6
|
||||
|
||||
onMoved: {
|
||||
progressWrapper.localSeekRatio = value
|
||||
seekDebounce.restart()
|
||||
progressWrapper.localSeekRatio = value;
|
||||
seekDebounce.restart();
|
||||
}
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
MediaService.isSeeking = true
|
||||
progressWrapper.localSeekRatio = value
|
||||
MediaService.seekByRatio(value)
|
||||
progressWrapper.lastSentSeekRatio = value
|
||||
MediaService.isSeeking = true;
|
||||
progressWrapper.localSeekRatio = value;
|
||||
MediaService.seekByRatio(value);
|
||||
progressWrapper.lastSentSeekRatio = value;
|
||||
} else {
|
||||
seekDebounce.stop()
|
||||
MediaService.seekByRatio(value)
|
||||
MediaService.isSeeking = false
|
||||
progressWrapper.localSeekRatio = -1
|
||||
progressWrapper.lastSentSeekRatio = -1
|
||||
seekDebounce.stop();
|
||||
MediaService.seekByRatio(value);
|
||||
MediaService.isSeeking = false;
|
||||
progressWrapper.localSeekRatio = -1;
|
||||
progressWrapper.lastSentSeekRatio = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,9 +61,9 @@ NBox {
|
||||
icon: "settings"
|
||||
tooltipText: I18n.tr("tooltips.open-settings")
|
||||
onClicked: {
|
||||
var panel = PanelService.getPanel("settingsPanel", screen)
|
||||
panel.requestedTab = SettingsPanel.Tab.General
|
||||
panel.open()
|
||||
var panel = PanelService.getPanel("settingsPanel", screen);
|
||||
panel.requestedTab = SettingsPanel.Tab.General;
|
||||
panel.open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@ NBox {
|
||||
icon: "power"
|
||||
tooltipText: I18n.tr("tooltips.session-menu")
|
||||
onClicked: {
|
||||
PanelService.getPanel("sessionMenuPanel", screen)?.open()
|
||||
PanelService.getPanel("controlCenterPanel", screen)?.close()
|
||||
PanelService.getPanel("sessionMenuPanel", screen)?.open();
|
||||
PanelService.getPanel("controlCenterPanel", screen)?.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ NBox {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("tooltips.close")
|
||||
onClicked: {
|
||||
PanelService.getPanel("controlCenterPanel", screen)?.close()
|
||||
PanelService.getPanel("controlCenterPanel", screen)?.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,14 +102,14 @@ NBox {
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0])
|
||||
uptimeText = Time.formatVagueHumanReadableDuration(uptimeSeconds)
|
||||
uptimeProcess.running = false
|
||||
var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0]);
|
||||
uptimeText = Time.formatVagueHumanReadableDuration(uptimeSeconds);
|
||||
uptimeProcess.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateSystemInfo() {
|
||||
uptimeProcess.running = true
|
||||
uptimeProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Modules.Panels.ControlCenter
|
||||
import qs.Modules.Panels.ControlCenter.Cards
|
||||
import qs.Widgets
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -28,9 +28,11 @@ NBox {
|
||||
height: content.widgetHeight
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
// Highlight color based on thresholds
|
||||
fillColor: (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor
|
||||
|| Color.mError) : Color.mError) : (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) : Color.mTertiary) : Color.mPrimary
|
||||
fillColor: (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor || Color.mError) : Color.mError) : (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (
|
||||
Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) :
|
||||
Color.mTertiary) :
|
||||
Color.mPrimary
|
||||
textColor: (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuCriticalThreshold) ? Color.mSurfaceVariant : (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuWarningThreshold) ? Color.mSurfaceVariant : Color.mOnSurface
|
||||
}
|
||||
NCircleStat {
|
||||
@@ -42,9 +44,11 @@ NBox {
|
||||
height: content.widgetHeight
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
// Highlight color based on thresholds
|
||||
fillColor: (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor
|
||||
|| Color.mError) : Color.mError) : (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) : Color.mTertiary) : Color.mPrimary
|
||||
fillColor: (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor || Color.mError) : Color.mError) : (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (
|
||||
Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) :
|
||||
Color.mTertiary) :
|
||||
Color.mPrimary
|
||||
textColor: (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempCriticalThreshold) ? Color.mSurfaceVariant : (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempWarningThreshold) ? Color.mSurfaceVariant : Color.mOnSurface
|
||||
}
|
||||
NCircleStat {
|
||||
@@ -55,9 +59,11 @@ NBox {
|
||||
height: content.widgetHeight
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
// Highlight color based on thresholds
|
||||
fillColor: (SystemStatService.memPercent > Settings.data.systemMonitor.memCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor
|
||||
|| Color.mError) : Color.mError) : (SystemStatService.memPercent > Settings.data.systemMonitor.memWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) : Color.mTertiary) : Color.mPrimary
|
||||
fillColor: (SystemStatService.memPercent > Settings.data.systemMonitor.memCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor || Color.mError) : Color.mError) : (SystemStatService.memPercent > Settings.data.systemMonitor.memWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (
|
||||
Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) :
|
||||
Color.mTertiary) :
|
||||
Color.mPrimary
|
||||
textColor: (SystemStatService.memPercent > Settings.data.systemMonitor.memCriticalThreshold) ? Color.mSurfaceVariant : (SystemStatService.memPercent > Settings.data.systemMonitor.memWarningThreshold) ? Color.mSurfaceVariant : Color.mOnSurface
|
||||
}
|
||||
NCircleStat {
|
||||
@@ -68,11 +74,12 @@ NBox {
|
||||
height: content.widgetHeight
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
// Highlight color based on thresholds
|
||||
fillColor: ((SystemStatService.diskPercents["/"]
|
||||
?? 0) > Settings.data.systemMonitor.diskCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor
|
||||
|| Color.mError) : Color.mError) : ((SystemStatService.diskPercents["/"]
|
||||
?? 0) > Settings.data.systemMonitor.diskWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) : Color.mTertiary) : Color.mPrimary
|
||||
fillColor: ((SystemStatService.diskPercents["/"] ?? 0) > Settings.data.systemMonitor.diskCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor || Color.mError) : Color.mError) : ((SystemStatService.diskPercents["/"] ?? 0) > Settings.data.systemMonitor.diskWarningThreshold) ? (
|
||||
Settings.data.systemMonitor.useCustomColors
|
||||
? (Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) :
|
||||
Color.mTertiary) :
|
||||
Color.mPrimary
|
||||
textColor: ((SystemStatService.diskPercents["/"] ?? 0) > Settings.data.systemMonitor.diskCriticalThreshold) ? Color.mSurfaceVariant : ((SystemStatService.diskPercents["/"] ?? 0) > Settings.data.systemMonitor.diskWarningThreshold) ? Color.mSurfaceVariant : Color.mOnSurface
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ NBox {
|
||||
NText {
|
||||
text: {
|
||||
// Ensure the name is not too long if one had to specify the country
|
||||
const chunks = Settings.data.location.name.split(",")
|
||||
return chunks[0]
|
||||
const chunks = Settings.data.location.name.split(",");
|
||||
return chunks[0];
|
||||
}
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
@@ -56,16 +56,16 @@ NBox {
|
||||
visible: weatherReady
|
||||
text: {
|
||||
if (!weatherReady) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
var temp = LocationService.data.weather.current_weather.temperature
|
||||
var suffix = "C"
|
||||
var temp = LocationService.data.weather.current_weather.temperature;
|
||||
var suffix = "C";
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
temp = LocationService.celsiusToFahrenheit(temp)
|
||||
var suffix = "F"
|
||||
temp = LocationService.celsiusToFahrenheit(temp);
|
||||
var suffix = "F";
|
||||
}
|
||||
temp = Math.round(temp)
|
||||
return `${temp}°${suffix}`
|
||||
temp = Math.round(temp);
|
||||
return `${temp}°${suffix}`;
|
||||
}
|
||||
pointSize: showLocation ? Style.fontSizeXL : Style.fontSizeXL * 1.6
|
||||
font.weight: Style.fontWeightBold
|
||||
@@ -103,8 +103,8 @@ NBox {
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||
text: {
|
||||
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"))
|
||||
return I18n.locale.toString(weatherDate, "ddd")
|
||||
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"));
|
||||
return I18n.locale.toString(weatherDate, "ddd");
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
@@ -117,15 +117,15 @@ NBox {
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: {
|
||||
var max = LocationService.data.weather.daily.temperature_2m_max[index]
|
||||
var min = LocationService.data.weather.daily.temperature_2m_min[index]
|
||||
var max = LocationService.data.weather.daily.temperature_2m_max[index];
|
||||
var min = LocationService.data.weather.daily.temperature_2m_min[index];
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
max = LocationService.celsiusToFahrenheit(max)
|
||||
min = LocationService.celsiusToFahrenheit(min)
|
||||
max = LocationService.celsiusToFahrenheit(max);
|
||||
min = LocationService.celsiusToFahrenheit(min);
|
||||
}
|
||||
max = Math.round(max)
|
||||
min = Math.round(min)
|
||||
return `${max}°/${min}°`
|
||||
max = Math.round(max);
|
||||
min = Math.round(min);
|
||||
return `${max}°/${min}°`;
|
||||
}
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
|
||||
@@ -17,8 +17,8 @@ SmartPanel {
|
||||
|
||||
// Check if there's a bar on this screen
|
||||
readonly property bool hasBarOnScreen: {
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
return monitors.length === 0 || monitors.includes(screen?.name)
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
return monitors.length === 0 || monitors.includes(screen?.name);
|
||||
}
|
||||
|
||||
// When position is "close_to_bar_button" but there's no bar, fall back to center
|
||||
@@ -33,39 +33,37 @@ SmartPanel {
|
||||
|
||||
preferredWidth: Math.round(460 * Style.uiScaleRatio)
|
||||
preferredHeight: {
|
||||
var height = 0
|
||||
var count = 0
|
||||
var height = 0;
|
||||
var count = 0;
|
||||
for (var i = 0; i < Settings.data.controlCenter.cards.length; i++) {
|
||||
const card = Settings.data.controlCenter.cards[i]
|
||||
const card = Settings.data.controlCenter.cards[i];
|
||||
if (!card.enabled)
|
||||
continue
|
||||
|
||||
const contributes = (card.id !== "weather-card" || Settings.data.location.weatherEnabled)
|
||||
continue;
|
||||
const contributes = (card.id !== "weather-card" || Settings.data.location.weatherEnabled);
|
||||
if (!contributes)
|
||||
continue
|
||||
|
||||
count++
|
||||
continue;
|
||||
count++;
|
||||
switch (card.id) {
|
||||
case "profile-card":
|
||||
height += profileHeight
|
||||
break
|
||||
height += profileHeight;
|
||||
break;
|
||||
case "shortcuts-card":
|
||||
height += shortcutsHeight
|
||||
break
|
||||
height += shortcutsHeight;
|
||||
break;
|
||||
case "audio-card":
|
||||
height += audioHeight
|
||||
break
|
||||
height += audioHeight;
|
||||
break;
|
||||
case "weather-card":
|
||||
height += weatherHeight
|
||||
break
|
||||
height += weatherHeight;
|
||||
break;
|
||||
case "media-sysmon-card":
|
||||
height += mediaSysMonHeight
|
||||
break
|
||||
height += mediaSysMonHeight;
|
||||
break;
|
||||
default:
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
return height + (count + 1) * Style.marginL
|
||||
return height + (count + 1) * Style.marginL;
|
||||
}
|
||||
|
||||
readonly property int profileHeight: Math.round(64 * Style.uiScaleRatio)
|
||||
@@ -77,11 +75,11 @@ SmartPanel {
|
||||
property int weatherHeight: Math.round(210 * Style.uiScaleRatio)
|
||||
|
||||
onOpened: {
|
||||
MediaService.autoSwitchingPaused = true
|
||||
MediaService.autoSwitchingPaused = true;
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
MediaService.autoSwitchingPaused = false
|
||||
MediaService.autoSwitchingPaused = false;
|
||||
}
|
||||
|
||||
panelContent: Item {
|
||||
@@ -103,31 +101,31 @@ SmartPanel {
|
||||
Layout.preferredHeight: {
|
||||
switch (modelData.id) {
|
||||
case "profile-card":
|
||||
return profileHeight
|
||||
return profileHeight;
|
||||
case "shortcuts-card":
|
||||
return shortcutsHeight
|
||||
return shortcutsHeight;
|
||||
case "audio-card":
|
||||
return audioHeight
|
||||
return audioHeight;
|
||||
case "weather-card":
|
||||
return weatherHeight
|
||||
return weatherHeight;
|
||||
case "media-sysmon-card":
|
||||
return mediaSysMonHeight
|
||||
return mediaSysMonHeight;
|
||||
default:
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
sourceComponent: {
|
||||
switch (modelData.id) {
|
||||
case "profile-card":
|
||||
return profileCard
|
||||
return profileCard;
|
||||
case "shortcuts-card":
|
||||
return shortcutsCard
|
||||
return shortcutsCard;
|
||||
case "audio-card":
|
||||
return audioCard
|
||||
return audioCard;
|
||||
case "weather-card":
|
||||
return weatherCard
|
||||
return weatherCard;
|
||||
case "media-sysmon-card":
|
||||
return mediaSysMonCard
|
||||
return mediaSysMonCard;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +151,7 @@ SmartPanel {
|
||||
id: weatherCard
|
||||
WeatherCard {
|
||||
Component.onCompleted: {
|
||||
root.weatherHeight = this.height
|
||||
root.weatherHeight = this.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Services.UI
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -18,7 +18,7 @@ Item {
|
||||
implicitHeight: getImplicitSize(loader.item, "implicitHeight")
|
||||
|
||||
function getImplicitSize(item, prop) {
|
||||
return (item && item.visible) ? item[prop] : 0
|
||||
return (item && item.visible) ? item[prop] : 0;
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -29,36 +29,36 @@ Item {
|
||||
|
||||
onLoaded: {
|
||||
if (!item)
|
||||
return
|
||||
return;
|
||||
|
||||
// Apply properties to loaded widget
|
||||
for (var prop in widgetProps) {
|
||||
if (item.hasOwnProperty(prop)) {
|
||||
item[prop] = widgetProps[prop]
|
||||
item[prop] = widgetProps[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// Set screen property
|
||||
if (item.hasOwnProperty("screen")) {
|
||||
item.screen = widgetScreen
|
||||
item.screen = widgetScreen;
|
||||
}
|
||||
|
||||
// Call custom onLoaded if it exists
|
||||
if (item.hasOwnProperty("onLoaded")) {
|
||||
item.onLoaded()
|
||||
item.onLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// Explicitly clear references
|
||||
widgetProps = null
|
||||
widgetProps = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Error handling
|
||||
Component.onCompleted: {
|
||||
if (!ControlCenterWidgetRegistry.hasWidget(widgetId)) {
|
||||
Logger.w("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId)
|
||||
Logger.w("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,28 +33,28 @@ Item {
|
||||
|
||||
function _updatePropertiesFromSettings() {
|
||||
if (!widgetSettings) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
onClickedCommand = widgetSettings.onClicked || ""
|
||||
onRightClickedCommand = widgetSettings.onRightClicked || ""
|
||||
onMiddleClickedCommand = widgetSettings.onMiddleClicked || ""
|
||||
stateChecksJson = widgetSettings.stateChecksJson || "[]" // Populate from widgetSettings
|
||||
onClickedCommand = widgetSettings.onClicked || "";
|
||||
onRightClickedCommand = widgetSettings.onRightClicked || "";
|
||||
onMiddleClickedCommand = widgetSettings.onMiddleClicked || "";
|
||||
stateChecksJson = widgetSettings.stateChecksJson || "[]"; // Populate from widgetSettings
|
||||
try {
|
||||
_parsedStateChecks = JSON.parse(stateChecksJson)
|
||||
_parsedStateChecks = JSON.parse(stateChecksJson);
|
||||
} catch (e) {
|
||||
console.error("CustomButton: Failed to parse stateChecksJson:", e.message)
|
||||
_parsedStateChecks = []
|
||||
console.error("CustomButton: Failed to parse stateChecksJson:", e.message);
|
||||
_parsedStateChecks = [];
|
||||
}
|
||||
generalTooltipText = widgetSettings.generalTooltipText || "Custom Button"
|
||||
enableOnStateLogic = widgetSettings.enableOnStateLogic || false
|
||||
generalTooltipText = widgetSettings.generalTooltipText || "Custom Button";
|
||||
enableOnStateLogic = widgetSettings.enableOnStateLogic || false;
|
||||
|
||||
updateState()
|
||||
updateState();
|
||||
}
|
||||
|
||||
function onWidgetSettingsChanged() {
|
||||
if (widgetSettings) {
|
||||
_updatePropertiesFromSettings()
|
||||
_updatePropertiesFromSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,16 +64,16 @@ Item {
|
||||
running: false
|
||||
command: _currentStateCheckIndex !== -1 && _parsedStateChecks.length > _currentStateCheckIndex ? ["sh", "-c", _parsedStateChecks[_currentStateCheckIndex].command] : []
|
||||
onExited: function (exitCode, stdout, stderr) {
|
||||
var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex]
|
||||
var currentCommand = currentCheckItem.command
|
||||
var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex];
|
||||
var currentCommand = currentCheckItem.command;
|
||||
if (exitCode === 0) {
|
||||
// Command succeeded, this is the active state
|
||||
_isHot = true
|
||||
_activeStateIcon = currentCheckItem.icon || widgetSettings.icon || "heart"
|
||||
_isHot = true;
|
||||
_activeStateIcon = currentCheckItem.icon || widgetSettings.icon || "heart";
|
||||
} else {
|
||||
// Command failed, try next one
|
||||
_currentStateCheckIndex++
|
||||
_checkNextState()
|
||||
_currentStateCheckIndex++;
|
||||
_checkNextState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,53 +85,53 @@ Item {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (enableOnStateLogic && _parsedStateChecks.length > 0) {
|
||||
_currentStateCheckIndex = 0
|
||||
_checkNextState()
|
||||
_currentStateCheckIndex = 0;
|
||||
_checkNextState();
|
||||
} else {
|
||||
_isHot = false
|
||||
_activeStateIcon = widgetSettings.icon || "heart"
|
||||
_isHot = false;
|
||||
_activeStateIcon = widgetSettings.icon || "heart";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _checkNextState() {
|
||||
if (_currentStateCheckIndex < _parsedStateChecks.length) {
|
||||
var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex]
|
||||
var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex];
|
||||
if (currentCheckItem && currentCheckItem.command) {
|
||||
stateCheckProcessExecutor.running = true
|
||||
stateCheckProcessExecutor.running = true;
|
||||
} else {
|
||||
_currentStateCheckIndex++
|
||||
_checkNextState()
|
||||
_currentStateCheckIndex++;
|
||||
_checkNextState();
|
||||
}
|
||||
} else {
|
||||
// All checks failed
|
||||
_isHot = false
|
||||
_activeStateIcon = widgetSettings.icon || "heart"
|
||||
_isHot = false;
|
||||
_activeStateIcon = widgetSettings.icon || "heart";
|
||||
}
|
||||
}
|
||||
|
||||
function updateState() {
|
||||
if (!enableOnStateLogic || _parsedStateChecks.length === 0) {
|
||||
_isHot = false
|
||||
_activeStateIcon = widgetSettings.icon || "heart"
|
||||
return
|
||||
_isHot = false;
|
||||
_activeStateIcon = widgetSettings.icon || "heart";
|
||||
return;
|
||||
}
|
||||
stateUpdateTimer.restart()
|
||||
stateUpdateTimer.restart();
|
||||
}
|
||||
|
||||
function _buildTooltipText() {
|
||||
let tooltip = generalTooltipText
|
||||
let tooltip = generalTooltipText;
|
||||
if (onClickedCommand) {
|
||||
tooltip += `\nLeft click: ${onClickedCommand}`
|
||||
tooltip += `\nLeft click: ${onClickedCommand}`;
|
||||
}
|
||||
if (onRightClickedCommand) {
|
||||
tooltip += `\nRight click: ${onRightClickedCommand}`
|
||||
tooltip += `\nRight click: ${onRightClickedCommand}`;
|
||||
}
|
||||
if (onMiddleClickedCommand) {
|
||||
tooltip += `\nMiddle click: ${onMiddleClickedCommand}`
|
||||
tooltip += `\nMiddle click: ${onMiddleClickedCommand}`;
|
||||
}
|
||||
|
||||
return tooltip
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
NIconButtonHot {
|
||||
@@ -141,20 +141,20 @@ Item {
|
||||
tooltipText: _buildTooltipText()
|
||||
onClicked: {
|
||||
if (onClickedCommand) {
|
||||
Quickshell.execDetached(["sh", "-c", onClickedCommand])
|
||||
updateState()
|
||||
Quickshell.execDetached(["sh", "-c", onClickedCommand]);
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
onRightClicked: {
|
||||
if (onRightClickedCommand) {
|
||||
Quickshell.execDetached(["sh", "-c", onRightClickedCommand])
|
||||
updateState()
|
||||
Quickshell.execDetached(["sh", "-c", onRightClickedCommand]);
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
onMiddleClicked: {
|
||||
if (onMiddleClickedCommand) {
|
||||
Quickshell.execDetached(["sh", "-c", onMiddleClickedCommand])
|
||||
updateState()
|
||||
Quickshell.execDetached(["sh", "-c", onMiddleClickedCommand]);
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,19 +16,19 @@ NIconButtonHot {
|
||||
|
||||
onClicked: {
|
||||
if (!Settings.data.nightLight.enabled) {
|
||||
Settings.data.nightLight.enabled = true
|
||||
Settings.data.nightLight.forced = false
|
||||
Settings.data.nightLight.enabled = true;
|
||||
Settings.data.nightLight.forced = false;
|
||||
} else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) {
|
||||
Settings.data.nightLight.forced = true
|
||||
Settings.data.nightLight.forced = true;
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false
|
||||
Settings.data.nightLight.forced = false
|
||||
Settings.data.nightLight.enabled = false;
|
||||
Settings.data.nightLight.forced = false;
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||
settingsPanel.open()
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display;
|
||||
settingsPanel.open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ NIconButtonHot {
|
||||
hot: ScreenRecorderService.isRecording
|
||||
tooltipText: I18n.tr("quickSettings.screenRecorder.tooltip.action")
|
||||
onClicked: {
|
||||
ScreenRecorderService.toggleRecording()
|
||||
ScreenRecorderService.toggleRecording();
|
||||
if (!ScreenRecorderService.isRecording) {
|
||||
PanelService.getPanel("controlCenterPanel", screen)?.close
|
||||
PanelService.getPanel("controlCenterPanel", screen)?.close;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,21 @@ NIconButtonHot {
|
||||
icon: {
|
||||
try {
|
||||
if (NetworkService.ethernetConnected) {
|
||||
return NetworkService.internetConnectivity ? "ethernet" : "ethernet-off"
|
||||
return NetworkService.internetConnectivity ? "ethernet" : "ethernet-off";
|
||||
}
|
||||
let connected = false
|
||||
let signalStrength = 0
|
||||
let connected = false;
|
||||
let signalStrength = 0;
|
||||
for (const net in NetworkService.networks) {
|
||||
if (NetworkService.networks[net].connected) {
|
||||
connected = true
|
||||
signalStrength = NetworkService.networks[net].signal
|
||||
break
|
||||
connected = true;
|
||||
signalStrength = NetworkService.networks[net].signal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return connected ? NetworkService.signalIcon(signalStrength, true) : "wifi-off"
|
||||
return connected ? NetworkService.signalIcon(signalStrength, true) : "wifi-off";
|
||||
} catch (error) {
|
||||
Logger.e("Wi-Fi", "Error getting icon:", error)
|
||||
return "wifi-off"
|
||||
Logger.e("Wi-Fi", "Error getting icon:", error);
|
||||
return "wifi-off";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
|
||||
import "Plugins"
|
||||
import qs.Commons
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.Keyboard
|
||||
import qs.Widgets
|
||||
|
||||
import "Plugins"
|
||||
|
||||
SmartPanel {
|
||||
id: root
|
||||
|
||||
@@ -23,12 +23,12 @@ SmartPanel {
|
||||
readonly property string panelPosition: {
|
||||
if (Settings.data.appLauncher.position === "follow_bar") {
|
||||
if (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") {
|
||||
return `center_${Settings.data.bar.position}`
|
||||
return `center_${Settings.data.bar.position}`;
|
||||
} else {
|
||||
return `${Settings.data.bar.position}_center`
|
||||
return `${Settings.data.bar.position}_center`;
|
||||
}
|
||||
} else {
|
||||
return Settings.data.appLauncher.position
|
||||
return Settings.data.appLauncher.position;
|
||||
}
|
||||
}
|
||||
panelAnchorHorizontalCenter: panelPosition === "center" || panelPosition.endsWith("_center")
|
||||
@@ -55,83 +55,83 @@ SmartPanel {
|
||||
// They are not coming from SmartPanelWindow as they are consumed by the search field before reaching the panel.
|
||||
// They are instead being forwared from the search field NTextInput below.
|
||||
function onTabPressed() {
|
||||
selectNextWrapped()
|
||||
selectNextWrapped();
|
||||
}
|
||||
|
||||
function onBackTabPressed() {
|
||||
selectPreviousWrapped()
|
||||
selectPreviousWrapped();
|
||||
}
|
||||
|
||||
function onUpPressed() {
|
||||
selectPreviousWrapped()
|
||||
selectPreviousWrapped();
|
||||
}
|
||||
|
||||
function onDownPressed() {
|
||||
selectNextWrapped()
|
||||
selectNextWrapped();
|
||||
}
|
||||
|
||||
function onReturnPressed() {
|
||||
activate()
|
||||
activate();
|
||||
}
|
||||
|
||||
function onHomePressed() {
|
||||
selectFirst()
|
||||
selectFirst();
|
||||
}
|
||||
|
||||
function onEndPressed() {
|
||||
selectLast()
|
||||
selectLast();
|
||||
}
|
||||
|
||||
function onPageUpPressed() {
|
||||
selectPreviousPage()
|
||||
selectPreviousPage();
|
||||
}
|
||||
|
||||
function onPageDownPressed() {
|
||||
selectNextPage()
|
||||
selectNextPage();
|
||||
}
|
||||
|
||||
function onCtrlJPressed() {
|
||||
selectNextWrapped()
|
||||
selectNextWrapped();
|
||||
}
|
||||
|
||||
function onCtrlKPressed() {
|
||||
selectPreviousWrapped()
|
||||
selectPreviousWrapped();
|
||||
}
|
||||
|
||||
function onCtrlNPressed() {
|
||||
selectNextWrapped()
|
||||
selectNextWrapped();
|
||||
}
|
||||
|
||||
function onCtrlPPressed() {
|
||||
selectPreviousWrapped()
|
||||
selectPreviousWrapped();
|
||||
}
|
||||
|
||||
// Public API for plugins
|
||||
function setSearchText(text) {
|
||||
searchText = text
|
||||
searchText = text;
|
||||
}
|
||||
|
||||
// Plugin registration
|
||||
function registerPlugin(plugin) {
|
||||
plugins.push(plugin)
|
||||
plugin.launcher = root
|
||||
plugins.push(plugin);
|
||||
plugin.launcher = root;
|
||||
if (plugin.init)
|
||||
plugin.init()
|
||||
plugin.init();
|
||||
}
|
||||
|
||||
// Search handling
|
||||
function updateResults() {
|
||||
results = []
|
||||
activePlugin = null
|
||||
results = [];
|
||||
activePlugin = null;
|
||||
|
||||
// Check for command mode
|
||||
if (searchText.startsWith(">")) {
|
||||
// Find plugin that handles this command
|
||||
for (let plugin of plugins) {
|
||||
if (plugin.handleCommand && plugin.handleCommand(searchText)) {
|
||||
activePlugin = plugin
|
||||
results = plugin.getResults(searchText)
|
||||
break
|
||||
activePlugin = plugin;
|
||||
results = plugin.getResults(searchText);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ SmartPanel {
|
||||
if (searchText === ">" && !activePlugin) {
|
||||
for (let plugin of plugins) {
|
||||
if (plugin.commands) {
|
||||
results = results.concat(plugin.commands())
|
||||
results = results.concat(plugin.commands());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,43 +147,43 @@ SmartPanel {
|
||||
// Regular search - let plugins contribute results
|
||||
for (let plugin of plugins) {
|
||||
if (plugin.handleSearch) {
|
||||
const pluginResults = plugin.getResults(searchText)
|
||||
results = results.concat(pluginResults)
|
||||
const pluginResults = plugin.getResults(searchText);
|
||||
results = results.concat(pluginResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedIndex = 0
|
||||
selectedIndex = 0;
|
||||
}
|
||||
|
||||
onSearchTextChanged: updateResults()
|
||||
|
||||
// Lifecycle
|
||||
onOpened: {
|
||||
resultsReady = false
|
||||
ignoreMouseHover = true
|
||||
resultsReady = false;
|
||||
ignoreMouseHover = true;
|
||||
|
||||
// Notify plugins and update results
|
||||
// Use Qt.callLater to ensure plugins are registered (Component.onCompleted runs first)
|
||||
Qt.callLater(() => {
|
||||
for (let plugin of plugins) {
|
||||
if (plugin.onOpened)
|
||||
plugin.onOpened()
|
||||
plugin.onOpened();
|
||||
}
|
||||
updateResults()
|
||||
resultsReady = true
|
||||
})
|
||||
updateResults();
|
||||
resultsReady = true;
|
||||
});
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
// Reset search text
|
||||
searchText = ""
|
||||
ignoreMouseHover = true
|
||||
searchText = "";
|
||||
ignoreMouseHover = true;
|
||||
|
||||
// Notify plugins
|
||||
for (let plugin of plugins) {
|
||||
if (plugin.onClosed)
|
||||
plugin.onClosed()
|
||||
plugin.onClosed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,16 +191,16 @@ SmartPanel {
|
||||
ApplicationsPlugin {
|
||||
id: appsPlugin
|
||||
Component.onCompleted: {
|
||||
registerPlugin(this)
|
||||
Logger.d("Launcher", "Registered: ApplicationsPlugin")
|
||||
registerPlugin(this);
|
||||
Logger.d("Launcher", "Registered: ApplicationsPlugin");
|
||||
}
|
||||
}
|
||||
|
||||
CalculatorPlugin {
|
||||
id: calcPlugin
|
||||
Component.onCompleted: {
|
||||
registerPlugin(this)
|
||||
Logger.d("Launcher", "Registered: CalculatorPlugin")
|
||||
registerPlugin(this);
|
||||
Logger.d("Launcher", "Registered: CalculatorPlugin");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@ SmartPanel {
|
||||
id: clipPlugin
|
||||
Component.onCompleted: {
|
||||
if (Settings.data.appLauncher.enableClipboardHistory) {
|
||||
registerPlugin(this)
|
||||
Logger.d("Launcher", "Registered: ClipboardPlugin")
|
||||
registerPlugin(this);
|
||||
Logger.d("Launcher", "Registered: ClipboardPlugin");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,47 +217,47 @@ SmartPanel {
|
||||
// Navigation functions
|
||||
function selectNextWrapped() {
|
||||
if (results.length > 0) {
|
||||
selectedIndex = (selectedIndex + 1) % results.length
|
||||
selectedIndex = (selectedIndex + 1) % results.length;
|
||||
}
|
||||
}
|
||||
|
||||
function selectPreviousWrapped() {
|
||||
if (results.length > 0) {
|
||||
selectedIndex = (((selectedIndex - 1) % results.length) + results.length) % results.length
|
||||
selectedIndex = (((selectedIndex - 1) % results.length) + results.length) % results.length;
|
||||
}
|
||||
}
|
||||
|
||||
function selectFirst() {
|
||||
selectedIndex = 0
|
||||
selectedIndex = 0;
|
||||
}
|
||||
|
||||
function selectLast() {
|
||||
if (results.length > 0) {
|
||||
selectedIndex = results.length - 1
|
||||
selectedIndex = results.length - 1;
|
||||
} else {
|
||||
selectedIndex = 0
|
||||
selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function selectNextPage() {
|
||||
if (results.length > 0) {
|
||||
const page = Math.max(1, Math.floor(600 / entryHeight)) // Use approximate height
|
||||
selectedIndex = Math.min(selectedIndex + page, results.length - 1)
|
||||
const page = Math.max(1, Math.floor(600 / entryHeight)); // Use approximate height
|
||||
selectedIndex = Math.min(selectedIndex + page, results.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
function selectPreviousPage() {
|
||||
if (results.length > 0) {
|
||||
const page = Math.max(1, Math.floor(600 / entryHeight)) // Use approximate height
|
||||
selectedIndex = Math.max(selectedIndex - page, 0)
|
||||
const page = Math.max(1, Math.floor(600 / entryHeight)); // Use approximate height
|
||||
selectedIndex = Math.max(selectedIndex - page, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function activate() {
|
||||
if (results.length > 0 && results[selectedIndex]) {
|
||||
const item = results[selectedIndex]
|
||||
const item = results[selectedIndex];
|
||||
if (item.onActivate) {
|
||||
item.onActivate()
|
||||
item.onActivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,19 +284,19 @@ SmartPanel {
|
||||
onPositionChanged: mouse => {
|
||||
// Store initial position
|
||||
if (!initialized) {
|
||||
lastX = mouse.x
|
||||
lastY = mouse.y
|
||||
initialized = true
|
||||
return
|
||||
lastX = mouse.x;
|
||||
lastY = mouse.y;
|
||||
initialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if mouse actually moved
|
||||
const deltaX = Math.abs(mouse.x - lastX)
|
||||
const deltaY = Math.abs(mouse.y - lastY)
|
||||
const deltaX = Math.abs(mouse.x - lastX);
|
||||
const deltaY = Math.abs(mouse.y - lastY);
|
||||
if (deltaX > 1 || deltaY > 1) {
|
||||
root.ignoreMouseHover = false
|
||||
lastX = mouse.x
|
||||
lastY = mouse.y
|
||||
root.ignoreMouseHover = false;
|
||||
lastX = mouse.x;
|
||||
lastY = mouse.y;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ SmartPanel {
|
||||
Connections {
|
||||
target: root
|
||||
function onOpened() {
|
||||
mouseMovementDetector.initialized = false
|
||||
mouseMovementDetector.initialized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,9 +316,9 @@ SmartPanel {
|
||||
// Delay focus to ensure window has keyboard focus
|
||||
Qt.callLater(() => {
|
||||
if (searchInput.inputItem) {
|
||||
searchInput.inputItem.forceActiveFocus()
|
||||
searchInput.inputItem.forceActiveFocus();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,17 +348,17 @@ SmartPanel {
|
||||
|
||||
Component.onCompleted: {
|
||||
if (searchInput.inputItem) {
|
||||
searchInput.inputItem.forceActiveFocus()
|
||||
searchInput.inputItem.forceActiveFocus();
|
||||
// Intercept Tab keys before TextField handles them
|
||||
searchInput.inputItem.Keys.onPressed.connect(function (event) {
|
||||
if (event.key === Qt.Key_Tab) {
|
||||
root.onTabPressed()
|
||||
event.accepted = true
|
||||
root.onTabPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
root.onBackTabPressed()
|
||||
event.accepted = true
|
||||
root.onBackTabPressed();
|
||||
event.accepted = true;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,14 +377,12 @@ SmartPanel {
|
||||
currentIndex: selectedIndex
|
||||
cacheBuffer: resultsList.height * 2
|
||||
onCurrentIndexChanged: {
|
||||
cancelFlick()
|
||||
cancelFlick();
|
||||
if (currentIndex >= 0) {
|
||||
positionViewAtIndex(currentIndex, ListView.Contain)
|
||||
positionViewAtIndex(currentIndex, ListView.Contain);
|
||||
}
|
||||
}
|
||||
onModelChanged: {
|
||||
|
||||
}
|
||||
onModelChanged: {}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: entry
|
||||
@@ -396,19 +394,19 @@ SmartPanel {
|
||||
// Pin helpers
|
||||
function togglePin(appId) {
|
||||
if (!appId)
|
||||
return
|
||||
let arr = (Settings.data.dock.pinnedApps || []).slice()
|
||||
const idx = arr.indexOf(appId)
|
||||
return;
|
||||
let arr = (Settings.data.dock.pinnedApps || []).slice();
|
||||
const idx = arr.indexOf(appId);
|
||||
if (idx >= 0)
|
||||
arr.splice(idx, 1)
|
||||
arr.splice(idx, 1);
|
||||
else
|
||||
arr.push(appId)
|
||||
Settings.data.dock.pinnedApps = arr
|
||||
arr.push(appId);
|
||||
Settings.data.dock.pinnedApps = arr;
|
||||
}
|
||||
|
||||
function isPinned(appId) {
|
||||
const arr = Settings.data.dock.pinnedApps || []
|
||||
return appId && arr.indexOf(appId) >= 0
|
||||
const arr = Settings.data.dock.pinnedApps || [];
|
||||
return appId && arr.indexOf(appId) >= 0;
|
||||
}
|
||||
|
||||
// Property to reliably track the current item's ID.
|
||||
@@ -419,7 +417,7 @@ SmartPanel {
|
||||
onCurrentClipboardIdChanged: {
|
||||
// Check if it's a valid ID and if the data isn't already cached.
|
||||
if (currentClipboardId && !ClipboardService.getImageData(currentClipboardId)) {
|
||||
ClipboardService.decodeToDataUrl(currentClipboardId, modelData.mime, null)
|
||||
ClipboardService.decodeToDataUrl(currentClipboardId, modelData.mime, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,8 +464,8 @@ SmartPanel {
|
||||
// Fetches from the service's cache.
|
||||
// The dependency on `_rev` ensures this binding is re-evaluated when the cache is updated.
|
||||
imagePath: {
|
||||
_rev
|
||||
return ClipboardService.getImageData(modelData.clipboardId) || ""
|
||||
_rev;
|
||||
return ClipboardService.getImageData(modelData.clipboardId) || "";
|
||||
}
|
||||
|
||||
// Loading indicator
|
||||
@@ -487,8 +485,8 @@ SmartPanel {
|
||||
// Error fallback
|
||||
onStatusChanged: status => {
|
||||
if (status === Image.Error) {
|
||||
iconLoader.visible = true
|
||||
imagePreview.visible = false
|
||||
iconLoader.visible = true;
|
||||
imagePreview.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -538,10 +536,10 @@ SmartPanel {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (!modelData.isImage)
|
||||
return ""
|
||||
const desc = modelData.description || ""
|
||||
const parts = desc.split(" • ")
|
||||
return parts[0] || "IMG"
|
||||
return "";
|
||||
const desc = modelData.description || "";
|
||||
const parts = desc.split(" • ");
|
||||
return parts[0] || "IMG";
|
||||
}
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mPrimary
|
||||
@@ -592,14 +590,14 @@ SmartPanel {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: {
|
||||
if (!root.ignoreMouseHover) {
|
||||
selectedIndex = index
|
||||
selectedIndex = index;
|
||||
}
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
selectedIndex = index
|
||||
root.activate()
|
||||
mouse.accepted = true
|
||||
selectedIndex = index;
|
||||
root.activate();
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
acceptedButtons: Qt.LeftButton
|
||||
@@ -616,9 +614,9 @@ SmartPanel {
|
||||
Layout.fillWidth: true
|
||||
text: {
|
||||
if (results.length === 0)
|
||||
return searchText ? "No results" : ""
|
||||
const prefix = activePlugin?.name ? `${activePlugin.name}: ` : ""
|
||||
return prefix + `${results.length} result${results.length !== 1 ? 's' : ''}`
|
||||
return searchText ? "No results" : "";
|
||||
const prefix = activePlugin?.name ? `${activePlugin.name}: ` : "";
|
||||
return prefix + `${results.length} result${results.length !== 1 ? 's' : ''}`;
|
||||
}
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import "../../../../Helpers/FuzzySort.js" as Fuzzysort
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
property var launcher: null
|
||||
@@ -29,7 +29,7 @@ Item {
|
||||
|
||||
onLoadFailed: function (error) {
|
||||
if (error.toString().includes("No such file") || error === 2) {
|
||||
writeAdapter()
|
||||
writeAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,89 +43,89 @@ Item {
|
||||
}
|
||||
|
||||
function init() {
|
||||
loadApplications()
|
||||
loadApplications();
|
||||
}
|
||||
|
||||
function onOpened() {
|
||||
// Refresh apps when launcher opens
|
||||
loadApplications()
|
||||
loadApplications();
|
||||
}
|
||||
|
||||
function loadApplications() {
|
||||
if (typeof DesktopEntries === 'undefined') {
|
||||
Logger.w("ApplicationsPlugin", "DesktopEntries service not available")
|
||||
return
|
||||
Logger.w("ApplicationsPlugin", "DesktopEntries service not available");
|
||||
return;
|
||||
}
|
||||
|
||||
const allApps = DesktopEntries.applications.values || []
|
||||
const allApps = DesktopEntries.applications.values || [];
|
||||
entries = allApps.filter(app => app && !app.noDisplay).map(app => {
|
||||
// Add executable name property for search
|
||||
app.executableName = getExecutableName(app)
|
||||
return app
|
||||
})
|
||||
Logger.d("ApplicationsPlugin", `Loaded ${entries.length} applications`)
|
||||
app.executableName = getExecutableName(app);
|
||||
return app;
|
||||
});
|
||||
Logger.d("ApplicationsPlugin", `Loaded ${entries.length} applications`);
|
||||
}
|
||||
|
||||
function getExecutableName(app) {
|
||||
if (!app)
|
||||
return ""
|
||||
return "";
|
||||
|
||||
// Try to get executable name from command array
|
||||
if (app.command && Array.isArray(app.command) && app.command.length > 0) {
|
||||
const cmd = app.command[0]
|
||||
const cmd = app.command[0];
|
||||
// Extract just the executable name from the full path
|
||||
const parts = cmd.split('/')
|
||||
const executable = parts[parts.length - 1]
|
||||
const parts = cmd.split('/');
|
||||
const executable = parts[parts.length - 1];
|
||||
// Remove any arguments or parameters
|
||||
return executable.split(' ')[0]
|
||||
return executable.split(' ')[0];
|
||||
}
|
||||
|
||||
// Try to get from exec property if available
|
||||
if (app.exec) {
|
||||
const parts = app.exec.split('/')
|
||||
const executable = parts[parts.length - 1]
|
||||
return executable.split(' ')[0]
|
||||
const parts = app.exec.split('/');
|
||||
const executable = parts[parts.length - 1];
|
||||
return executable.split(' ')[0];
|
||||
}
|
||||
|
||||
// Fallback to app id (desktop file name without .desktop)
|
||||
if (app.id) {
|
||||
return app.id.replace('.desktop', '')
|
||||
return app.id.replace('.desktop', '');
|
||||
}
|
||||
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
|
||||
function getResults(query) {
|
||||
if (!entries || entries.length === 0)
|
||||
return []
|
||||
return [];
|
||||
|
||||
if (!query || query.trim() === "") {
|
||||
// Return all apps, optionally sorted by usage
|
||||
const favoriteApps = Settings.data.appLauncher.favoriteApps || []
|
||||
let sorted
|
||||
const favoriteApps = Settings.data.appLauncher.favoriteApps || [];
|
||||
let sorted;
|
||||
if (Settings.data.appLauncher.sortByMostUsed) {
|
||||
sorted = entries.slice().sort((a, b) => {
|
||||
// Favorites first
|
||||
const aFav = favoriteApps.includes(getAppKey(a))
|
||||
const bFav = favoriteApps.includes(getAppKey(b))
|
||||
const aFav = favoriteApps.includes(getAppKey(a));
|
||||
const bFav = favoriteApps.includes(getAppKey(b));
|
||||
if (aFav !== bFav)
|
||||
return aFav ? -1 : 1
|
||||
const ua = getUsageCount(a)
|
||||
const ub = getUsageCount(b)
|
||||
return aFav ? -1 : 1;
|
||||
const ua = getUsageCount(a);
|
||||
const ub = getUsageCount(b);
|
||||
if (ub !== ua)
|
||||
return ub - ua
|
||||
return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase())
|
||||
})
|
||||
return ub - ua;
|
||||
return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase());
|
||||
});
|
||||
} else {
|
||||
sorted = entries.slice().sort((a, b) => {
|
||||
const aFav = favoriteApps.includes(getAppKey(a))
|
||||
const bFav = favoriteApps.includes(getAppKey(b))
|
||||
const aFav = favoriteApps.includes(getAppKey(a));
|
||||
const bFav = favoriteApps.includes(getAppKey(b));
|
||||
if (aFav !== bFav)
|
||||
return aFav ? -1 : 1
|
||||
return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase())
|
||||
})
|
||||
return aFav ? -1 : 1;
|
||||
return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase());
|
||||
});
|
||||
}
|
||||
return sorted.map(app => createResultEntry(app))
|
||||
return sorted.map(app => createResultEntry(app));
|
||||
}
|
||||
|
||||
// Use fuzzy search if available, fallback to simple search
|
||||
@@ -134,54 +134,54 @@ Item {
|
||||
"keys": ["name", "comment", "genericName", "executableName"],
|
||||
"threshold": -1000,
|
||||
"limit": 20
|
||||
})
|
||||
});
|
||||
|
||||
// Sort favorites first within fuzzy results while preserving fuzzysort order otherwise
|
||||
const favoriteApps = Settings.data.appLauncher.favoriteApps || []
|
||||
const fav = []
|
||||
const nonFav = []
|
||||
const favoriteApps = Settings.data.appLauncher.favoriteApps || [];
|
||||
const fav = [];
|
||||
const nonFav = [];
|
||||
for (const r of fuzzyResults) {
|
||||
const app = r.obj
|
||||
const app = r.obj;
|
||||
if (favoriteApps.includes(getAppKey(app)))
|
||||
fav.push(r)
|
||||
fav.push(r);
|
||||
else
|
||||
nonFav.push(r)
|
||||
nonFav.push(r);
|
||||
}
|
||||
return fav.concat(nonFav).map(result => createResultEntry(result.obj))
|
||||
return fav.concat(nonFav).map(result => createResultEntry(result.obj));
|
||||
} else {
|
||||
// Fallback to simple search
|
||||
const searchTerm = query.toLowerCase()
|
||||
const searchTerm = query.toLowerCase();
|
||||
return entries.filter(app => {
|
||||
const name = (app.name || "").toLowerCase()
|
||||
const comment = (app.comment || "").toLowerCase()
|
||||
const generic = (app.genericName || "").toLowerCase()
|
||||
const executable = getExecutableName(app).toLowerCase()
|
||||
return name.includes(searchTerm) || comment.includes(searchTerm) || generic.includes(searchTerm) || executable.includes(searchTerm)
|
||||
const name = (app.name || "").toLowerCase();
|
||||
const comment = (app.comment || "").toLowerCase();
|
||||
const generic = (app.genericName || "").toLowerCase();
|
||||
const executable = getExecutableName(app).toLowerCase();
|
||||
return name.includes(searchTerm) || comment.includes(searchTerm) || generic.includes(searchTerm) || executable.includes(searchTerm);
|
||||
}).sort((a, b) => {
|
||||
// Prioritize name matches, then executable matches
|
||||
const aName = a.name.toLowerCase()
|
||||
const bName = b.name.toLowerCase()
|
||||
const aExecutable = getExecutableName(a).toLowerCase()
|
||||
const bExecutable = getExecutableName(b).toLowerCase()
|
||||
const aStarts = aName.startsWith(searchTerm)
|
||||
const bStarts = bName.startsWith(searchTerm)
|
||||
const aExecStarts = aExecutable.startsWith(searchTerm)
|
||||
const bExecStarts = bExecutable.startsWith(searchTerm)
|
||||
const aName = a.name.toLowerCase();
|
||||
const bName = b.name.toLowerCase();
|
||||
const aExecutable = getExecutableName(a).toLowerCase();
|
||||
const bExecutable = getExecutableName(b).toLowerCase();
|
||||
const aStarts = aName.startsWith(searchTerm);
|
||||
const bStarts = bName.startsWith(searchTerm);
|
||||
const aExecStarts = aExecutable.startsWith(searchTerm);
|
||||
const bExecStarts = bExecutable.startsWith(searchTerm);
|
||||
|
||||
// Prioritize name matches first
|
||||
if (aStarts && !bStarts)
|
||||
return -1
|
||||
return -1;
|
||||
if (!aStarts && bStarts)
|
||||
return 1
|
||||
return 1;
|
||||
|
||||
// Then prioritize executable matches
|
||||
if (aExecStarts && !bExecStarts)
|
||||
return -1
|
||||
return -1;
|
||||
if (!aExecStarts && bExecStarts)
|
||||
return 1
|
||||
return 1;
|
||||
|
||||
return aName.localeCompare(bName)
|
||||
}).slice(0, 20).map(app => createResultEntry(app))
|
||||
return aName.localeCompare(bName);
|
||||
}).slice(0, 20).map(app => createResultEntry(app));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,75 +195,75 @@ Item {
|
||||
"onActivate": function () {
|
||||
// Close the launcher/SmartPanel immediately without any animations.
|
||||
// Ensures we are not preventing the future focusing of the app
|
||||
launcher.close()
|
||||
launcher.close();
|
||||
|
||||
Logger.d("ApplicationsPlugin", `Launching: ${app.name}`)
|
||||
Logger.d("ApplicationsPlugin", `Launching: ${app.name}`);
|
||||
// Record usage and persist asynchronously
|
||||
if (Settings.data.appLauncher.sortByMostUsed)
|
||||
recordUsage(app)
|
||||
recordUsage(app);
|
||||
if (Settings.data.appLauncher.customLaunchPrefixEnabled && Settings.data.appLauncher.customLaunchPrefix) {
|
||||
// Use custom launch prefix
|
||||
const prefix = Settings.data.appLauncher.customLaunchPrefix.split(" ")
|
||||
const prefix = Settings.data.appLauncher.customLaunchPrefix.split(" ");
|
||||
|
||||
if (app.runInTerminal) {
|
||||
const terminal = Settings.data.appLauncher.terminalCommand.split(" ")
|
||||
const command = prefix.concat(terminal.concat(app.command))
|
||||
Quickshell.execDetached(command)
|
||||
const terminal = Settings.data.appLauncher.terminalCommand.split(" ");
|
||||
const command = prefix.concat(terminal.concat(app.command));
|
||||
Quickshell.execDetached(command);
|
||||
} else {
|
||||
const command = prefix.concat(app.command)
|
||||
Quickshell.execDetached(command)
|
||||
const command = prefix.concat(app.command);
|
||||
Quickshell.execDetached(command);
|
||||
}
|
||||
} else if (Settings.data.appLauncher.useApp2Unit && app.id) {
|
||||
Logger.d("ApplicationsPlugin", `Using app2unit for: ${app.id}`)
|
||||
Logger.d("ApplicationsPlugin", `Using app2unit for: ${app.id}`);
|
||||
if (app.runInTerminal)
|
||||
Quickshell.execDetached(["app2unit", "--", app.id + ".desktop"])
|
||||
Quickshell.execDetached(["app2unit", "--", app.id + ".desktop"]);
|
||||
else
|
||||
Quickshell.execDetached(["app2unit", "--"].concat(app.command))
|
||||
Quickshell.execDetached(["app2unit", "--"].concat(app.command));
|
||||
} else {
|
||||
// Fallback logic when app2unit is not used
|
||||
if (app.runInTerminal) {
|
||||
// If app.execute() fails for terminal apps, we handle it manually.
|
||||
Logger.d("ApplicationsPlugin", "Executing terminal app manually: " + app.name)
|
||||
const terminal = Settings.data.appLauncher.terminalCommand.split(" ")
|
||||
const command = terminal.concat(app.command)
|
||||
Quickshell.execDetached(command)
|
||||
Logger.d("ApplicationsPlugin", "Executing terminal app manually: " + app.name);
|
||||
const terminal = Settings.data.appLauncher.terminalCommand.split(" ");
|
||||
const command = terminal.concat(app.command);
|
||||
Quickshell.execDetached(command);
|
||||
} else if (app.execute) {
|
||||
// Default execution for GUI apps
|
||||
app.execute()
|
||||
app.execute();
|
||||
} else {
|
||||
Logger.w("ApplicationsPlugin", `Could not launch: ${app.name}. No valid launch method.`)
|
||||
Logger.w("ApplicationsPlugin", `Could not launch: ${app.name}. No valid launch method.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// Usage tracking helpers
|
||||
function getAppKey(app) {
|
||||
if (app && app.id)
|
||||
return String(app.id)
|
||||
return String(app.id);
|
||||
if (app && app.command && app.command.join)
|
||||
return app.command.join(" ")
|
||||
return String(app && app.name ? app.name : "unknown")
|
||||
return app.command.join(" ");
|
||||
return String(app && app.name ? app.name : "unknown");
|
||||
}
|
||||
|
||||
function getUsageCount(app) {
|
||||
const key = getAppKey(app)
|
||||
const m = usageAdapter && usageAdapter.counts ? usageAdapter.counts : null
|
||||
const key = getAppKey(app);
|
||||
const m = usageAdapter && usageAdapter.counts ? usageAdapter.counts : null;
|
||||
if (!m)
|
||||
return 0
|
||||
const v = m[key]
|
||||
return typeof v === 'number' && isFinite(v) ? v : 0
|
||||
return 0;
|
||||
const v = m[key];
|
||||
return typeof v === 'number' && isFinite(v) ? v : 0;
|
||||
}
|
||||
|
||||
function recordUsage(app) {
|
||||
const key = getAppKey(app)
|
||||
const key = getAppKey(app);
|
||||
if (!usageAdapter.counts)
|
||||
usageAdapter.counts = ({})
|
||||
const current = getUsageCount(app)
|
||||
usageAdapter.counts[key] = current + 1
|
||||
usageAdapter.counts = ({});
|
||||
const current = getUsageCount(app);
|
||||
usageAdapter.counts[key] = current + 1;
|
||||
// Trigger save via debounced timer
|
||||
saveTimer.restart()
|
||||
saveTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
import "../../../../Helpers/AdvancedMath.js" as AdvancedMath
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
property var launcher: null
|
||||
@@ -8,98 +8,106 @@ Item {
|
||||
|
||||
function handleCommand(query) {
|
||||
// Handle >calc command or direct math expressions after >
|
||||
return query.startsWith(">calc") || (query.startsWith(">") && query.length > 1 && isMathExpression(query.substring(1)))
|
||||
return query.startsWith(">calc") || (query.startsWith(">") && query.length > 1 && isMathExpression(query.substring(1)));
|
||||
}
|
||||
|
||||
function commands() {
|
||||
return [{
|
||||
"name": ">calc",
|
||||
"description": I18n.tr("plugins.calculator-description"),
|
||||
"icon": "accessories-calculator",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
launcher.setSearchText(">calc ")
|
||||
}
|
||||
}]
|
||||
return [
|
||||
{
|
||||
"name": ">calc",
|
||||
"description": I18n.tr("plugins.calculator-description"),
|
||||
"icon": "accessories-calculator",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
launcher.setSearchText(">calc ");
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function getResults(query) {
|
||||
let expression = ""
|
||||
let expression = "";
|
||||
|
||||
if (query.startsWith(">calc")) {
|
||||
expression = query.substring(5).trim()
|
||||
expression = query.substring(5).trim();
|
||||
} else if (query.startsWith(">")) {
|
||||
expression = query.substring(1).trim()
|
||||
expression = query.substring(1).trim();
|
||||
} else {
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!expression) {
|
||||
return [{
|
||||
"name": I18n.tr("plugins.calculator-name"),
|
||||
"description": I18n.tr("plugins.calculator-enter-expression"),
|
||||
"icon": "accessories-calculator",
|
||||
"isImage": false,
|
||||
"onActivate": function () {}
|
||||
}]
|
||||
return [
|
||||
{
|
||||
"name": I18n.tr("plugins.calculator-name"),
|
||||
"description": I18n.tr("plugins.calculator-enter-expression"),
|
||||
"icon": "accessories-calculator",
|
||||
"isImage": false,
|
||||
"onActivate": function () {}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
let result = AdvancedMath.evaluate(expression.trim())
|
||||
let result = AdvancedMath.evaluate(expression.trim());
|
||||
|
||||
return [{
|
||||
"name": AdvancedMath.formatResult(result),
|
||||
"description": `${expression} = ${result}`,
|
||||
"icon": "accessories-calculator",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
// TODO: copy entry to clipboard via ClipHist
|
||||
launcher.close()
|
||||
}
|
||||
}]
|
||||
return [
|
||||
{
|
||||
"name": AdvancedMath.formatResult(result),
|
||||
"description": `${expression} = ${result}`,
|
||||
"icon": "accessories-calculator",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
// TODO: copy entry to clipboard via ClipHist
|
||||
launcher.close();
|
||||
}
|
||||
}
|
||||
];
|
||||
} catch (error) {
|
||||
return [{
|
||||
"name": I18n.tr("plugins.calculator-error"),
|
||||
"description": error.message || "Invalid expression",
|
||||
"icon": "dialog-error",
|
||||
"isImage": false,
|
||||
"onActivate": function () {}
|
||||
}]
|
||||
return [
|
||||
{
|
||||
"name": I18n.tr("plugins.calculator-error"),
|
||||
"description": error.message || "Invalid expression",
|
||||
"icon": "dialog-error",
|
||||
"isImage": false,
|
||||
"onActivate": function () {}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateExpression(expr) {
|
||||
// Sanitize input - only allow safe characters
|
||||
const sanitized = expr.replace(/[^0-9\+\-\*\/\(\)\.\s\%]/g, '')
|
||||
const sanitized = expr.replace(/[^0-9\+\-\*\/\(\)\.\s\%]/g, '');
|
||||
if (sanitized !== expr) {
|
||||
throw new Error("Invalid characters in expression")
|
||||
throw new Error("Invalid characters in expression");
|
||||
}
|
||||
|
||||
// Don't allow empty expressions
|
||||
if (!sanitized.trim()) {
|
||||
throw new Error("Empty expression")
|
||||
throw new Error("Empty expression");
|
||||
}
|
||||
|
||||
try {
|
||||
// Use Function constructor for safe evaluation
|
||||
// This is safer than eval() but still evaluate math
|
||||
const result = Function('"use strict"; return (' + sanitized + ')')()
|
||||
const result = Function('"use strict"; return (' + sanitized + ')')();
|
||||
|
||||
// Check for valid result
|
||||
if (!isFinite(result)) {
|
||||
throw new Error("Result is not a finite number")
|
||||
throw new Error("Result is not a finite number");
|
||||
}
|
||||
|
||||
// Round to reasonable precision to avoid floating point issues
|
||||
return Math.round(result * 1000000000) / 1000000000
|
||||
return Math.round(result * 1000000000) / 1000000000;
|
||||
} catch (e) {
|
||||
throw new Error("Invalid mathematical expression")
|
||||
throw new Error("Invalid mathematical expression");
|
||||
}
|
||||
}
|
||||
|
||||
function isMathExpression(expr) {
|
||||
// Check if string looks like a math expression
|
||||
// Allow digits, operators, parentheses, decimal points, and whitespace
|
||||
return /^[\d\s\+\-\*\/\(\)\.\%]+$/.test(expr)
|
||||
}
|
||||
return /^[\d\s\+\-\*\/\(\)\.\%]+$/.test(expr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ Item {
|
||||
if (gotResults && (lastSearchText === searchText)) {
|
||||
// Do not update results after the first fetch.
|
||||
// This will avoid the list resetting every 2seconds when the service updates.
|
||||
return
|
||||
return;
|
||||
}
|
||||
// Refresh results if we're waiting for data or if clipboard plugin is active
|
||||
if (isWaitingForData || (launcher && launcher.searchText.startsWith(">clip"))) {
|
||||
isWaitingForData = false
|
||||
gotResults = true
|
||||
isWaitingForData = false;
|
||||
gotResults = true;
|
||||
if (launcher) {
|
||||
launcher.updateResults()
|
||||
launcher.updateResults();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,142 +40,153 @@ Item {
|
||||
|
||||
// Initialize plugin
|
||||
function init() {
|
||||
Logger.i("ClipboardPlugin", "Initialized")
|
||||
Logger.i("ClipboardPlugin", "Initialized");
|
||||
// Pre-load clipboard data if service is active
|
||||
if (ClipboardService.active) {
|
||||
ClipboardService.list(100)
|
||||
ClipboardService.list(100);
|
||||
}
|
||||
}
|
||||
|
||||
// Called when launcher opens
|
||||
function onOpened() {
|
||||
isWaitingForData = true
|
||||
gotResults = false
|
||||
lastSearchText = ""
|
||||
isWaitingForData = true;
|
||||
gotResults = false;
|
||||
lastSearchText = "";
|
||||
|
||||
// Refresh clipboard history when launcher opens
|
||||
if (ClipboardService.active) {
|
||||
ClipboardService.list(100)
|
||||
ClipboardService.list(100);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this plugin handles the command
|
||||
function handleCommand(searchText) {
|
||||
return searchText.startsWith(">clip")
|
||||
return searchText.startsWith(">clip");
|
||||
}
|
||||
|
||||
// Return available commands when user types ">"
|
||||
function commands() {
|
||||
return [{
|
||||
"name": ">clip",
|
||||
"description": I18n.tr("plugins.clipboard-search-description"),
|
||||
"icon": "text-x-generic",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
launcher.setSearchText(">clip ")
|
||||
}
|
||||
}, {
|
||||
"name": ">clip clear",
|
||||
"description": I18n.tr("plugins.clipboard-clear-description"),
|
||||
"icon": "text-x-generic",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
ClipboardService.wipeAll()
|
||||
launcher.close()
|
||||
}
|
||||
}]
|
||||
return [
|
||||
{
|
||||
"name": ">clip",
|
||||
"description": I18n.tr("plugins.clipboard-search-description"),
|
||||
"icon": "text-x-generic",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
launcher.setSearchText(">clip ");
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ">clip clear",
|
||||
"description": I18n.tr("plugins.clipboard-clear-description"),
|
||||
"icon": "text-x-generic",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
ClipboardService.wipeAll();
|
||||
launcher.close();
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Get search results
|
||||
function getResults(searchText) {
|
||||
if (!searchText.startsWith(">clip")) {
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
|
||||
lastSearchText = searchText
|
||||
const results = []
|
||||
const query = searchText.slice(5).trim()
|
||||
lastSearchText = searchText;
|
||||
const results = [];
|
||||
const query = searchText.slice(5).trim();
|
||||
|
||||
// Check if clipboard service is not active
|
||||
if (!ClipboardService.active) {
|
||||
return [{
|
||||
"name": I18n.tr("plugins.clipboard-history-disabled"),
|
||||
"description": I18n.tr("plugins.clipboard-history-disabled-description"),
|
||||
"icon": "view-refresh",
|
||||
"isImage": false,
|
||||
"onActivate": function () {}
|
||||
}]
|
||||
return [
|
||||
{
|
||||
"name": I18n.tr("plugins.clipboard-history-disabled"),
|
||||
"description": I18n.tr("plugins.clipboard-history-disabled-description"),
|
||||
"icon": "view-refresh",
|
||||
"isImage": false,
|
||||
"onActivate": function () {}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Special command: clear
|
||||
if (query === "clear") {
|
||||
return [{
|
||||
"name": I18n.tr("plugins.clipboard-clear-history"),
|
||||
"description": I18n.tr("plugins.clipboard-clear-description-full"),
|
||||
"icon": "delete_sweep",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
ClipboardService.wipeAll()
|
||||
launcher.close()
|
||||
}
|
||||
}]
|
||||
return [
|
||||
{
|
||||
"name": I18n.tr("plugins.clipboard-clear-history"),
|
||||
"description": I18n.tr("plugins.clipboard-clear-description-full"),
|
||||
"icon": "delete_sweep",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
ClipboardService.wipeAll();
|
||||
launcher.close();
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Show loading state if data is being loaded
|
||||
if (ClipboardService.loading || isWaitingForData) {
|
||||
return [{
|
||||
"name": I18n.tr("plugins.clipboard-loading"),
|
||||
"description": I18n.tr("plugins.clipboard-loading-description"),
|
||||
"icon": "view-refresh",
|
||||
"isImage": false,
|
||||
"onActivate": function () {}
|
||||
}]
|
||||
return [
|
||||
{
|
||||
"name": I18n.tr("plugins.clipboard-loading"),
|
||||
"description": I18n.tr("plugins.clipboard-loading-description"),
|
||||
"icon": "view-refresh",
|
||||
"isImage": false,
|
||||
"onActivate": function () {}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Get clipboard items
|
||||
const items = ClipboardService.items || []
|
||||
const items = ClipboardService.items || [];
|
||||
|
||||
// If no items and we haven't tried loading yet, trigger a load
|
||||
if (items.count === 0 && !ClipboardService.loading) {
|
||||
isWaitingForData = true
|
||||
ClipboardService.list(100)
|
||||
return [{
|
||||
"name": I18n.tr("plugins.clipboard-loading"),
|
||||
"description": I18n.tr("plugins.clipboard-loading-description"),
|
||||
"icon": "view-refresh",
|
||||
"isImage": false,
|
||||
"onActivate": function () {}
|
||||
}]
|
||||
isWaitingForData = true;
|
||||
ClipboardService.list(100);
|
||||
return [
|
||||
{
|
||||
"name": I18n.tr("plugins.clipboard-loading"),
|
||||
"description": I18n.tr("plugins.clipboard-loading-description"),
|
||||
"icon": "view-refresh",
|
||||
"isImage": false,
|
||||
"onActivate": function () {}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Search clipboard items
|
||||
const searchTerm = query.toLowerCase()
|
||||
const searchTerm = query.toLowerCase();
|
||||
|
||||
// Filter and format results
|
||||
items.forEach(function (item) {
|
||||
const preview = (item.preview || "").toLowerCase()
|
||||
const preview = (item.preview || "").toLowerCase();
|
||||
|
||||
// Skip if search term doesn't match
|
||||
if (searchTerm && preview.indexOf(searchTerm) === -1) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Format the result based on type
|
||||
let entry
|
||||
let entry;
|
||||
if (item.isImage) {
|
||||
entry = formatImageEntry(item)
|
||||
entry = formatImageEntry(item);
|
||||
} else {
|
||||
entry = formatTextEntry(item)
|
||||
entry = formatTextEntry(item);
|
||||
}
|
||||
|
||||
// Add activation handler
|
||||
entry.onActivate = function () {
|
||||
ClipboardService.copyToClipboard(item.id)
|
||||
launcher.close()
|
||||
}
|
||||
ClipboardService.copyToClipboard(item.id);
|
||||
launcher.close();
|
||||
};
|
||||
|
||||
results.push(entry)
|
||||
})
|
||||
results.push(entry);
|
||||
});
|
||||
|
||||
// Show empty state if no results
|
||||
if (results.length === 0) {
|
||||
@@ -186,16 +197,16 @@ Item {
|
||||
"isImage": false,
|
||||
"onActivate": function () {// Do nothing
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
//Logger.i("ClipboardPlugin", `Returning ${results.length} results for query: "${query}"`)
|
||||
return results
|
||||
return results;
|
||||
}
|
||||
|
||||
// Helper: Format image clipboard entry
|
||||
function formatImageEntry(item) {
|
||||
const meta = parseImageMeta(item.preview)
|
||||
const meta = parseImageMeta(item.preview);
|
||||
|
||||
// The launcher's delegate will now be responsible for fetching the image data.
|
||||
// This function's role is to provide the necessary metadata for that request.
|
||||
@@ -208,31 +219,31 @@ Item {
|
||||
"imageHeight": meta ? meta.h : 0,
|
||||
"clipboardId": item.id,
|
||||
"mime": item.mime
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper: Format text clipboard entry with preview
|
||||
function formatTextEntry(item) {
|
||||
const preview = (item.preview || "").trim()
|
||||
const lines = preview.split('\n').filter(l => l.trim())
|
||||
const preview = (item.preview || "").trim();
|
||||
const lines = preview.split('\n').filter(l => l.trim());
|
||||
|
||||
// Use first line as title, limit length
|
||||
let title = lines[0] || "Empty text"
|
||||
let title = lines[0] || "Empty text";
|
||||
if (title.length > 60) {
|
||||
title = title.substring(0, 57) + "..."
|
||||
title = title.substring(0, 57) + "...";
|
||||
}
|
||||
|
||||
// Use second line or character count as description
|
||||
let description = ""
|
||||
let description = "";
|
||||
if (lines.length > 1) {
|
||||
description = lines[1]
|
||||
description = lines[1];
|
||||
if (description.length > 80) {
|
||||
description = description.substring(0, 77) + "..."
|
||||
description = description.substring(0, 77) + "...";
|
||||
}
|
||||
} else {
|
||||
const chars = preview.length
|
||||
const words = preview.split(/\s+/).length
|
||||
description = `${chars} characters, ${words} word${words !== 1 ? 's' : ''}`
|
||||
const chars = preview.length;
|
||||
const words = preview.split(/\s+/).length;
|
||||
description = `${chars} characters, ${words} word${words !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -240,16 +251,16 @@ Item {
|
||||
"description": description,
|
||||
"icon": "text-x-generic",
|
||||
"isImage": false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper: Parse image metadata from preview string
|
||||
function parseImageMeta(preview) {
|
||||
const re = /\[\[\s*binary data\s+([\d\.]+\s*(?:KiB|MiB|GiB|B))\s+(\w+)\s+(\d+)x(\d+)\s*\]\]/i
|
||||
const match = (preview || "").match(re)
|
||||
const re = /\[\[\s*binary data\s+([\d\.]+\s*(?:KiB|MiB|GiB|B))\s+(\w+)\s+(\d+)x(\d+)\s*\]\]/i;
|
||||
const match = (preview || "").match(re);
|
||||
|
||||
if (!match) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -257,12 +268,12 @@ Item {
|
||||
"fmt": (match[2] || "").toUpperCase(),
|
||||
"w": Number(match[3]),
|
||||
"h": Number(match[4])
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Public method to get image data for a clipboard item
|
||||
// This can be called by the launcher when rendering
|
||||
function getImageForItem(clipboardId) {
|
||||
return ClipboardService.getImageData ? ClipboardService.getImageData(clipboardId) : null
|
||||
return ClipboardService.getImageData ? ClipboardService.getImageData(clipboardId) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
import qs.Modules.MainScreen
|
||||
|
||||
// Notification History panel
|
||||
SmartPanel {
|
||||
@@ -17,7 +17,7 @@ SmartPanel {
|
||||
preferredHeight: Math.round(540 * Style.uiScaleRatio)
|
||||
|
||||
onOpened: function () {
|
||||
NotificationService.updateLastSeenTs()
|
||||
NotificationService.updateLastSeenTs();
|
||||
}
|
||||
|
||||
panelContent: Rectangle {
|
||||
@@ -67,9 +67,9 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.clear-history")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
NotificationService.clearHistory()
|
||||
NotificationService.clearHistory();
|
||||
// Close panel as there is nothing more to see.
|
||||
root.close()
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,9 +177,9 @@ SmartPanel {
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
if (scrollView.expandedId === notificationId) {
|
||||
scrollView.expandedId = ""
|
||||
scrollView.expandedId = "";
|
||||
} else {
|
||||
scrollView.expandedId = notificationId
|
||||
scrollView.expandedId = notificationId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,11 +227,11 @@ SmartPanel {
|
||||
visible: model.urgency !== 1
|
||||
color: {
|
||||
if (model.urgency === 2)
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
else if (model.urgency === 0)
|
||||
return Color.mOnSurfaceVariant
|
||||
return Color.mOnSurfaceVariant;
|
||||
else
|
||||
return Color.transparent
|
||||
return Color.transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ SmartPanel {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
onClicked: {
|
||||
NotificationService.removeFromHistory(notificationId)
|
||||
NotificationService.removeFromHistory(notificationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,31 +4,31 @@ import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Modules.MainScreen
|
||||
|
||||
SmartPanel {
|
||||
id: root
|
||||
|
||||
preferredWidth: Math.round(400 * Style.uiScaleRatio)
|
||||
preferredHeight: {
|
||||
var headerHeight = Settings.data.sessionMenu.showHeader ? Style.baseWidgetSize * 0.6 : 0
|
||||
var headerHeight = Settings.data.sessionMenu.showHeader ? Style.baseWidgetSize * 0.6 : 0;
|
||||
|
||||
var dividerHeight = Settings.data.sessionMenu.showHeader ? Style.marginS : 0
|
||||
var buttonHeight = Style.baseWidgetSize * 1.3 * Style.uiScaleRatio
|
||||
var buttonSpacing = Style.marginS
|
||||
var enabledCount = powerOptions.length
|
||||
var dividerHeight = Settings.data.sessionMenu.showHeader ? Style.marginS : 0;
|
||||
var buttonHeight = Style.baseWidgetSize * 1.3 * Style.uiScaleRatio;
|
||||
var buttonSpacing = Style.marginS;
|
||||
var enabledCount = powerOptions.length;
|
||||
|
||||
var headerSpacing = Settings.data.sessionMenu.showHeader ? (Style.marginL * 2) : 0
|
||||
var baseHeight = (Style.marginL * 4) + headerHeight + dividerHeight + headerSpacing
|
||||
var buttonsHeight = enabledCount > 0 ? (buttonHeight * enabledCount) + (buttonSpacing * (enabledCount - 1)) : 0
|
||||
var headerSpacing = Settings.data.sessionMenu.showHeader ? (Style.marginL * 2) : 0;
|
||||
var baseHeight = (Style.marginL * 4) + headerHeight + dividerHeight + headerSpacing;
|
||||
var buttonsHeight = enabledCount > 0 ? (buttonHeight * enabledCount) + (buttonSpacing * (enabledCount - 1)) : 0;
|
||||
|
||||
return Math.round(baseHeight + buttonsHeight)
|
||||
return Math.round(baseHeight + buttonsHeight);
|
||||
}
|
||||
|
||||
// Positioning
|
||||
@@ -89,216 +89,216 @@ SmartPanel {
|
||||
|
||||
// Build powerOptions from settings, filtering enabled ones and adding metadata
|
||||
property var powerOptions: {
|
||||
var options = []
|
||||
var settingsOptions = Settings.data.sessionMenu.powerOptions || []
|
||||
var options = [];
|
||||
var settingsOptions = Settings.data.sessionMenu.powerOptions || [];
|
||||
|
||||
for (var i = 0; i < settingsOptions.length; i++) {
|
||||
var settingOption = settingsOptions[i]
|
||||
var settingOption = settingsOptions[i];
|
||||
if (settingOption.enabled && actionMetadata[settingOption.action]) {
|
||||
var metadata = actionMetadata[settingOption.action]
|
||||
var metadata = actionMetadata[settingOption.action];
|
||||
options.push({
|
||||
"action": settingOption.action,
|
||||
"icon": metadata.icon,
|
||||
"title": metadata.title,
|
||||
"isShutdown": metadata.isShutdown,
|
||||
"countdownEnabled": settingOption.countdownEnabled !== undefined ? settingOption.countdownEnabled : true
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
return options;
|
||||
}
|
||||
|
||||
// Update powerOptions when settings change
|
||||
Connections {
|
||||
target: Settings.data.sessionMenu
|
||||
function onPowerOptionsChanged() {
|
||||
var options = []
|
||||
var settingsOptions = Settings.data.sessionMenu.powerOptions || []
|
||||
var options = [];
|
||||
var settingsOptions = Settings.data.sessionMenu.powerOptions || [];
|
||||
|
||||
for (var i = 0; i < settingsOptions.length; i++) {
|
||||
var settingOption = settingsOptions[i]
|
||||
var settingOption = settingsOptions[i];
|
||||
if (settingOption.enabled && actionMetadata[settingOption.action]) {
|
||||
var metadata = actionMetadata[settingOption.action]
|
||||
var metadata = actionMetadata[settingOption.action];
|
||||
options.push({
|
||||
"action": settingOption.action,
|
||||
"icon": metadata.icon,
|
||||
"title": metadata.title,
|
||||
"isShutdown": metadata.isShutdown,
|
||||
"countdownEnabled": settingOption.countdownEnabled !== undefined ? settingOption.countdownEnabled : true
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
root.powerOptions = options
|
||||
root.powerOptions = options;
|
||||
}
|
||||
}
|
||||
|
||||
// Lifecycle handlers
|
||||
onOpened: {
|
||||
selectedIndex = 0
|
||||
selectedIndex = 0;
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
cancelTimer()
|
||||
selectedIndex = 0
|
||||
cancelTimer();
|
||||
selectedIndex = 0;
|
||||
}
|
||||
|
||||
// Timer management
|
||||
function startTimer(action) {
|
||||
// Check if global countdown is disabled
|
||||
if (!Settings.data.sessionMenu.enableCountdown) {
|
||||
executeAction(action)
|
||||
return
|
||||
executeAction(action);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check per-item countdown setting
|
||||
var option = null
|
||||
var option = null;
|
||||
for (var i = 0; i < powerOptions.length; i++) {
|
||||
if (powerOptions[i].action === action) {
|
||||
option = powerOptions[i]
|
||||
break
|
||||
option = powerOptions[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If this specific action has countdown disabled, execute immediately
|
||||
if (option && option.countdownEnabled === false) {
|
||||
executeAction(action)
|
||||
return
|
||||
executeAction(action);
|
||||
return;
|
||||
}
|
||||
|
||||
if (timerActive && pendingAction === action) {
|
||||
// Second click - execute immediately
|
||||
executeAction(action)
|
||||
return
|
||||
executeAction(action);
|
||||
return;
|
||||
}
|
||||
|
||||
pendingAction = action
|
||||
timeRemaining = timerDuration
|
||||
timerActive = true
|
||||
countdownTimer.start()
|
||||
pendingAction = action;
|
||||
timeRemaining = timerDuration;
|
||||
timerActive = true;
|
||||
countdownTimer.start();
|
||||
}
|
||||
|
||||
function cancelTimer() {
|
||||
timerActive = false
|
||||
pendingAction = ""
|
||||
timeRemaining = 0
|
||||
countdownTimer.stop()
|
||||
timerActive = false;
|
||||
pendingAction = "";
|
||||
timeRemaining = 0;
|
||||
countdownTimer.stop();
|
||||
}
|
||||
|
||||
function executeAction(action) {
|
||||
// Stop timer but don't reset other properties yet
|
||||
countdownTimer.stop()
|
||||
countdownTimer.stop();
|
||||
|
||||
switch (action) {
|
||||
case "lock":
|
||||
// Access lockScreen via PanelService
|
||||
if (PanelService.lockScreen && !PanelService.lockScreen.active) {
|
||||
PanelService.lockScreen.active = true
|
||||
PanelService.lockScreen.active = true;
|
||||
}
|
||||
break
|
||||
break;
|
||||
case "suspend":
|
||||
// Check if we should lock before suspending
|
||||
if (Settings.data.general.lockOnSuspend) {
|
||||
CompositorService.lockAndSuspend()
|
||||
CompositorService.lockAndSuspend();
|
||||
} else {
|
||||
CompositorService.suspend()
|
||||
CompositorService.suspend();
|
||||
}
|
||||
break
|
||||
break;
|
||||
case "hibernate":
|
||||
CompositorService.hibernate()
|
||||
break
|
||||
CompositorService.hibernate();
|
||||
break;
|
||||
case "reboot":
|
||||
CompositorService.reboot()
|
||||
break
|
||||
CompositorService.reboot();
|
||||
break;
|
||||
case "logout":
|
||||
CompositorService.logout()
|
||||
break
|
||||
CompositorService.logout();
|
||||
break;
|
||||
case "shutdown":
|
||||
CompositorService.shutdown()
|
||||
break
|
||||
CompositorService.shutdown();
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset timer state and close panel
|
||||
cancelTimer()
|
||||
root.close()
|
||||
cancelTimer();
|
||||
root.close();
|
||||
}
|
||||
|
||||
// Navigation functions
|
||||
function selectNextWrapped() {
|
||||
if (powerOptions.length > 0) {
|
||||
selectedIndex = (selectedIndex + 1) % powerOptions.length
|
||||
selectedIndex = (selectedIndex + 1) % powerOptions.length;
|
||||
}
|
||||
}
|
||||
|
||||
function selectPreviousWrapped() {
|
||||
if (powerOptions.length > 0) {
|
||||
selectedIndex = (((selectedIndex - 1) % powerOptions.length) + powerOptions.length) % powerOptions.length
|
||||
selectedIndex = (((selectedIndex - 1) % powerOptions.length) + powerOptions.length) % powerOptions.length;
|
||||
}
|
||||
}
|
||||
|
||||
function selectFirst() {
|
||||
selectedIndex = 0
|
||||
selectedIndex = 0;
|
||||
}
|
||||
|
||||
function selectLast() {
|
||||
if (powerOptions.length > 0) {
|
||||
selectedIndex = powerOptions.length - 1
|
||||
selectedIndex = powerOptions.length - 1;
|
||||
} else {
|
||||
selectedIndex = 0
|
||||
selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function activate() {
|
||||
if (powerOptions.length > 0 && powerOptions[selectedIndex]) {
|
||||
const option = powerOptions[selectedIndex]
|
||||
startTimer(option.action)
|
||||
const option = powerOptions[selectedIndex];
|
||||
startTimer(option.action);
|
||||
}
|
||||
}
|
||||
|
||||
// Override keyboard handlers from SmartPanel
|
||||
function onEscapePressed() {
|
||||
if (timerActive) {
|
||||
cancelTimer()
|
||||
cancelTimer();
|
||||
} else {
|
||||
root.close()
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
function onTabPressed() {
|
||||
selectNextWrapped()
|
||||
selectNextWrapped();
|
||||
}
|
||||
|
||||
function onBackTabPressed() {
|
||||
selectPreviousWrapped()
|
||||
selectPreviousWrapped();
|
||||
}
|
||||
|
||||
function onUpPressed() {
|
||||
selectPreviousWrapped()
|
||||
selectPreviousWrapped();
|
||||
}
|
||||
|
||||
function onDownPressed() {
|
||||
selectNextWrapped()
|
||||
selectNextWrapped();
|
||||
}
|
||||
|
||||
function onReturnPressed() {
|
||||
activate()
|
||||
activate();
|
||||
}
|
||||
|
||||
function onHomePressed() {
|
||||
selectFirst()
|
||||
selectFirst();
|
||||
}
|
||||
|
||||
function onEndPressed() {
|
||||
selectLast()
|
||||
selectLast();
|
||||
}
|
||||
|
||||
function onCtrlJPressed() {
|
||||
selectNextWrapped()
|
||||
selectNextWrapped();
|
||||
}
|
||||
|
||||
function onCtrlKPressed() {
|
||||
selectPreviousWrapped()
|
||||
selectPreviousWrapped();
|
||||
}
|
||||
|
||||
// Countdown timer
|
||||
@@ -307,9 +307,9 @@ SmartPanel {
|
||||
interval: 100
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
timeRemaining -= interval
|
||||
timeRemaining -= interval;
|
||||
if (timeRemaining <= 0) {
|
||||
executeAction(pendingAction)
|
||||
executeAction(pendingAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,23 +320,23 @@ SmartPanel {
|
||||
|
||||
// Navigation functions
|
||||
function selectFirst() {
|
||||
root.selectFirst()
|
||||
root.selectFirst();
|
||||
}
|
||||
|
||||
function selectLast() {
|
||||
root.selectLast()
|
||||
root.selectLast();
|
||||
}
|
||||
|
||||
function selectNextWrapped() {
|
||||
root.selectNextWrapped()
|
||||
root.selectNextWrapped();
|
||||
}
|
||||
|
||||
function selectPreviousWrapped() {
|
||||
root.selectPreviousWrapped()
|
||||
root.selectPreviousWrapped();
|
||||
}
|
||||
|
||||
function activate() {
|
||||
root.activate()
|
||||
root.activate();
|
||||
}
|
||||
|
||||
NBox {
|
||||
@@ -379,10 +379,10 @@ SmartPanel {
|
||||
colorFg: timerActive ? Color.mError : Color.mOnSurface
|
||||
onClicked: {
|
||||
if (timerActive) {
|
||||
cancelTimer()
|
||||
cancelTimer();
|
||||
} else {
|
||||
cancelTimer()
|
||||
root.close()
|
||||
cancelTimer();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,8 +407,8 @@ SmartPanel {
|
||||
isShutdown: modelData.isShutdown || false
|
||||
isSelected: index === selectedIndex
|
||||
onClicked: {
|
||||
selectedIndex = index
|
||||
startTimer(modelData.action)
|
||||
selectedIndex = index;
|
||||
startTimer(modelData.action);
|
||||
}
|
||||
pending: timerActive && pendingAction === modelData.action
|
||||
}
|
||||
@@ -434,12 +434,12 @@ SmartPanel {
|
||||
radius: Style.radiusS
|
||||
color: {
|
||||
if (pending) {
|
||||
return Qt.alpha(Color.mPrimary, 0.08)
|
||||
return Qt.alpha(Color.mPrimary, 0.08);
|
||||
}
|
||||
if (isSelected || mouseArea.containsMouse) {
|
||||
return Color.mHover
|
||||
return Color.mHover;
|
||||
}
|
||||
return Color.transparent
|
||||
return Color.transparent;
|
||||
}
|
||||
|
||||
border.width: pending ? Math.max(Style.borderM) : 0
|
||||
@@ -464,12 +464,12 @@ SmartPanel {
|
||||
icon: buttonRoot.icon
|
||||
color: {
|
||||
if (buttonRoot.pending)
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse)
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
if (buttonRoot.isSelected || mouseArea.containsMouse)
|
||||
return Color.mOnHover
|
||||
return Color.mOnSurface
|
||||
return Color.mOnHover;
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
pointSize: Style.fontSizeXXL
|
||||
width: Style.baseWidgetSize * 0.5
|
||||
@@ -499,12 +499,12 @@ SmartPanel {
|
||||
pointSize: Style.fontSizeM
|
||||
color: {
|
||||
if (buttonRoot.pending)
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse)
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
if (buttonRoot.isSelected || mouseArea.containsMouse)
|
||||
return Color.mOnHover
|
||||
return Color.mOnSurface
|
||||
return Color.mOnHover;
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
|
||||
@@ -3,8 +3,8 @@ import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
// Widget Settings Dialog Component
|
||||
Popup {
|
||||
@@ -27,7 +27,7 @@ Popup {
|
||||
onOpened: {
|
||||
// Load settings when popup opens with data
|
||||
if (widgetData && widgetId) {
|
||||
loadWidgetSettings()
|
||||
loadWidgetSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,9 +102,9 @@ Popup {
|
||||
icon: "check"
|
||||
onClicked: {
|
||||
if (settingsLoader.item && settingsLoader.item.saveSettings) {
|
||||
var newSettings = settingsLoader.item.saveSettings()
|
||||
root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings)
|
||||
root.close()
|
||||
var newSettings = settingsLoader.item.saveSettings();
|
||||
root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings);
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,13 +112,13 @@ Popup {
|
||||
}
|
||||
|
||||
function loadWidgetSettings() {
|
||||
const source = BarWidgetRegistry.widgetSettingsMap[widgetId]
|
||||
const source = BarWidgetRegistry.widgetSettingsMap[widgetId];
|
||||
if (source) {
|
||||
// Use setSource to pass properties at creation time
|
||||
settingsLoader.setSource(source, {
|
||||
"widgetData": widgetData,
|
||||
"widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,35 +22,39 @@ ColumnLayout {
|
||||
|
||||
Component.onCompleted: {
|
||||
if (widgetData && widgetData.hideMode !== undefined) {
|
||||
valueHideMode = widgetData.hideMode
|
||||
valueHideMode = widgetData.hideMode;
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.hideMode = valueHideMode
|
||||
settings.showIcon = valueShowIcon
|
||||
settings.scrollingMode = valueScrollingMode
|
||||
settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth
|
||||
settings.useFixedWidth = valueUseFixedWidth
|
||||
settings.colorizeIcons = valueColorizeIcons
|
||||
return settings
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.hideMode = valueHideMode;
|
||||
settings.showIcon = valueShowIcon;
|
||||
settings.scrollingMode = valueScrollingMode;
|
||||
settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth;
|
||||
settings.useFixedWidth = valueUseFixedWidth;
|
||||
settings.colorizeIcons = valueColorizeIcons;
|
||||
return settings;
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.widget-settings.active-window.hide-mode.label")
|
||||
description: I18n.tr("bar.widget-settings.active-window.hide-mode.description")
|
||||
model: [{
|
||||
model: [
|
||||
{
|
||||
"key": "visible",
|
||||
"name": I18n.tr("options.hide-modes.visible")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "hidden",
|
||||
"name": I18n.tr("options.hide-modes.hidden")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "transparent",
|
||||
"name": I18n.tr("options.hide-modes.transparent")
|
||||
}]
|
||||
}
|
||||
]
|
||||
currentKey: root.valueHideMode
|
||||
onSelected: key => root.valueHideMode = key
|
||||
}
|
||||
@@ -91,16 +95,20 @@ ColumnLayout {
|
||||
NComboBox {
|
||||
label: I18n.tr("bar.widget-settings.active-window.scrolling-mode.label")
|
||||
description: I18n.tr("bar.widget-settings.active-window.scrolling-mode.description")
|
||||
model: [{
|
||||
model: [
|
||||
{
|
||||
"key": "always",
|
||||
"name": I18n.tr("options.scrolling-modes.always")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "hover",
|
||||
"name": I18n.tr("options.scrolling-modes.hover")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "never",
|
||||
"name": I18n.tr("options.scrolling-modes.never")
|
||||
}]
|
||||
}
|
||||
]
|
||||
currentKey: valueScrollingMode
|
||||
onSelected: key => valueScrollingMode = key
|
||||
minimumWidth: 200
|
||||
|
||||
@@ -17,11 +17,11 @@ ColumnLayout {
|
||||
property string valueColorName: widgetData.colorName !== undefined ? widgetData.colorName : widgetMetadata.colorName
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.width = parseInt(widthInput.text) || widgetMetadata.width
|
||||
settings.hideWhenIdle = valueHideWhenIdle
|
||||
settings.colorName = valueColorName
|
||||
return settings
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.width = parseInt(widthInput.text) || widgetMetadata.width;
|
||||
settings.hideWhenIdle = valueHideWhenIdle;
|
||||
settings.colorName = valueColorName;
|
||||
return settings;
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
@@ -37,22 +37,28 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.widget-settings.audio-visualizer.color-name.label")
|
||||
description: I18n.tr("bar.widget-settings.audio-visualizer.color-name.description")
|
||||
model: [{
|
||||
model: [
|
||||
{
|
||||
"key": "primary",
|
||||
"name": I18n.tr("options.colors.primary")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "secondary",
|
||||
"name": I18n.tr("options.colors.secondary")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "tertiary",
|
||||
"name": I18n.tr("options.colors.tertiary")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "onSurface",
|
||||
"name": I18n.tr("options.colors.onSurface")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "error",
|
||||
"name": I18n.tr("options.colors.error")
|
||||
}]
|
||||
}
|
||||
]
|
||||
currentKey: root.valueColorName
|
||||
onSelected: key => root.valueColorName = key
|
||||
}
|
||||
|
||||
@@ -17,26 +17,30 @@ ColumnLayout {
|
||||
property int valueWarningThreshold: widgetData.warningThreshold !== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.displayMode = valueDisplayMode
|
||||
settings.warningThreshold = valueWarningThreshold
|
||||
return settings
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.displayMode = valueDisplayMode;
|
||||
settings.warningThreshold = valueWarningThreshold;
|
||||
return settings;
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("bar.widget-settings.battery.display-mode.label")
|
||||
description: I18n.tr("bar.widget-settings.battery.display-mode.description")
|
||||
minimumWidth: 134
|
||||
model: [{
|
||||
model: [
|
||||
{
|
||||
"key": "onhover",
|
||||
"name": I18n.tr("options.display-mode.on-hover")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "alwaysShow",
|
||||
"name": I18n.tr("options.display-mode.always-show")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "alwaysHide",
|
||||
"name": I18n.tr("options.display-mode.always-hide")
|
||||
}]
|
||||
}
|
||||
]
|
||||
currentKey: root.valueDisplayMode
|
||||
onSelected: key => root.valueDisplayMode = key
|
||||
}
|
||||
|
||||
@@ -15,25 +15,29 @@ ColumnLayout {
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.displayMode = valueDisplayMode
|
||||
return settings
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.displayMode = valueDisplayMode;
|
||||
return settings;
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("bar.widget-settings.battery.display-mode.label")
|
||||
description: I18n.tr("bar.widget-settings.battery.display-mode.description")
|
||||
minimumWidth: 134
|
||||
model: [{
|
||||
model: [
|
||||
{
|
||||
"key": "onhover",
|
||||
"name": I18n.tr("options.display-mode.on-hover")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "alwaysShow",
|
||||
"name": I18n.tr("options.display-mode.always-show")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "alwaysHide",
|
||||
"name": I18n.tr("options.display-mode.always-hide")
|
||||
}]
|
||||
}
|
||||
]
|
||||
currentKey: root.valueDisplayMode
|
||||
onSelected: key => root.valueDisplayMode = key
|
||||
}
|
||||
|
||||
@@ -16,25 +16,29 @@ ColumnLayout {
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.displayMode = valueDisplayMode
|
||||
return settings
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.displayMode = valueDisplayMode;
|
||||
return settings;
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("bar.widget-settings.brightness.display-mode.label")
|
||||
description: I18n.tr("bar.widget-settings.brightness.display-mode.description")
|
||||
minimumWidth: 134
|
||||
model: [{
|
||||
model: [
|
||||
{
|
||||
"key": "onhover",
|
||||
"name": I18n.tr("options.display-mode.on-hover")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "alwaysShow",
|
||||
"name": I18n.tr("options.display-mode.always-show")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "alwaysHide",
|
||||
"name": I18n.tr("options.display-mode.always-hide")
|
||||
}]
|
||||
}
|
||||
]
|
||||
currentKey: valueDisplayMode
|
||||
onSelected: key => valueDisplayMode = key
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
@@ -28,13 +28,13 @@ ColumnLayout {
|
||||
readonly property var now: Time.now
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.usePrimaryColor = valueUsePrimaryColor
|
||||
settings.useCustomFont = valueUseCustomFont
|
||||
settings.customFont = valueCustomFont
|
||||
settings.formatHorizontal = valueFormatHorizontal.trim()
|
||||
settings.formatVertical = valueFormatVertical.trim()
|
||||
return settings
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.usePrimaryColor = valueUsePrimaryColor;
|
||||
settings.useCustomFont = valueUseCustomFont;
|
||||
settings.customFont = valueCustomFont;
|
||||
settings.formatHorizontal = valueFormatHorizontal.trim();
|
||||
settings.formatVertical = valueFormatVertical.trim();
|
||||
return settings;
|
||||
}
|
||||
|
||||
// Function to insert token at cursor position in the focused input
|
||||
@@ -42,25 +42,25 @@ ColumnLayout {
|
||||
if (!focusedInput || !focusedInput.inputItem) {
|
||||
// If no input is focused, default to horiz
|
||||
if (inputHoriz.inputItem) {
|
||||
inputHoriz.inputItem.focus = true
|
||||
focusedInput = inputHoriz
|
||||
inputHoriz.inputItem.focus = true;
|
||||
focusedInput = inputHoriz;
|
||||
}
|
||||
}
|
||||
|
||||
if (focusedInput && focusedInput.inputItem) {
|
||||
var input = focusedInput.inputItem
|
||||
var cursorPos = input.cursorPosition
|
||||
var currentText = input.text
|
||||
var input = focusedInput.inputItem;
|
||||
var cursorPos = input.cursorPosition;
|
||||
var currentText = input.text;
|
||||
|
||||
// Insert token at cursor position
|
||||
var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos)
|
||||
input.text = newText + " "
|
||||
var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos);
|
||||
input.text = newText + " ";
|
||||
|
||||
// Move cursor after the inserted token
|
||||
input.cursorPosition = cursorPos + token.length + 1
|
||||
input.cursorPosition = cursorPos + token.length + 1;
|
||||
|
||||
// Ensure the input keeps focus
|
||||
input.focus = true
|
||||
input.focus = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ ColumnLayout {
|
||||
popupHeight: 420
|
||||
minimumWidth: 300
|
||||
onSelected: function (key) {
|
||||
valueCustomFont = key
|
||||
valueCustomFont = key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,9 +131,9 @@ ColumnLayout {
|
||||
if (inputItem) {
|
||||
inputItem.onActiveFocusChanged.connect(function () {
|
||||
if (inputItem.activeFocus) {
|
||||
root.focusedInput = inputHoriz
|
||||
root.focusedInput = inputHoriz;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,9 +155,9 @@ ColumnLayout {
|
||||
if (inputItem) {
|
||||
inputItem.onActiveFocusChanged.connect(function () {
|
||||
if (inputItem.activeFocus) {
|
||||
root.focusedInput = inputVert
|
||||
root.focusedInput = inputVert;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ ColumnLayout {
|
||||
property bool valueColorizeDistroLogo: widgetData.colorizeDistroLogo !== undefined ? widgetData.colorizeDistroLogo : (widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false)
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.icon = valueIcon
|
||||
settings.useDistroLogo = valueUseDistroLogo
|
||||
settings.customIconPath = valueCustomIconPath
|
||||
settings.colorizeDistroLogo = valueColorizeDistroLogo
|
||||
return settings
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.icon = valueIcon;
|
||||
settings.useDistroLogo = valueUseDistroLogo;
|
||||
settings.customIconPath = valueCustomIconPath;
|
||||
settings.colorizeDistroLogo = valueColorizeDistroLogo;
|
||||
return settings;
|
||||
}
|
||||
|
||||
NToggle {
|
||||
@@ -33,10 +33,10 @@ ColumnLayout {
|
||||
description: I18n.tr("bar.widget-settings.control-center.use-distro-logo.description")
|
||||
checked: valueUseDistroLogo
|
||||
onToggled: function (checked) {
|
||||
valueUseDistroLogo = checked
|
||||
valueUseDistroLogo = checked;
|
||||
if (checked) {
|
||||
valueCustomIconPath = ""
|
||||
valueIcon = ""
|
||||
valueCustomIconPath = "";
|
||||
valueIcon = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ ColumnLayout {
|
||||
description: I18n.tr("bar.widget-settings.control-center.colorize-distro-logo.description")
|
||||
checked: valueColorizeDistroLogo
|
||||
onToggled: function (checked) {
|
||||
valueColorizeDistroLogo = checked
|
||||
valueColorizeDistroLogo = checked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +94,8 @@ ColumnLayout {
|
||||
id: iconPicker
|
||||
initialIcon: valueIcon
|
||||
onIconSelected: iconName => {
|
||||
valueIcon = iconName
|
||||
valueCustomIconPath = ""
|
||||
valueIcon = iconName;
|
||||
valueCustomIconPath = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ ColumnLayout {
|
||||
initialPath: Quickshell.env("HOME")
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
valueCustomIconPath = paths[0] // Use first selected file
|
||||
valueCustomIconPath = paths[0]; // Use first selected file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,21 +19,21 @@ ColumnLayout {
|
||||
property bool valueHideTextInVerticalBar: widgetData.hideTextInVerticalBar !== undefined ? widgetData.hideTextInVerticalBar : widgetMetadata.hideTextInVerticalBar
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.icon = valueIcon
|
||||
settings.leftClickExec = leftClickExecInput.text
|
||||
settings.leftClickUpdateText = leftClickUpdateText.checked
|
||||
settings.rightClickExec = rightClickExecInput.text
|
||||
settings.rightClickUpdateText = rightClickUpdateText.checked
|
||||
settings.middleClickExec = middleClickExecInput.text
|
||||
settings.middleClickUpdateText = middleClickUpdateText.checked
|
||||
settings.textCommand = textCommandInput.text
|
||||
settings.textCollapse = textCollapseInput.text
|
||||
settings.textStream = valueTextStream
|
||||
settings.parseJson = valueParseJson
|
||||
settings.hideTextInVerticalBar = valueHideTextInVerticalBar
|
||||
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10)
|
||||
return settings
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.icon = valueIcon;
|
||||
settings.leftClickExec = leftClickExecInput.text;
|
||||
settings.leftClickUpdateText = leftClickUpdateText.checked;
|
||||
settings.rightClickExec = rightClickExecInput.text;
|
||||
settings.rightClickUpdateText = rightClickUpdateText.checked;
|
||||
settings.middleClickExec = middleClickExecInput.text;
|
||||
settings.middleClickUpdateText = middleClickUpdateText.checked;
|
||||
settings.textCommand = textCommandInput.text;
|
||||
settings.textCollapse = textCollapseInput.text;
|
||||
settings.textStream = valueTextStream;
|
||||
settings.parseJson = valueParseJson;
|
||||
settings.hideTextInVerticalBar = valueHideTextInVerticalBar;
|
||||
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10);
|
||||
return settings;
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -61,7 +61,7 @@ ColumnLayout {
|
||||
id: iconPicker
|
||||
initialIcon: valueIcon
|
||||
onIconSelected: function (iconName) {
|
||||
valueIcon = iconName
|
||||
valueIcon = iconName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,25 +16,29 @@ ColumnLayout {
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.displayMode = valueDisplayMode
|
||||
return settings
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.displayMode = valueDisplayMode;
|
||||
return settings;
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("bar.widget-settings.keyboard-layout.display-mode.label")
|
||||
description: I18n.tr("bar.widget-settings.keyboard-layout.display-mode.description")
|
||||
minimumWidth: 134
|
||||
model: [{
|
||||
model: [
|
||||
{
|
||||
"key": "onhover",
|
||||
"name": I18n.tr("options.display-mode.on-hover")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "forceOpen",
|
||||
"name": I18n.tr("options.display-mode.force-open")
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"key": "alwaysHide",
|
||||
"name": I18n.tr("options.display-mode.always-hide")
|
||||
}]
|
||||
}
|
||||
]
|
||||
currentKey: valueDisplayMode
|
||||
onSelected: key => valueDisplayMode = key
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user