Merge branch 'noctalia-dev:main' into pr/bluetooth-rework

This commit is contained in:
Turann_
2026-01-15 23:23:34 +03:00
committed by GitHub
79 changed files with 2158 additions and 1635 deletions
+21 -21
View File
@@ -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}}"
}
}
+3
View File
@@ -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",
+3
View File
@@ -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",
+3
View File
@@ -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",
+3
View File
@@ -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",
+4 -1
View File
@@ -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"
},
+3
View File
@@ -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": "操作方法を理解している場合のみ有効にしてください。オンラインドキュメントを参照してください。",
+4 -1
View File
@@ -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"
},
+3
View File
@@ -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",
+3
View File
@@ -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",
+3
View File
@@ -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",
+3
View File
@@ -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": "Включайте только если вы знаете, что делаете — обратитесь к нашей онлайн-документации",
+4 -1
View File
@@ -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ı"
},
+3
View File
@@ -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": "Увімкніть лише якщо ви знаєте, що робите — зверніться до нашої онлайн-документації",
+3
View File
@@ -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": "仅在您知道自己在做什么时启用,请参阅我们的在线文档",
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -402,7 +402,8 @@
"screenLock": "",
"screenUnlock": "",
"performanceModeEnabled": "",
"performanceModeDisabled": ""
"performanceModeDisabled": "",
"session": ""
},
"desktopWidgets": {
"enabled": false,
+14 -3
View File
@@ -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
View File
@@ -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") {
+1
View File
@@ -645,6 +645,7 @@ Singleton {
property string screenUnlock: ""
property string performanceModeEnabled: ""
property string performanceModeDisabled: ""
property string session: ""
}
// desktop widgets
+2
View File
@@ -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)
+84 -12
View File
@@ -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
}
}
}
+2 -2
View File
@@ -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
+1 -1
View File
@@ -254,7 +254,7 @@ PopupWindow {
NDivider {
anchors.centerIn: parent
width: parent.width - (Style.marginM * 2)
width: parent.width - (Style.marginXL)
visible: modelData?.isSeparator ?? false
}
+4 -4
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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
+4 -4
View File
@@ -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
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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
+4 -4
View File
@@ -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 {
+2 -2
View File
@@ -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
File diff suppressed because it is too large Load Diff
+284
View File
@@ -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()
}
}
}
+138
View File
@@ -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
}
}
}
+750
View File
@@ -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()
}
}
}
}
}
}
+1 -1
View File
@@ -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
+6 -6
View File
@@ -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
+1 -1
View File
@@ -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
+4 -4
View File
@@ -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)
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+9 -9
View File
@@ -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
+7 -7
View File
@@ -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);
}
+2 -2
View File
@@ -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)
+1 -5
View File
@@ -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
+1 -1
View File
@@ -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
+6 -3
View File
@@ -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
+2 -2
View File
@@ -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}!")
+21 -21
View File
@@ -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": [
{
+46 -1
View File
@@ -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
// -------------------------------------------------
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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";
+4 -4
View File
@@ -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 {
+1
View File
@@ -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
+1 -1
View File
@@ -67,7 +67,7 @@ PopupWindow {
itemWidth += iconMeasure.width + Style.marginS;
}
itemWidth += Style.marginM * 2;
itemWidth += Style.marginXL;
if (itemWidth > maxWidth) {
maxWidth = itemWidth;
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+48
View File
@@ -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
}
}
}
}
+1
View File
@@ -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)