mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Merge branch 'noctalia-dev:main' into pr/bluetooth-rework
This commit is contained in:
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"wallpaper": "{{image}}",
|
||||
"alpha": "100",
|
||||
"colors": {
|
||||
"color0": "{{colors.background.default.hex}}",
|
||||
"color1": "",
|
||||
"color2": "",
|
||||
"color3": "",
|
||||
"color4": "",
|
||||
"color5": "",
|
||||
"color6": "",
|
||||
"color7": "",
|
||||
"color8": "",
|
||||
"color9": "",
|
||||
"color10": "{{colors.primary.default.hex}}",
|
||||
"color11": "",
|
||||
"color12": "",
|
||||
"color13": "{{colors.surface_bright.default.hex}}",
|
||||
"color14": "",
|
||||
"color15": "{{colors.on_surface.default.hex}}"
|
||||
}
|
||||
}
|
||||
"wallpaper": "{{image}}",
|
||||
"alpha": "100",
|
||||
"colors": {
|
||||
"color0": "{{colors.background.default.hex}}",
|
||||
"color1": "{{colors.surface_container.default.hex}}",
|
||||
"color2": "{{colors.surface_container_highest.default.hex}}",
|
||||
"color3": "",
|
||||
"color4": "",
|
||||
"color5": "",
|
||||
"color6": "",
|
||||
"color7": "",
|
||||
"color8": "",
|
||||
"color9": "",
|
||||
"color10": "{{colors.primary.default.hex}}",
|
||||
"color11": "{{colors.secondary.default.hex}}",
|
||||
"color12": "{{colors.tertiary.default.hex}}",
|
||||
"color13": "{{colors.surface_bright.default.hex}}",
|
||||
"color14": "",
|
||||
"color15": "{{colors.on_surface.default.hex}}"
|
||||
}
|
||||
}
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "Zufällig",
|
||||
"reboot": "Neustart",
|
||||
"refresh": "Aktualisieren",
|
||||
"required": "Erforderlich",
|
||||
"reset": "Zurücksetzen",
|
||||
"resume": "Fortsetzen",
|
||||
"save": "Speichern",
|
||||
@@ -746,6 +747,8 @@
|
||||
"predefined-generate-templates-label": "Templates für vordefinierte Schemata generieren",
|
||||
"predefined-title": "Vordefinierte Farbschemata",
|
||||
"templates-desc": "Farben auf externe Anwendungen anwenden.",
|
||||
"templates-filter-description": "Vorlagen aus einer bestimmten Kategorie anzeigen.",
|
||||
"templates-filter-label": "Nach Kategorie filtern",
|
||||
"templates-misc-description": "Erstellen Sie Ihre eigenen Vorlagen.",
|
||||
"templates-misc-label": "Erweitert",
|
||||
"templates-misc-user-templates-description": "Nur aktivieren, wenn Sie wissen, was Sie tun — weitere Informationen finden Sie in unserer Online-Dokumentation",
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "Random",
|
||||
"reboot": "Reboot",
|
||||
"refresh": "Refresh",
|
||||
"required": "(required)",
|
||||
"reset": "Reset",
|
||||
"resume": "Resume",
|
||||
"save": "Save",
|
||||
@@ -746,6 +747,8 @@
|
||||
"predefined-generate-templates-label": "Generate templates for predefined schemes",
|
||||
"predefined-title": "Predefined color schemes",
|
||||
"templates-desc": "Apply colors to external applications.",
|
||||
"templates-filter-description": "Show templates from a specific category.",
|
||||
"templates-filter-label": "Filter by category",
|
||||
"templates-misc-description": "Create your own templates.",
|
||||
"templates-misc-label": "Advanced",
|
||||
"templates-misc-user-templates-description": "Only enable if you know what you are doing — refer to our online documentation",
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "Aleatorio",
|
||||
"reboot": "Reiniciar",
|
||||
"refresh": "Refrescar",
|
||||
"required": "Obligatorio",
|
||||
"reset": "Restablecer",
|
||||
"resume": "Reanudar",
|
||||
"save": "Guardar",
|
||||
@@ -746,6 +747,8 @@
|
||||
"predefined-generate-templates-label": "Generar plantillas para esquemas predefinidos",
|
||||
"predefined-title": "Esquemas de colores predefinidos",
|
||||
"templates-desc": "Aplicar colores a aplicaciones externas.",
|
||||
"templates-filter-description": "Mostrar plantillas de una categoría específica.",
|
||||
"templates-filter-label": "Filtrar por categoría",
|
||||
"templates-misc-description": "Crea tus propias plantillas.",
|
||||
"templates-misc-label": "Avanzado",
|
||||
"templates-misc-user-templates-description": "Solo habilita si sabes lo que estás haciendo — consulta nuestra documentación en línea",
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "Aléatoire",
|
||||
"reboot": "Redémarrer",
|
||||
"refresh": "Actualiser",
|
||||
"required": "Obligatoire",
|
||||
"reset": "Réinitialiser",
|
||||
"resume": "Reprendrer",
|
||||
"save": "Enregistrer",
|
||||
@@ -746,6 +747,8 @@
|
||||
"predefined-generate-templates-label": "Générer des modèles pour les schémas prédéfinis",
|
||||
"predefined-title": "Jeux de couleurs prédéfinis",
|
||||
"templates-desc": "Appliquer des couleurs aux applications externes.",
|
||||
"templates-filter-description": "Afficher les modèles d'une catégorie spécifique.",
|
||||
"templates-filter-label": "Filtrer par catégorie",
|
||||
"templates-misc-description": "Créez vos propres modèles.",
|
||||
"templates-misc-label": "Avancé",
|
||||
"templates-misc-user-templates-description": "N'activez que si vous savez ce que vous faites — consultez notre documentation en ligne",
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "Véletlen",
|
||||
"reboot": "Újraindítás",
|
||||
"refresh": "Frissítés",
|
||||
"required": "Kötelező",
|
||||
"reset": "Visszaállítás",
|
||||
"resume": "Folytatás",
|
||||
"save": "Mentés",
|
||||
@@ -746,11 +747,13 @@
|
||||
"predefined-generate-templates-label": "Sablonok generálása előre definiált sémákhoz",
|
||||
"predefined-title": "Előre definiált színsémák",
|
||||
"templates-desc": "Színek alkalmazása külő alkalmazásokra.",
|
||||
"templates-filter-description": "Sablonok megjelenítése egy adott kategóriából.",
|
||||
"templates-filter-label": "Szűrés kategória szerint",
|
||||
"templates-misc-description": "Hozzon létre saját sablonokat.",
|
||||
"templates-misc-label": "Haladó",
|
||||
"templates-misc-user-templates-description": "Csak akkor engedélyezze, ha tudja, mit csinál — lásd az online dokumentációt",
|
||||
"templates-misc-user-templates-label": "Felhasználói sablonok engedélyezése",
|
||||
"templates-none-detected": "Nem észlelt",
|
||||
"templates-none-detected": "Nem észlelt semmit",
|
||||
"templates-write-path": "Írja: {filepath}",
|
||||
"title": "Színséma"
|
||||
},
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "ランダム",
|
||||
"reboot": "再起動",
|
||||
"refresh": "更新",
|
||||
"required": "必須",
|
||||
"reset": "リセット",
|
||||
"resume": "再開",
|
||||
"save": "保存",
|
||||
@@ -746,6 +747,8 @@
|
||||
"predefined-generate-templates-label": "プリセット配色のテンプレートを生成する",
|
||||
"predefined-title": "プリセット配色",
|
||||
"templates-desc": "外部アプリケーションに配色を適用します。",
|
||||
"templates-filter-description": "特定のカテゴリのテンプレートを表示します。",
|
||||
"templates-filter-label": "カテゴリで絞り込む",
|
||||
"templates-misc-description": "独自のテンプレートを作成。",
|
||||
"templates-misc-label": "詳細設定",
|
||||
"templates-misc-user-templates-description": "操作方法を理解している場合のみ有効にしてください。オンラインドキュメントを参照してください。",
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "Bêserûber",
|
||||
"reboot": "Ji nû ve dest pê bike",
|
||||
"refresh": "Nûkirin",
|
||||
"required": "Pêwîst",
|
||||
"reset": "Vegerandin",
|
||||
"resume": "Berdewam kirin",
|
||||
"save": "Tomar bike",
|
||||
@@ -746,11 +747,13 @@
|
||||
"predefined-generate-templates-label": "Şablonan ji bo şemayên pêşdiyarkirî çêbike",
|
||||
"predefined-title": "Şemayên rengan ên pêşdiyarkirî",
|
||||
"templates-desc": "ڕەنگان بخە سەر ئەپڵیکەیشنێن دەرەکی.",
|
||||
"templates-filter-description": "Şablonên ji kategoriyek taybet nîşan bide.",
|
||||
"templates-filter-label": "Li gorî kategoriyê parzûn bike",
|
||||
"templates-misc-description": "Şablonên xwe biafirîne.",
|
||||
"templates-misc-label": "Pêşketî",
|
||||
"templates-misc-user-templates-description": "Tenê eger tu dizanî çi dikî çalak bike — serî li belgekirina me ya serhêl bide",
|
||||
"templates-misc-user-templates-label": "Şablonên bikarhêner çalak bike",
|
||||
"templates-none-detected": "Nehatiya dîtin",
|
||||
"templates-none-detected": "Tiştek nehat dîtin",
|
||||
"templates-write-path": "Dinivîse: {filepath}",
|
||||
"title": "Şêweya rengan"
|
||||
},
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "Willekeurig",
|
||||
"reboot": "Herstarten",
|
||||
"refresh": "Vernieuwen",
|
||||
"required": "Vereist",
|
||||
"reset": "Resetten",
|
||||
"resume": "Hervatten",
|
||||
"save": "Opslaan",
|
||||
@@ -746,6 +747,8 @@
|
||||
"predefined-generate-templates-label": "Sjablonen genereren voor vooraf gedefinieerde schema's",
|
||||
"predefined-title": "Vooraf gedefinieerde kleurenschema's",
|
||||
"templates-desc": "Pas kleuren toe op externe applicaties.",
|
||||
"templates-filter-description": "Sjablonen uit een specifieke categorie weergeven.",
|
||||
"templates-filter-label": "Filteren op categorie",
|
||||
"templates-misc-description": "Maak uw eigen sjablonen.",
|
||||
"templates-misc-label": "Geavanceerd",
|
||||
"templates-misc-user-templates-description": "Alleen inschakelen als u weet wat u doet — raadpleeg onze online documentatie",
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "Losowy",
|
||||
"reboot": "Uruchom ponownie",
|
||||
"refresh": "Odśwież",
|
||||
"required": "Wymagane",
|
||||
"reset": "Zresetuj",
|
||||
"resume": "Wznów",
|
||||
"save": "Zapisz",
|
||||
@@ -746,6 +747,8 @@
|
||||
"predefined-generate-templates-label": "Generuj szablony dla predefiniowanych schematów",
|
||||
"predefined-title": "Predefiniowane schematy kolorów",
|
||||
"templates-desc": "Zastosuj kolory w aplikacjach zewnętrznych.",
|
||||
"templates-filter-description": "Pokaż szablony z określonej kategorii.",
|
||||
"templates-filter-label": "Filtruj według kategorii",
|
||||
"templates-misc-description": "Twórz własne szablony.",
|
||||
"templates-misc-label": "Zaawansowane",
|
||||
"templates-misc-user-templates-description": "Włącz tylko jeśli wiesz co robisz — zapoznaj się z dokumentacją online",
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "Aleatório",
|
||||
"reboot": "Reiniciar",
|
||||
"refresh": "Atualizar",
|
||||
"required": "Obrigatório",
|
||||
"reset": "Reiniciar",
|
||||
"resume": "Retomar",
|
||||
"save": "Salvar",
|
||||
@@ -746,6 +747,8 @@
|
||||
"predefined-generate-templates-label": "Gerar modelos para esquemas predefinidos",
|
||||
"predefined-title": "Esquemas de cores predefinidos",
|
||||
"templates-desc": "Aplicar cores a aplicações externas.",
|
||||
"templates-filter-description": "Mostrar templates de uma categoria específica.",
|
||||
"templates-filter-label": "Filtrar por categoria",
|
||||
"templates-misc-description": "Crie seus próprios modelos.",
|
||||
"templates-misc-label": "Avançado",
|
||||
"templates-misc-user-templates-description": "Ative apenas se souber o que está fazendo — consulte nossa documentação online",
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "Случайный",
|
||||
"reboot": "Перезагрузка",
|
||||
"refresh": "Обновить",
|
||||
"required": "Обязательно",
|
||||
"reset": "Сброс",
|
||||
"resume": "Продолжить",
|
||||
"save": "Сохранить",
|
||||
@@ -746,6 +747,8 @@
|
||||
"predefined-generate-templates-label": "Генерировать шаблоны для предопределенных схем",
|
||||
"predefined-title": "Предопределенные цветовые схемы",
|
||||
"templates-desc": "Применение цветов к внешним приложениям.",
|
||||
"templates-filter-description": "Показать шаблоны из определенной категории.",
|
||||
"templates-filter-label": "Фильтровать по категории",
|
||||
"templates-misc-description": "Создайте свои собственные шаблоны.",
|
||||
"templates-misc-label": "Дополнительно",
|
||||
"templates-misc-user-templates-description": "Включайте только если вы знаете, что делаете — обратитесь к нашей онлайн-документации",
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "Rastgele",
|
||||
"reboot": "Yeniden başlat",
|
||||
"refresh": "Yenile",
|
||||
"required": "Gerekli",
|
||||
"reset": "Sıfırla",
|
||||
"resume": "Sürdür",
|
||||
"save": "Kaydet",
|
||||
@@ -746,11 +747,13 @@
|
||||
"predefined-generate-templates-label": "Önceden tanımlanmış şemalar için şablonlar oluşturun",
|
||||
"predefined-title": "Önceden tanımlanmış renk şemaları",
|
||||
"templates-desc": "Renkleri harici uygulamalara uygula.",
|
||||
"templates-filter-description": "Belirli bir kategoriden şablonları göster.",
|
||||
"templates-filter-label": "Kategoriye göre filtrele",
|
||||
"templates-misc-description": "Kendi şablonlarınızı oluşturun.",
|
||||
"templates-misc-label": "Gelişmiş",
|
||||
"templates-misc-user-templates-description": "Yalnızca ne yaptığınızı biliyorsanız etkinleştirin — çevrimiçi belgelerimize bakın",
|
||||
"templates-misc-user-templates-label": "Kullanıcı şablonlarını etkinleştir",
|
||||
"templates-none-detected": "Algılanmadı",
|
||||
"templates-none-detected": "Hiçbiri algılanmadı",
|
||||
"templates-write-path": "Yazıyor: {filepath}",
|
||||
"title": "Renk şeması"
|
||||
},
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "Випадковий",
|
||||
"reboot": "Перезавантаження",
|
||||
"refresh": "Оновити",
|
||||
"required": "Обов'язково",
|
||||
"reset": "Скинути",
|
||||
"resume": "Продовжити",
|
||||
"save": "Зберегти",
|
||||
@@ -746,6 +747,8 @@
|
||||
"predefined-generate-templates-label": "Генерувати шаблони для попередньо визначених схем",
|
||||
"predefined-title": "Попередньо визначені колірні схеми",
|
||||
"templates-desc": "Застосовувати кольори до зовнішніх застосункiв.",
|
||||
"templates-filter-description": "Показати шаблони з певної категорії.",
|
||||
"templates-filter-label": "Фільтрувати за категорією",
|
||||
"templates-misc-description": "Створіть власні шаблони.",
|
||||
"templates-misc-label": "Розширено",
|
||||
"templates-misc-user-templates-description": "Увімкніть лише якщо ви знаєте, що робите — зверніться до нашої онлайн-документації",
|
||||
|
||||
@@ -415,6 +415,7 @@
|
||||
"random": "随机",
|
||||
"reboot": "重启",
|
||||
"refresh": "刷新",
|
||||
"required": "必需",
|
||||
"reset": "重置",
|
||||
"resume": "继续",
|
||||
"save": "保存",
|
||||
@@ -746,6 +747,8 @@
|
||||
"predefined-generate-templates-label": "为预定义方案生成模板",
|
||||
"predefined-title": "预定义配色方案",
|
||||
"templates-desc": "将颜色应用于外部应用程序。",
|
||||
"templates-filter-description": "显示特定类别的模板。",
|
||||
"templates-filter-label": "按类别筛选",
|
||||
"templates-misc-description": "创建您自己的模板。",
|
||||
"templates-misc-label": "高级",
|
||||
"templates-misc-user-templates-description": "仅在您知道自己在做什么时启用,请参阅我们的在线文档",
|
||||
|
||||
+391
-67
File diff suppressed because it is too large
Load Diff
@@ -402,7 +402,8 @@
|
||||
"screenLock": "",
|
||||
"screenUnlock": "",
|
||||
"performanceModeEnabled": "",
|
||||
"performanceModeDisabled": ""
|
||||
"performanceModeDisabled": "",
|
||||
"session": ""
|
||||
},
|
||||
"desktopWidgets": {
|
||||
"enabled": false,
|
||||
|
||||
+14
-3
@@ -1,14 +1,15 @@
|
||||
#!/usr/bin/env -S bash
|
||||
|
||||
# Ensure exactly one argument is provided.
|
||||
if [ "$#" -ne 1 ]; then
|
||||
# Ensure at least one argument is provided.
|
||||
if [ "$#" -lt 1 ]; then
|
||||
# Print usage information to standard error.
|
||||
echo "Error: No application specified." >&2
|
||||
echo "Usage: $0 {kitty|ghostty|foot|alacritty|wezterm|fuzzel|walker|pywalfox|cava|niri|hyprland|mango}" >&2
|
||||
echo "Usage: $0 {kitty|ghostty|foot|alacritty|wezterm|fuzzel|walker|pywalfox|cava|niri|hyprland|mango} [dark|light]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APP_NAME="$1"
|
||||
MODE="${2:-}" # Optional second argument for dark/light mode
|
||||
|
||||
# --- Apply theme based on the application name ---
|
||||
case "$APP_NAME" in
|
||||
@@ -202,6 +203,16 @@ vicinae)
|
||||
|
||||
pywalfox)
|
||||
echo "🎨 Updating pywalfox themes..."
|
||||
# Set dark/light mode first if MODE is specified
|
||||
if [ -n "$MODE" ]; then
|
||||
if [ "$MODE" = "dark" ] || [ "$MODE" = "light" ]; then
|
||||
echo "Setting pywalfox to $MODE mode..."
|
||||
pywalfox "$MODE"
|
||||
else
|
||||
echo "Warning: Invalid mode '$MODE'. Expected 'dark' or 'light'. Skipping mode switch." >&2
|
||||
fi
|
||||
fi
|
||||
# Update the theme
|
||||
pywalfox update
|
||||
;;
|
||||
|
||||
|
||||
+2
-5
@@ -318,15 +318,12 @@ Singleton {
|
||||
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>`;
|
||||
} else if (notFound) {
|
||||
// No fallback available
|
||||
return `## ${key} ##`;
|
||||
return `!!${key}!!`;
|
||||
}
|
||||
|
||||
if (typeof value !== "string") {
|
||||
|
||||
@@ -645,6 +645,7 @@ Singleton {
|
||||
property string screenUnlock: ""
|
||||
property string performanceModeEnabled: ""
|
||||
property string performanceModeDisabled: ""
|
||||
property string session: ""
|
||||
}
|
||||
|
||||
// desktop widgets
|
||||
|
||||
@@ -24,6 +24,7 @@ Singleton {
|
||||
readonly property int fontWeightBold: 700
|
||||
|
||||
// Container Radii: major layout sections (sidebars, cards, content panels)
|
||||
readonly property int radiusXXXS: Math.round(3 * Settings.data.general.radiusRatio)
|
||||
readonly property int radiusXXS: Math.round(4 * Settings.data.general.radiusRatio)
|
||||
readonly property int radiusXS: Math.round(8 * Settings.data.general.radiusRatio)
|
||||
readonly property int radiusS: Math.round(12 * Settings.data.general.radiusRatio)
|
||||
@@ -31,6 +32,7 @@ Singleton {
|
||||
readonly property int radiusL: Math.round(20 * Settings.data.general.radiusRatio)
|
||||
|
||||
// Input radii: interactive elements (buttons, toggles, text fields)
|
||||
readonly property int iRadiusXXXS: Math.round(3 * Settings.data.general.iRadiusRatio)
|
||||
readonly property int iRadiusXXS: Math.round(4 * Settings.data.general.iRadiusRatio)
|
||||
readonly property int iRadiusXS: Math.round(8 * Settings.data.general.iRadiusRatio)
|
||||
readonly property int iRadiusS: Math.round(12 * Settings.data.general.iRadiusRatio)
|
||||
|
||||
@@ -18,8 +18,12 @@ Loader {
|
||||
required property ShellScreen modelData
|
||||
property string wallpaper: ""
|
||||
property string cachedWallpaper: ""
|
||||
property string pendingWallpaper: ""
|
||||
property bool isSolidColor: false
|
||||
property bool useQtBlur: false // Fallback when ImageMagick not available
|
||||
property color solidColor: Settings.data.wallpaper.solidColor
|
||||
// Watch the actual tint color value, not just darkMode setting, since colors reload asynchronously
|
||||
property color tintColor: Settings.data.colorSchemes.darkMode ? Color.mSurface : Color.mOnSurface
|
||||
|
||||
Component.onCompleted: {
|
||||
if (modelData) {
|
||||
@@ -29,8 +33,6 @@ Loader {
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// Clean up resources to prevent memory leak when overviewEnabled is toggled off
|
||||
bgImage.layer.enabled = false;
|
||||
bgImage.source = "";
|
||||
}
|
||||
|
||||
@@ -63,6 +65,43 @@ Loader {
|
||||
}
|
||||
}
|
||||
|
||||
function requestBlurredOverview() {
|
||||
requestBlurredOverviewWithTint(tintColor.toString(), Settings.data.colorSchemes.darkMode);
|
||||
}
|
||||
|
||||
function requestBlurredOverviewWithTint(tint, isDarkMode) {
|
||||
if (!wallpaper || isSolidColor)
|
||||
return;
|
||||
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
const targetWidth = Math.round(modelData.width * compositorScale);
|
||||
const targetHeight = Math.round(modelData.height * compositorScale);
|
||||
|
||||
// Start fade out, then request new image
|
||||
if (cachedWallpaper) {
|
||||
fadeOutAnim.start();
|
||||
}
|
||||
|
||||
ImageCacheService.getBlurredOverview(wallpaper, targetWidth, targetHeight, tint, isDarkMode, function (path, success) {
|
||||
if (path) {
|
||||
useQtBlur = !success; // Use Qt blur fallback if ImageMagick failed
|
||||
pendingWallpaper = path;
|
||||
// If fade out is done or wasn't needed, apply immediately
|
||||
if (!fadeOutAnim.running) {
|
||||
applyPendingWallpaper();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyPendingWallpaper() {
|
||||
if (pendingWallpaper) {
|
||||
cachedWallpaper = pendingWallpaper;
|
||||
pendingWallpaper = "";
|
||||
fadeInAnim.start();
|
||||
}
|
||||
}
|
||||
|
||||
// Request cached wallpaper when source changes
|
||||
onWallpaperChanged: {
|
||||
if (!wallpaper)
|
||||
@@ -78,10 +117,22 @@ Loader {
|
||||
}
|
||||
|
||||
isSolidColor = false;
|
||||
// Use 1280x720 for overview since it's heavily blurred anyway
|
||||
ImageCacheService.getLarge(wallpaper, 1280, 720, function (path, success) {
|
||||
cachedWallpaper = path;
|
||||
});
|
||||
requestBlurredOverview();
|
||||
}
|
||||
|
||||
// Watch for color reloads - use the actual Color properties directly to avoid stale values
|
||||
Connections {
|
||||
target: Color
|
||||
function onMSurfaceChanged() {
|
||||
if (!isSolidColor && wallpaper && Settings.data.colorSchemes.darkMode) {
|
||||
requestBlurredOverviewWithTint(Color.mSurface.toString(), true);
|
||||
}
|
||||
}
|
||||
function onMOnSurfaceChanged() {
|
||||
if (!isSolidColor && wallpaper && !Settings.data.colorSchemes.darkMode) {
|
||||
requestBlurredOverviewWithTint(Color.mOnSurface.toString(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
color: "transparent"
|
||||
@@ -105,12 +156,11 @@ Loader {
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Settings.data.colorSchemes.darkMode ? Color.mSurface : Color.mOnSurface
|
||||
opacity: 0.8
|
||||
color: tintColor
|
||||
}
|
||||
}
|
||||
|
||||
// Image background
|
||||
// Image background (pre-blurred and tinted by ImageMagick, or Qt fallback)
|
||||
Image {
|
||||
id: bgImage
|
||||
anchors.fill: parent
|
||||
@@ -122,7 +172,8 @@ Loader {
|
||||
cache: false
|
||||
asynchronous: true
|
||||
|
||||
layer.enabled: true
|
||||
// Qt blur fallback when ImageMagick not available
|
||||
layer.enabled: useQtBlur
|
||||
layer.smooth: false
|
||||
layer.effect: MultiEffect {
|
||||
blurEnabled: true
|
||||
@@ -130,10 +181,31 @@ Loader {
|
||||
blurMax: 32
|
||||
}
|
||||
|
||||
// Tint overlay for Qt blur fallback
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Settings.data.colorSchemes.darkMode ? Color.mSurface : Color.mOnSurface
|
||||
opacity: 0.8
|
||||
visible: useQtBlur
|
||||
color: tintColor
|
||||
opacity: 0.6
|
||||
}
|
||||
|
||||
NumberAnimation on opacity {
|
||||
id: fadeOutAnim
|
||||
running: false
|
||||
from: 1
|
||||
to: 0
|
||||
duration: 200
|
||||
easing.type: Easing.OutQuad
|
||||
onFinished: applyPendingWallpaper()
|
||||
}
|
||||
|
||||
NumberAnimation on opacity {
|
||||
id: fadeInAnim
|
||||
running: false
|
||||
from: 0
|
||||
to: 1
|
||||
duration: 200
|
||||
easing.type: Easing.InQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ Item {
|
||||
readonly property int buttonSize: Style.capsuleHeight
|
||||
readonly property int pillHeight: buttonSize
|
||||
readonly property int pillOverlap: Math.round(buttonSize * 0.5)
|
||||
readonly property int maxPillWidth: rotateText ? Math.max(buttonSize, Math.round(textItem.implicitHeight + Style.marginM * 2)) : buttonSize
|
||||
readonly property int maxPillHeight: rotateText ? Math.max(1, Math.round(textItem.implicitWidth + Style.marginM * 2 + Math.round(iconCircle.height / 4))) : Math.max(1, Math.round(textItem.implicitHeight + Style.marginM * 2))
|
||||
readonly property int maxPillWidth: rotateText ? Math.max(buttonSize, Math.round(textItem.implicitHeight + Style.marginXL)) : buttonSize
|
||||
readonly property int maxPillHeight: rotateText ? Math.max(1, Math.round(textItem.implicitWidth + Style.marginXL + Math.round(iconCircle.height / 4))) : Math.max(1, Math.round(textItem.implicitHeight + Style.marginXL))
|
||||
|
||||
// Determine pill direction based on section position
|
||||
readonly property bool openDownward: oppositeDirection
|
||||
|
||||
@@ -254,7 +254,7 @@ PopupWindow {
|
||||
|
||||
NDivider {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - (Style.marginM * 2)
|
||||
width: parent.width - (Style.marginXL)
|
||||
visible: modelData?.isSeparator ?? false
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ Item {
|
||||
contentWidth += titleContainer.measuredWidth;
|
||||
|
||||
// Additional small margin for text
|
||||
contentWidth += Style.marginXXS * 2;
|
||||
contentWidth += Style.marginXS;
|
||||
|
||||
// Add container margins
|
||||
contentWidth += margins;
|
||||
@@ -249,7 +249,7 @@ Item {
|
||||
maxWidth: {
|
||||
// Calculate available width based on other elements
|
||||
var iconWidth = (showIcon && windowIcon.visible ? (iconSize + Style.marginS) : 0);
|
||||
var totalMargins = Style.marginXXS * 2;
|
||||
var totalMargins = Style.marginXS;
|
||||
var availableWidth = mainContainer.width - iconWidth - totalMargins;
|
||||
return Math.max(20, availableWidth);
|
||||
}
|
||||
@@ -273,8 +273,8 @@ Item {
|
||||
// Vertical layout for left/right bars - icon only
|
||||
Item {
|
||||
id: verticalLayout
|
||||
width: parent.width - Style.marginM * 2
|
||||
height: parent.height - Style.marginM * 2
|
||||
width: parent.width - Style.marginXL
|
||||
height: parent.height - Style.marginXL
|
||||
x: Style.pixelAlignCenter(parent.width, width)
|
||||
y: Style.pixelAlignCenter(parent.height, height)
|
||||
visible: isVerticalBar
|
||||
|
||||
@@ -41,7 +41,7 @@ Rectangle {
|
||||
readonly property string formatVertical: widgetSettings.formatVertical !== undefined ? widgetSettings.formatVertical : widgetMetadata.formatVertical
|
||||
readonly property string tooltipFormat: widgetSettings.tooltipFormat !== undefined ? widgetSettings.tooltipFormat : widgetMetadata.tooltipFormat
|
||||
|
||||
implicitWidth: isBarVertical ? Style.capsuleHeight : Math.round((isBarVertical ? verticalLoader.implicitWidth : horizontalLoader.implicitWidth) + Style.marginM * 2)
|
||||
implicitWidth: isBarVertical ? Style.capsuleHeight : Math.round((isBarVertical ? verticalLoader.implicitWidth : horizontalLoader.implicitWidth) + Style.marginXL)
|
||||
|
||||
implicitHeight: isBarVertical ? Math.round(verticalLoader.implicitHeight + Style.marginS * 2) : Style.capsuleHeight
|
||||
|
||||
|
||||
@@ -44,8 +44,8 @@ Rectangle {
|
||||
|
||||
readonly property bool hideWhenOff: (widgetSettings.hideWhenOff !== undefined) ? widgetSettings.hideWhenOff : (widgetMetadata.hideWhenOff !== undefined ? widgetMetadata.hideWhenOff : false)
|
||||
|
||||
implicitWidth: isVertical ? Style.capsuleHeight : Math.round(layout.implicitWidth + Style.marginM * 2)
|
||||
implicitHeight: isVertical ? Math.round(layout.implicitHeight + Style.marginM * 2) : Style.capsuleHeight
|
||||
implicitWidth: isVertical ? Style.capsuleHeight : Math.round(layout.implicitWidth + Style.marginXL)
|
||||
implicitHeight: isVertical ? Math.round(layout.implicitHeight + Style.marginXL) : Style.capsuleHeight
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ Item {
|
||||
var textWidth = 0;
|
||||
if (titleContainer.measuredWidth > 0) {
|
||||
margins += Style.marginS;
|
||||
textWidth = titleContainer.measuredWidth + Style.marginXXS * 2;
|
||||
textWidth = titleContainer.measuredWidth + Style.marginXS;
|
||||
}
|
||||
|
||||
var total = iconWidth + textWidth + margins;
|
||||
|
||||
@@ -109,8 +109,8 @@ Rectangle {
|
||||
readonly property bool diskCritical: showDiskUsage && SystemStatService.isDiskCritical(diskPath)
|
||||
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: isVertical ? Style.capsuleHeight : Math.round(mainGrid.implicitWidth + Style.marginM * 2)
|
||||
implicitHeight: isVertical ? Math.round(mainGrid.implicitHeight + Style.marginM * 2) : Style.capsuleHeight
|
||||
implicitWidth: isVertical ? Style.capsuleHeight : Math.round(mainGrid.implicitWidth + Style.marginXL)
|
||||
implicitHeight: isVertical ? Math.round(mainGrid.implicitHeight + Style.marginXL) : Style.capsuleHeight
|
||||
radius: Style.radiusM
|
||||
color: Style.capsuleColor
|
||||
border.color: Style.capsuleBorderColor
|
||||
|
||||
@@ -61,7 +61,7 @@ Rectangle {
|
||||
var calculatedWidth = baseWidth / Math.sqrt(entriesCount);
|
||||
|
||||
if (maxTaskbarWidth > 0) {
|
||||
var maxWidthPerEntry = (maxTaskbarWidth / entriesCount) - itemSize - Style.marginS - Style.marginM * 2;
|
||||
var maxWidthPerEntry = (maxTaskbarWidth / entriesCount) - itemSize - Style.marginS - Style.marginXL;
|
||||
calculatedWidth = Math.min(calculatedWidth, maxWidthPerEntry);
|
||||
}
|
||||
|
||||
@@ -501,7 +501,7 @@ Rectangle {
|
||||
if (isVerticalBar)
|
||||
return Style.capsuleHeight;
|
||||
|
||||
var calculatedWidth = showTitle ? taskbarLayout.implicitWidth : taskbarLayout.implicitWidth + Style.marginM * 2;
|
||||
var calculatedWidth = showTitle ? taskbarLayout.implicitWidth : taskbarLayout.implicitWidth + Style.marginXL;
|
||||
|
||||
// Apply maximum width constraint when smartWidth is enabled
|
||||
if (smartWidth && maxTaskbarWidth > 0) {
|
||||
@@ -510,7 +510,7 @@ Rectangle {
|
||||
|
||||
return Math.round(calculatedWidth);
|
||||
}
|
||||
implicitHeight: visible ? (isVerticalBar ? Math.round(taskbarLayout.implicitHeight + Style.marginM * 2) : Style.capsuleHeight) : 0
|
||||
implicitHeight: visible ? (isVerticalBar ? Math.round(taskbarLayout.implicitHeight + Style.marginXL) : Style.capsuleHeight) : 0
|
||||
radius: Style.radiusM
|
||||
color: Style.capsuleColor
|
||||
border.color: Style.capsuleBorderColor
|
||||
@@ -551,7 +551,7 @@ Rectangle {
|
||||
readonly property color titleBgColor: (isHovered || isFocused) ? Color.mHover : Style.capsuleColor
|
||||
readonly property color titleFgColor: (isHovered || isFocused) ? Color.mOnHover : Color.mOnSurface
|
||||
|
||||
Layout.preferredWidth: root.showTitle ? Math.round(contentWidth + Style.marginM * 2) : Math.round(contentWidth) // Add margins for both pinned and running apps
|
||||
Layout.preferredWidth: root.showTitle ? Math.round(contentWidth + Style.marginXL) : Math.round(contentWidth) // Add margins for both pinned and running apps
|
||||
Layout.preferredHeight: root.itemSize
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: (60 * Style.uiScaleRatio) + (Style.marginM * 2)
|
||||
Layout.preferredHeight: (60 * Style.uiScaleRatio) + (Style.marginM * 2)
|
||||
implicitHeight: (60 * Style.uiScaleRatio) + (Style.marginM * 2)
|
||||
Layout.minimumHeight: (60 * Style.uiScaleRatio) + (Style.marginXL)
|
||||
Layout.preferredHeight: (60 * Style.uiScaleRatio) + (Style.marginXL)
|
||||
implicitHeight: (60 * Style.uiScaleRatio) + (Style.marginXL)
|
||||
radius: Style.radiusL
|
||||
color: Color.mPrimary
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import qs.Widgets
|
||||
NBox {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: calendarContent.implicitHeight + Style.marginM * 2
|
||||
implicitHeight: calendarContent.implicitHeight + Style.marginXL
|
||||
|
||||
// Internal state - independent from header
|
||||
readonly property var now: Time.now
|
||||
|
||||
@@ -58,7 +58,7 @@ DraggableDesktopWidget {
|
||||
readonly property int visibleButtonCount: root.showButtons ? (1 + (showPrev ? 1 : 0) + (showNext ? 1 : 0)) : 0
|
||||
|
||||
implicitWidth: Math.round(400 * widgetScale)
|
||||
implicitHeight: Math.round(64 * widgetScale + Style.marginM * widgetScale * 2)
|
||||
implicitHeight: Math.round(64 * widgetScale + Style.marginXL * widgetScale)
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
|
||||
@@ -44,8 +44,8 @@ DraggableDesktopWidget {
|
||||
return chunks[0];
|
||||
}
|
||||
|
||||
implicitWidth: Math.round(Math.max(240 * widgetScale, contentLayout.implicitWidth + Style.marginM * widgetScale * 2))
|
||||
implicitHeight: Math.round(64 * widgetScale + Style.marginM * widgetScale * 2)
|
||||
implicitWidth: Math.round(Math.max(240 * widgetScale, contentLayout.implicitWidth + Style.marginXL * widgetScale))
|
||||
implicitHeight: Math.round(64 * widgetScale + Style.marginXL * widgetScale)
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
|
||||
@@ -444,8 +444,8 @@ Loader {
|
||||
Rectangle {
|
||||
id: dockContainer
|
||||
// For vertical dock, swap width and height logic
|
||||
width: isVertical ? Math.round(iconSize * 1.5) : dockLayout.implicitWidth + Style.marginM * 2
|
||||
height: isVertical ? dockLayout.implicitHeight + Style.marginM * 2 : Math.round(iconSize * 1.5)
|
||||
width: isVertical ? Math.round(iconSize * 1.5) : dockLayout.implicitWidth + Style.marginXL
|
||||
height: isVertical ? dockLayout.implicitHeight + Style.marginXL : Math.round(iconSize * 1.5)
|
||||
color: Qt.alpha(Color.mSurface, Settings.data.dock.backgroundOpacity)
|
||||
|
||||
// Anchor based on padding to achieve centering shift
|
||||
@@ -494,8 +494,8 @@ Loader {
|
||||
Item {
|
||||
id: dock
|
||||
// Swap dimensions based on orientation
|
||||
width: isVertical ? parent.width - (Style.marginM * 2) : dockLayout.implicitWidth
|
||||
height: isVertical ? dockLayout.implicitHeight : parent.height - (Style.marginM * 2)
|
||||
width: isVertical ? parent.width - (Style.marginXL) : dockLayout.implicitWidth
|
||||
height: isVertical ? dockLayout.implicitHeight : parent.height - (Style.marginXL)
|
||||
anchors.centerIn: parent
|
||||
|
||||
function getAppIcon(appData): string {
|
||||
|
||||
@@ -26,8 +26,8 @@ PopupWindow {
|
||||
|
||||
property real menuContentWidth: 160
|
||||
|
||||
implicitWidth: menuContentWidth + (Style.marginM * 2)
|
||||
implicitHeight: contextMenuColumn.implicitHeight + (Style.marginM * 2)
|
||||
implicitWidth: menuContentWidth + (Style.marginXL)
|
||||
implicitHeight: contextMenuColumn.implicitHeight + (Style.marginXL)
|
||||
color: "transparent"
|
||||
visible: false
|
||||
|
||||
|
||||
+39
-1202
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,284 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
|
||||
Item {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
|
||||
// Cached wallpaper path - exposed for parent components
|
||||
property string resolvedWallpaperPath: ""
|
||||
|
||||
required property var screen
|
||||
|
||||
// Request preprocessed wallpaper when lock screen becomes active or dimensions change
|
||||
Component.onCompleted: {
|
||||
if (screen) {
|
||||
Qt.callLater(requestCachedWallpaper);
|
||||
}
|
||||
}
|
||||
|
||||
onWidthChanged: {
|
||||
if (screen && width > 0 && height > 0) {
|
||||
Qt.callLater(requestCachedWallpaper);
|
||||
}
|
||||
}
|
||||
|
||||
onHeightChanged: {
|
||||
if (screen && width > 0 && height > 0) {
|
||||
Qt.callLater(requestCachedWallpaper);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for wallpaper changes
|
||||
Connections {
|
||||
target: WallpaperService
|
||||
function onWallpaperChanged(screenName, path) {
|
||||
if (screen && screenName === screen.name) {
|
||||
Qt.callLater(requestCachedWallpaper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for display scale changes
|
||||
Connections {
|
||||
target: CompositorService
|
||||
function onDisplayScalesChanged() {
|
||||
if (screen && width > 0 && height > 0) {
|
||||
Qt.callLater(requestCachedWallpaper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function requestCachedWallpaper() {
|
||||
if (!screen || width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for solid color mode first
|
||||
if (Settings.data.wallpaper.useSolidColor) {
|
||||
resolvedWallpaperPath = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const originalPath = WallpaperService.getWallpaper(screen.name) || "";
|
||||
if (originalPath === "") {
|
||||
resolvedWallpaperPath = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle solid color paths
|
||||
if (WallpaperService.isSolidColorPath(originalPath)) {
|
||||
resolvedWallpaperPath = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ImageCacheService || !ImageCacheService.initialized) {
|
||||
// Fallback to original if services not ready
|
||||
resolvedWallpaperPath = originalPath;
|
||||
return;
|
||||
}
|
||||
|
||||
const compositorScale = CompositorService.getDisplayScale(screen.name);
|
||||
const targetWidth = Math.round(width * compositorScale);
|
||||
const targetHeight = Math.round(height * compositorScale);
|
||||
if (targetWidth <= 0 || targetHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't set resolvedWallpaperPath until cache is ready
|
||||
// This prevents loading the original huge image
|
||||
ImageCacheService.getLarge(originalPath, targetWidth, targetHeight, function (cachedPath, success) {
|
||||
if (success) {
|
||||
resolvedWallpaperPath = cachedPath;
|
||||
} else {
|
||||
// Only fall back to original if caching failed
|
||||
resolvedWallpaperPath = originalPath;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Background - solid color or black fallback
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Settings.data.wallpaper.useSolidColor ? Settings.data.wallpaper.solidColor : "#000000"
|
||||
}
|
||||
|
||||
Image {
|
||||
id: lockBgImage
|
||||
visible: source !== "" && Settings.data.wallpaper.enabled && !Settings.data.wallpaper.useSolidColor
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
source: resolvedWallpaperPath
|
||||
cache: false
|
||||
smooth: true
|
||||
mipmap: false
|
||||
antialiasing: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: Qt.alpha(Color.mShadow, 0.8)
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.3
|
||||
color: Qt.alpha(Color.mShadow, 0.4)
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.7
|
||||
color: Qt.alpha(Color.mShadow, 0.5)
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Qt.alpha(Color.mShadow, 0.9)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Screen corners for lock screen
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: Settings.data.general.showScreenCorners
|
||||
|
||||
property color cornerColor: Settings.data.general.forceBlackScreenCorners ? "black" : Color.mSurface
|
||||
property real cornerRadius: Style.screenRadius
|
||||
property real cornerSize: Style.screenRadius
|
||||
|
||||
// Top-left concave corner
|
||||
Canvas {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: parent.cornerSize
|
||||
height: parent.cornerSize
|
||||
antialiasing: true
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
smooth: false
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d");
|
||||
if (!ctx)
|
||||
return;
|
||||
ctx.reset();
|
||||
ctx.clearRect(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();
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
requestPaint()
|
||||
onHeightChanged: if (available)
|
||||
requestPaint()
|
||||
}
|
||||
|
||||
// Top-right concave corner
|
||||
Canvas {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.cornerSize
|
||||
height: parent.cornerSize
|
||||
antialiasing: true
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d");
|
||||
if (!ctx)
|
||||
return;
|
||||
ctx.reset();
|
||||
ctx.clearRect(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();
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
requestPaint()
|
||||
onHeightChanged: if (available)
|
||||
requestPaint()
|
||||
}
|
||||
|
||||
// Bottom-left concave corner
|
||||
Canvas {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
width: parent.cornerSize
|
||||
height: parent.cornerSize
|
||||
antialiasing: true
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d");
|
||||
if (!ctx)
|
||||
return;
|
||||
ctx.reset();
|
||||
ctx.clearRect(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();
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
requestPaint()
|
||||
onHeightChanged: if (available)
|
||||
requestPaint()
|
||||
}
|
||||
|
||||
// Bottom-right concave corner
|
||||
Canvas {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: parent.cornerSize
|
||||
height: parent.cornerSize
|
||||
antialiasing: true
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d");
|
||||
if (!ctx)
|
||||
return;
|
||||
ctx.reset();
|
||||
ctx.clearRect(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();
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
requestPaint()
|
||||
onHeightChanged: if (available)
|
||||
requestPaint()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
// Time, Date, and User Profile Container
|
||||
Rectangle {
|
||||
id: root
|
||||
width: Math.max(500, contentRow.implicitWidth + 32)
|
||||
height: Math.max(120, contentRow.implicitHeight + 32)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 100
|
||||
radius: Style.radiusL
|
||||
color: Color.mSurface
|
||||
border.color: Qt.alpha(Color.mOutline, 0.2)
|
||||
border.width: Style.borderS
|
||||
|
||||
RowLayout {
|
||||
id: contentRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginXL * 2
|
||||
|
||||
// Left side: Avatar
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 70
|
||||
Layout.preferredHeight: 70
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
radius: width / 2
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: "transparent"
|
||||
border.color: Qt.alpha(Color.mPrimary, 0.8)
|
||||
border.width: Style.borderM
|
||||
|
||||
SequentialAnimation on border.color {
|
||||
loops: Animation.Infinite
|
||||
ColorAnimation {
|
||||
to: Qt.alpha(Color.mPrimary, 1.0)
|
||||
duration: 2000
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
ColorAnimation {
|
||||
to: Qt.alpha(Color.mPrimary, 0.8)
|
||||
duration: 2000
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NImageRounded {
|
||||
anchors.centerIn: parent
|
||||
width: 66
|
||||
height: 66
|
||||
radius: width / 2
|
||||
imagePath: Settings.preprocessPath(Settings.data.general.avatarImage)
|
||||
fallbackIcon: "person"
|
||||
|
||||
SequentialAnimation on scale {
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
to: 1.02
|
||||
duration: 4000
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
to: 1.0
|
||||
duration: 4000
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Center: User Info Column (left-aligned text)
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Style.marginXXS
|
||||
|
||||
// Welcome back + Username on one line
|
||||
NText {
|
||||
text: I18n.tr("system.welcome-back") + " " + HostService.displayName + "!"
|
||||
pointSize: Style.fontSizeXXL
|
||||
color: Color.mOnSurface
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
// Date below
|
||||
NText {
|
||||
text: {
|
||||
var lang = I18n.locale.name.split("_")[0];
|
||||
var formats = {
|
||||
"de": "dddd, d. MMMM",
|
||||
"en": "dddd, MMMM d",
|
||||
"es": "dddd, d 'de' MMMM",
|
||||
"fr": "dddd d MMMM",
|
||||
"hu": "dddd, MMMM d.",
|
||||
"ja": "yyyy年M月d日 dddd",
|
||||
"ku": "dddd, dê MMMM",
|
||||
"nl": "dddd d MMMM",
|
||||
"pt": "dddd, d 'de' MMMM",
|
||||
"zh": "yyyy年M月d日 dddd"
|
||||
};
|
||||
var dateString = I18n.locale.toString(Time.now, formats[lang] || "dddd, d MMMM");
|
||||
return dateString.charAt(0).toUpperCase() + dateString.slice(1);
|
||||
}
|
||||
pointSize: Style.fontSizeXL
|
||||
color: Color.mOnSurfaceVariant
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer to push time to the right
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Clock
|
||||
NClock {
|
||||
now: Time.now
|
||||
clockStyle: Settings.data.location.analogClockInCalendar ? "analog" : "digital"
|
||||
Layout.preferredWidth: 70
|
||||
Layout.preferredHeight: 70
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
backgroundColor: Color.mSurface
|
||||
clockColor: Color.mOnSurface
|
||||
secondHandColor: Color.mPrimary
|
||||
hoursFontSize: Style.fontSizeL
|
||||
minutesFontSize: Style.fontSizeL
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,750 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
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.Widgets
|
||||
import qs.Widgets.AudioSpectrum
|
||||
|
||||
Item {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
|
||||
required property var lockContext
|
||||
required property var batteryIndicator
|
||||
required property var keyboardLayout
|
||||
required property TextInput passwordInput
|
||||
|
||||
// Compact status indicators container (compact mode only)
|
||||
Rectangle {
|
||||
width: {
|
||||
var hasBattery = batteryIndicator.isReady && BatteryService.hasAnyBattery();
|
||||
var hasKeyboard = keyboardLayout.currentLayout !== "Unknown";
|
||||
|
||||
if (hasBattery && hasKeyboard) {
|
||||
return 200;
|
||||
} else if (hasBattery || hasKeyboard) {
|
||||
return 120;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
height: 40
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 96 + (Settings.data.general.compactLockScreen ? 116 : 220)
|
||||
topLeftRadius: Style.radiusL
|
||||
topRightRadius: Style.radiusL
|
||||
color: Color.mSurface
|
||||
visible: Settings.data.general.compactLockScreen && ((batteryIndicator.isReady && BatteryService.hasAnyBattery()) || keyboardLayout.currentLayout !== "Unknown")
|
||||
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginL
|
||||
|
||||
// Battery indicator
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
visible: batteryIndicator.isReady && BatteryService.hasAnyBattery()
|
||||
|
||||
NIcon {
|
||||
icon: BatteryService.getIcon(Math.round(batteryIndicator.percent), batteryIndicator.charging, batteryIndicator.isReady)
|
||||
pointSize: Style.fontSizeM
|
||||
color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Math.round(batteryIndicator.percent) + "%"
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard layout indicator
|
||||
RowLayout {
|
||||
spacing: 6
|
||||
visible: keyboardLayout.currentLayout !== "Unknown"
|
||||
|
||||
NIcon {
|
||||
icon: "keyboard"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: keyboardLayout.currentLayout
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeM
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom container with weather, password input and controls
|
||||
Rectangle {
|
||||
id: bottomContainer
|
||||
|
||||
// Support for removing the session/power buttons at the bottom.
|
||||
readonly property int deltaY: Settings.data.general.showSessionButtonsOnLockScreen ? 0 : (Settings.data.general.compactLockScreen ? 36 : 48) + 14
|
||||
|
||||
height: {
|
||||
let calcHeight = Settings.data.general.compactLockScreen ? 120 : 220;
|
||||
if (!Settings.data.general.showSessionButtonsOnLockScreen) {
|
||||
calcHeight -= bottomContainer.deltaY;
|
||||
}
|
||||
return calcHeight;
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 100 + bottomContainer.deltaY
|
||||
radius: Style.radiusL
|
||||
color: Color.mSurface
|
||||
|
||||
width: Settings.data.general.showHibernateOnLockScreen ? 800 : 750
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 14
|
||||
spacing: Style.marginL
|
||||
|
||||
// Top info row
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 65
|
||||
spacing: Style.marginXL
|
||||
visible: !Settings.data.general.compactLockScreen
|
||||
|
||||
// Media widget with visualizer
|
||||
Item {
|
||||
Layout.preferredWidth: Style.marginM
|
||||
visible: MediaService.currentPlayer && MediaService.canPlay
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 220
|
||||
// Expand to take remaining space when weather is hidden
|
||||
Layout.fillWidth: !(Settings.data.location.weatherEnabled && LocationService.data.weather !== null)
|
||||
Layout.preferredHeight: 50
|
||||
radius: Style.radiusL
|
||||
color: "transparent"
|
||||
clip: true
|
||||
visible: MediaService.currentPlayer && MediaService.canPlay
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
active: Settings.data.audio.visualizerType === "linear"
|
||||
z: 0
|
||||
sourceComponent: NLinearSpectrum {
|
||||
anchors.fill: parent
|
||||
values: CavaService.values
|
||||
fillColor: Color.mPrimary
|
||||
opacity: 0.4
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
active: Settings.data.audio.visualizerType === "mirrored"
|
||||
z: 0
|
||||
sourceComponent: NMirroredSpectrum {
|
||||
anchors.fill: parent
|
||||
values: CavaService.values
|
||||
fillColor: Color.mPrimary
|
||||
opacity: 0.4
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
active: Settings.data.audio.visualizerType === "wave"
|
||||
z: 0
|
||||
sourceComponent: NWaveSpectrum {
|
||||
anchors.fill: parent
|
||||
values: CavaService.values
|
||||
fillColor: Color.mPrimary
|
||||
opacity: 0.4
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: Style.marginM
|
||||
z: 1
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 34
|
||||
Layout.preferredHeight: 34
|
||||
radius: Math.min(Style.radiusL, width / 2)
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
NImageRounded {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
radius: Math.min(Style.radiusL, width / 2)
|
||||
imagePath: MediaService.trackArtUrl
|
||||
fallbackIcon: "disc"
|
||||
fallbackIconSize: Style.fontSizeM
|
||||
borderColor: Color.mOutline
|
||||
borderWidth: Style.borderS
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginXXS
|
||||
|
||||
NText {
|
||||
text: MediaService.trackTitle || "No media"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
NText {
|
||||
text: MediaService.trackArtist || ""
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 1
|
||||
Layout.fillHeight: true
|
||||
Layout.rightMargin: 4
|
||||
color: Qt.alpha(Color.mOutline, 0.3)
|
||||
visible: MediaService.currentPlayer && MediaService.canPlay
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: Style.marginM
|
||||
visible: !(MediaService.currentPlayer && MediaService.canPlay)
|
||||
}
|
||||
|
||||
// Current weather
|
||||
RowLayout {
|
||||
visible: Settings.data.location.weatherEnabled && LocationService.data.weather !== null
|
||||
Layout.preferredWidth: 180
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode)
|
||||
pointSize: Style.fontSizeXXXL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginXXS
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
|
||||
NText {
|
||||
text: {
|
||||
var temp = LocationService.data.weather.current_weather.temperature;
|
||||
var suffix = "C";
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
temp = LocationService.celsiusToFahrenheit(temp);
|
||||
suffix = "F";
|
||||
}
|
||||
temp = Math.round(temp);
|
||||
return temp + "°" + suffix;
|
||||
}
|
||||
pointSize: Style.fontSizeXL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: {
|
||||
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 = Math.round(wind);
|
||||
return wind + " " + unit;
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
text: Settings.data.location.name.split(",")[0]
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
visible: !Settings.data.location.hideWeatherCityName
|
||||
}
|
||||
|
||||
NText {
|
||||
text: (LocationService.data.weather.current && LocationService.data.weather.current.relativehumidity_2m) ? LocationService.data.weather.current.relativehumidity_2m + "% humidity" : ""
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forecast
|
||||
RowLayout {
|
||||
visible: Settings.data.location.weatherEnabled && LocationService.data.weather !== null
|
||||
Layout.preferredWidth: 260
|
||||
Layout.rightMargin: 8
|
||||
spacing: Style.marginXS
|
||||
|
||||
Repeater {
|
||||
model: MediaService.currentPlayer && MediaService.canPlay ? 2 : 4
|
||||
delegate: ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginXXS + 1
|
||||
|
||||
NText {
|
||||
text: {
|
||||
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"));
|
||||
return I18n.locale.toString(weatherDate, "ddd");
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
|
||||
pointSize: Style.fontSizeXL
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: {
|
||||
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 = Math.round(max);
|
||||
min = Math.round(min);
|
||||
return max + "°/" + min + "°";
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: Color.mOnSurfaceVariant
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: batteryIndicator.isReady && BatteryService.hasAnyBattery()
|
||||
}
|
||||
|
||||
// Battery and Keyboard Layout (full mode only)
|
||||
ColumnLayout {
|
||||
Layout.alignment: (batteryIndicator.isReady && BatteryService.hasAnyBattery()) ? (Qt.AlignRight | Qt.AlignVCenter) : Qt.AlignVCenter
|
||||
spacing: Style.marginM
|
||||
visible: (batteryIndicator.isReady && BatteryService.hasAnyBattery()) || keyboardLayout.currentLayout !== "Unknown"
|
||||
|
||||
// Battery
|
||||
RowLayout {
|
||||
spacing: Style.marginXS
|
||||
visible: batteryIndicator.isReady && BatteryService.hasAnyBattery()
|
||||
|
||||
NIcon {
|
||||
icon: BatteryService.getIcon(Math.round(batteryIndicator.percent), batteryIndicator.charging, batteryIndicator.isReady)
|
||||
pointSize: Style.fontSizeM
|
||||
color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Math.round(batteryIndicator.percent) + "%"
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard Layout
|
||||
RowLayout {
|
||||
spacing: Style.marginXS
|
||||
visible: keyboardLayout.currentLayout !== "Unknown"
|
||||
|
||||
NIcon {
|
||||
icon: "keyboard"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: keyboardLayout.currentLayout
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeM
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: Style.marginM
|
||||
}
|
||||
}
|
||||
|
||||
// Password input
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: Style.marginM
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 48
|
||||
radius: Style.iRadiusL
|
||||
color: Color.mSurface
|
||||
border.color: passwordInput.activeFocus ? Color.mPrimary : Qt.alpha(Color.mOutline, 0.3)
|
||||
border.width: passwordInput.activeFocus ? 2 : 1
|
||||
|
||||
property bool passwordVisible: false
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 18
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginL
|
||||
|
||||
NIcon {
|
||||
icon: "lock"
|
||||
pointSize: Style.fontSizeL
|
||||
color: passwordInput.activeFocus ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
width: 2
|
||||
height: 20
|
||||
color: Color.mPrimary
|
||||
visible: passwordInput.activeFocus && passwordInput.text.length === 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
loops: Animation.Infinite
|
||||
running: passwordInput.activeFocus && passwordInput.text.length === 0
|
||||
NumberAnimation {
|
||||
to: 0
|
||||
duration: 530
|
||||
}
|
||||
NumberAnimation {
|
||||
to: 1
|
||||
duration: 530
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Password display - show dots or actual text based on passwordVisible
|
||||
Item {
|
||||
width: Math.min(passwordDisplayContent.width, 550)
|
||||
height: 20
|
||||
visible: passwordInput.text.length > 0 && !parent.parent.parent.passwordVisible
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clip: true
|
||||
|
||||
Row {
|
||||
id: passwordDisplayContent
|
||||
spacing: Style.marginS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Repeater {
|
||||
model: passwordInput.text.length
|
||||
|
||||
NIcon {
|
||||
icon: "circle-filled"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mPrimary
|
||||
opacity: 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: passwordInput.text
|
||||
color: Color.mPrimary
|
||||
pointSize: Style.fontSizeM
|
||||
visible: passwordInput.text.length > 0 && parent.parent.parent.passwordVisible
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
width: Math.min(implicitWidth, 550)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 2
|
||||
height: 20
|
||||
color: Color.mPrimary
|
||||
visible: passwordInput.activeFocus && passwordInput.text.length > 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
loops: Animation.Infinite
|
||||
running: passwordInput.activeFocus && passwordInput.text.length > 0
|
||||
NumberAnimation {
|
||||
to: 0
|
||||
duration: 530
|
||||
}
|
||||
NumberAnimation {
|
||||
to: 1
|
||||
duration: 530
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Eye button to toggle password visibility
|
||||
Rectangle {
|
||||
anchors.right: submitButton.left
|
||||
anchors.rightMargin: 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 36
|
||||
height: 36
|
||||
radius: Math.min(Style.iRadiusL, width / 2)
|
||||
color: eyeButtonArea.containsMouse ? Color.mPrimary : "transparent"
|
||||
visible: passwordInput.text.length > 0
|
||||
enabled: !lockContext || !lockContext.unlockInProgress || lockContext.waitingForPassword
|
||||
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: parent.parent.passwordVisible ? "eye-off" : "eye"
|
||||
pointSize: Style.fontSizeM
|
||||
color: eyeButtonArea.containsMouse ? Color.mOnPrimary : Color.mOnSurfaceVariant
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: eyeButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: parent.parent.passwordVisible = !parent.parent.passwordVisible
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Submit button
|
||||
Rectangle {
|
||||
id: submitButton
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 36
|
||||
height: 36
|
||||
radius: Math.min(Style.iRadiusL, width / 2)
|
||||
color: submitButtonArea.containsMouse ? Color.mPrimary : "transparent"
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderS
|
||||
enabled: !lockContext || !lockContext.unlockInProgress || lockContext.waitingForPassword
|
||||
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: "arrow-forward"
|
||||
pointSize: Style.fontSizeM
|
||||
color: submitButtonArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: submitButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: if (lockContext)
|
||||
lockContext.tryUnlock()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: Style.marginM
|
||||
}
|
||||
}
|
||||
|
||||
// Session control buttons
|
||||
RowLayout {
|
||||
id: sessionButtonRow
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Style.marginM
|
||||
visible: Settings.data.general.showSessionButtonsOnLockScreen
|
||||
|
||||
readonly property int buttonCount: Settings.data.general.showHibernateOnLockScreen ? 5 : 4
|
||||
readonly property real availableWidth: bottomContainer.width - 48
|
||||
readonly property real buttonWidth: (availableWidth - (buttonCount - 1) * spacing) / buttonCount
|
||||
readonly property real buttonHeight: sessionButtonRow.height
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: sessionButtonRow.buttonWidth
|
||||
Layout.preferredHeight: sessionButtonRow.buttonHeight
|
||||
|
||||
NButton {
|
||||
anchors.fill: parent
|
||||
icon: "logout"
|
||||
text: I18n.tr("common.logout")
|
||||
outlined: true
|
||||
backgroundColor: Color.mOnSurfaceVariant
|
||||
textColor: Color.mOnPrimary
|
||||
hoverColor: Color.mPrimary
|
||||
fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
|
||||
iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
buttonRadius: Style.radiusL
|
||||
onClicked: CompositorService.logout()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: sessionButtonRow.buttonWidth
|
||||
Layout.preferredHeight: sessionButtonRow.buttonHeight
|
||||
|
||||
NButton {
|
||||
anchors.fill: parent
|
||||
icon: "suspend"
|
||||
text: I18n.tr("common.suspend")
|
||||
outlined: true
|
||||
backgroundColor: Color.mOnSurfaceVariant
|
||||
textColor: Color.mOnPrimary
|
||||
hoverColor: Color.mPrimary
|
||||
fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
|
||||
iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
buttonRadius: Style.radiusL
|
||||
onClicked: CompositorService.suspend()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: sessionButtonRow.buttonWidth
|
||||
Layout.preferredHeight: sessionButtonRow.buttonHeight
|
||||
visible: Settings.data.general.showHibernateOnLockScreen
|
||||
|
||||
NButton {
|
||||
anchors.fill: parent
|
||||
icon: "hibernate"
|
||||
text: I18n.tr("common.hibernate")
|
||||
outlined: true
|
||||
backgroundColor: Color.mOnSurfaceVariant
|
||||
textColor: Color.mOnPrimary
|
||||
hoverColor: Color.mPrimary
|
||||
fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
|
||||
iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
buttonRadius: Style.radiusL
|
||||
onClicked: CompositorService.hibernate()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: sessionButtonRow.buttonWidth
|
||||
Layout.preferredHeight: sessionButtonRow.buttonHeight
|
||||
|
||||
NButton {
|
||||
anchors.fill: parent
|
||||
icon: "reboot"
|
||||
text: I18n.tr("common.reboot")
|
||||
outlined: true
|
||||
backgroundColor: Color.mOnSurfaceVariant
|
||||
textColor: Color.mOnPrimary
|
||||
hoverColor: Color.mPrimary
|
||||
fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
|
||||
iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
buttonRadius: Style.radiusL
|
||||
onClicked: CompositorService.reboot()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: sessionButtonRow.buttonWidth
|
||||
Layout.preferredHeight: sessionButtonRow.buttonHeight
|
||||
|
||||
NButton {
|
||||
anchors.fill: parent
|
||||
icon: "shutdown"
|
||||
text: I18n.tr("common.shutdown")
|
||||
outlined: true
|
||||
backgroundColor: Color.mError
|
||||
textColor: Color.mOnError
|
||||
hoverColor: Color.mError
|
||||
fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
|
||||
iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
buttonRadius: Style.radiusL
|
||||
onClicked: CompositorService.shutdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +197,7 @@ Variants {
|
||||
readonly property int slideDistance: 300
|
||||
|
||||
Layout.preferredWidth: notifWidth + notifWindow.shadowPadding * 2
|
||||
Layout.preferredHeight: notificationContent.implicitHeight + Style.marginM * 2 + notifWindow.shadowPadding * 2
|
||||
Layout.preferredHeight: notificationContent.implicitHeight + Style.marginXL + notifWindow.shadowPadding * 2
|
||||
Layout.maximumHeight: Layout.preferredHeight
|
||||
|
||||
// Animation properties
|
||||
|
||||
@@ -282,7 +282,7 @@ SmartPanel {
|
||||
// HEADER
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: header.implicitHeight + (Style.marginM * 2)
|
||||
implicitHeight: header.implicitHeight + (Style.marginXL)
|
||||
|
||||
ColumnLayout {
|
||||
id: header
|
||||
@@ -360,7 +360,7 @@ SmartPanel {
|
||||
// Output Volume
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: outputVolumeColumn.implicitHeight + (Style.marginM * 2)
|
||||
Layout.preferredHeight: outputVolumeColumn.implicitHeight + (Style.marginXL)
|
||||
|
||||
RowLayout {
|
||||
id: outputVolumeColumn
|
||||
@@ -425,7 +425,7 @@ SmartPanel {
|
||||
// Input Volume
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: inputVolumeColumn.implicitHeight + (Style.marginM * 2)
|
||||
Layout.preferredHeight: inputVolumeColumn.implicitHeight + (Style.marginXL)
|
||||
|
||||
RowLayout {
|
||||
id: inputVolumeColumn
|
||||
@@ -500,7 +500,7 @@ SmartPanel {
|
||||
id: appBox
|
||||
required property PwNode modelData
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: appRow.implicitHeight + (Style.marginM * 2)
|
||||
Layout.preferredHeight: appRow.implicitHeight + (Style.marginXL)
|
||||
visible: !isCaptureStream
|
||||
|
||||
// Track individual node to ensure properties are bound
|
||||
@@ -802,7 +802,7 @@ SmartPanel {
|
||||
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: outputColumn.implicitHeight + (Style.marginM * 2)
|
||||
Layout.preferredHeight: outputColumn.implicitHeight + (Style.marginXL)
|
||||
|
||||
ColumnLayout {
|
||||
id: outputColumn
|
||||
@@ -844,7 +844,7 @@ SmartPanel {
|
||||
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: inputColumn.implicitHeight + (Style.marginM * 2)
|
||||
Layout.preferredHeight: inputColumn.implicitHeight + (Style.marginXL)
|
||||
|
||||
ColumnLayout {
|
||||
id: inputColumn
|
||||
|
||||
@@ -205,7 +205,7 @@ SmartPanel {
|
||||
// HEADER
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: headerRow.implicitHeight + (Style.marginM * 2)
|
||||
implicitHeight: headerRow.implicitHeight + (Style.marginXL)
|
||||
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
|
||||
@@ -25,7 +25,7 @@ NBox {
|
||||
property bool detailsGrid: (Settings.data && Settings.data.ui && Settings.data.network.bluetoothDetailsViewMode !== undefined) ? (Settings.data.network.bluetoothDetailsViewMode === "grid") : true
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: column.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: column.implicitHeight + Style.marginXL
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
@@ -89,7 +89,7 @@ NBox {
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: deviceColumn.implicitHeight + (Style.marginM * 2)
|
||||
Layout.preferredHeight: deviceColumn.implicitHeight + (Style.marginXL)
|
||||
radius: Style.radiusM
|
||||
clip: true
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ SmartPanel {
|
||||
// Header
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: headerRow.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: headerRow.implicitHeight + Style.marginXL
|
||||
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
@@ -94,7 +94,7 @@ SmartPanel {
|
||||
id: disabledBox
|
||||
visible: !(BluetoothService.adapter && BluetoothService.adapter.enabled)
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: disabledColumn.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: disabledColumn.implicitHeight + Style.marginXL
|
||||
|
||||
// Center the content within this rectangle
|
||||
ColumnLayout {
|
||||
@@ -275,7 +275,7 @@ SmartPanel {
|
||||
return (availableCount === 0);
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: emptyColumn.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: emptyColumn.implicitHeight + Style.marginXL
|
||||
|
||||
ColumnLayout {
|
||||
id: emptyColumn
|
||||
@@ -319,7 +319,7 @@ SmartPanel {
|
||||
// Fallback - No devices, scanning
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: scanningColumn.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: scanningColumn.implicitHeight + Style.marginXL
|
||||
visible: {
|
||||
if (!(BluetoothService.adapter && BluetoothService.adapter.devices) || !BluetoothService.scanningActive) {
|
||||
return false;
|
||||
|
||||
@@ -32,7 +32,7 @@ SmartPanel {
|
||||
// HEADER
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: headerRow.implicitHeight + (Style.marginM * 2)
|
||||
implicitHeight: headerRow.implicitHeight + (Style.marginXL)
|
||||
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
@@ -82,7 +82,7 @@ SmartPanel {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: outputColumn.implicitHeight + (Style.marginM * 2)
|
||||
Layout.preferredHeight: outputColumn.implicitHeight + (Style.marginXL)
|
||||
|
||||
property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData)
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ SmartPanel {
|
||||
readonly property var currentProvider: activeProvider || defaultProvider
|
||||
|
||||
readonly property int badgeSize: Math.round(Style.baseWidgetSize * 1.6 * Style.uiScaleRatio)
|
||||
readonly property int entryHeight: Math.round(badgeSize + Style.marginM * 2)
|
||||
readonly property int entryHeight: Math.round(badgeSize + Style.marginXL)
|
||||
// Whether current provider is showing categorized view (vs filtered search results)
|
||||
readonly property bool providerShowsCategories: {
|
||||
return currentProvider.showsCategories === true;
|
||||
@@ -1123,7 +1123,7 @@ SmartPanel {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 2
|
||||
width: formatLabel.width + Style.marginXXS * 2
|
||||
width: formatLabel.width + Style.marginXS
|
||||
height: formatLabel.height + Style.marginXXS
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusXXS
|
||||
|
||||
@@ -101,7 +101,7 @@ SmartPanel {
|
||||
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: headerRow.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: headerRow.implicitHeight + Style.marginXL
|
||||
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
|
||||
@@ -133,7 +133,7 @@ SmartPanel {
|
||||
// Header
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: header.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: header.implicitHeight + Style.marginXL
|
||||
|
||||
ColumnLayout {
|
||||
id: header
|
||||
@@ -243,7 +243,7 @@ SmartPanel {
|
||||
Rectangle {
|
||||
visible: panelViewMode === "wifi" && NetworkService.lastError.length > 0
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: errorRow.implicitHeight + (Style.marginM * 2)
|
||||
Layout.preferredHeight: errorRow.implicitHeight + (Style.marginXL)
|
||||
color: Qt.alpha(Color.mError, 0.1)
|
||||
radius: Style.radiusS
|
||||
border.width: Style.borderS
|
||||
@@ -296,7 +296,7 @@ SmartPanel {
|
||||
id: disabledBox
|
||||
visible: panelViewMode === "wifi" && !Settings.data.network.wifiEnabled
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: disabledColumn.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: disabledColumn.implicitHeight + Style.marginXL
|
||||
|
||||
ColumnLayout {
|
||||
id: disabledColumn
|
||||
@@ -342,7 +342,7 @@ SmartPanel {
|
||||
id: scanningBox
|
||||
visible: panelViewMode === "wifi" && Settings.data.network.wifiEnabled && Object.keys(NetworkService.networks).length === 0 && !root.hasHadNetworks
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: scanningColumn.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: scanningColumn.implicitHeight + Style.marginXL
|
||||
|
||||
ColumnLayout {
|
||||
id: scanningColumn
|
||||
@@ -379,7 +379,7 @@ SmartPanel {
|
||||
id: emptyBox
|
||||
visible: panelViewMode === "wifi" && Settings.data.network.wifiEnabled && !NetworkService.scanning && Object.keys(NetworkService.networks).length === 0 && root.hasHadNetworks
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: emptyColumn.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: emptyColumn.implicitHeight + Style.marginXL
|
||||
|
||||
ColumnLayout {
|
||||
id: emptyColumn
|
||||
@@ -488,7 +488,7 @@ SmartPanel {
|
||||
NBox {
|
||||
visible: !(NetworkService.ethernetInterfaces && NetworkService.ethernetInterfaces.length > 0)
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: emptyEthColumn.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: emptyEthColumn.implicitHeight + Style.marginXL
|
||||
|
||||
ColumnLayout {
|
||||
id: emptyEthColumn
|
||||
@@ -535,7 +535,7 @@ SmartPanel {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginXS
|
||||
Layout.rightMargin: Style.marginXS
|
||||
implicitHeight: ethItemColumn.implicitHeight + (Style.marginM * 2)
|
||||
implicitHeight: ethItemColumn.implicitHeight + (Style.marginXL)
|
||||
radius: Style.radiusM
|
||||
border.width: Style.borderS
|
||||
border.color: modelData.connected ? Color.mPrimary : Color.mOutline
|
||||
@@ -543,7 +543,7 @@ SmartPanel {
|
||||
|
||||
ColumnLayout {
|
||||
id: ethItemColumn
|
||||
width: parent.width - (Style.marginM * 2)
|
||||
width: parent.width - (Style.marginXL)
|
||||
x: Style.marginM
|
||||
y: Style.marginM
|
||||
spacing: Style.marginS
|
||||
@@ -585,7 +585,7 @@ SmartPanel {
|
||||
color: Color.mPrimary
|
||||
radius: height * 0.5
|
||||
width: ethConnectedText.implicitWidth + (Style.marginS * 2)
|
||||
height: ethConnectedText.implicitHeight + (Style.marginXXS * 2)
|
||||
height: ethConnectedText.implicitHeight + (Style.marginXS)
|
||||
|
||||
NText {
|
||||
id: ethConnectedText
|
||||
|
||||
@@ -48,7 +48,7 @@ NBox {
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: column.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: column.implicitHeight + Style.marginXL
|
||||
visible: root.model.length > 0
|
||||
|
||||
ColumnLayout {
|
||||
@@ -81,7 +81,7 @@ NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginXS
|
||||
Layout.rightMargin: Style.marginXS
|
||||
implicitHeight: netColumn.implicitHeight + (Style.marginM * 2)
|
||||
implicitHeight: netColumn.implicitHeight + (Style.marginXL)
|
||||
|
||||
opacity: (NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid) ? 0.6 : 1.0
|
||||
|
||||
@@ -95,7 +95,7 @@ NBox {
|
||||
|
||||
ColumnLayout {
|
||||
id: netColumn
|
||||
width: parent.width - (Style.marginM * 2)
|
||||
width: parent.width - (Style.marginXL)
|
||||
x: Style.marginM
|
||||
y: Style.marginM
|
||||
spacing: Style.marginS
|
||||
@@ -158,7 +158,7 @@ NBox {
|
||||
color: NetworkService.internetConnectivity ? Color.mPrimary : Color.mError
|
||||
radius: height * 0.5
|
||||
width: connectedText.implicitWidth + (Style.marginS * 2)
|
||||
height: connectedText.implicitHeight + (Style.marginXXS * 2)
|
||||
height: connectedText.implicitHeight + (Style.marginXS)
|
||||
|
||||
NText {
|
||||
id: connectedText
|
||||
@@ -188,7 +188,7 @@ NBox {
|
||||
color: Color.mError
|
||||
radius: height * 0.5
|
||||
width: disconnectingText.implicitWidth + (Style.marginS * 2)
|
||||
height: disconnectingText.implicitHeight + (Style.marginXXS * 2)
|
||||
height: disconnectingText.implicitHeight + (Style.marginXS)
|
||||
|
||||
NText {
|
||||
id: disconnectingText
|
||||
@@ -204,7 +204,7 @@ NBox {
|
||||
color: Color.mError
|
||||
radius: height * 0.5
|
||||
width: forgettingText.implicitWidth + (Style.marginS * 2)
|
||||
height: forgettingText.implicitHeight + (Style.marginXXS * 2)
|
||||
height: forgettingText.implicitHeight + (Style.marginXS)
|
||||
|
||||
NText {
|
||||
id: forgettingText
|
||||
@@ -222,7 +222,7 @@ NBox {
|
||||
border.width: Style.borderS
|
||||
radius: height * 0.5
|
||||
width: savedText.implicitWidth + (Style.marginS * 2)
|
||||
height: savedText.implicitHeight + (Style.marginXXS * 2)
|
||||
height: savedText.implicitHeight + (Style.marginXS)
|
||||
|
||||
NText {
|
||||
id: savedText
|
||||
|
||||
@@ -142,7 +142,7 @@ SmartPanel {
|
||||
totalHeight = visibleCount * avgNotificationHeight + (visibleCount - 1) * Style.marginM;
|
||||
return totalHeight;
|
||||
}
|
||||
property real calculatedHeight: headerHeight + tabsHeight + contentHeight + (Style.marginL * 2) + (Style.marginM * 2)
|
||||
property real calculatedHeight: headerHeight + tabsHeight + contentHeight + (Style.marginL * 2) + (Style.marginXL)
|
||||
property real contentPreferredHeight: {
|
||||
if (NotificationService.historyList.count === 0) {
|
||||
// Empty state: smaller height
|
||||
@@ -162,101 +162,101 @@ SmartPanel {
|
||||
NBox {
|
||||
id: headerBox
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: headerRow.implicitHeight + (Style.marginM * 2)
|
||||
implicitHeight: header.implicitHeight + (Style.marginXL)
|
||||
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
ColumnLayout {
|
||||
id: header
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "bell"
|
||||
pointSize: Style.fontSizeXXL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
NIcon {
|
||||
icon: "bell"
|
||||
pointSize: Style.fontSizeXXL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("common.notifications")
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NText {
|
||||
text: I18n.tr("common.notifications")
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: NotificationService.doNotDisturb ? "bell-off" : "bell"
|
||||
tooltipText: NotificationService.doNotDisturb ? I18n.tr("tooltips.do-not-disturb-enabled") : I18n.tr("tooltips.do-not-disturb-enabled")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: NotificationService.doNotDisturb = !NotificationService.doNotDisturb
|
||||
}
|
||||
NIconButton {
|
||||
icon: NotificationService.doNotDisturb ? "bell-off" : "bell"
|
||||
tooltipText: NotificationService.doNotDisturb ? I18n.tr("tooltips.do-not-disturb-enabled") : I18n.tr("tooltips.do-not-disturb-enabled")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: NotificationService.doNotDisturb = !NotificationService.doNotDisturb
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("actions.clear-history")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
NotificationService.clearHistory();
|
||||
// Close panel as there is nothing more to see.
|
||||
root.close();
|
||||
NIconButton {
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("actions.clear-history")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
NotificationService.clearHistory();
|
||||
// Close panel as there is nothing more to see.
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("common.close")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("common.close")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: root.close()
|
||||
// Time range tabs ([All] / [Today] / [Yesterday] / [Earlier])
|
||||
NTabBar {
|
||||
id: tabsBox
|
||||
Layout.fillWidth: true
|
||||
visible: NotificationService.historyList.count > 0 && panelContent.groupByDate
|
||||
currentIndex: panelContent.currentRange
|
||||
tabHeight: Style.toOdd(Style.baseWidgetSize * 0.8)
|
||||
spacing: Style.marginXS
|
||||
distributeEvenly: true
|
||||
|
||||
NTabButton {
|
||||
tabIndex: 0
|
||||
text: I18n.tr("launcher.categories.all") + " (" + panelContent.countForRange(0) + ")"
|
||||
checked: tabsBox.currentIndex === 0
|
||||
onClicked: panelContent.currentRange = 0
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
tabIndex: 1
|
||||
text: I18n.tr("notifications.range.today") + " (" + panelContent.countForRange(1) + ")"
|
||||
checked: tabsBox.currentIndex === 1
|
||||
onClicked: panelContent.currentRange = 1
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
tabIndex: 2
|
||||
text: I18n.tr("notifications.range.yesterday") + " (" + panelContent.countForRange(2) + ")"
|
||||
checked: tabsBox.currentIndex === 2
|
||||
onClicked: panelContent.currentRange = 2
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
tabIndex: 3
|
||||
text: I18n.tr("notifications.range.earlier") + " (" + panelContent.countForRange(3) + ")"
|
||||
checked: tabsBox.currentIndex === 3
|
||||
onClicked: panelContent.currentRange = 3
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Time range tabs ([All] / [Today] / [Yesterday] / [Earlier])
|
||||
NTabBar {
|
||||
id: tabsBox
|
||||
Layout.fillWidth: true
|
||||
margins: Style.marginS
|
||||
visible: NotificationService.historyList.count > 0 && panelContent.groupByDate
|
||||
currentIndex: panelContent.currentRange
|
||||
tabHeight: Style.baseWidgetSize * 0.7
|
||||
spacing: Style.marginXS
|
||||
distributeEvenly: true
|
||||
border.color: Style.boxBorderColor
|
||||
border.width: Style.borderS
|
||||
|
||||
NTabButton {
|
||||
tabIndex: 0
|
||||
text: I18n.tr("launcher.categories.all") + " (" + panelContent.countForRange(0) + ")"
|
||||
checked: tabsBox.currentIndex === 0
|
||||
onClicked: panelContent.currentRange = 0
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
tabIndex: 1
|
||||
text: I18n.tr("notifications.range.today") + " (" + panelContent.countForRange(1) + ")"
|
||||
checked: tabsBox.currentIndex === 1
|
||||
onClicked: panelContent.currentRange = 1
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
tabIndex: 2
|
||||
text: I18n.tr("notifications.range.yesterday") + " (" + panelContent.countForRange(2) + ")"
|
||||
checked: tabsBox.currentIndex === 2
|
||||
onClicked: panelContent.currentRange = 2
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
tabIndex: 3
|
||||
text: I18n.tr("notifications.range.earlier") + " (" + panelContent.countForRange(3) + ")"
|
||||
checked: tabsBox.currentIndex === 3
|
||||
onClicked: panelContent.currentRange = 3
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state when no notifications
|
||||
ColumnLayout {
|
||||
id: emptyState
|
||||
@@ -328,7 +328,7 @@ SmartPanel {
|
||||
id: notificationDelegate
|
||||
width: parent.width
|
||||
visible: panelContent.isInCurrentRange(model.timestamp)
|
||||
height: visible ? contentColumn.height + (Style.marginM * 2) : 0
|
||||
height: visible ? contentColumn.height + (Style.marginXL) : 0
|
||||
|
||||
property string notificationId: model.id
|
||||
property bool isExpanded: scrollView.expandedId === notificationId
|
||||
@@ -478,7 +478,7 @@ SmartPanel {
|
||||
model: notificationDelegate.actionsList
|
||||
delegate: NButton {
|
||||
text: modelData.text
|
||||
// Explicitly set primary colors
|
||||
fontSize: Style.fontSizeS
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
outlined: false
|
||||
@@ -486,9 +486,6 @@ SmartPanel {
|
||||
|
||||
// Capture modelData in a property to avoid reference errors
|
||||
property var actionData: modelData
|
||||
|
||||
fontWeight: Style.fontWeightRegular // Use regular font weight
|
||||
|
||||
onClicked: {
|
||||
NotificationService.invokeAction(notificationDelegate.notificationId, actionData.identifier);
|
||||
}
|
||||
|
||||
@@ -650,7 +650,7 @@ SmartPanel {
|
||||
anchors.left: countdownText.visible ? countdownText.right : parent.left
|
||||
anchors.leftMargin: countdownText.visible ? Style.marginXS : 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Style.marginM * 2
|
||||
width: Style.marginXL
|
||||
height: width
|
||||
radius: Math.min(Style.radiusM, height / 2)
|
||||
color: (buttonRoot.isSelected || mouseArea.containsMouse) ? Color.mPrimary : Qt.alpha(Color.mSurfaceVariant, 0.5)
|
||||
@@ -774,7 +774,7 @@ SmartPanel {
|
||||
id: numberIndicator
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Style.marginM * 2
|
||||
width: Style.marginXL
|
||||
height: width
|
||||
radius: Math.min(Style.radiusM, height / 2)
|
||||
color: Qt.alpha(Color.mSurfaceVariant, 0.5)
|
||||
|
||||
@@ -351,7 +351,7 @@ Item {
|
||||
readonly property bool panelVeryTransparent: Settings.data.ui.panelBackgroundOpacity <= 0.75
|
||||
|
||||
clip: true
|
||||
Layout.preferredWidth: Math.round(root.sidebarExpanded ? 200 * Style.uiScaleRatio : sidebarToggle.width + (panelVeryTransparent ? Style.marginM * 2 : 0) + (sidebarList.verticalScrollBarActive ? Style.marginM : 0))
|
||||
Layout.preferredWidth: Math.round(root.sidebarExpanded ? 200 * Style.uiScaleRatio : sidebarToggle.width + (panelVeryTransparent ? Style.marginXL : 0) + (sidebarList.verticalScrollBarActive ? Style.marginM : 0))
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
@@ -627,10 +627,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Tab content area
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -207,7 +207,7 @@ ColumnLayout {
|
||||
model: Math.max(0, root.contributors.length - root.topContributorsCount)
|
||||
|
||||
delegate: Rectangle {
|
||||
width: nameText.implicitWidth + Style.marginM * 2
|
||||
width: nameText.implicitWidth + Style.marginXL
|
||||
height: nameText.implicitHeight + Style.marginS * 2
|
||||
radius: Style.radiusS
|
||||
color: nameArea.containsMouse ? Color.mHover : "transparent"
|
||||
|
||||
@@ -451,15 +451,15 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
icon: "heart"
|
||||
text: I18n.tr("panels.about.support")
|
||||
outlined: true
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://ko-fi.com/lysec"]);
|
||||
ToastService.showNotice(I18n.tr("panels.about.support"), I18n.tr("toast.kofi-opened"));
|
||||
}
|
||||
}
|
||||
// NButton {
|
||||
// icon: "heart"
|
||||
// text: I18n.tr("panels.about.support")
|
||||
// outlined: true
|
||||
// onClicked: {
|
||||
// Quickshell.execDetached(["xdg-open", "https://ko-fi.com/lysec"]);
|
||||
// ToastService.showNotice(I18n.tr("panels.about.support"), I18n.tr("toast.kofi-opened"));
|
||||
// }
|
||||
// }
|
||||
|
||||
NButton {
|
||||
icon: "copy"
|
||||
|
||||
@@ -141,8 +141,4 @@ ColumnLayout {
|
||||
onToggled: checked => Settings.data.audio.volumeOverdrive = checked
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ ColumnLayout {
|
||||
});
|
||||
}
|
||||
|
||||
// Add terminals
|
||||
// Add terminals with category "terminal"
|
||||
for (var i = 0; i < TemplateRegistry.terminals.length; i++) {
|
||||
var t = TemplateRegistry.terminals[i];
|
||||
templates.push({
|
||||
@@ -74,7 +74,7 @@ ColumnLayout {
|
||||
templates.push({
|
||||
"id": app.id,
|
||||
"name": app.name,
|
||||
"category": app.category || "applications",
|
||||
"category": app.category || "misc",
|
||||
"tooltip": getDesc(path)
|
||||
});
|
||||
}
|
||||
@@ -85,24 +85,43 @@ ColumnLayout {
|
||||
return templates;
|
||||
}
|
||||
|
||||
// Category filter
|
||||
property string selectedCategory: ""
|
||||
|
||||
// Build available categories dynamically
|
||||
readonly property var availableCategories: {
|
||||
var cats = {};
|
||||
for (var i = 0; i < allTemplates.length; i++) {
|
||||
cats[allTemplates[i].category] = true;
|
||||
}
|
||||
return Object.keys(cats).sort();
|
||||
}
|
||||
|
||||
// Filter toggle
|
||||
property bool showOnlyActive: false
|
||||
|
||||
// Filtered templates based on search and toggle
|
||||
// Filtered templates based on category, search, and toggle
|
||||
property string searchText: ""
|
||||
readonly property var filteredTemplates: {
|
||||
// Search overrides toggle
|
||||
var result = allTemplates;
|
||||
|
||||
// Filter by category first (unless searching)
|
||||
if (selectedCategory !== "" && searchText.trim() === "") {
|
||||
result = result.filter(t => t.category === selectedCategory);
|
||||
}
|
||||
|
||||
// Search overrides category filter
|
||||
if (searchText.trim() !== "") {
|
||||
var query = searchText.toLowerCase().trim();
|
||||
return allTemplates.filter(t => t.name.toLowerCase().includes(query));
|
||||
result = result.filter(t => t.name.toLowerCase().includes(query));
|
||||
}
|
||||
|
||||
// Filter by active if enabled
|
||||
if (showOnlyActive) {
|
||||
return allTemplates.filter(t => isTemplateActive(t.id));
|
||||
// Filter by active if enabled (and not searching)
|
||||
if (showOnlyActive && searchText.trim() === "") {
|
||||
result = result.filter(t => isTemplateActive(t.id));
|
||||
}
|
||||
|
||||
return allTemplates;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check if a template is active
|
||||
@@ -153,6 +172,16 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Category filter chips
|
||||
NTagFilter {
|
||||
tags: root.availableCategories
|
||||
selectedTag: root.selectedCategory
|
||||
onSelectedTagChanged: root.selectedCategory = selectedTag
|
||||
label: I18n.tr("panels.color-scheme.templates-filter-label")
|
||||
description: I18n.tr("panels.color-scheme.templates-filter-description")
|
||||
expanded: true
|
||||
}
|
||||
|
||||
// Search/filter input row
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -24,7 +24,6 @@ ColumnLayout {
|
||||
text: I18n.tr("common.monitors")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
visible: Settings.data.dock.enabled
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
enabled: Settings.data.dock.enabled
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
||||
@@ -41,8 +41,4 @@ ColumnLayout {
|
||||
visible: Settings.data.general.showSessionButtonsOnLockScreen
|
||||
defaultValue: Settings.getDefaultValue("general.showSessionButtonsOnLockScreen")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ ColumnLayout {
|
||||
property int availablePluginsRefreshCounter: 0
|
||||
|
||||
// Pseudo tags for filtering by download status
|
||||
readonly property var pseudoTags: ["", "downloaded", "notDownloaded"]
|
||||
readonly property var pseudoTags: ["downloaded", "notDownloaded"]
|
||||
|
||||
readonly property var availableTags: {
|
||||
// Reference counter to force re-evaluation
|
||||
@@ -44,41 +44,22 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
// Tag filter chips in collapsible
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
NTagFilter {
|
||||
tags: root.pseudoTags.concat(root.availableTags)
|
||||
selectedTag: root.selectedTag
|
||||
onSelectedTagChanged: root.selectedTag = selectedTag
|
||||
label: I18n.tr("panels.plugins.filter-tags-label")
|
||||
description: I18n.tr("panels.plugins.filter-tags-description")
|
||||
expanded: true
|
||||
contentSpacing: Style.marginXS
|
||||
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginXS
|
||||
flow: Flow.LeftToRight
|
||||
|
||||
Repeater {
|
||||
id: tagRepeater
|
||||
model: root.pseudoTags.concat(root.availableTags)
|
||||
|
||||
delegate: NButton {
|
||||
text: {
|
||||
if (modelData === "")
|
||||
return I18n.tr("launcher.categories.all");
|
||||
if (modelData === "downloaded")
|
||||
return I18n.tr("panels.plugins.filter-downloaded");
|
||||
if (modelData === "notDownloaded")
|
||||
return I18n.tr("panels.plugins.filter-not-downloaded");
|
||||
return modelData;
|
||||
}
|
||||
backgroundColor: root.selectedTag === modelData ? Color.mPrimary : Color.mSurfaceVariant
|
||||
textColor: root.selectedTag === modelData ? Color.mOnPrimary : Color.mOnSurfaceVariant
|
||||
onClicked: root.selectedTag = modelData
|
||||
fontSize: Style.fontSizeS
|
||||
iconSize: Style.fontSizeS
|
||||
fontWeight: Style.fontWeightSemiBold
|
||||
buttonRadius: Style.iRadiusM
|
||||
}
|
||||
}
|
||||
formatTag: function (tag) {
|
||||
if (tag === "")
|
||||
return I18n.tr("launcher.categories.all");
|
||||
if (tag === "downloaded")
|
||||
return I18n.tr("panels.plugins.filter-downloaded");
|
||||
if (tag === "notDownloaded")
|
||||
return I18n.tr("panels.plugins.filter-not-downloaded");
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ Popup {
|
||||
// Default command display
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: defaultCommandText.implicitHeight + Style.marginM * 2
|
||||
Layout.preferredHeight: defaultCommandText.implicitHeight + Style.marginXL
|
||||
radius: Style.radiusM
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: Color.mOutline
|
||||
|
||||
@@ -406,7 +406,7 @@ ColumnLayout {
|
||||
// Predefined schemes Grid (matches ColorSchemeTab)
|
||||
GridLayout {
|
||||
id: schemesGrid
|
||||
columns: Math.max(2, Math.floor((parent.width - Style.marginM * 2) / 180))
|
||||
columns: Math.max(2, Math.floor((parent.width - Style.marginXL) / 180))
|
||||
rowSpacing: Style.marginM
|
||||
columnSpacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -231,7 +231,7 @@ SmartPanel {
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.preferredHeight: childrenRect.height + Style.marginM * 2
|
||||
Layout.preferredHeight: childrenRect.height + Style.marginXL
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusL
|
||||
opacity: 0.4
|
||||
|
||||
@@ -36,7 +36,7 @@ SmartPanel {
|
||||
// HEADER
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: headerRow.implicitHeight + (Style.marginM * 2)
|
||||
implicitHeight: headerRow.implicitHeight + (Style.marginXL)
|
||||
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
@@ -72,7 +72,7 @@ SmartPanel {
|
||||
// Stats Grid + Bottom section
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: statsContainer.implicitHeight + (Style.marginM * 2)
|
||||
Layout.preferredHeight: statsContainer.implicitHeight + (Style.marginXL)
|
||||
|
||||
ColumnLayout {
|
||||
id: statsContainer
|
||||
|
||||
@@ -22,7 +22,7 @@ Item {
|
||||
readonly property int shadowPadding: Style.shadowBlurMax + Style.marginL
|
||||
|
||||
width: notificationWidth + shadowPadding * 2
|
||||
height: Math.round(contentLayout.implicitHeight + Style.marginM * 2 * 2 + shadowPadding * 2)
|
||||
height: Math.round(contentLayout.implicitHeight + Style.marginXL * 2 + shadowPadding * 2)
|
||||
visible: true
|
||||
opacity: 0
|
||||
scale: initialScale
|
||||
@@ -99,6 +99,9 @@ Item {
|
||||
MouseArea {
|
||||
anchors.fill: background
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
onEntered: hideTimer.stop()
|
||||
onExited: hideTimer.restart()
|
||||
onClicked: root.hide()
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
@@ -108,8 +111,8 @@ Item {
|
||||
anchors.fill: background
|
||||
anchors.topMargin: Style.marginM
|
||||
anchors.bottomMargin: Style.marginM
|
||||
anchors.leftMargin: Style.marginM * 2
|
||||
anchors.rightMargin: Style.marginM * 2
|
||||
anchors.leftMargin: Style.marginXL
|
||||
anchors.rightMargin: Style.marginXL
|
||||
spacing: Style.marginL
|
||||
|
||||
// Icon
|
||||
|
||||
@@ -985,12 +985,12 @@ Singleton {
|
||||
|
||||
// Return formatted key if translation not found
|
||||
if (translation === undefined || translation === null) {
|
||||
return '## ' + key + ' ##';
|
||||
return `!!${key}!!`;
|
||||
}
|
||||
|
||||
// Ensure translation is a string
|
||||
if (typeof translation !== 'string') {
|
||||
return '## ' + key + ' ##';
|
||||
return `!!${key}!!`;
|
||||
}
|
||||
|
||||
// Handle interpolations (e.g., "Hello {name}!")
|
||||
|
||||
@@ -51,7 +51,7 @@ Singleton {
|
||||
{
|
||||
"id": "gtk",
|
||||
"name": "GTK",
|
||||
"category": "ui",
|
||||
"category": "system",
|
||||
"input": "gtk.css",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -66,7 +66,7 @@ Singleton {
|
||||
{
|
||||
"id": "qt",
|
||||
"name": "Qt",
|
||||
"category": "ui",
|
||||
"category": "system",
|
||||
"input": "qtct.conf",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -80,7 +80,7 @@ Singleton {
|
||||
{
|
||||
"id": "kcolorscheme",
|
||||
"name": "KColorScheme",
|
||||
"category": "ui",
|
||||
"category": "system",
|
||||
"input": "kcolorscheme.colors",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -91,7 +91,7 @@ Singleton {
|
||||
{
|
||||
"id": "fuzzel",
|
||||
"name": "Fuzzel",
|
||||
"category": "launchers",
|
||||
"category": "launcher",
|
||||
"input": "fuzzel.conf",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -103,7 +103,7 @@ Singleton {
|
||||
{
|
||||
"id": "vicinae",
|
||||
"name": "Vicinae",
|
||||
"category": "launchers",
|
||||
"category": "launcher",
|
||||
"input": "vicinae.toml",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -115,7 +115,7 @@ Singleton {
|
||||
{
|
||||
"id": "walker",
|
||||
"name": "Walker",
|
||||
"category": "launchers",
|
||||
"category": "launcher",
|
||||
"input": "walker.css",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -128,20 +128,20 @@ Singleton {
|
||||
{
|
||||
"id": "pywalfox",
|
||||
"name": "Pywalfox",
|
||||
"category": "applications",
|
||||
"category": "browser",
|
||||
"input": "pywalfox.json",
|
||||
"outputs": [
|
||||
{
|
||||
"path": "~/.cache/wal/colors.json"
|
||||
}
|
||||
],
|
||||
"postProcess": () => `${colorsApplyScript} pywalfox`
|
||||
"postProcess": mode => `${colorsApplyScript} pywalfox ${mode}`
|
||||
} // CONSOLIDATED DISCORD CLIENTS
|
||||
,
|
||||
{
|
||||
"id": "discord",
|
||||
"name": "Discord",
|
||||
"category": "applications",
|
||||
"category": "misc",
|
||||
"input": "vesktop.css",
|
||||
"clients": [
|
||||
{
|
||||
@@ -189,7 +189,7 @@ Singleton {
|
||||
{
|
||||
"id": "code",
|
||||
"name": "VSCode",
|
||||
"category": "applications",
|
||||
"category": "editor",
|
||||
"input": "code.json",
|
||||
"clients": [
|
||||
{
|
||||
@@ -205,7 +205,7 @@ Singleton {
|
||||
{
|
||||
"id": "zed",
|
||||
"name": "Zed",
|
||||
"category": "applications",
|
||||
"category": "editor",
|
||||
"input": "zed.json",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -217,7 +217,7 @@ Singleton {
|
||||
{
|
||||
"id": "helix",
|
||||
"name": "Helix",
|
||||
"category": "applications",
|
||||
"category": "editor",
|
||||
"input": "helix.toml",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -228,7 +228,7 @@ Singleton {
|
||||
{
|
||||
"id": "spicetify",
|
||||
"name": "Spicetify",
|
||||
"category": "applications",
|
||||
"category": "audio",
|
||||
"input": "spicetify.ini",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -240,7 +240,7 @@ Singleton {
|
||||
{
|
||||
"id": "telegram",
|
||||
"name": "Telegram",
|
||||
"category": "applications",
|
||||
"category": "misc",
|
||||
"input": "telegram.tdesktop-theme",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -251,7 +251,7 @@ Singleton {
|
||||
{
|
||||
"id": "zenBrowser",
|
||||
"name": "Zen Browser",
|
||||
"category": "applications",
|
||||
"category": "browser",
|
||||
"input": "zen-browser/zen-userChrome.css",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -268,7 +268,7 @@ Singleton {
|
||||
{
|
||||
"id": "cava",
|
||||
"name": "Cava",
|
||||
"category": "applications",
|
||||
"category": "audio",
|
||||
"input": "cava.ini",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -280,7 +280,7 @@ Singleton {
|
||||
{
|
||||
"id": "yazi",
|
||||
"name": "Yazi",
|
||||
"category": "applications",
|
||||
"category": "misc",
|
||||
"input": "yazi.toml",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -291,7 +291,7 @@ Singleton {
|
||||
{
|
||||
"id": "emacs",
|
||||
"name": "Emacs",
|
||||
"category": "applications",
|
||||
"category": "editor",
|
||||
"input": "emacs.el",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -306,7 +306,7 @@ Singleton {
|
||||
{
|
||||
"id": "niri",
|
||||
"name": "Niri",
|
||||
"category": "compositors",
|
||||
"category": "compositor",
|
||||
"input": "niri.kdl",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -318,7 +318,7 @@ Singleton {
|
||||
{
|
||||
"id": "hyprland",
|
||||
"name": "Hyprland",
|
||||
"category": "compositors",
|
||||
"category": "compositor",
|
||||
"input": "hyprland.conf",
|
||||
"outputs": [
|
||||
{
|
||||
@@ -330,7 +330,7 @@ Singleton {
|
||||
{
|
||||
"id": "mango",
|
||||
"name": "Mango",
|
||||
"category": "compositors",
|
||||
"category": "compositor",
|
||||
"input": "mango.conf",
|
||||
"outputs": [
|
||||
{
|
||||
|
||||
@@ -20,6 +20,7 @@ Singleton {
|
||||
readonly property string baseDir: Settings.cacheDir + "images/"
|
||||
readonly property string wpThumbDir: baseDir + "wallpapers/thumbnails/"
|
||||
readonly property string wpLargeDir: baseDir + "wallpapers/large/"
|
||||
readonly property string wpOverviewDir: baseDir + "wallpapers/overview/"
|
||||
readonly property string notificationsDir: baseDir + "notifications/"
|
||||
readonly property string contributorsDir: baseDir + "contributors/"
|
||||
|
||||
@@ -62,12 +63,13 @@ Singleton {
|
||||
function createDirectories() {
|
||||
Quickshell.execDetached(["mkdir", "-p", wpThumbDir]);
|
||||
Quickshell.execDetached(["mkdir", "-p", wpLargeDir]);
|
||||
Quickshell.execDetached(["mkdir", "-p", wpOverviewDir]);
|
||||
Quickshell.execDetached(["mkdir", "-p", notificationsDir]);
|
||||
Quickshell.execDetached(["mkdir", "-p", contributorsDir]);
|
||||
}
|
||||
|
||||
function cleanupOldCache() {
|
||||
const dirs = [wpThumbDir, wpLargeDir, notificationsDir, contributorsDir];
|
||||
const dirs = [wpThumbDir, wpLargeDir, wpOverviewDir, notificationsDir, contributorsDir];
|
||||
dirs.forEach(function (dir) {
|
||||
Quickshell.execDetached(["find", dir, "-type", "f", "-mtime", "+30", "-delete"]);
|
||||
});
|
||||
@@ -188,6 +190,31 @@ Singleton {
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Public API: Get Blurred Overview (for Niri overview background)
|
||||
// -------------------------------------------------
|
||||
function getBlurredOverview(sourcePath, width, height, tintColor, isDarkMode, callback) {
|
||||
if (!sourcePath || sourcePath === "") {
|
||||
callback("", false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!imageMagickAvailable) {
|
||||
Logger.d("ImageCache", "ImageMagick not available for overview blur, using original:", sourcePath);
|
||||
callback(sourcePath, false);
|
||||
return;
|
||||
}
|
||||
|
||||
getMtime(sourcePath, function (mtime) {
|
||||
const cacheKey = generateOverviewKey(sourcePath, width, height, tintColor, isDarkMode, mtime);
|
||||
const cachedPath = wpOverviewDir + cacheKey + ".png";
|
||||
|
||||
processRequest(cacheKey, cachedPath, sourcePath, callback, function () {
|
||||
startOverviewProcessing(sourcePath, cachedPath, width, height, tintColor, isDarkMode, cacheKey);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Cache Key Generation
|
||||
// -------------------------------------------------
|
||||
@@ -208,6 +235,11 @@ Singleton {
|
||||
return Checksum.sha256(imageUri);
|
||||
}
|
||||
|
||||
function generateOverviewKey(sourcePath, width, height, tintColor, isDarkMode, mtime) {
|
||||
const keyString = sourcePath + "@" + width + "x" + height + "@" + tintColor + "@" + (isDarkMode ? "dark" : "light") + "@" + (mtime || "unknown");
|
||||
return Checksum.sha256(keyString);
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Request Processing (with coalescing)
|
||||
// -------------------------------------------------
|
||||
@@ -288,6 +320,19 @@ Singleton {
|
||||
runProcess(command, cacheKey, outputPath, sourcePath);
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// ImageMagick Processing: Blurred Overview
|
||||
// -------------------------------------------------
|
||||
function startOverviewProcessing(sourcePath, outputPath, width, height, tintColor, isDarkMode, cacheKey) {
|
||||
const srcEsc = sourcePath.replace(/'/g, "'\\''");
|
||||
const dstEsc = outputPath.replace(/'/g, "'\\''");
|
||||
|
||||
// Resize, blur, then tint overlay
|
||||
const command = `magick '${srcEsc}' -auto-orient -resize '${width}x${height}^' -gravity center -extent ${width}x${height} -gaussian-blur 0x5 \\( +clone -fill '${tintColor}' -colorize 100 -alpha set -channel A -evaluate set 50% +channel \\) -composite '${dstEsc}'`;
|
||||
|
||||
runProcess(command, cacheKey, outputPath, sourcePath);
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// ImageMagick Processing: Circular Avatar
|
||||
// -------------------------------------------------
|
||||
|
||||
@@ -23,7 +23,7 @@ ColumnLayout {
|
||||
Rectangle {
|
||||
id: headerContainer
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: headerContent.implicitHeight + (Style.marginM * 2)
|
||||
Layout.preferredHeight: headerContent.implicitHeight + (Style.marginXL)
|
||||
color: root.expanded ? Color.mSecondary : Color.mPrimary
|
||||
radius: Style.iRadiusM
|
||||
border.color: root.expanded ? Color.mOnSecondary : Color.mOutline
|
||||
|
||||
@@ -396,7 +396,7 @@ Popup {
|
||||
NColorSlider {
|
||||
id: selectedSlider
|
||||
Layout.fillHeight: true
|
||||
rainbowMode: root.editMode === "h"
|
||||
rainbowMode: root.editMode === NColorPickerDialog.EditMode.H
|
||||
topColor: {
|
||||
if (rainbowMode)
|
||||
return "transparent";
|
||||
|
||||
@@ -91,8 +91,8 @@ RowLayout {
|
||||
}
|
||||
}
|
||||
return I18n.tr("panels.indicator.default-value", {
|
||||
"value": displayValue
|
||||
});
|
||||
"value": displayValue
|
||||
});
|
||||
}
|
||||
|
||||
function itemCount() {
|
||||
@@ -187,7 +187,7 @@ RowLayout {
|
||||
popup: Popup {
|
||||
y: combo.height
|
||||
implicitWidth: combo.width - Style.marginM
|
||||
implicitHeight: Math.min(Math.round(root.popupHeight * Style.uiScaleRatio), listView.contentHeight + Style.marginM * 2)
|
||||
implicitHeight: Math.min(Math.round(root.popupHeight * Style.uiScaleRatio), listView.contentHeight + Style.marginXL)
|
||||
padding: Style.marginM
|
||||
|
||||
contentItem: ListView {
|
||||
@@ -226,7 +226,7 @@ RowLayout {
|
||||
implicitHeight: 100
|
||||
color: "transparent"
|
||||
opacity: parent.active ? 0.3 : 0.0
|
||||
radius: Style.iRadiusM / 2
|
||||
radius: Style.iRadiusXS
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
|
||||
@@ -9,6 +9,7 @@ ColumnLayout {
|
||||
property string description: ""
|
||||
property bool enableDescriptionRichText: false
|
||||
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
|
||||
@@ -67,7 +67,7 @@ PopupWindow {
|
||||
itemWidth += iconMeasure.width + Style.marginS;
|
||||
}
|
||||
|
||||
itemWidth += Style.marginM * 2;
|
||||
itemWidth += Style.marginXL;
|
||||
|
||||
if (itemWidth > maxWidth) {
|
||||
maxWidth = itemWidth;
|
||||
|
||||
@@ -251,7 +251,7 @@ Item {
|
||||
// Required indicator
|
||||
NText {
|
||||
visible: delegateItem.required
|
||||
text: "(required)"
|
||||
text: I18n.tr("common.required")
|
||||
color: Color.mOnSurfaceVariant
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
+26
-9
@@ -9,7 +9,7 @@ Rectangle {
|
||||
|
||||
// Public properties
|
||||
property int currentIndex: 0
|
||||
property real spacing: Style.marginS
|
||||
property real spacing: Style.marginXS
|
||||
property real margins: 0
|
||||
property real tabHeight: Style.baseWidgetSize
|
||||
property bool distributeEvenly: false
|
||||
@@ -18,6 +18,27 @@ Rectangle {
|
||||
onDistributeEvenlyChanged: _applyDistribution()
|
||||
Component.onCompleted: _applyDistribution()
|
||||
|
||||
function _updateFirstLast() {
|
||||
var kids = tabRow.children;
|
||||
var len = kids.length;
|
||||
var firstVisible = -1;
|
||||
var lastVisible = -1;
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (kids[i].visible) {
|
||||
if (firstVisible === -1)
|
||||
firstVisible = i;
|
||||
lastVisible = i;
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < len; i++) {
|
||||
var child = kids[i];
|
||||
if ("isFirst" in child)
|
||||
child.isFirst = (i === firstVisible);
|
||||
if ("isLast" in child)
|
||||
child.isLast = (i === lastVisible);
|
||||
}
|
||||
}
|
||||
|
||||
function _applyDistribution() {
|
||||
if (!distributeEvenly) {
|
||||
for (var i = 0; i < tabRow.children.length; i++) {
|
||||
@@ -47,15 +68,11 @@ Rectangle {
|
||||
spacing: root.spacing
|
||||
|
||||
onChildrenChanged: {
|
||||
var kids = children;
|
||||
var len = kids.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
var child = kids[i];
|
||||
if ("isFirst" in child)
|
||||
child.isFirst = (i === 0);
|
||||
if ("isLast" in child)
|
||||
child.isLast = (i === len - 1);
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
child.visibleChanged.connect(root._updateFirstLast);
|
||||
}
|
||||
root._updateFirstLast();
|
||||
root._applyDistribution();
|
||||
}
|
||||
}
|
||||
|
||||
+8
-54
@@ -14,6 +14,7 @@ Rectangle {
|
||||
property real pointSize: Style.fontSizeM
|
||||
property bool isFirst: false
|
||||
property bool isLast: false
|
||||
|
||||
// Internal state
|
||||
property bool isHovered: false
|
||||
|
||||
@@ -21,64 +22,17 @@ Rectangle {
|
||||
|
||||
// Sizing
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: tabText.implicitWidth + Style.marginM * 2
|
||||
implicitWidth: tabText.implicitWidth + Style.marginXL
|
||||
|
||||
topLeftRadius: isFirst ? Style.iRadiusM : Style.iRadiusXXXS
|
||||
bottomLeftRadius: isFirst ? Style.iRadiusM : Style.iRadiusXXXS
|
||||
topRightRadius: isLast ? Style.iRadiusM : Style.iRadiusXXXS
|
||||
bottomRightRadius: isLast ? Style.iRadiusM : Style.iRadiusXXXS
|
||||
|
||||
// Styling
|
||||
radius: (isFirst || isLast) ? Style.iRadiusM : 0
|
||||
color: root.isHovered ? Color.mHover : (root.checked ? Color.mPrimary : Color.mSurface)
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
// Squares off the RIGHT side of FIRST tab.
|
||||
Item {
|
||||
visible: root.isFirst
|
||||
width: root.radius
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
width: parent.width + border.width
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
color: root.color
|
||||
border.width: root.border.width
|
||||
border.color: root.border.color
|
||||
}
|
||||
}
|
||||
|
||||
// Squares off the LEFT side of LAST tab.
|
||||
Item {
|
||||
visible: root.isLast
|
||||
width: root.radius
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
width: parent.width + border.width
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
color: root.color
|
||||
border.width: root.border.width
|
||||
border.color: root.border.color
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
@@ -88,10 +42,10 @@ Rectangle {
|
||||
|
||||
NText {
|
||||
id: tabText
|
||||
y: Style.pixelAlignCenter(parent.height, height)
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
leftMargin: Style.marginS
|
||||
rightMargin: Style.marginS
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
NCollapsible {
|
||||
id: root
|
||||
|
||||
// Public API
|
||||
property var tags: [] // Array of tag strings
|
||||
property string selectedTag: ""
|
||||
property alias label: root.label
|
||||
property alias description: root.description
|
||||
property alias expanded: root.expanded
|
||||
|
||||
// Formatting function for tag display (optional override)
|
||||
property var formatTag: function (tag) {
|
||||
if (tag === "")
|
||||
return I18n.tr("launcher.categories.all");
|
||||
// Default: capitalize first letter
|
||||
return tag.charAt(0).toUpperCase() + tag.slice(1);
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
contentSpacing: Style.marginXS
|
||||
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginXS
|
||||
flow: Flow.LeftToRight
|
||||
|
||||
Repeater {
|
||||
model: [""].concat(root.tags)
|
||||
|
||||
delegate: NButton {
|
||||
text: root.formatTag(modelData)
|
||||
backgroundColor: root.selectedTag === modelData ? Color.mPrimary : Color.mSurfaceVariant
|
||||
textColor: root.selectedTag === modelData ? Color.mOnPrimary : Color.mOnSurfaceVariant
|
||||
onClicked: root.selectedTag = modelData
|
||||
fontSize: Style.fontSizeS
|
||||
iconSize: Style.fontSizeS
|
||||
fontWeight: Style.fontWeightSemiBold
|
||||
buttonRadius: Style.iRadiusM
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ Text {
|
||||
return fontScale;
|
||||
}
|
||||
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
font.family: root.family
|
||||
font.weight: Style.fontWeightMedium
|
||||
font.pointSize: Math.max(1, root.pointSize * fontScale)
|
||||
|
||||
Reference in New Issue
Block a user