Compare commits
262 Commits
b2f601df80
...
93dd3ee2a3
| Author | SHA1 | Date | |
|---|---|---|---|
| 93dd3ee2a3 | |||
| 97fb7bf0be | |||
| d27f0c2486 | |||
| 0397cadf91 | |||
|
|
85fca41c50 | ||
|
|
b6c1f6e90a | ||
|
|
5d3c91f3ad | ||
|
|
5b73ae6bcb | ||
|
|
d74cbe356b | ||
|
|
5c2d4f4412 | ||
|
|
e972e1f7aa | ||
|
|
13af9227c9 | ||
|
|
1139addd58 | ||
|
|
7f88725023 | ||
|
|
087c9b4ced | ||
|
|
0d2b93dee1 | ||
|
|
f04622ade7 | ||
|
|
925bbe7a5e | ||
|
|
a773300469 | ||
|
|
759539c101 | ||
|
|
a84525ea52 | ||
|
|
a17082d27f | ||
|
|
c7f947d235 | ||
|
|
eaff0c6434 | ||
|
|
a81205f444 | ||
|
|
d738f14a81 | ||
|
|
94132dce6d | ||
|
|
946c8883ca | ||
|
|
225e6d3914 | ||
|
|
1a7ab224ca | ||
|
|
1a2de1da11 | ||
|
|
80b93ab895 | ||
|
|
111170fbee | ||
|
|
92853b4700 | ||
|
|
67b4971b65 | ||
|
|
2a6b236faf | ||
|
|
ca04156375 | ||
|
|
9266ccfec4 | ||
|
|
5b5d41acf8 | ||
|
|
f52c4491b8 | ||
|
|
4887be96f5 | ||
|
|
6aca04cddb | ||
|
|
3f00bec8f4 | ||
|
|
ae2bf590ee | ||
|
|
e0f38ff80b | ||
|
|
4f9ba6f601 | ||
|
|
1c9d659635 | ||
|
|
8f7d2f28f2 | ||
|
|
5f175a4f9a | ||
|
|
6fb840ef0b | ||
|
|
017a5a6f91 | ||
|
|
5451985e48 | ||
|
|
fe25840dfa | ||
|
|
078195f54b | ||
|
|
669665a5af | ||
|
|
260b2e9a11 | ||
|
|
dc1c0e8f47 | ||
|
|
30db679207 | ||
|
|
993b6bc422 | ||
|
|
25bd796d7d | ||
|
|
05ceff017d | ||
|
|
9d4ac03d21 | ||
|
|
588a5782ae | ||
|
|
ddfde344bc | ||
|
|
69b9ecbb30 | ||
|
|
c9e479275c | ||
|
|
ad755bb3fb | ||
|
|
6d6261ca00 | ||
|
|
9a9ebf11fb | ||
|
|
aabe251f0d | ||
|
|
59f70e803b | ||
|
|
01d42e55f3 | ||
|
|
0ab8458ca2 | ||
|
|
cdc5725e1b | ||
|
|
d53a3d8de2 | ||
|
|
e627e67463 | ||
|
|
e07f2d34c0 | ||
|
|
75b17b9185 | ||
|
|
9d5ac132c7 | ||
|
|
7b091ad7c5 | ||
|
|
366c867f94 | ||
|
|
fd9341d2f1 | ||
|
|
7366298026 | ||
|
|
17b09739ad | ||
|
|
3db394c80a | ||
|
|
0d6b70a4c1 | ||
|
|
ce7a412956 | ||
|
|
43ecd3ce9b | ||
|
|
5d70941a24 | ||
|
|
1242082a9e | ||
|
|
4cc6d8b54e | ||
|
|
2867048d9b | ||
|
|
9fd914875c | ||
|
|
aeee91d08a | ||
|
|
46f881026e | ||
|
|
4301eae37d | ||
|
|
f6080b9aa7 | ||
|
|
aa892fceab | ||
|
|
4e5046eb91 | ||
|
|
816689dca2 | ||
|
|
4812d9d1e6 | ||
|
|
69004c072c | ||
|
|
76982e5de6 | ||
|
|
6b27db0d4f | ||
|
|
ff78afeb82 | ||
|
|
8d495cea3a | ||
|
|
722539796e | ||
|
|
5a9cebf420 | ||
|
|
1d74157d15 | ||
|
|
238b2f3ea3 | ||
|
|
c6b28bec4d | ||
|
|
9d25f9c9e7 | ||
|
|
5e205ad69a | ||
|
|
3ec973ca21 | ||
|
|
a177031265 | ||
|
|
a48e4dcecd | ||
|
|
a4193382df | ||
|
|
9f015ebd9a | ||
|
|
0e46c4bb2b | ||
|
|
f66e063d5a | ||
|
|
3f02b28ecc | ||
|
|
bfc5afa947 | ||
|
|
af6ef8e763 | ||
|
|
4e4a974f2c | ||
|
|
7cb293733c | ||
|
|
2fe915e3bc | ||
|
|
fd17032fe5 | ||
|
|
ee2ff95de0 | ||
|
|
ec16b4eafc | ||
|
|
8c339fc199 | ||
|
|
c8dc62c981 | ||
|
|
e6a4db9707 | ||
|
|
7ab4049942 | ||
|
|
453727795c | ||
|
|
94a582b634 | ||
|
|
72263f198e | ||
|
|
778dce21c1 | ||
|
|
f70e49ad9e | ||
|
|
705334169b | ||
|
|
3e5cf91bfb | ||
|
|
5e833f0683 | ||
|
|
84246e0d5d | ||
|
|
0c8b0cb395 | ||
|
|
6e4f450f97 | ||
|
|
64c1f4383e | ||
|
|
04f5a0cbf8 | ||
|
|
1c1232dc5b | ||
|
|
bee2414333 | ||
|
|
b344e41828 | ||
|
|
d2023500a9 | ||
|
|
ba1e783c8f | ||
|
|
4d24791ec1 | ||
|
|
81c35aaee9 | ||
|
|
40f70cdbb1 | ||
|
|
1aa0cc6467 | ||
|
|
23d3eb642e | ||
|
|
a188aa2e17 | ||
|
|
8419549183 | ||
|
|
e2854f2079 | ||
|
|
05c90909d2 | ||
|
|
cdb93a3d96 | ||
|
|
309648d6d6 | ||
|
|
649ec5ac5a | ||
|
|
601f297b35 | ||
|
|
7f9bb6f0a5 | ||
|
|
4d72a0bd0c | ||
|
|
f79aad5f0e | ||
|
|
a4d9463c5d | ||
|
|
f10207a159 | ||
|
|
f611e3a2c0 | ||
|
|
94d1d9dc9c | ||
|
|
c0b836af26 | ||
|
|
a44137f81f | ||
|
|
60eb9c6e78 | ||
|
|
42211c6eda | ||
|
|
3ef5e169e4 | ||
|
|
cb0609451d | ||
|
|
737bde0a6a | ||
|
|
bc9c27baf8 | ||
|
|
331519bba4 | ||
|
|
d2f018c133 | ||
|
|
66d949ec2a | ||
|
|
b3cd4568f3 | ||
|
|
22ee8904a4 | ||
|
|
3c5dfd87db | ||
|
|
58d4730814 | ||
|
|
ad044882a9 | ||
|
|
7742bb5cc0 | ||
|
|
a2e686bb21 | ||
|
|
b7d4e74012 | ||
|
|
12fe6c5559 | ||
|
|
a35123918c | ||
|
|
764acef4e7 | ||
|
|
82c629278d | ||
|
|
724d991d9f | ||
|
|
8277ce1631 | ||
|
|
5a247f9de4 | ||
|
|
cb3af2d0d6 | ||
|
|
e91e3d9a4e | ||
|
|
15a936bebc | ||
|
|
cc11971fc8 | ||
|
|
83f8028d47 | ||
|
|
3f08493134 | ||
|
|
5c2da31155 | ||
|
|
f79d9ce852 | ||
|
|
1d396afb05 | ||
|
|
8ab2d84c85 | ||
|
|
4a57803847 | ||
|
|
f9f83a6db3 | ||
|
|
4c6cf8d21b | ||
|
|
9cf44be361 | ||
|
|
f9c0c0a480 | ||
|
|
6a427b2cfc | ||
|
|
ce7b27c316 | ||
|
|
adfee30f8c | ||
|
|
70b19791bb | ||
|
|
7f48ea73b2 | ||
|
|
6b5a2d2339 | ||
|
|
8afb6cfb6a | ||
|
|
f38861061e | ||
|
|
1cf9178a26 | ||
|
|
1408af4a0a | ||
|
|
71455e4af9 | ||
|
|
ff983d7c54 | ||
|
|
7ba8ac28c8 | ||
|
|
e1dc72216e | ||
|
|
5983ba2fd1 | ||
|
|
1b4e6c9bb5 | ||
|
|
3cf4e1f95b | ||
|
|
7be37eadf9 | ||
|
|
a894511711 | ||
|
|
4e63b54c0e | ||
|
|
c70098a738 | ||
|
|
5a6a175558 | ||
|
|
49747dffcc | ||
|
|
38721a1a80 | ||
|
|
a57480320f | ||
|
|
0e899d5559 | ||
|
|
c7116827a4 | ||
|
|
54cd3d74e5 | ||
|
|
fab0d3d8db | ||
|
|
73b6aa8c47 | ||
|
|
611ddbe612 | ||
|
|
56c228b4da | ||
|
|
4a4b25ae96 | ||
|
|
8f850cdbfd | ||
|
|
e61a073f57 | ||
|
|
b93c5051d0 | ||
|
|
e6c9a828af | ||
|
|
9c01319261 | ||
|
|
bd2507d9f8 | ||
|
|
b53f5ef504 | ||
|
|
a22a3c1345 | ||
|
|
87dd944075 | ||
|
|
ad96d2b05c | ||
|
|
80bc4f9c55 | ||
|
|
f033ebb854 | ||
|
|
04c8f5b54e | ||
|
|
d28c89afcd | ||
|
|
c9eead1d9e | ||
|
|
16486ba054 | ||
|
|
40a717e009 |
1
.gitignore
vendored
@@ -1,4 +1,3 @@
|
||||
.qmlls.ini
|
||||
.zed
|
||||
Bin/battery-manager/uninstall-battery-manager.sh
|
||||
.idea
|
||||
339
Assets/MatugenTemplates/emacs.el
Normal file
@@ -0,0 +1,339 @@
|
||||
;;; noctalia-theme.el --- Theme using Matugen SCSS variables
|
||||
|
||||
;; Copyright (C) 2025
|
||||
|
||||
;; Author: Generated (Improved)
|
||||
;; Version: 1.2
|
||||
;; Package-Requires: ((emacs "24.1"))
|
||||
;; Keywords: faces
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; A theme using Matugen SCSS variables with quality of life improvements:
|
||||
;; - Better source block distinction
|
||||
;; - Improved text visibility when selected
|
||||
;; - Refined org-mode styling with hidden asterisks
|
||||
;; - Enhanced contrast and readability
|
||||
;; - Seamless integration of source blocks with consistent styling
|
||||
|
||||
;;; Code:
|
||||
|
||||
(deftheme noctalia "Theme using Matugen variables with quality of life improvements.")
|
||||
|
||||
;; Define all the color variables (replaced by template processor)
|
||||
(let* ((bg "{{colors.background.default.hex}}")
|
||||
(err "{{colors.error.default.hex}}")
|
||||
(err-container "{{colors.error_container.default.hex}}")
|
||||
(on-background "{{colors.on_background.default.hex}}")
|
||||
(on-err "{{colors.on_error.default.hex}}")
|
||||
(on-err-container "{{colors.on_error_container.default.hex}}")
|
||||
(on-primary "{{colors.on_primary.default.hex}}")
|
||||
(on-primary-container "{{colors.on_primary_container.default.hex}}")
|
||||
(on-secondary "{{colors.on_secondary.default.hex}}")
|
||||
(on-secondary-container "{{colors.on_secondary_container.default.hex}}")
|
||||
(on-surface "{{colors.on_surface.default.hex}}")
|
||||
(on-surface-variant "{{colors.on_surface_variant.default.hex}}")
|
||||
(on-tertiary "{{colors.on_tertiary.default.hex}}")
|
||||
(on-tertiary-container "{{colors.on_tertiary_container.default.hex}}")
|
||||
(outline-color "{{colors.outline.default.hex}}")
|
||||
(outline-variant "{{colors.outline_variant.default.hex}}")
|
||||
(primary "{{colors.primary.default.hex}}")
|
||||
(primary-container "{{colors.primary_container.default.hex}}")
|
||||
(secondary "{{colors.secondary.default.hex}}")
|
||||
(secondary-container "{{colors.secondary_container.default.hex}}")
|
||||
(shadow "{{colors.shadow.default.hex}}")
|
||||
(surface "{{colors.surface.default.hex}}")
|
||||
(surface-container "{{colors.surface_container.default.hex}}")
|
||||
(surface-container-high "{{colors.surface_container_high.default.hex}}")
|
||||
(surface-container-highest "{{colors.surface_container_highest.default.hex}}")
|
||||
(surface-container-low "{{colors.surface_container_low.default.hex}}")
|
||||
(surface-container-lowest "{{colors.surface_container_lowest.default.hex}}")
|
||||
(surface-variant "{{colors.surface_variant.default.hex}}")
|
||||
(tertiary "{{colors.tertiary.default.hex}}")
|
||||
(tertiary-container "{{colors.tertiary_container.default.hex}}")
|
||||
;; Map success colors to tertiary (as used in other templates)
|
||||
(success "{{colors.tertiary.default.hex}}")
|
||||
(on-success "{{colors.on_tertiary.default.hex}}")
|
||||
(success-container "{{colors.tertiary_container.default.hex}}")
|
||||
(on-success-container "{{colors.on_tertiary_container.default.hex}}")
|
||||
;; Map fixed colors to regular colors
|
||||
(primary-fixed "{{colors.primary.default.hex}}")
|
||||
(primary-fixed-dim "{{colors.primary_container.default.hex}}")
|
||||
(secondary-fixed "{{colors.secondary.default.hex}}")
|
||||
(secondary-fixed-dim "{{colors.secondary_container.default.hex}}")
|
||||
(tertiary-fixed "{{colors.tertiary.default.hex}}")
|
||||
(tertiary-fixed-dim "{{colors.tertiary_container.default.hex}}")
|
||||
(on-primary-fixed "{{colors.on_primary.default.hex}}")
|
||||
(on-primary-fixed-variant "{{colors.on_primary_container.default.hex}}")
|
||||
(on-secondary-fixed "{{colors.on_secondary.default.hex}}")
|
||||
(on-secondary-fixed-variant "{{colors.on_secondary_container.default.hex}}")
|
||||
(on-tertiary-fixed "{{colors.on_tertiary.default.hex}}")
|
||||
(on-tertiary-fixed-variant "{{colors.on_tertiary_container.default.hex}}")
|
||||
;; Map inverse colors to surface variants
|
||||
(inverse-on-surface "{{colors.on_surface.default.hex}}")
|
||||
(inverse-primary "{{colors.primary.default.hex}}")
|
||||
(inverse-surface "{{colors.surface.default.hex}}")
|
||||
;; Map terminal colors (term0-term15) to available colors
|
||||
(term0 "{{colors.surface.default.hex}}")
|
||||
(term1 "{{colors.error.default.hex}}")
|
||||
(term2 "{{colors.tertiary.default.hex}}")
|
||||
(term3 "{{colors.secondary.default.hex}}")
|
||||
(term4 "{{colors.primary.default.hex}}")
|
||||
(term5 "{{colors.tertiary_container.default.hex}}")
|
||||
(term6 "{{colors.secondary_container.default.hex}}")
|
||||
(term7 "{{colors.on_surface.default.hex}}")
|
||||
(term8 "{{colors.outline.default.hex}}")
|
||||
(term9 "{{colors.error.default.hex}}")
|
||||
(term10 "{{colors.tertiary.default.hex}}")
|
||||
(term11 "{{colors.secondary.default.hex}}")
|
||||
(term12 "{{colors.primary.default.hex}}")
|
||||
(term13 "{{colors.tertiary_container.default.hex}}")
|
||||
(term14 "{{colors.secondary_container.default.hex}}")
|
||||
(term15 "{{colors.on_surface.default.hex}}"))
|
||||
|
||||
(custom-theme-set-faces
|
||||
'noctalia
|
||||
;; Basic faces
|
||||
`(default ((t (:background ,bg :foreground ,on-background))))
|
||||
`(cursor ((t (:background ,primary))))
|
||||
`(highlight ((t (:background ,primary-container :foreground ,on-primary-container))))
|
||||
`(region ((t (:background ,primary-container :foreground ,on-primary-container :extend t))))
|
||||
`(secondary-selection ((t (:background ,secondary-container :foreground ,on-secondary-container :extend t))))
|
||||
`(isearch ((t (:background ,tertiary-container :foreground ,on-tertiary-container :weight bold))))
|
||||
`(lazy-highlight ((t (:background ,secondary-container :foreground ,on-secondary-container))))
|
||||
`(vertical-border ((t (:foreground ,surface-variant))))
|
||||
`(border ((t (:background ,surface-variant :foreground ,surface-variant))))
|
||||
`(fringe ((t (:background ,surface :foreground ,outline-variant))))
|
||||
`(shadow ((t (:foreground ,outline-variant))))
|
||||
`(link ((t (:foreground ,primary :underline t))))
|
||||
`(link-visited ((t (:foreground ,tertiary :underline t))))
|
||||
`(success ((t (:foreground ,success))))
|
||||
`(warning ((t (:foreground ,secondary))))
|
||||
`(error ((t (:foreground ,err))))
|
||||
`(match ((t (:background ,secondary-container :foreground ,on-secondary-container))))
|
||||
|
||||
;; Font-lock
|
||||
`(font-lock-builtin-face ((t (:foreground ,primary))))
|
||||
`(font-lock-comment-face ((t (:foreground ,outline-color :slant italic))))
|
||||
`(font-lock-comment-delimiter-face ((t (:foreground ,outline-variant))))
|
||||
`(font-lock-constant-face ((t (:foreground ,tertiary :weight bold))))
|
||||
`(font-lock-doc-face ((t (:foreground ,on-surface-variant :slant italic))))
|
||||
`(font-lock-function-name-face ((t (:foreground ,primary :weight bold))))
|
||||
`(font-lock-keyword-face ((t (:foreground ,secondary :weight bold))))
|
||||
`(font-lock-string-face ((t (:foreground ,tertiary))))
|
||||
`(font-lock-type-face ((t (:foreground ,primary-fixed))))
|
||||
`(font-lock-variable-name-face ((t (:foreground ,on-surface))))
|
||||
`(font-lock-warning-face ((t (:foreground ,err :weight bold))))
|
||||
`(font-lock-preprocessor-face ((t (:foreground ,secondary-fixed-dim))))
|
||||
`(font-lock-negation-char-face ((t (:foreground ,tertiary-fixed))))
|
||||
|
||||
;; Show paren
|
||||
`(show-paren-match ((t (:background ,primary-container :foreground ,on-primary-container :weight bold))))
|
||||
`(show-paren-mismatch ((t (:background ,err-container :foreground ,on-err-container :weight bold))))
|
||||
|
||||
;; Mode line - improved status bar styling
|
||||
`(mode-line ((t (:background ,surface-container :foreground ,on-surface :box nil))))
|
||||
`(mode-line-inactive ((t (:background ,surface :foreground ,on-surface-variant :box nil))))
|
||||
`(mode-line-buffer-id ((t (:foreground ,primary :weight bold))))
|
||||
`(mode-line-emphasis ((t (:foreground ,primary :weight bold))))
|
||||
`(mode-line-highlight ((t (:foreground ,primary :box nil))))
|
||||
|
||||
;; Improved Source blocks - make them integrated with the theme
|
||||
`(org-block ((t (:background ,surface-container-low :extend t :inherit fixed-pitch))))
|
||||
`(org-block-begin-line ((t (:background ,surface-container-low :foreground ,primary-fixed-dim :extend t :slant italic :inherit fixed-pitch))))
|
||||
`(org-block-end-line ((t (:background ,surface-container-low :foreground ,primary-fixed-dim :extend t :slant italic :inherit fixed-pitch))))
|
||||
`(org-code ((t (:background ,surface-container-low :foreground ,tertiary-fixed :inherit fixed-pitch))))
|
||||
`(org-verbatim ((t (:background ,surface-container-low :foreground ,primary-fixed :inherit fixed-pitch))))
|
||||
`(org-meta-line ((t (:foreground ,outline-color :slant italic))))
|
||||
|
||||
;; Org mode with hidden asterisks
|
||||
`(org-level-1 ((t (:foreground ,primary :weight bold :height 1.2))))
|
||||
`(org-level-2 ((t (:foreground ,primary-container :weight bold :height 1.1))))
|
||||
`(org-level-3 ((t (:foreground ,secondary :weight bold))))
|
||||
`(org-level-4 ((t (:foreground ,secondary-container :weight bold))))
|
||||
`(org-level-5 ((t (:foreground ,tertiary :weight bold))))
|
||||
`(org-level-6 ((t (:foreground ,tertiary-container :weight bold))))
|
||||
`(org-level-7 ((t (:foreground ,primary-fixed :weight bold))))
|
||||
`(org-level-8 ((t (:foreground ,primary-fixed-dim :weight bold))))
|
||||
`(org-document-title ((t (:foreground ,primary :weight bold :height 1.3))))
|
||||
`(org-document-info ((t (:foreground ,primary-container))))
|
||||
`(org-todo ((t (:foreground ,err :weight bold))))
|
||||
`(org-done ((t (:foreground ,success :weight bold))))
|
||||
`(org-headline-done ((t (:foreground ,on-surface-variant))))
|
||||
`(org-hide ((t (:foreground ,bg)))) ;; Hide leading asterisks
|
||||
`(org-ellipsis ((t (:foreground ,tertiary :underline nil)))) ;; Style for folded content indicator
|
||||
`(org-table ((t (:foreground ,secondary-fixed :inherit fixed-pitch))))
|
||||
`(org-formula ((t (:foreground ,tertiary :inherit fixed-pitch))))
|
||||
`(org-checkbox ((t (:foreground ,primary :weight bold :inherit fixed-pitch))))
|
||||
`(org-date ((t (:foreground ,secondary-fixed :underline t))))
|
||||
`(org-special-keyword ((t (:foreground ,on-surface-variant :slant italic))))
|
||||
`(org-tag ((t (:foreground ,outline-color :weight normal))))
|
||||
|
||||
;; Magit
|
||||
`(magit-section-highlight ((t (:background ,surface-container-low))))
|
||||
`(magit-diff-hunk-heading ((t (:background ,surface-container :foreground ,on-surface-variant))))
|
||||
`(magit-diff-hunk-heading-highlight ((t (:background ,surface-container-high :foreground ,on-surface))))
|
||||
`(magit-diff-context ((t (:foreground ,on-surface-variant))))
|
||||
`(magit-diff-context-highlight ((t (:background ,surface-container-low :foreground ,on-surface))))
|
||||
`(magit-diff-added ((t (:background ,success-container :foreground ,on-success-container))))
|
||||
`(magit-diff-added-highlight ((t (:background ,success-container :foreground ,on-success-container :weight bold))))
|
||||
`(magit-diff-removed ((t (:background ,err-container :foreground ,on-err-container))))
|
||||
`(magit-diff-removed-highlight ((t (:background ,err-container :foreground ,on-err-container :weight bold))))
|
||||
`(magit-hash ((t (:foreground ,outline-color))))
|
||||
`(magit-branch-local ((t (:foreground ,tertiary :weight bold))))
|
||||
`(magit-branch-remote ((t (:foreground ,primary :weight bold))))
|
||||
|
||||
;; Company
|
||||
`(company-tooltip ((t (:background ,surface-container :foreground ,on-surface))))
|
||||
`(company-tooltip-selection ((t (:background ,primary-container :foreground ,on-primary-container))))
|
||||
`(company-tooltip-common ((t (:foreground ,primary))))
|
||||
`(company-tooltip-common-selection ((t (:foreground ,on-primary-container :weight bold))))
|
||||
`(company-tooltip-annotation ((t (:foreground ,tertiary))))
|
||||
`(company-scrollbar-fg ((t (:background ,primary))))
|
||||
`(company-scrollbar-bg ((t (:background ,surface-variant))))
|
||||
`(company-preview ((t (:foreground ,on-surface-variant :slant italic))))
|
||||
`(company-preview-common ((t (:foreground ,primary :slant italic))))
|
||||
|
||||
;; Ido
|
||||
`(ido-first-match ((t (:foreground ,primary :weight bold))))
|
||||
`(ido-only-match ((t (:foreground ,tertiary :weight bold))))
|
||||
`(ido-subdir ((t (:foreground ,secondary))))
|
||||
`(ido-indicator ((t (:foreground ,err))))
|
||||
`(ido-virtual ((t (:foreground ,outline-color))))
|
||||
|
||||
;; Helm
|
||||
`(helm-selection ((t (:background ,primary-container :foreground ,on-primary-container))))
|
||||
`(helm-match ((t (:foreground ,primary :weight bold))))
|
||||
`(helm-source-header ((t (:background ,surface-container-high :foreground ,primary :weight bold :height 1.1))))
|
||||
`(helm-candidate-number ((t (:foreground ,tertiary :weight bold))))
|
||||
`(helm-ff-directory ((t (:foreground ,primary :weight bold))))
|
||||
`(helm-ff-file ((t (:foreground ,on-surface))))
|
||||
`(helm-ff-executable ((t (:foreground ,tertiary))))
|
||||
|
||||
;; Which-key
|
||||
`(which-key-key-face ((t (:foreground ,primary :weight bold))))
|
||||
`(which-key-separator-face ((t (:foreground ,outline-variant))))
|
||||
`(which-key-command-description-face ((t (:foreground ,on-surface))))
|
||||
`(which-key-group-description-face ((t (:foreground ,secondary))))
|
||||
`(which-key-special-key-face ((t (:foreground ,tertiary :weight bold))))
|
||||
|
||||
;; Line numbers
|
||||
`(line-number ((t (:foreground ,outline-variant :inherit fixed-pitch))))
|
||||
`(line-number-current-line ((t (:foreground ,primary :weight bold :inherit fixed-pitch))))
|
||||
|
||||
;; Parenthesis matching
|
||||
`(sp-show-pair-match-face ((t (:background ,primary-container :foreground ,on-primary-container))))
|
||||
`(sp-show-pair-mismatch-face ((t (:background ,err-container :foreground ,on-err-container))))
|
||||
|
||||
;; Rainbow delimiters
|
||||
`(rainbow-delimiters-depth-1-face ((t (:foreground ,primary))))
|
||||
`(rainbow-delimiters-depth-2-face ((t (:foreground ,secondary))))
|
||||
`(rainbow-delimiters-depth-3-face ((t (:foreground ,tertiary))))
|
||||
`(rainbow-delimiters-depth-4-face ((t (:foreground ,primary-fixed))))
|
||||
`(rainbow-delimiters-depth-5-face ((t (:foreground ,secondary-fixed))))
|
||||
`(rainbow-delimiters-depth-6-face ((t (:foreground ,tertiary-fixed))))
|
||||
`(rainbow-delimiters-depth-7-face ((t (:foreground ,primary-fixed-dim))))
|
||||
`(rainbow-delimiters-depth-8-face ((t (:foreground ,secondary-fixed-dim))))
|
||||
`(rainbow-delimiters-depth-9-face ((t (:foreground ,tertiary-fixed-dim))))
|
||||
`(rainbow-delimiters-mismatched-face ((t (:foreground ,err :weight bold))))
|
||||
`(rainbow-delimiters-unmatched-face ((t (:foreground ,err :weight bold))))
|
||||
|
||||
;; Dired
|
||||
`(dired-directory ((t (:foreground ,primary :weight bold))))
|
||||
`(dired-ignored ((t (:foreground ,outline-variant))))
|
||||
`(dired-flagged ((t (:foreground ,err))))
|
||||
`(dired-marked ((t (:foreground ,tertiary :weight bold))))
|
||||
`(dired-symlink ((t (:foreground ,secondary :slant italic))))
|
||||
`(dired-header ((t (:foreground ,primary :weight bold :height 1.1))))
|
||||
|
||||
;; Terminal colors
|
||||
`(term-color-black ((t (:foreground ,term0 :background ,term0))))
|
||||
`(term-color-red ((t (:foreground ,term1 :background ,term1))))
|
||||
`(term-color-green ((t (:foreground ,term2 :background ,term2))))
|
||||
`(term-color-yellow ((t (:foreground ,term3 :background ,term3))))
|
||||
`(term-color-blue ((t (:foreground ,term4 :background ,term4))))
|
||||
`(term-color-magenta ((t (:foreground ,term5 :background ,term5))))
|
||||
`(term-color-cyan ((t (:foreground ,term6 :background ,term6))))
|
||||
`(term-color-white ((t (:foreground ,term7 :background ,term7))))
|
||||
|
||||
;; EShell
|
||||
`(eshell-prompt ((t (:foreground ,primary :weight bold))))
|
||||
`(eshell-ls-directory ((t (:foreground ,primary :weight bold))))
|
||||
`(eshell-ls-symlink ((t (:foreground ,secondary :slant italic))))
|
||||
`(eshell-ls-executable ((t (:foreground ,tertiary))))
|
||||
`(eshell-ls-archive ((t (:foreground ,on-tertiary-container))))
|
||||
`(eshell-ls-backup ((t (:foreground ,outline-variant))))
|
||||
`(eshell-ls-clutter ((t (:foreground ,err))))
|
||||
`(eshell-ls-missing ((t (:foreground ,err))))
|
||||
`(eshell-ls-product ((t (:foreground ,on-surface-variant))))
|
||||
`(eshell-ls-readonly ((t (:foreground ,on-surface-variant))))
|
||||
`(eshell-ls-special ((t (:foreground ,secondary-fixed))))
|
||||
`(eshell-ls-unreadable ((t (:foreground ,outline-variant))))
|
||||
|
||||
;; Improved markdown mode
|
||||
`(markdown-header-face ((t (:foreground ,primary :weight bold))))
|
||||
`(markdown-header-face-1 ((t (:foreground ,primary :weight bold :height 1.2))))
|
||||
`(markdown-header-face-2 ((t (:foreground ,primary-container :weight bold :height 1.1))))
|
||||
`(markdown-header-face-3 ((t (:foreground ,secondary :weight bold))))
|
||||
`(markdown-header-face-4 ((t (:foreground ,secondary-container :weight bold))))
|
||||
`(markdown-inline-code-face ((t (:foreground ,tertiary-fixed :background ,surface-container-low :inherit fixed-pitch))))
|
||||
`(markdown-code-face ((t (:background ,surface-container-low :extend t :inherit fixed-pitch))))
|
||||
`(markdown-pre-face ((t (:background ,surface-container-low :inherit fixed-pitch))))
|
||||
`(markdown-table-face ((t (:foreground ,secondary-fixed :inherit fixed-pitch))))
|
||||
|
||||
;; Web mode
|
||||
`(web-mode-html-tag-face ((t (:foreground ,primary))))
|
||||
`(web-mode-html-tag-bracket-face ((t (:foreground ,on-surface-variant))))
|
||||
`(web-mode-html-attr-name-face ((t (:foreground ,secondary))))
|
||||
`(web-mode-html-attr-value-face ((t (:foreground ,tertiary))))
|
||||
`(web-mode-css-selector-face ((t (:foreground ,primary))))
|
||||
`(web-mode-css-property-name-face ((t (:foreground ,secondary))))
|
||||
`(web-mode-css-string-face ((t (:foreground ,tertiary))))
|
||||
|
||||
;; Flycheck
|
||||
`(flycheck-error ((t (:underline (:style wave :color ,err)))))
|
||||
`(flycheck-warning ((t (:underline (:style wave :color ,secondary)))))
|
||||
`(flycheck-info ((t (:underline (:style wave :color ,tertiary)))))
|
||||
`(flycheck-fringe-error ((t (:foreground ,err))))
|
||||
`(flycheck-fringe-warning ((t (:foreground ,secondary))))
|
||||
`(flycheck-fringe-info ((t (:foreground ,tertiary))))
|
||||
|
||||
;; Mini-buffer customization
|
||||
`(minibuffer-prompt ((t (:foreground ,primary :weight bold))))
|
||||
|
||||
;; Improved search highlighting
|
||||
`(lsp-face-highlight-textual ((t (:background ,primary-container :foreground ,on-primary-container :weight bold))))
|
||||
`(lsp-face-highlight-read ((t (:background ,secondary-container :foreground ,on-secondary-container :weight bold))))
|
||||
`(lsp-face-highlight-write ((t (:background ,tertiary-container :foreground ,on-tertiary-container :weight bold))))
|
||||
|
||||
;; Info and help modes
|
||||
`(info-title-1 ((t (:foreground ,primary :weight bold :height 1.3))))
|
||||
`(info-title-2 ((t (:foreground ,primary-container :weight bold :height 1.2))))
|
||||
`(info-title-3 ((t (:foreground ,secondary :weight bold :height 1.1))))
|
||||
`(info-title-4 ((t (:foreground ,secondary-container :weight bold))))
|
||||
`(Info-quoted ((t (:foreground ,tertiary))))
|
||||
`(info-menu-header ((t (:foreground ,primary :weight bold))))
|
||||
`(info-menu-star ((t (:foreground ,primary))))
|
||||
`(info-node ((t (:foreground ,tertiary :weight bold))))
|
||||
|
||||
;; Fixed-pitch faces
|
||||
`(fixed-pitch ((t (:family "monospace"))))
|
||||
`(fixed-pitch-serif ((t (:family "monospace serif"))))
|
||||
|
||||
;; Variable-pitch face
|
||||
`(variable-pitch ((t (:family "sans serif"))))
|
||||
))
|
||||
|
||||
;; Add org-mode hooks for hiding leading stars
|
||||
(with-eval-after-load 'org
|
||||
(setq org-hide-leading-stars t)
|
||||
(setq org-startup-indented t))
|
||||
|
||||
;;;###autoload
|
||||
(when load-file-name
|
||||
(add-to-list 'custom-theme-load-path
|
||||
(file-name-as-directory (file-name-directory load-file-name))))
|
||||
|
||||
(provide-theme 'noctalia)
|
||||
;;; noctalia-theme.el ends here
|
||||
|
||||
29
Assets/MatugenTemplates/niri.kdl
Normal file
@@ -0,0 +1,29 @@
|
||||
layout {
|
||||
background-color "transparent"
|
||||
|
||||
focus-ring {
|
||||
active-color "{{colors.primary.default.hex}}"
|
||||
inactive-color "{{colors.outline.default.hex}}"
|
||||
urgent-color "{{colors.error.default.hex}}"
|
||||
}
|
||||
|
||||
border {
|
||||
active-color "{{colors.primary.default.hex}}"
|
||||
inactive-color "{{colors.outline.default.hex}}"
|
||||
urgent-color "{{colors.error.default.hex}}"
|
||||
}
|
||||
|
||||
shadow {
|
||||
color "{{colors.shadow.default.hex}}70"
|
||||
}
|
||||
|
||||
tab-indicator {
|
||||
active-color "{{colors.primary.default.hex}}"
|
||||
inactive-color "{{colors.outline.default.hex}}"
|
||||
urgent-color "{{colors.error.default.hex}}"
|
||||
}
|
||||
|
||||
insert-hint {
|
||||
color "{{colors.primary.default.hex}}80"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 3.5 MiB |
|
Before Width: | Height: | Size: 4.8 MiB After Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 4.0 MiB After Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 3.7 MiB After Width: | Height: | Size: 3.5 MiB |
BIN
Assets/Sounds/alarm-beep.wav
Normal file
@@ -46,6 +46,11 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"device": {
|
||||
"default": "Standard (Anzeigegerät)",
|
||||
"description": "Wählen Sie das anzuzeigende Batteriegerät aus.",
|
||||
"label": "Batteriebetriebenes Gerät"
|
||||
},
|
||||
"display-mode": {
|
||||
"description": "Wählen Sie, wie dieser Wert angezeigt werden soll.",
|
||||
"label": "Anzeigemodus"
|
||||
@@ -97,9 +102,13 @@
|
||||
"control-center": {
|
||||
"browse-file": "Datei durchsuchen",
|
||||
"browse-library": "Bibliothek durchsuchen",
|
||||
"colorize-distro-logo": {
|
||||
"description": "Wende die Designfarben auf das Logo deiner Distribution an.",
|
||||
"label": "Distro-Logo einfärben"
|
||||
"color-selection": {
|
||||
"description": "Wendet Themenfarben auf Symbole an.",
|
||||
"label": "Farbauswahl"
|
||||
},
|
||||
"enable-colorization": {
|
||||
"description": "Aktiviert die Färbung für das Kontrollzentrum-Symbol und wendet Themenfarben an.",
|
||||
"label": "Färbung aktivieren"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Symbol aus der Bibliothek oder eine benutzerdefinierte Datei auswählen.",
|
||||
@@ -133,11 +142,11 @@
|
||||
"update-text": "Text auf Linksklick aktualisieren"
|
||||
},
|
||||
"max-text-length-horizontal": {
|
||||
"description": "Maximale Anzahl an Zeichen, die in horizontaler Leiste angezeigt werden (0 zum Ausblenden des Textes)",
|
||||
"description": "Maximale Anzahl an Zeichen, die in horizontaler Leiste angezeigt werden (0 zum Ausblenden des Textes).",
|
||||
"label": "Max. Textlänge (horizontal)"
|
||||
},
|
||||
"max-text-length-vertical": {
|
||||
"description": "Maximale Anzahl an Zeichen, die in vertikaler Leiste angezeigt werden (0 zum Ausblenden des Textes)",
|
||||
"description": "Maximale Anzahl an Zeichen, die in vertikaler Leiste angezeigt werden (0 zum Ausblenden des Textes).",
|
||||
"label": "Max. Textlänge (vertikal)"
|
||||
},
|
||||
"middle-click": {
|
||||
@@ -163,7 +172,7 @@
|
||||
"label": "Stream"
|
||||
},
|
||||
"wheel": {
|
||||
"description": "Befehl, der bei Verwendung des Scrollrads ausgeführt wird.\nVerwenden Sie $delta für die Scrollrad-Delta im Befehl",
|
||||
"description": "Befehl, der bei Verwendung des Scrollrads ausgeführt wird.\nVerwenden Sie $delta für die Scrollrad-Delta im Befehl.",
|
||||
"label": "Scrollrad",
|
||||
"update-text": "Anzeigetext beim Scrollen aktualisieren"
|
||||
},
|
||||
@@ -172,7 +181,7 @@
|
||||
"label": "Scrollrad runter Befehl"
|
||||
},
|
||||
"wheel-mode-separate": {
|
||||
"description": "Separate Befehle für Scrollrad hoch und runter aktivieren",
|
||||
"description": "Separate Befehle für Scrollrad hoch und runter aktivieren.",
|
||||
"label": "Separate Scrollrad-Befehle"
|
||||
},
|
||||
"wheel-up": {
|
||||
@@ -202,7 +211,7 @@
|
||||
},
|
||||
"show-scroll-lock": {
|
||||
"description": "Scroll-Lock-Status anzeigen.",
|
||||
"label": "Rollenfeststelltaste"
|
||||
"label": "Rollen-Taste"
|
||||
}
|
||||
},
|
||||
"media-mini": {
|
||||
@@ -227,14 +236,14 @@
|
||||
"description": "Künstler - Titel anstatt Titel - Künstler anzeigen.",
|
||||
"label": "Künstler zuerst anzeigen"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Audio-Visualizer anzeigen, wenn Musik abgespielt wird.",
|
||||
"label": "Visualizer anzeigen"
|
||||
},
|
||||
"show-progress-ring": {
|
||||
"description": "Runden Fortschrittsindikator anzeigen, der den Titelfortschritt anzeigt.",
|
||||
"label": "Fortschrittsring anzeigen"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Audio-Visualizer anzeigen, wenn Musik abgespielt wird.",
|
||||
"label": "Visualizer anzeigen"
|
||||
},
|
||||
"use-fixed-width": {
|
||||
"description": "Wenn aktiviert, verwendet das Widget immer die maximale Breite, anstatt sich dynamisch an den Inhalt anzupassen.",
|
||||
"label": "Feste Breite verwenden"
|
||||
@@ -271,9 +280,6 @@
|
||||
}
|
||||
},
|
||||
"system-monitor": {
|
||||
"cpu-critical-threshold": {
|
||||
"label": "CPU Usage (Critical)"
|
||||
},
|
||||
"cpu-temperature": {
|
||||
"description": "CPU-Temperaturwerte anzeigen, falls verfügbar.",
|
||||
"label": "CPU-Temperatur"
|
||||
@@ -282,25 +288,10 @@
|
||||
"description": "Aktuelle CPU-Auslastung in Prozent anzeigen.",
|
||||
"label": "CPU-Auslastung"
|
||||
},
|
||||
"cpu-warning-threshold": {
|
||||
"label": "CPU Usage (Warning)"
|
||||
},
|
||||
"disk-critical-threshold": {
|
||||
"label": "Storage Space (Critical)"
|
||||
},
|
||||
"disk-path": {
|
||||
"description": "Wählen Sie den Festplatten-Mountpunkt aus, der überwacht werden soll.",
|
||||
"label": "Festplattenpfad"
|
||||
},
|
||||
"disk-warning-threshold": {
|
||||
"label": "Storage Space (Warning)"
|
||||
},
|
||||
"mem-critical-threshold": {
|
||||
"label": "Memory Usage (Critical)"
|
||||
},
|
||||
"mem-warning-threshold": {
|
||||
"label": "Memory Usage (Warning)"
|
||||
},
|
||||
"memory-percentage": {
|
||||
"description": "Speicherverbrauch als Prozentsatz statt absolute Werte anzeigen.",
|
||||
"label": "Speicher als Prozentsatz"
|
||||
@@ -316,16 +307,6 @@
|
||||
"storage-usage": {
|
||||
"description": "Festplattenspeicher-Nutzungsinformationen anzeigen.",
|
||||
"label": "Speichernutzung"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "CPU Temperature (Critical)"
|
||||
},
|
||||
"temp-warning-threshold": {
|
||||
"label": "CPU Temperature (Warning)"
|
||||
},
|
||||
"thresholds": {
|
||||
"description": "Schwellenwerte für visuelle Indikatoren konfigurieren. Warn-/Kritik-Indikatoren erscheinen in hervorgehobenen Farben bei Überschreitung.",
|
||||
"header": "Schwellenwerte"
|
||||
}
|
||||
},
|
||||
"taskbar": {
|
||||
@@ -373,14 +354,14 @@
|
||||
"description": "Anzahl der Zeichen, die von Arbeitsbereichsnamen angezeigt werden (1-10).",
|
||||
"label": "Zeichenanzahl"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Arbeitsbereiche ohne Fenster nicht anzeigen.",
|
||||
"label": "Unbesetzte ausblenden"
|
||||
},
|
||||
"follow-focused-screen": {
|
||||
"description": "Zeigt Arbeitsbereiche vom aktuell fokussierten Bildschirm an, statt vom Bildschirm, auf dem sich die Leiste befindet.",
|
||||
"label": "Fokussiertem Bildschirm folgen"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Arbeitsbereiche ohne Fenster nicht anzeigen.",
|
||||
"label": "Unbesetzte ausblenden"
|
||||
},
|
||||
"label-mode": {
|
||||
"description": "Wählen Sie, wie Arbeitsbereichs-Beschriftungen angezeigt werden.",
|
||||
"label": "Beschriftungsmodus"
|
||||
@@ -389,8 +370,8 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"battery-level": "Akkustand",
|
||||
"brightness": "Helligkeit",
|
||||
"charge-level": "Ladestand",
|
||||
"charging": "Wird geladen.",
|
||||
"charging-rate": "Laderate: {rate} W.",
|
||||
"discharging": "Wird entladen.",
|
||||
@@ -404,7 +385,7 @@
|
||||
"plugged-in": "Angeschlossen.",
|
||||
"power-profile": "Energieprofil",
|
||||
"time-left": "Verbleibende Zeit: {time}.",
|
||||
"time-until-full": "Zeit bis vollständig geladen: {time}."
|
||||
"time-until-full": "Verbleibende Ladezeit: {time}."
|
||||
},
|
||||
"bluetooth": {
|
||||
"panel": {
|
||||
@@ -412,6 +393,7 @@
|
||||
"blocked": "Blockiert",
|
||||
"connect": "Verbinden",
|
||||
"connected-devices": "Verbundene Geräte",
|
||||
"connecting": "Verbinden...",
|
||||
"disabled": "Bluetooth ist deaktiviert",
|
||||
"disconnect": "Trennen",
|
||||
"enable-message": "Aktivieren Sie Bluetooth, um verfügbare Geräte zu sehen.",
|
||||
@@ -426,6 +408,19 @@
|
||||
"panel": {
|
||||
"week": "Woche"
|
||||
},
|
||||
"timer": {
|
||||
"countdown": "Countdown",
|
||||
"duration": "Dauer",
|
||||
"hours": "h",
|
||||
"minutes": "m",
|
||||
"pause": "Pause",
|
||||
"reset": "Zurücksetzen",
|
||||
"seconds": "s",
|
||||
"start": "Start",
|
||||
"stopwatch": "Stoppuhr",
|
||||
"timer": "Timer",
|
||||
"title": "Zeitmesser"
|
||||
},
|
||||
"weather": {
|
||||
"loading": "Wetter wird geladen…"
|
||||
}
|
||||
@@ -466,11 +461,11 @@
|
||||
"connect-vpn": "Mit {name} verbinden",
|
||||
"cycle-visualizer": "Zyklus-Visualisierer",
|
||||
"disable-bluetooth": "Bluetooth deaktivieren",
|
||||
"disable-dnd": "Bitte nicht stören deaktivieren",
|
||||
"disable-dnd": "'Nicht stören' deaktivieren",
|
||||
"disable-wifi": "WLAN deaktivieren",
|
||||
"disconnect-vpn": "Verbindung zu {name} trennen",
|
||||
"enable-bluetooth": "Bluetooth aktivieren",
|
||||
"enable-dnd": "Nicht stören aktivieren",
|
||||
"enable-dnd": "'Nicht stören' aktivieren",
|
||||
"enable-wifi": "WLAN aktivieren",
|
||||
"next": "Nächste/r/s",
|
||||
"open-calendar": "Kalender öffnen",
|
||||
@@ -518,14 +513,20 @@
|
||||
"no-notifications": "Keine Benachrichtigungen",
|
||||
"title": "Benachrichtigungen"
|
||||
},
|
||||
"range": {
|
||||
"all": "Alle",
|
||||
"earlier": "Älter",
|
||||
"today": "Heute",
|
||||
"yesterday": "Gestern"
|
||||
},
|
||||
"time": {
|
||||
"now": "jetzt",
|
||||
"diffM": "vor 1 Minute",
|
||||
"diffMM": "vor {diff} Minuten",
|
||||
"diffD": "vor 1 Tag",
|
||||
"diffDD": "vor {diff} Tagen",
|
||||
"diffH": "vor 1 Stunde",
|
||||
"diffHH": "vor {diff} Stunden",
|
||||
"diffD": "vor 1 Tag",
|
||||
"diffDD": "vor {diff} Tagen"
|
||||
"diffM": "vor 1 Minute",
|
||||
"diffMM": "vor {diff} Minuten",
|
||||
"now": "jetzt"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@@ -545,7 +546,8 @@
|
||||
},
|
||||
"colors": {
|
||||
"error": "Fehler",
|
||||
"onSurface": "An der Oberfläche",
|
||||
"none": "Keine",
|
||||
"onSurface": "Auf der Oberfläche",
|
||||
"primary": "Primär",
|
||||
"secondary": "Sekundär",
|
||||
"tertiary": "Tertiär"
|
||||
@@ -706,7 +708,7 @@
|
||||
"enabled": "Bluetooth"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Klicken zum Verwalten der Bluetooth-Geräte"
|
||||
"action": "Bluetooth"
|
||||
}
|
||||
},
|
||||
"keepAwake": {
|
||||
@@ -715,7 +717,7 @@
|
||||
"enabled": "Wach halten"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Klicken zum Umschalten des Wachmodus"
|
||||
"action": "Wach halten"
|
||||
}
|
||||
},
|
||||
"nightLight": {
|
||||
@@ -725,7 +727,7 @@
|
||||
"forced": "Nachtlicht"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Klicken zum Wechseln des Nachtlicht-Modus\nRechtsklick: Einstellungen öffnen"
|
||||
"action": "Nachtlicht"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -734,7 +736,7 @@
|
||||
"enabled": "Benachrichtigungen"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Linksklick: Benachrichtigungsverlauf öffnen\nRechtsklick: 'Nicht stören' umschalten"
|
||||
"action": "Benachrichtigungen"
|
||||
}
|
||||
},
|
||||
"powerProfile": {
|
||||
@@ -742,7 +744,7 @@
|
||||
"unavailable": "Energieprofil"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Klicken zum Wechseln des Energieprofils",
|
||||
"action": "Energieprofil",
|
||||
"disabled": "Installiere power-profiles-daemon, um Energieprofile zu verwenden"
|
||||
}
|
||||
},
|
||||
@@ -752,13 +754,13 @@
|
||||
"stopped": "Aufnehmen"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Klicken zum Starten/Stoppen einer Bildschirmaufnahme"
|
||||
"action": "Bildschirmrekorder"
|
||||
}
|
||||
},
|
||||
"wallpaperSelector": {
|
||||
"label": "Hintergrundbild",
|
||||
"tooltip": {
|
||||
"action": "Linksklick: Hintergrundbild-Auswahl öffnen\nRechtsklick: Zufälliges Hintergrundbild setzen"
|
||||
"action": "Hintergrundbild-Auswahl"
|
||||
}
|
||||
},
|
||||
"wifi": {
|
||||
@@ -768,7 +770,7 @@
|
||||
"wifi": "WLAN"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Klicken zum Verwalten der WLAN-Verbindungen"
|
||||
"action": "WLAN"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -794,6 +796,8 @@
|
||||
},
|
||||
"noctalia": {
|
||||
"download-latest": "Neueste Version herunterladen",
|
||||
"git-commit": "Git-Commit:",
|
||||
"git-commit-loading": "Lädt...",
|
||||
"installed-version": "Installierte Version:",
|
||||
"latest-version": "Neueste Version:",
|
||||
"section": {
|
||||
@@ -983,15 +987,15 @@
|
||||
},
|
||||
"dark-mode": {
|
||||
"mode": {
|
||||
"description": "Ermöglicht einen automatischen Wechsel zwischen dem hellen und dunklen Modus.",
|
||||
"label": "Automatischer dunkler Modus",
|
||||
"description": "Ermöglicht einen automatischen Wechsel zwischem hellen und dunklen Modus.",
|
||||
"label": "Automatischer Dunkelmodus",
|
||||
"location": "Standort",
|
||||
"manual": "Manuell",
|
||||
"off": "Aus"
|
||||
},
|
||||
"switch": {
|
||||
"description": "Wechselt zu einem dunkleren Theme für einfachere Betrachtung bei Nacht.",
|
||||
"label": "Dunkler Modus"
|
||||
"label": "Dunkelmodus"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -1040,18 +1044,30 @@
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"compositors": {
|
||||
"description": "Compositor-Theming.",
|
||||
"label": "Compositor/innen",
|
||||
"niri": {
|
||||
"description": "Schreibe {filepath}. Benötigt niri v25.11+",
|
||||
"description-missing": "Benötigt die Installation von {app}"
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"description": "Zusätzliche Konfigurationsoptionen.",
|
||||
"label": "Verschiedenes",
|
||||
"description": "Erstellen Sie Ihre eigenen Vorlagen",
|
||||
"label": "Erweitert",
|
||||
"user-templates": {
|
||||
"description": "Benutzerdefinierte Matugen-Konfiguration aktivieren. Eine Vorlagendatei wird beim ersten Aktivieren unter ~/.config/noctalia/user-templates.toml erstellt",
|
||||
"label": "Benutzer-Vorlagen"
|
||||
"description": "Nur aktivieren, wenn Sie wissen, was Sie tun. Weitere Informationen finden Sie in unserer Online-Dokumentation",
|
||||
"label": "Benutzer-Vorlagen aktivieren"
|
||||
}
|
||||
},
|
||||
"programs": {
|
||||
"cava": {
|
||||
"description": "Schreibe {filepath}",
|
||||
"description-missing": "Benötigt die Installation von {app}"
|
||||
},
|
||||
"code": {
|
||||
"description": "Schreibe {Dateipfad}. Das Hyprluna-Theme muss manuell installiert und aktiviert werden",
|
||||
"description-missing": "Kein Code-Client erkannt. Installieren Sie VSCode oder VSCodium."
|
||||
"description-missing": "Kein Code-Client erkannt. Installieren Sie VSCode oder VSCodium"
|
||||
},
|
||||
"description": "Anwendungsspezifisches Theming.",
|
||||
"discord": {
|
||||
@@ -1072,11 +1088,7 @@
|
||||
"description-missing": "Benötigt die Installation von {app}"
|
||||
},
|
||||
"telegram": {
|
||||
"description": "Schreibe {filepath}.",
|
||||
"description-missing": "Benötigt die Installation von {app}"
|
||||
},
|
||||
"cava": {
|
||||
"description": "Schreibe {filepath}.",
|
||||
"description": "Schreibe {filepath}",
|
||||
"description-missing": "Benötigt die Installation von {app}"
|
||||
},
|
||||
"vicinae": {
|
||||
@@ -1262,9 +1274,11 @@
|
||||
},
|
||||
"temperature": {
|
||||
"day": "Tag",
|
||||
"day-description": "Steuert die Farbtemperatur tagsüber.",
|
||||
"description": "Farbwärme für Nacht- und Tageszeit einstellen.",
|
||||
"label": "Farbtemperatur",
|
||||
"night": "Nacht"
|
||||
"night": "Nacht",
|
||||
"night-description": "Steuert die Farbtemperatur nachts."
|
||||
}
|
||||
},
|
||||
"title": "Anzeige"
|
||||
@@ -1368,7 +1382,8 @@
|
||||
"description": "Ändern Sie Ihren Avatar.",
|
||||
"label": "Profil"
|
||||
},
|
||||
"select-avatar": "Avatar auswählen"
|
||||
"select-avatar": "Avatar auswählen",
|
||||
"tooltip": "Nach Avatarbild suchen"
|
||||
},
|
||||
"screen-corners": {
|
||||
"radius": {
|
||||
@@ -1446,6 +1461,10 @@
|
||||
"description": "Verwenden Sie ein benutzerdefiniertes Präfix zum Starten von Anwendungen anstelle der Standardmethode.",
|
||||
"label": "Benutzerdefiniertes Start-Präfix aktivieren"
|
||||
},
|
||||
"grid-view": {
|
||||
"description": "Elemente in einem Raster statt in einer Liste anzeigen.",
|
||||
"label": "Rasteransicht"
|
||||
},
|
||||
"position": {
|
||||
"description": "Wählen Sie, wo das Starter-Panel erscheint.",
|
||||
"label": "Position"
|
||||
@@ -1470,6 +1489,20 @@
|
||||
"title": "Starter"
|
||||
},
|
||||
"location": {
|
||||
"calendar": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "Karten im Kalenderfeld organisieren und aktivieren/deaktivieren.",
|
||||
"label": "Kalenderkarten"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"label": "Kalenderkopfzeile"
|
||||
},
|
||||
"month": {
|
||||
"label": "Kalendermonat"
|
||||
}
|
||||
},
|
||||
"date-time": {
|
||||
"12hour-format": {
|
||||
"description": "Zeigt die Uhrzeit im 12-Stunden-Format auf dem Sperrbildschirm und im Kalender an. Die Uhr in der Statusleiste hat eigene Einstellungen.",
|
||||
@@ -1671,7 +1704,29 @@
|
||||
"label": "Allgemein"
|
||||
}
|
||||
},
|
||||
"title": "On-Screen Display"
|
||||
"title": "On-Screen Display",
|
||||
"types": {
|
||||
"brightness": {
|
||||
"description": "OSD anzeigen, wenn sich die Bildschirmhelligkeit ändert.",
|
||||
"label": "Helligkeit"
|
||||
},
|
||||
"input-volume": {
|
||||
"description": "OSD anzeigen, wenn sich die Mikrofonlautstärke ändert.",
|
||||
"label": "Eingangslautstärke"
|
||||
},
|
||||
"lockkey": {
|
||||
"description": "OSD anzeigen, wenn Feststelltaste, Num-Taste oder Rollen-Taste umgeschaltet werden.",
|
||||
"label": "Feststelltasten"
|
||||
},
|
||||
"section": {
|
||||
"description": "Wählen Sie die Ereignisse aus, die das OSD auslösen. Wenn keine Ereignisse ausgewählt werden, lösen alle verfügbaren Ereignisse das OSD aus.",
|
||||
"label": "OSD-Auslöseereignisse"
|
||||
},
|
||||
"volume": {
|
||||
"description": "OSD anzeigen, wenn sich die Ausgabelautstärke ändert.",
|
||||
"label": "Ausgangslautstärke"
|
||||
}
|
||||
}
|
||||
},
|
||||
"screen-recorder": {
|
||||
"audio": {
|
||||
@@ -1806,6 +1861,12 @@
|
||||
"memory-section": {
|
||||
"label": "Speicherverbrauch"
|
||||
},
|
||||
"network-section": {
|
||||
"label": "Netzwerk"
|
||||
},
|
||||
"polling-interval": {
|
||||
"label": "Abfrageintervall"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "Kritische Schwelle"
|
||||
},
|
||||
@@ -1816,7 +1877,7 @@
|
||||
"label": "CPU-Temperatur"
|
||||
},
|
||||
"thresholds-section": {
|
||||
"description": "Setzen Sie Schwellenwerte für Systemmetriken; Werte darüber werden hervorgehoben.",
|
||||
"description": "Passe die Warn-/Kritisch-Schwellen und die Abfrageintervalle für jede Systemmetrik an.",
|
||||
"label": "Schwellenwerte"
|
||||
},
|
||||
"title": "Systemmonitor",
|
||||
@@ -1857,7 +1918,7 @@
|
||||
"reset": "Deckkraft des gedimmten Desktops zurücksetzen"
|
||||
},
|
||||
"panel-background-opacity": {
|
||||
"description": "Hintergrunddeckkraft für alle Paneele festlegen (Launcher, Control Center, Einstellungen usw.).",
|
||||
"description": "Hintergrunddeckkraft für alle Paneele festlegen (Launcher, Kontrollzentrum, Einstellungen usw.).",
|
||||
"label": "Paneel-Hintergrunddeckkraft"
|
||||
},
|
||||
"panels-attached-to-bar": {
|
||||
@@ -2048,6 +2109,11 @@
|
||||
"unavailable": "Zwischenablageverlauf nicht verfügbar",
|
||||
"unavailable-desc": "Die 'cliphist' Anwendung ist nicht installiert. Bitte installieren Sie sie, um Zwischenablageverlauf-Features zu nutzen."
|
||||
},
|
||||
"dark-mode": {
|
||||
"dark-mode": "Dunkler Modus",
|
||||
"enabled": "Aktiviert",
|
||||
"light-mode": "Heller Modus"
|
||||
},
|
||||
"do-not-disturb": {
|
||||
"disabled": "'Nicht stören' deaktiviert",
|
||||
"disabled-desc": "Alle Benachrichtigungen werden angezeigt.",
|
||||
@@ -2128,64 +2194,70 @@
|
||||
"tooltips": {
|
||||
"add-widget": "Widget hinzufügen",
|
||||
"bluetooth-devices": "Bluetooth-Geräte",
|
||||
"brightness-at": "Helligkeit: {brightness}%\nRechtsklick für Einstellungen.\nScrollen zum Ändern der Helligkeit.",
|
||||
"cancel-timer": "Timer abbrechen",
|
||||
"brightness-at": "Helligkeit: {brightness}%",
|
||||
"cancel-timer": "Timer",
|
||||
"clear-history": "Verlauf löschen",
|
||||
"click-to-start-recording": "Klicken zum Starten einer Bildschirmaufnahme",
|
||||
"click-to-stop-recording": "Klicken zum Stoppen einer Bildschirmaufnahme",
|
||||
"close": "Schließen",
|
||||
"connect-disconnect-devices": "Linksklick zum Verbinden. Rechtsklick zum Vergessen.",
|
||||
"click-to-start-recording": "Bildschirmrekorder (Aufnahme starten)",
|
||||
"click-to-stop-recording": "Bildschirmrekorder (Aufnahme stoppen)",
|
||||
"close": "Schließen-Button",
|
||||
"connect-disconnect-devices": "Bluetooth-Gerät",
|
||||
"delete-notification": "Benachrichtigung löschen",
|
||||
"disable-keep-awake": "Klicken, um Wach halten zu deaktivieren.\nScrollen, um Zeitlimit anzupassen.",
|
||||
"do-not-disturb-disabled": "'Nicht stören' deaktiviert",
|
||||
"do-not-disturb-enabled": "'Nicht stören' aktiviert",
|
||||
"enable-keep-awake": "Klicken, um Wach halten zu aktivieren.\nScrollen, um Zeitlimit anzupassen.",
|
||||
"disable-keep-awake": "Wach halten",
|
||||
"do-not-disturb-disabled": "Nicht stören",
|
||||
"do-not-disturb-enabled": "Nicht stören",
|
||||
"enable-keep-awake": "Wach halten",
|
||||
"forget-network": "Netzwerk vergessen",
|
||||
"home": "Home",
|
||||
"input-muted": "Audio-Eingabe stummschalten",
|
||||
"grid-view": "Rasteransicht",
|
||||
"hidden-files-hide": "Versteckte Dateien",
|
||||
"hidden-files-show": "Versteckte Dateien",
|
||||
"home": "Home-Verzeichnis",
|
||||
"input-muted": "Mikrofon",
|
||||
"keep-awake": "Wach halten",
|
||||
"keyboard-layout": "{layout} Tastaturlayout",
|
||||
"manage-vpn": "VPN-Verbindungen verwalten",
|
||||
"manage-wifi": "WLAN verwalten",
|
||||
"microphone-volume-at": "Mikrofonlautstärke bei {volume}%.\nLinksklick für Einstellungen. Rechtsklick zum Stummschalten.\nScrollen zum Ändern der Lautstärke.",
|
||||
"move-to-center-section": "Zur mittleren Sektion verschieben",
|
||||
"move-to-left-section": "Zur linken Sektion verschieben",
|
||||
"move-to-right-section": "Zur rechten Sektion verschieben",
|
||||
"next-media": "Nächstes Medium",
|
||||
"list-view": "Listenansicht",
|
||||
"manage-vpn": "VPN-Verbindungen",
|
||||
"manage-wifi": "WLAN",
|
||||
"microphone-volume-at": "Mikrofonlautstärke: {volume}%",
|
||||
"move-to-center-section": "Mittlere Sektion",
|
||||
"move-to-left-section": "Linke Sektion",
|
||||
"move-to-right-section": "Rechte Sektion",
|
||||
"next-media": "Nächster Titel",
|
||||
"next-month": "Nächster Monat",
|
||||
"night-light-disabled": "Nachtlicht ist deaktiviert.\nLinksklick zum Wechseln des Modus.\nRechtsklick für Einstellungen.",
|
||||
"night-light-enabled": "Nachtlicht ist aktiviert.\nLinksklick zum Wechseln des Modus.\nRechtsklick für Einstellungen.",
|
||||
"night-light-forced": "Nachtlicht ist erzwungen.\nLinksklick zum Wechseln des Modus.\nRechtsklick für Einstellungen.",
|
||||
"night-light-not-installed": "Nachtlicht ist nicht verfügbar.\nwlsunset ist nicht installiert.",
|
||||
"noctalia-performance-disabled": "Noctalia-Leistungsmodus ist deaktiviert.\nLinksklick zum Aktivieren.",
|
||||
"noctalia-performance-enabled": "Noctalia-Leistungsmodus ist aktiviert.\nLinksklick zum Deaktivieren.",
|
||||
"open-control-center": "Kontrollzentrum öffnen",
|
||||
"open-notification-history-disable-dnd": "Benachrichtigungsverlauf öffnen\nRechtsklick um 'Nicht stören' zu deaktivieren.",
|
||||
"open-notification-history-enable-dnd": "Benachrichtigungsverlauf öffnen\nRechtsklick um 'Nicht stören' zu aktivieren.",
|
||||
"open-settings": "Einstellungen öffnen",
|
||||
"open-tray-dropdown": "Tray-Dropdown öffnen",
|
||||
"open-wallpaper-selector": "Hintergrundbild-Auswahl öffnen",
|
||||
"output-muted": "Audio-Ausgabe stummschalten",
|
||||
"pause": "Pausieren",
|
||||
"play": "Wiedergeben",
|
||||
"power-profile": "'{profile}' Energieprofil",
|
||||
"previous-media": "Vorheriges Medium",
|
||||
"night-light-disabled": "Nachtlicht",
|
||||
"night-light-enabled": "Nachtlicht",
|
||||
"night-light-forced": "Nachtlicht",
|
||||
"night-light-not-installed": "Nachtlicht (nicht verfügbar)",
|
||||
"noctalia-performance-disabled": "Noctalia-Leistungsmodus",
|
||||
"noctalia-performance-enabled": "Noctalia-Leistungsmodus",
|
||||
"open-control-center": "Kontrollzentrum",
|
||||
"open-notification-history-disable-dnd": "Benachrichtigungsverlauf",
|
||||
"open-notification-history-enable-dnd": "Benachrichtigungsverlauf",
|
||||
"open-settings": "Einstellungen",
|
||||
"open-tray-dropdown": "System-Tray",
|
||||
"open-wallpaper-selector": "Hintergrundbild-Auswahl",
|
||||
"output-muted": "Audio-Ausgabe",
|
||||
"pause": "Pause-Button",
|
||||
"play": "Wiedergabe-Button",
|
||||
"power-profile": "{profile} Energieprofil",
|
||||
"previous-media": "Vorheriger Titel",
|
||||
"previous-month": "Vorheriger Monat",
|
||||
"refresh": "Aktualisieren",
|
||||
"refresh-devices": "Geräte aktualisieren",
|
||||
"refresh-wallhaven": "Wallhaven-Ergebnisse aktualisieren",
|
||||
"refresh-wallpaper-list": "Hintergrundbild-Liste aktualisieren",
|
||||
"remove-widget": "Widget entfernen",
|
||||
"screen-recorder-not-installed": "Bildschirmrekorder ist nicht installiert",
|
||||
"screen-recorder-not-installed": "Bildschirmrekorder (nicht installiert)",
|
||||
"search": "Suchen",
|
||||
"search-close": "Suche schließen",
|
||||
"session-menu": "Sitzungsmenü",
|
||||
"set-power-profile": "'{profile}' Energieprofil setzen",
|
||||
"start-screen-recording": "Bildschirmaufnahme starten",
|
||||
"stop-screen-recording": "Bildschirmaufnahme stoppen",
|
||||
"switch-to-dark-mode": "Zum dunklen Modus wechseln",
|
||||
"switch-to-light-mode": "Zum hellen Modus wechseln",
|
||||
"up": "Nach oben",
|
||||
"volume-at": "Lautstärke bei {volume}%.\nLinksklick für Einstellungen. Rechtsklick zum Stummschalten.\nScrollen zum Ändern der Lautstärke.",
|
||||
"wallpaper-selector": "Linksklick: Hintergrundbild-Auswahl öffnen.\nRechtsklick: Zufälliges Hintergrundbild setzen.",
|
||||
"set-power-profile": "{profile} Energieprofil",
|
||||
"start-screen-recording": "Bildschirmrekorder",
|
||||
"stop-screen-recording": "Bildschirmrekorder",
|
||||
"switch-to-dark-mode": "Dunkler Modus",
|
||||
"switch-to-light-mode": "Heller Modus",
|
||||
"up": "Übergeordnetes Verzeichnis",
|
||||
"volume-at": "Lautstärke: {volume}%",
|
||||
"wallpaper-selector": "Hintergrundbild-Auswahl",
|
||||
"widget-settings": "Widget-Einstellungen"
|
||||
},
|
||||
"wallpaper": {
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"device": {
|
||||
"default": "Predeterminado (Dispositivo de visualización)",
|
||||
"description": "Seleccione qué dispositivo de batería mostrar.",
|
||||
"label": "Dispositivo de batería"
|
||||
},
|
||||
"display-mode": {
|
||||
"description": "Elige cómo te gustaría que apareciera este valor.",
|
||||
"label": "Modo de visualización"
|
||||
@@ -97,9 +102,13 @@
|
||||
"control-center": {
|
||||
"browse-file": "Explorar archivo",
|
||||
"browse-library": "Explorar biblioteca",
|
||||
"colorize-distro-logo": {
|
||||
"description": "Aplica los colores del tema a tu logotipo de distribución.",
|
||||
"label": "Colorear logotipo de la distribución"
|
||||
"color-selection": {
|
||||
"description": "Aplica colores del tema a los íconos.",
|
||||
"label": "Seleccionar Color"
|
||||
},
|
||||
"enable-colorization": {
|
||||
"description": "Habilita la coloración para el ícono del centro de control, aplicando colores del tema.",
|
||||
"label": "Habilitar Coloración"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Selecciona un icono de la biblioteca o un archivo personalizado.",
|
||||
@@ -227,14 +236,14 @@
|
||||
"description": "Mostrar artista - título en lugar de título - artista.",
|
||||
"label": "Mostrar primero al artista"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Mostrar un visualizador de audio cuando se reproduce música.",
|
||||
"label": "Mostrar visualizador"
|
||||
},
|
||||
"show-progress-ring": {
|
||||
"description": "Mostrar un indicador de progreso circular que muestre el progreso de la pista.",
|
||||
"label": "Mostrar anillo de progreso"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Mostrar un visualizador de audio cuando se reproduce música.",
|
||||
"label": "Mostrar visualizador"
|
||||
},
|
||||
"use-fixed-width": {
|
||||
"description": "Cuando está activado, el widget siempre usará el ancho máximo en lugar de ajustarse dinámicamente al contenido.",
|
||||
"label": "Usar Ancho Fijo"
|
||||
@@ -271,9 +280,6 @@
|
||||
}
|
||||
},
|
||||
"system-monitor": {
|
||||
"cpu-critical-threshold": {
|
||||
"label": "CPU Usage (Critical)"
|
||||
},
|
||||
"cpu-temperature": {
|
||||
"description": "Mostrar lecturas de temperatura de CPU si están disponibles.",
|
||||
"label": "Temperatura de la CPU"
|
||||
@@ -282,25 +288,10 @@
|
||||
"description": "Mostrar el porcentaje actual de uso de CPU.",
|
||||
"label": "Uso de CPU"
|
||||
},
|
||||
"cpu-warning-threshold": {
|
||||
"label": "CPU Usage (Warning)"
|
||||
},
|
||||
"disk-critical-threshold": {
|
||||
"label": "Storage Space (Critical)"
|
||||
},
|
||||
"disk-path": {
|
||||
"description": "Seleccione qué punto de montaje de disco desea supervisar.",
|
||||
"label": "Ruta del disco"
|
||||
},
|
||||
"disk-warning-threshold": {
|
||||
"label": "Storage Space (Warning)"
|
||||
},
|
||||
"mem-critical-threshold": {
|
||||
"label": "Memory Usage (Critical)"
|
||||
},
|
||||
"mem-warning-threshold": {
|
||||
"label": "Memory Usage (Warning)"
|
||||
},
|
||||
"memory-percentage": {
|
||||
"description": "Mostrar el uso de memoria como porcentaje en lugar de valores absolutos.",
|
||||
"label": "Memoria como porcentaje"
|
||||
@@ -316,16 +307,6 @@
|
||||
"storage-usage": {
|
||||
"description": "Mostrar información del uso del espacio en disco.",
|
||||
"label": "Uso de almacenamiento"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "CPU Temperature (Critical)"
|
||||
},
|
||||
"temp-warning-threshold": {
|
||||
"label": "CPU Temperature (Warning)"
|
||||
},
|
||||
"thresholds": {
|
||||
"description": "Establece umbrales para alertas de métricas del sistema. Resalta cuando se superan.",
|
||||
"header": "Configuración de Umbrales"
|
||||
}
|
||||
},
|
||||
"taskbar": {
|
||||
@@ -373,14 +354,14 @@
|
||||
"description": "Número de caracteres a mostrar de los nombres de espacios de trabajo (1-10).",
|
||||
"label": "Número de caracteres"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "No mostrar espacios de trabajo sin ventanas.",
|
||||
"label": "Ocultar desocupados"
|
||||
},
|
||||
"follow-focused-screen": {
|
||||
"description": "Mostrar espacios de trabajo de la pantalla actualmente enfocada, en lugar de la pantalla donde se encuentra la barra.",
|
||||
"label": "Seguir Pantalla Enfocada"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "No mostrar espacios de trabajo sin ventanas.",
|
||||
"label": "Ocultar desocupados"
|
||||
},
|
||||
"label-mode": {
|
||||
"description": "Elegir cómo se muestran las etiquetas de los espacios de trabajo.",
|
||||
"label": "Modo de etiqueta"
|
||||
@@ -389,8 +370,8 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"battery-level": "Nivel de batería",
|
||||
"brightness": "Brillo",
|
||||
"charge-level": "Nivel de carga",
|
||||
"charging": "Cargando.",
|
||||
"charging-rate": "Tasa de carga: {rate} W.",
|
||||
"discharging": "Descargando.",
|
||||
@@ -412,6 +393,7 @@
|
||||
"blocked": "Bloqueado",
|
||||
"connect": "Conectar",
|
||||
"connected-devices": "Dispositivos conectados",
|
||||
"connecting": "Conectando...",
|
||||
"disabled": "Bluetooth está desactivado",
|
||||
"disconnect": "Desconectar",
|
||||
"enable-message": "Activa Bluetooth para ver los dispositivos disponibles.",
|
||||
@@ -426,6 +408,19 @@
|
||||
"panel": {
|
||||
"week": "Semana"
|
||||
},
|
||||
"timer": {
|
||||
"countdown": "Cuenta regresiva",
|
||||
"duration": "Duración",
|
||||
"hours": "h",
|
||||
"minutes": "m",
|
||||
"pause": "Pausa",
|
||||
"reset": "Restablecer",
|
||||
"seconds": "s",
|
||||
"start": "Comenzar",
|
||||
"stopwatch": "Cronómetro",
|
||||
"timer": "Temporizador",
|
||||
"title": "Temporizador"
|
||||
},
|
||||
"weather": {
|
||||
"loading": "Cargando el clima…"
|
||||
}
|
||||
@@ -479,7 +474,7 @@
|
||||
"open-mixer": "Mezclador de audio",
|
||||
"open-settings": "Abrir ajustes",
|
||||
"pause": "Pausa",
|
||||
"play": "Jugar",
|
||||
"play": "Reproducir",
|
||||
"previous": "Anterior",
|
||||
"random-wallpaper": "Fondo de pantalla aleatorio",
|
||||
"toggle-mute": "Activar/desactivar silencio",
|
||||
@@ -518,14 +513,20 @@
|
||||
"no-notifications": "No hay notificaciones",
|
||||
"title": "Notificaciones"
|
||||
},
|
||||
"range": {
|
||||
"all": "Todas",
|
||||
"earlier": "Anteriores",
|
||||
"today": "Hoy",
|
||||
"yesterday": "Ayer"
|
||||
},
|
||||
"time": {
|
||||
"now": "ahora",
|
||||
"diffM": "hace 1 minuto",
|
||||
"diffMM": "hace {diff} minutos",
|
||||
"diffD": "hace 1 día",
|
||||
"diffDD": "hace {diff} días",
|
||||
"diffH": "hace 1 hora",
|
||||
"diffHH": "hace {diff} horas",
|
||||
"diffD": "hace 1 día",
|
||||
"diffDD": "hace {diff} días"
|
||||
"diffM": "hace 1 minuto",
|
||||
"diffMM": "hace {diff} minutos",
|
||||
"now": "ahora"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@@ -545,6 +546,7 @@
|
||||
},
|
||||
"colors": {
|
||||
"error": "Error",
|
||||
"none": "Ninguno",
|
||||
"onSurface": "En la superficie",
|
||||
"primary": "Primario",
|
||||
"secondary": "Secundario",
|
||||
@@ -706,7 +708,7 @@
|
||||
"enabled": "Bluetooth"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Hacer clic para gestionar los dispositivos Bluetooth"
|
||||
"action": "Bluetooth"
|
||||
}
|
||||
},
|
||||
"keepAwake": {
|
||||
@@ -715,7 +717,7 @@
|
||||
"enabled": "Mantener despierto"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Hacer clic para alternar el modo Mantener despierto"
|
||||
"action": "Mantener despierto"
|
||||
}
|
||||
},
|
||||
"nightLight": {
|
||||
@@ -725,7 +727,7 @@
|
||||
"forced": "Luz nocturna"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Hacer clic para alternar el modo Luz nocturna\nClic derecho: Abrir configuración"
|
||||
"action": "Luz nocturna"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -734,7 +736,7 @@
|
||||
"enabled": "Notificaciones"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Clic izquierdo: Abrir historial de notificaciones\nClic derecho: Alternar No molestar"
|
||||
"action": "Notificaciones"
|
||||
}
|
||||
},
|
||||
"powerProfile": {
|
||||
@@ -742,7 +744,7 @@
|
||||
"unavailable": "Perfil de energía"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Hacer clic para cambiar el perfil de energía",
|
||||
"action": "Perfil de energía",
|
||||
"disabled": "Instala power-profiles-daemon para usar perfiles de energía"
|
||||
}
|
||||
},
|
||||
@@ -752,13 +754,13 @@
|
||||
"stopped": "Grabar"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Hacer clic para iniciar/detener la grabación de pantalla"
|
||||
"action": "Grabadora de pantalla"
|
||||
}
|
||||
},
|
||||
"wallpaperSelector": {
|
||||
"label": "Fondo de pantalla",
|
||||
"tooltip": {
|
||||
"action": "Clic izquierdo: Abrir selector de fondo de pantalla\nClic derecho: Establecer fondo de pantalla aleatorio"
|
||||
"action": "Selector de fondos de pantalla"
|
||||
}
|
||||
},
|
||||
"wifi": {
|
||||
@@ -768,7 +770,7 @@
|
||||
"wifi": "Wi-Fi"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Hacer clic para gestionar las conexiones Wi-Fi"
|
||||
"action": "Wi-Fi"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -794,6 +796,8 @@
|
||||
},
|
||||
"noctalia": {
|
||||
"download-latest": "Descargar la última versión",
|
||||
"git-commit": "Commit de Git:",
|
||||
"git-commit-loading": "Cargando...",
|
||||
"installed-version": "Versión instalada:",
|
||||
"latest-version": "Última versión:",
|
||||
"section": {
|
||||
@@ -1040,15 +1044,27 @@
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"compositors": {
|
||||
"description": "Tematización del compositor.",
|
||||
"label": "Compositores",
|
||||
"niri": {
|
||||
"description": "Escribir {filepath}. Requiere niri v25.11+",
|
||||
"description-missing": "Requiere que {app} esté instalada."
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"description": "Opciones de configuración adicionales.",
|
||||
"label": "Varios",
|
||||
"description": "Crea tus propias plantillas",
|
||||
"label": "Avanzado",
|
||||
"user-templates": {
|
||||
"description": "Habilitar configuración de Matugen definida por el usuario. Se creará un archivo de plantilla en ~/.config/noctalia/user-templates.toml al activar por primera vez",
|
||||
"label": "Plantillas de usuario"
|
||||
"description": "Solo habilita si sabes lo que estás haciendo, consulta nuestra documentación en línea",
|
||||
"label": "Habilitar plantillas de usuario"
|
||||
}
|
||||
},
|
||||
"programs": {
|
||||
"cava": {
|
||||
"description": "Escribe {filepath}.",
|
||||
"description-missing": "Requiere que {app} esté instalado/a."
|
||||
},
|
||||
"code": {
|
||||
"description": "Escribe {filepath}. El tema Hyprluna debe ser instalado y activado manualmente.",
|
||||
"description-missing": "No se detectó cliente de Code. Instala VSCode o VSCodium."
|
||||
@@ -1075,10 +1091,6 @@
|
||||
"description": "Escribe {filepath}.",
|
||||
"description-missing": "Requiere que {app} esté instalado/a."
|
||||
},
|
||||
"cava": {
|
||||
"description": "Escribe {filepath}.",
|
||||
"description-missing": "Requiere que {app} esté instalado/a."
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Escribir {filepath} y recargar",
|
||||
"description-missing": "Requiere que {app} esté instalado"
|
||||
@@ -1262,9 +1274,11 @@
|
||||
},
|
||||
"temperature": {
|
||||
"day": "Día",
|
||||
"day-description": "Controla la temperatura durante el día.",
|
||||
"description": "Establece la calidez del color para la noche y el día.",
|
||||
"label": "Temperatura de color",
|
||||
"night": "Noche"
|
||||
"night": "Noche",
|
||||
"night-description": "Controla la temperatura durante la noche."
|
||||
}
|
||||
},
|
||||
"title": "Pantalla"
|
||||
@@ -1368,7 +1382,8 @@
|
||||
"description": "Edita los detalles de tu usuario y tu avatar.",
|
||||
"label": "Perfil"
|
||||
},
|
||||
"select-avatar": "Seleccionar imagen de avatar"
|
||||
"select-avatar": "Seleccionar imagen de avatar",
|
||||
"tooltip": "Buscar imagen de avatar"
|
||||
},
|
||||
"screen-corners": {
|
||||
"radius": {
|
||||
@@ -1446,6 +1461,10 @@
|
||||
"description": "Usar un prefijo personalizado para lanzar aplicaciones en lugar del método predeterminado.",
|
||||
"label": "Habilitar prefijo de lanzamiento personalizado"
|
||||
},
|
||||
"grid-view": {
|
||||
"description": "Mostrar elementos en una cuadrícula en lugar de una lista.",
|
||||
"label": "Vista de cuadrícula"
|
||||
},
|
||||
"position": {
|
||||
"description": "Elige dónde aparece el panel del lanzador.",
|
||||
"label": "Posición"
|
||||
@@ -1470,6 +1489,20 @@
|
||||
"title": "Lanzador"
|
||||
},
|
||||
"location": {
|
||||
"calendar": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "Organizar y activar/desactivar tarjetas en el panel del calendario.",
|
||||
"label": "Tarjetas de calendario"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"label": "Encabezado del calendario"
|
||||
},
|
||||
"month": {
|
||||
"label": "Mes del calendario"
|
||||
}
|
||||
},
|
||||
"date-time": {
|
||||
"12hour-format": {
|
||||
"description": "Muestra la hora en formato de 12 horas en la pantalla de bloqueo y el calendario. El reloj de la barra tiene su propia configuración.",
|
||||
@@ -1671,7 +1704,29 @@
|
||||
"label": "General"
|
||||
}
|
||||
},
|
||||
"title": "Visualización en pantalla"
|
||||
"title": "Visualización en pantalla",
|
||||
"types": {
|
||||
"brightness": {
|
||||
"description": "Mostrar el OSD cuando cambie el brillo de la pantalla.",
|
||||
"label": "Brillo"
|
||||
},
|
||||
"input-volume": {
|
||||
"description": "Mostrar el OSD cuando cambie el volumen del micrófono.",
|
||||
"label": "Volumen de entrada"
|
||||
},
|
||||
"lockkey": {
|
||||
"description": "Mostrar el OSD cuando se activen o desactiven Bloq Mayús, Bloq Num o Bloq Despl.",
|
||||
"label": "Teclas de bloqueo"
|
||||
},
|
||||
"section": {
|
||||
"description": "Seleccione los eventos que activan el OSD. Si no se selecciona ningún evento, todos los eventos disponibles activarán el OSD.",
|
||||
"label": "Eventos de activación OSD"
|
||||
},
|
||||
"volume": {
|
||||
"description": "Mostrar el OSD cuando cambie el volumen de salida de audio.",
|
||||
"label": "Volumen de salida"
|
||||
}
|
||||
}
|
||||
},
|
||||
"screen-recorder": {
|
||||
"audio": {
|
||||
@@ -1806,6 +1861,12 @@
|
||||
"memory-section": {
|
||||
"label": "Uso de memoria"
|
||||
},
|
||||
"network-section": {
|
||||
"label": "Red"
|
||||
},
|
||||
"polling-interval": {
|
||||
"label": "Intervalo de sondeo"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "Umbral crítico"
|
||||
},
|
||||
@@ -1816,7 +1877,7 @@
|
||||
"label": "Temperatura de la CPU"
|
||||
},
|
||||
"thresholds-section": {
|
||||
"description": "Establece umbrales para métricas del sistema; los valores por encima se resaltarán.",
|
||||
"description": "Ajusta los umbrales de advertencia/crítico y los intervalos de sondeo para cada métrica del sistema.",
|
||||
"label": "Umbrales"
|
||||
},
|
||||
"title": "Monitor del sistema",
|
||||
@@ -2048,6 +2109,11 @@
|
||||
"unavailable": "Historial del portapapeles no disponible",
|
||||
"unavailable-desc": "La aplicación 'cliphist' no está instalada. Por favor, instálala para usar las funciones de historial del portapapeles."
|
||||
},
|
||||
"dark-mode": {
|
||||
"dark-mode": "Modo oscuro",
|
||||
"enabled": "Activado",
|
||||
"light-mode": "Modo claro"
|
||||
},
|
||||
"do-not-disturb": {
|
||||
"disabled": "'No molestar' desactivado",
|
||||
"disabled-desc": "Mostrando todas las notificaciones.",
|
||||
@@ -2128,64 +2194,70 @@
|
||||
"tooltips": {
|
||||
"add-widget": "Añadir widget",
|
||||
"bluetooth-devices": "Dispositivos Bluetooth",
|
||||
"brightness-at": "Brillo: {brightness}%\nClic derecho para configuración.\nDesplaza para modificar el brillo.",
|
||||
"cancel-timer": "Cancelar temporizador",
|
||||
"brightness-at": "Brillo: {brightness}%",
|
||||
"cancel-timer": "Timer",
|
||||
"clear-history": "Limpiar historial",
|
||||
"click-to-start-recording": "Haz clic para iniciar la grabación",
|
||||
"click-to-stop-recording": "Haz clic para detener la grabación",
|
||||
"close": "Cerrar",
|
||||
"connect-disconnect-devices": "Clic izquierdo para conectar. Clic derecho para olvidar.",
|
||||
"click-to-start-recording": "Grabadora de pantalla (iniciar grabación)",
|
||||
"click-to-stop-recording": "Grabadora de pantalla (detener grabación)",
|
||||
"close": "Botón cerrar",
|
||||
"connect-disconnect-devices": "Dispositivo Bluetooth",
|
||||
"delete-notification": "Eliminar notificación",
|
||||
"disable-keep-awake": "Haz clic para desactivar mantener despierto.\nDesplázate para ajustar el tiempo de espera.",
|
||||
"do-not-disturb-disabled": "'No molestar' desactivado",
|
||||
"do-not-disturb-enabled": "'No molestar' activado",
|
||||
"enable-keep-awake": "Haz clic para activar mantener despierto.\nDesplázate para ajustar el tiempo de espera.",
|
||||
"disable-keep-awake": "Mantener despierto",
|
||||
"do-not-disturb-disabled": "No molestar",
|
||||
"do-not-disturb-enabled": "No molestar",
|
||||
"enable-keep-awake": "Mantener despierto",
|
||||
"forget-network": "Olvidar red",
|
||||
"home": "Inicio",
|
||||
"input-muted": "Silenciar entrada de audio",
|
||||
"grid-view": "Vista de cuadrícula",
|
||||
"hidden-files-hide": "Archivos ocultos",
|
||||
"hidden-files-show": "Archivos ocultos",
|
||||
"home": "Directorio home",
|
||||
"input-muted": "Micrófono",
|
||||
"keep-awake": "Mantener despierto",
|
||||
"keyboard-layout": "Distribución de teclado {layout}",
|
||||
"manage-vpn": "Administrar conexiones VPN",
|
||||
"manage-wifi": "Gestionar Wi-Fi",
|
||||
"microphone-volume-at": "Volumen del micrófono al {volume}%.\nClic izquierdo para ajustes. Clic derecho para activar/desactivar el silencio.\nDesplázate para modificar el volumen.",
|
||||
"move-to-center-section": "Mover a la sección central",
|
||||
"move-to-left-section": "Mover a la sección izquierda",
|
||||
"move-to-right-section": "Mover a la sección derecha",
|
||||
"next-media": "Siguiente medio",
|
||||
"list-view": "Vista de lista",
|
||||
"manage-vpn": "Conexiones VPN",
|
||||
"manage-wifi": "Wi-Fi",
|
||||
"microphone-volume-at": "Volumen del micrófono: {volume}%",
|
||||
"move-to-center-section": "Sección central",
|
||||
"move-to-left-section": "Sección izquierda",
|
||||
"move-to-right-section": "Sección derecha",
|
||||
"next-media": "Siguiente pista",
|
||||
"next-month": "Mes siguiente",
|
||||
"night-light-disabled": "Luz nocturna desactivada.\nClic izquierdo para cambiar de modo.\nClic derecho para acceder a la configuración.",
|
||||
"night-light-enabled": "Luz nocturna activada.\nClic izquierdo para cambiar de modo.\nClic derecho para acceder a la configuración.",
|
||||
"night-light-forced": "Luz nocturna forzada.\nClic izquierdo para cambiar de modo.\nClic derecho para acceder a la configuración.",
|
||||
"night-light-not-installed": "Luz nocturna no disponible.\nwlsunset no está instalado.",
|
||||
"noctalia-performance-disabled": "El modo de rendimiento de Noctalia está desactivado.\nClic izquierdo para activar.",
|
||||
"noctalia-performance-enabled": "El modo de rendimiento de Noctalia está activado.\nClic izquierdo para desactivar.",
|
||||
"open-control-center": "Abrir el centro de control",
|
||||
"open-notification-history-disable-dnd": "Abrir historial de notificaciones\nClic derecho para desactivar \"No molestar\".",
|
||||
"open-notification-history-enable-dnd": "Abrir historial de notificaciones\nClic derecho para activar \"No molestar\".",
|
||||
"open-settings": "Abrir configuración",
|
||||
"open-tray-dropdown": "Abrir menú desplegable de bandeja",
|
||||
"open-wallpaper-selector": "Abrir selector de fondos de pantalla",
|
||||
"output-muted": "Silenciar salida de audio",
|
||||
"pause": "Pausa",
|
||||
"play": "Reproducir",
|
||||
"power-profile": "Perfil de energía '{profile}'",
|
||||
"previous-media": "Medio anterior",
|
||||
"night-light-disabled": "Luz nocturna",
|
||||
"night-light-enabled": "Luz nocturna",
|
||||
"night-light-forced": "Luz nocturna",
|
||||
"night-light-not-installed": "Luz nocturna (no disponible)",
|
||||
"noctalia-performance-disabled": "Modo de rendimiento Noctalia",
|
||||
"noctalia-performance-enabled": "Modo de rendimiento Noctalia",
|
||||
"open-control-center": "Centro de control",
|
||||
"open-notification-history-disable-dnd": "Historial de notificaciones",
|
||||
"open-notification-history-enable-dnd": "Historial de notificaciones",
|
||||
"open-settings": "Configuración",
|
||||
"open-tray-dropdown": "Bandeja del sistema",
|
||||
"open-wallpaper-selector": "Selector de fondos de pantalla",
|
||||
"output-muted": "Salida de audio",
|
||||
"pause": "Botón pausa",
|
||||
"play": "Botón reproducir",
|
||||
"power-profile": "{profile} perfil de energía",
|
||||
"previous-media": "Pista anterior",
|
||||
"previous-month": "Mes anterior",
|
||||
"refresh": "Actualizar",
|
||||
"refresh-devices": "Actualizar dispositivos",
|
||||
"refresh-wallhaven": "Actualizar resultados de Wallhaven",
|
||||
"refresh-wallpaper-list": "Actualizar lista de fondos de pantalla",
|
||||
"remove-widget": "Eliminar widget",
|
||||
"screen-recorder-not-installed": "La grabadora de pantalla no está instalada",
|
||||
"screen-recorder-not-installed": "Grabadora de pantalla (no instalada)",
|
||||
"search": "Buscar",
|
||||
"search-close": "Cerrar búsqueda",
|
||||
"session-menu": "Menú de sesión",
|
||||
"set-power-profile": "Establecer perfil de energía \"{profile}\"",
|
||||
"start-screen-recording": "Iniciar grabación de pantalla",
|
||||
"stop-screen-recording": "Detener grabación de pantalla",
|
||||
"switch-to-dark-mode": "Cambiar a modo oscuro",
|
||||
"switch-to-light-mode": "Cambiar a modo claro",
|
||||
"up": "Arriba",
|
||||
"volume-at": "Volumen de salida al {volume}%.\nClic izquierdo para ajustes. Clic derecho para activar/desactivar el silencio.\nDesplázate para modificar el volumen.",
|
||||
"wallpaper-selector": "Clic izquierdo: Abrir selector de fondos de pantalla.\nClic derecho: Establecer fondo de pantalla aleatorio.",
|
||||
"set-power-profile": "{profile} perfil de energía",
|
||||
"start-screen-recording": "Grabadora de pantalla",
|
||||
"stop-screen-recording": "Grabadora de pantalla",
|
||||
"switch-to-dark-mode": "Modo oscuro",
|
||||
"switch-to-light-mode": "Modo claro",
|
||||
"up": "Directorio superior",
|
||||
"volume-at": "Volumen de salida: {volume}%",
|
||||
"wallpaper-selector": "Selector de fondos de pantalla",
|
||||
"widget-settings": "Configuración del widget"
|
||||
},
|
||||
"wallpaper": {
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"device": {
|
||||
"default": "Par défaut (périphérique d'affichage)",
|
||||
"description": "Sélectionnez le périphérique de batterie à afficher.",
|
||||
"label": "Dispositif à pile"
|
||||
},
|
||||
"display-mode": {
|
||||
"description": "Choisissez comment vous souhaitez que cette valeur apparaisse.",
|
||||
"label": "Mode d'affichage"
|
||||
@@ -97,9 +102,13 @@
|
||||
"control-center": {
|
||||
"browse-file": "Parcourir les fichiers",
|
||||
"browse-library": "Parcourir la bibliothèque",
|
||||
"colorize-distro-logo": {
|
||||
"description": "Appliquez les couleurs du thème au logo de votre distribution.",
|
||||
"label": "Coloriser le logo de la distribution"
|
||||
"color-selection": {
|
||||
"description": "Applique les couleurs du thème aux icônes.",
|
||||
"label": "Sélectionner la Couleur"
|
||||
},
|
||||
"enable-colorization": {
|
||||
"description": "Active la coloration pour l'icône du centre de contrôle, en appliquant les couleurs du thème.",
|
||||
"label": "Activer la Coloration"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Sélectionnez une icône de la bibliothèque ou un fichier personnalisé.",
|
||||
@@ -227,14 +236,14 @@
|
||||
"description": "Afficher artiste - titre au lieu de titre - artiste.",
|
||||
"label": "Afficher l'artiste en premier"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Afficher un visualiseur audio quand la musique est en cours de lecture.",
|
||||
"label": "Afficher le visualiseur"
|
||||
},
|
||||
"show-progress-ring": {
|
||||
"description": "Afficher un indicateur de progression circulaire montrant la progression de la piste.",
|
||||
"label": "Afficher l'anneau de progression"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Afficher un visualiseur audio quand la musique est en cours de lecture.",
|
||||
"label": "Afficher le visualiseur"
|
||||
},
|
||||
"use-fixed-width": {
|
||||
"description": "Lorsque activé, le widget utilisera toujours la largeur maximale au lieu de s'ajuster dynamiquement au contenu.",
|
||||
"label": "Utiliser une Largeur Fixe"
|
||||
@@ -271,9 +280,6 @@
|
||||
}
|
||||
},
|
||||
"system-monitor": {
|
||||
"cpu-critical-threshold": {
|
||||
"label": "CPU Usage (Critical)"
|
||||
},
|
||||
"cpu-temperature": {
|
||||
"description": "Afficher les lectures de température du CPU si disponibles.",
|
||||
"label": "Température du CPU"
|
||||
@@ -282,25 +288,10 @@
|
||||
"description": "Afficher le pourcentage d'utilisation actuel du CPU.",
|
||||
"label": "Utilisation du CPU"
|
||||
},
|
||||
"cpu-warning-threshold": {
|
||||
"label": "CPU Usage (Warning)"
|
||||
},
|
||||
"disk-critical-threshold": {
|
||||
"label": "Storage Space (Critical)"
|
||||
},
|
||||
"disk-path": {
|
||||
"description": "Sélectionnez le point de montage du disque à surveiller.",
|
||||
"label": "Chemin du disque"
|
||||
},
|
||||
"disk-warning-threshold": {
|
||||
"label": "Storage Space (Warning)"
|
||||
},
|
||||
"mem-critical-threshold": {
|
||||
"label": "Memory Usage (Critical)"
|
||||
},
|
||||
"mem-warning-threshold": {
|
||||
"label": "Memory Usage (Warning)"
|
||||
},
|
||||
"memory-percentage": {
|
||||
"description": "Afficher l'utilisation de la mémoire en pourcentage au lieu de valeurs absolues.",
|
||||
"label": "Mémoire en pourcentage"
|
||||
@@ -316,16 +307,6 @@
|
||||
"storage-usage": {
|
||||
"description": "Afficher les informations d'utilisation de l'espace disque.",
|
||||
"label": "Utilisation du stockage"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "CPU Temperature (Critical)"
|
||||
},
|
||||
"temp-warning-threshold": {
|
||||
"label": "CPU Temperature (Warning)"
|
||||
},
|
||||
"thresholds": {
|
||||
"description": "Configurez les seuils des indicateurs visuels. Les indicateurs d'avertissement/critique apparaissent en couleurs mises en surbrillance lorsque les valeurs dépassent ces limites.",
|
||||
"header": "Seuils"
|
||||
}
|
||||
},
|
||||
"taskbar": {
|
||||
@@ -373,14 +354,14 @@
|
||||
"description": "Nombre de caractères à afficher des noms d'espaces de travail (1-10).",
|
||||
"label": "Nombre de caractères"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Ne pas afficher les espaces de travail sans fenêtres.",
|
||||
"label": "Masquer les inoccupés"
|
||||
},
|
||||
"follow-focused-screen": {
|
||||
"description": "Afficher les espaces de travail de l'écran actuellement ciblé, plutôt que de l'écran où se trouve la barre.",
|
||||
"label": "Suivre l'Écran Ciblé"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Ne pas afficher les espaces de travail sans fenêtres.",
|
||||
"label": "Masquer les inoccupés"
|
||||
},
|
||||
"label-mode": {
|
||||
"description": "Choisir comment les étiquettes d'espace de travail sont affichées.",
|
||||
"label": "Mode d'étiquette"
|
||||
@@ -389,8 +370,8 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"battery-level": "Niveau de batterie",
|
||||
"brightness": "Luminosité",
|
||||
"charge-level": "Niveau de charge",
|
||||
"charging": "En charge.",
|
||||
"charging-rate": "Taux de charge : {rate} W.",
|
||||
"discharging": "En décharge.",
|
||||
@@ -412,11 +393,12 @@
|
||||
"blocked": "Bloqué",
|
||||
"connect": "Connecter",
|
||||
"connected-devices": "Appareils connectés",
|
||||
"connecting": "Connexion en cours...",
|
||||
"disabled": "Le Bluetooth est désactivé",
|
||||
"disconnect": "Déconnecter",
|
||||
"enable-message": "Activez le Bluetooth pour voir les appareils disponibles.",
|
||||
"known-devices": "Appareils connus",
|
||||
"pairing": "Appairage...",
|
||||
"pairing": "Appairage en cours...",
|
||||
"pairing-mode": "Assurez-vous que votre appareil est en mode d'appairage.",
|
||||
"scanning": "Recherche d'appareils en cours...",
|
||||
"title": "Bluetooth"
|
||||
@@ -426,6 +408,19 @@
|
||||
"panel": {
|
||||
"week": "Semaine"
|
||||
},
|
||||
"timer": {
|
||||
"countdown": "Compte à rebours",
|
||||
"duration": "Durée",
|
||||
"hours": "h",
|
||||
"minutes": "m",
|
||||
"pause": "Pause",
|
||||
"reset": "Réinitialiser",
|
||||
"seconds": "s",
|
||||
"start": "Commencer",
|
||||
"stopwatch": "Chronomètre",
|
||||
"timer": "Minuteur",
|
||||
"title": "Minuteur"
|
||||
},
|
||||
"weather": {
|
||||
"loading": "Chargement de la météo…"
|
||||
}
|
||||
@@ -518,14 +513,20 @@
|
||||
"no-notifications": "Aucune notification",
|
||||
"title": "Notifications"
|
||||
},
|
||||
"range": {
|
||||
"all": "Tout",
|
||||
"earlier": "Plus anciennes",
|
||||
"today": "Aujourd'hui",
|
||||
"yesterday": "Hier"
|
||||
},
|
||||
"time": {
|
||||
"now": "maintenant",
|
||||
"diffM": "il y a 1 minute",
|
||||
"diffMM": "il y a {diff} minutes",
|
||||
"diffD": "il y a 1 jour",
|
||||
"diffDD": "il y a {diff} jours",
|
||||
"diffH": "il y a 1 heure",
|
||||
"diffHH": "il y a {diff} heures",
|
||||
"diffD": "il y a 1 jour",
|
||||
"diffDD": "il y a {diff} jours"
|
||||
"diffM": "il y a 1 minute",
|
||||
"diffMM": "il y a {diff} minutes",
|
||||
"now": "maintenant"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@@ -545,6 +546,7 @@
|
||||
},
|
||||
"colors": {
|
||||
"error": "Erreur",
|
||||
"none": "Aucun",
|
||||
"onSurface": "En surface",
|
||||
"primary": "Primaire",
|
||||
"secondary": "Secondaire",
|
||||
@@ -706,7 +708,7 @@
|
||||
"enabled": "Bluetooth"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Cliquer pour gérer les appareils Bluetooth"
|
||||
"action": "Bluetooth"
|
||||
}
|
||||
},
|
||||
"keepAwake": {
|
||||
@@ -715,7 +717,7 @@
|
||||
"enabled": "Rester éveillé"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Cliquer pour basculer le mode Rester éveillé"
|
||||
"action": "Rester éveillé"
|
||||
}
|
||||
},
|
||||
"nightLight": {
|
||||
@@ -725,7 +727,7 @@
|
||||
"forced": "Lumière nocturne"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Cliquer pour basculer le mode Lumière nocturne\nClic droit : Ouvrir les paramètres"
|
||||
"action": "Lumière nocturne"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -734,7 +736,7 @@
|
||||
"enabled": "Notifications"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Clic gauche : Ouvrir l'historique des notifications\nClic droit : Basculer Ne pas déranger"
|
||||
"action": "Notifications"
|
||||
}
|
||||
},
|
||||
"powerProfile": {
|
||||
@@ -742,7 +744,7 @@
|
||||
"unavailable": "Profil d'alimentation"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Cliquer pour changer de profil d'alimentation",
|
||||
"action": "Profil d'alimentation",
|
||||
"disabled": "Installez power-profiles-daemon pour utiliser les profils d'alimentation"
|
||||
}
|
||||
},
|
||||
@@ -752,13 +754,13 @@
|
||||
"stopped": "Enregistrer"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Cliquer pour démarrer/arrêter l'enregistrement d'écran"
|
||||
"action": "Enregistreur d'écran"
|
||||
}
|
||||
},
|
||||
"wallpaperSelector": {
|
||||
"label": "Fond d'écran",
|
||||
"tooltip": {
|
||||
"action": "Clic gauche : Ouvrir le sélecteur de fond d'écran\nClic droit : Définir un fond d'écran aléatoire"
|
||||
"action": "Sélecteur de fond d'écran"
|
||||
}
|
||||
},
|
||||
"wifi": {
|
||||
@@ -768,7 +770,7 @@
|
||||
"wifi": "Wi-Fi"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Cliquer pour gérer les connexions Wi-Fi"
|
||||
"action": "Wi-Fi"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -794,6 +796,8 @@
|
||||
},
|
||||
"noctalia": {
|
||||
"download-latest": "Télécharger la dernière version",
|
||||
"git-commit": "Commit Git :",
|
||||
"git-commit-loading": "Chargement...",
|
||||
"installed-version": "Version installée :",
|
||||
"latest-version": "Dernière version :",
|
||||
"section": {
|
||||
@@ -1040,15 +1044,27 @@
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"compositors": {
|
||||
"description": "Thématisation du compositeur.",
|
||||
"label": "Compositeurs",
|
||||
"niri": {
|
||||
"description": "Écrire {filepath}. Requiert niri v25.11+",
|
||||
"description-missing": "Nécessite l'installation de {app}"
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"description": "Options de configuration supplémentaires.",
|
||||
"label": "Divers",
|
||||
"description": "Créez vos propres modèles",
|
||||
"label": "Avancé",
|
||||
"user-templates": {
|
||||
"description": "Activer la configuration Matugen définie par l'utilisateur. Un fichier modèle sera créé dans ~/.config/noctalia/user-templates.toml lors de la première activation",
|
||||
"label": "Modèles utilisateur"
|
||||
"description": "N'activez que si vous savez ce que vous faites, consultez notre documentation en ligne",
|
||||
"label": "Activer les modèles utilisateur"
|
||||
}
|
||||
},
|
||||
"programs": {
|
||||
"cava": {
|
||||
"description": "Écrire {filepath}.",
|
||||
"description-missing": "Nécessite l'installation de {app}"
|
||||
},
|
||||
"code": {
|
||||
"description": "Écrire {filepath}. Le thème Hyprluna doit être installé et activé manuellement.",
|
||||
"description-missing": "Aucun client Code détecté. Installez VSCode ou VSCodium."
|
||||
@@ -1075,10 +1091,6 @@
|
||||
"description": "Écrire {filepath}.",
|
||||
"description-missing": "Nécessite l'installation de {app}"
|
||||
},
|
||||
"cava": {
|
||||
"description": "Écrire {filepath}.",
|
||||
"description-missing": "Nécessite l'installation de {app}"
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Écrire {filepath} et recharger",
|
||||
"description-missing": "Nécessite que le lanceur {app} soit installé"
|
||||
@@ -1262,9 +1274,11 @@
|
||||
},
|
||||
"temperature": {
|
||||
"day": "Jour",
|
||||
"day-description": "Contrôle la température pendant la journée.",
|
||||
"description": "Réglez la chaleur des couleurs pour la nuit et le jour.",
|
||||
"label": "Température de couleur",
|
||||
"night": "Nuit"
|
||||
"night": "Nuit",
|
||||
"night-description": "Contrôle la température pendant la nuit."
|
||||
}
|
||||
},
|
||||
"title": "Affichage"
|
||||
@@ -1368,7 +1382,8 @@
|
||||
"description": "Modifiez vos informations utilisateur et votre avatar.",
|
||||
"label": "Profil"
|
||||
},
|
||||
"select-avatar": "Sélectionner une image d'avatar"
|
||||
"select-avatar": "Sélectionner une image d'avatar",
|
||||
"tooltip": "Parcourir pour une image d’avatar"
|
||||
},
|
||||
"screen-corners": {
|
||||
"radius": {
|
||||
@@ -1446,6 +1461,10 @@
|
||||
"description": "Utiliser un préfixe personnalisé pour lancer les applications au lieu de la méthode par défaut.",
|
||||
"label": "Activer le préfixe de lancement personnalisé"
|
||||
},
|
||||
"grid-view": {
|
||||
"description": "Afficher les éléments dans une grille au lieu d'une liste.",
|
||||
"label": "Vue grille"
|
||||
},
|
||||
"position": {
|
||||
"description": "Choisissez où le panneau du lanceur apparaît.",
|
||||
"label": "Position"
|
||||
@@ -1470,6 +1489,20 @@
|
||||
"title": "Lanceur"
|
||||
},
|
||||
"location": {
|
||||
"calendar": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "Organiser et activer/désactiver les cartes dans le panneau de calendrier.",
|
||||
"label": "Cartes de calendrier"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"label": "En-tête du calendrier"
|
||||
},
|
||||
"month": {
|
||||
"label": "Mois calendaire"
|
||||
}
|
||||
},
|
||||
"date-time": {
|
||||
"12hour-format": {
|
||||
"description": "Affiche l'heure au format 12 heures sur l'écran de verrouillage et dans le calendrier. L'horloge de la barre possède ses propres paramètres.",
|
||||
@@ -1671,7 +1704,29 @@
|
||||
"label": "Général"
|
||||
}
|
||||
},
|
||||
"title": "Affichage à l'écran"
|
||||
"title": "Affichage à l'écran",
|
||||
"types": {
|
||||
"brightness": {
|
||||
"description": "Afficher l'OSD lorsque la luminosité de l'écran change.",
|
||||
"label": "Luminosité"
|
||||
},
|
||||
"input-volume": {
|
||||
"description": "Afficher l'OSD lorsque le volume du microphone change.",
|
||||
"label": "Volume d'entrée"
|
||||
},
|
||||
"lockkey": {
|
||||
"description": "Afficher l'OSD lorsque Verr Maj, Verr Num ou Arrêt défil est activée ou désactivée.",
|
||||
"label": "Touches de verrouillage"
|
||||
},
|
||||
"section": {
|
||||
"description": "Sélectionnez les événements qui déclenchent l'OSD. Si aucun événement n'est sélectionné, tous les événements disponibles déclencheront l'OSD.",
|
||||
"label": "Événements de déclenchement OSD"
|
||||
},
|
||||
"volume": {
|
||||
"description": "Afficher l'OSD lorsque le volume de sortie audio change.",
|
||||
"label": "Volume de sortie"
|
||||
}
|
||||
}
|
||||
},
|
||||
"screen-recorder": {
|
||||
"audio": {
|
||||
@@ -1806,6 +1861,12 @@
|
||||
"memory-section": {
|
||||
"label": "Utilisation mémoire"
|
||||
},
|
||||
"network-section": {
|
||||
"label": "Réseau"
|
||||
},
|
||||
"polling-interval": {
|
||||
"label": "Intervalle d'interrogation"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "Seuil critique"
|
||||
},
|
||||
@@ -1816,7 +1877,7 @@
|
||||
"label": "Température CPU"
|
||||
},
|
||||
"thresholds-section": {
|
||||
"description": "Définir des seuils pour les métriques système ; les valeurs supérieures à ces seuils seront mises en évidence.",
|
||||
"description": "Ajustez les seuils d’avertissement/critiques et les intervalles d’interrogation pour chaque métrique système.",
|
||||
"label": "Seuils"
|
||||
},
|
||||
"title": "Moniteur système",
|
||||
@@ -2048,6 +2109,11 @@
|
||||
"unavailable": "Historique du presse-papiers indisponible",
|
||||
"unavailable-desc": "L'application 'cliphist' n'est pas installée. Veuillez l'installer pour utiliser les fonctionnalités d'historique du presse-papiers."
|
||||
},
|
||||
"dark-mode": {
|
||||
"dark-mode": "Mode sombre",
|
||||
"enabled": "Activé",
|
||||
"light-mode": "Mode clair"
|
||||
},
|
||||
"do-not-disturb": {
|
||||
"disabled": "'Ne pas déranger' désactivé",
|
||||
"disabled-desc": "Affichage de toutes les notifications.",
|
||||
@@ -2128,64 +2194,70 @@
|
||||
"tooltips": {
|
||||
"add-widget": "Ajouter un widget",
|
||||
"bluetooth-devices": "Appareils Bluetooth",
|
||||
"brightness-at": "Luminosité : {brightness}%\nClic droit pour les paramètres.\nFaites défiler pour modifier la luminosité.",
|
||||
"cancel-timer": "Annuler le minuteur",
|
||||
"brightness-at": "Luminosité : {brightness}%",
|
||||
"cancel-timer": "Minuteur",
|
||||
"clear-history": "Effacer l'historique",
|
||||
"click-to-start-recording": "Cliquez pour démarrer l'enregistrement",
|
||||
"click-to-stop-recording": "Cliquez pour arrêter l'enregistrement",
|
||||
"close": "Fermer",
|
||||
"connect-disconnect-devices": "Clic gauche pour connecter. Clic droit pour oublier.",
|
||||
"click-to-start-recording": "Enregistreur d'écran (démarrer l'enregistrement)",
|
||||
"click-to-stop-recording": "Enregistreur d'écran (arrêter l'enregistrement)",
|
||||
"close": "Bouton fermer",
|
||||
"connect-disconnect-devices": "Appareil Bluetooth",
|
||||
"delete-notification": "Supprimer la notification",
|
||||
"disable-keep-awake": "Cliquez pour désactiver le mode 'rester éveillé'.\nFaites défiler pour ajuster le délai.",
|
||||
"do-not-disturb-disabled": "'Ne pas déranger' désactivé",
|
||||
"do-not-disturb-enabled": "'Ne pas déranger' activé",
|
||||
"enable-keep-awake": "Cliquez pour activer le mode 'rester éveillé'.\nFaites défiler pour ajuster le délai.",
|
||||
"disable-keep-awake": "Rester éveillé",
|
||||
"do-not-disturb-disabled": "Ne pas déranger",
|
||||
"do-not-disturb-enabled": "Ne pas déranger",
|
||||
"enable-keep-awake": "Rester éveillé",
|
||||
"forget-network": "Oublier le réseau",
|
||||
"home": "Accueil",
|
||||
"input-muted": "Couper l'entrée audio",
|
||||
"grid-view": "Vue en grille",
|
||||
"hidden-files-hide": "Fichiers cachés",
|
||||
"hidden-files-show": "Fichiers cachés",
|
||||
"home": "Répertoire home",
|
||||
"input-muted": "Microphone",
|
||||
"keep-awake": "Rester éveillé",
|
||||
"keyboard-layout": "Disposition du clavier {layout}",
|
||||
"manage-vpn": "Gérer les connexions VPN",
|
||||
"manage-wifi": "Gérer le Wi-Fi",
|
||||
"microphone-volume-at": "Volume du microphone à {volume}%.\nClic gauche pour les paramètres. Clic droit pour activer/désactiver le mode muet.\nFaites défiler pour modifier le volume.",
|
||||
"move-to-center-section": "Déplacer vers la section centrale",
|
||||
"move-to-left-section": "Déplacer vers la section de gauche",
|
||||
"move-to-right-section": "Déplacer vers la section de droite",
|
||||
"next-media": "Média suivant",
|
||||
"list-view": "Vue en liste",
|
||||
"manage-vpn": "Connexions VPN",
|
||||
"manage-wifi": "Wi-Fi",
|
||||
"microphone-volume-at": "Volume du microphone : {volume}%",
|
||||
"move-to-center-section": "Section centrale",
|
||||
"move-to-left-section": "Section de gauche",
|
||||
"move-to-right-section": "Section de droite",
|
||||
"next-media": "Piste suivante",
|
||||
"next-month": "Mois suivant",
|
||||
"night-light-disabled": "L'éclairage nocturne est désactivé.\nClic gauche pour changer de mode.\nClic droit pour accéder aux paramètres.",
|
||||
"night-light-enabled": "L'éclairage nocturne est activé.\nClic gauche pour changer de mode.\nClic droit pour accéder aux paramètres.",
|
||||
"night-light-forced": "L'éclairage nocturne est forcé.\nClic gauche pour changer de mode.\nClic droit pour accéder aux paramètres.",
|
||||
"night-light-not-installed": "L'éclairage nocturne n'est pas disponible.\nwlsunset n'est pas installé.",
|
||||
"noctalia-performance-disabled": "Le mode performance Noctalia est désactivé.\nClic gauche pour activer.",
|
||||
"noctalia-performance-enabled": "Le mode performance Noctalia est activé.\nClic gauche pour désactiver.",
|
||||
"open-control-center": "Ouvrir le centre de contrôle",
|
||||
"open-notification-history-disable-dnd": "Ouvrir l'historique des notifications\nClic droit pour désactiver \"Ne pas déranger\".",
|
||||
"open-notification-history-enable-dnd": "Ouvrir l'historique des notifications\nClic droit pour activer \"Ne pas déranger\".",
|
||||
"open-settings": "Ouvrir les paramètres",
|
||||
"open-tray-dropdown": "Ouvrir le menu déroulant de la barre d'état",
|
||||
"open-wallpaper-selector": "Ouvrir le sélecteur de fond d'écran",
|
||||
"output-muted": "Couper la sortie audio",
|
||||
"pause": "Pause",
|
||||
"play": "Lecture",
|
||||
"power-profile": "Profil d'alimentation '{profile}'",
|
||||
"previous-media": "Média précédent",
|
||||
"night-light-disabled": "Éclairage nocturne",
|
||||
"night-light-enabled": "Éclairage nocturne",
|
||||
"night-light-forced": "Éclairage nocturne",
|
||||
"night-light-not-installed": "Éclairage nocturne (non disponible)",
|
||||
"noctalia-performance-disabled": "Mode performance Noctalia",
|
||||
"noctalia-performance-enabled": "Mode performance Noctalia",
|
||||
"open-control-center": "Centre de contrôle",
|
||||
"open-notification-history-disable-dnd": "Historique des notifications",
|
||||
"open-notification-history-enable-dnd": "Historique des notifications",
|
||||
"open-settings": "Paramètres",
|
||||
"open-tray-dropdown": "Barre d'état système",
|
||||
"open-wallpaper-selector": "Sélecteur de fond d'écran",
|
||||
"output-muted": "Sortie audio",
|
||||
"pause": "Bouton pause",
|
||||
"play": "Bouton lecture",
|
||||
"power-profile": "{profile} profil d'alimentation",
|
||||
"previous-media": "Piste précédente",
|
||||
"previous-month": "Mois précédent",
|
||||
"refresh": "Actualiser",
|
||||
"refresh-devices": "Actualiser les appareils",
|
||||
"refresh-wallhaven": "Actualiser les résultats de Wallhaven",
|
||||
"refresh-wallpaper-list": "Actualiser la liste des fonds d'écran",
|
||||
"remove-widget": "Supprimer le widget",
|
||||
"screen-recorder-not-installed": "L'enregistreur d'écran n'est pas installé",
|
||||
"screen-recorder-not-installed": "Enregistreur d'écran (non installé)",
|
||||
"search": "Rechercher",
|
||||
"search-close": "Fermer la recherche",
|
||||
"session-menu": "Menu de session",
|
||||
"set-power-profile": "Définir le profil d'alimentation \"{profile}\"",
|
||||
"start-screen-recording": "Démarrer l'enregistrement d'écran",
|
||||
"stop-screen-recording": "Arrêter l'enregistrement d'écran",
|
||||
"switch-to-dark-mode": "Passer en mode sombre",
|
||||
"switch-to-light-mode": "Passer en mode clair",
|
||||
"up": "Remonter",
|
||||
"volume-at": "Volume de sortie à {volume}%.\nClic gauche pour les paramètres. Clic droit pour activer/désactiver le mode muet.\nFaites défiler pour modifier le volume.",
|
||||
"wallpaper-selector": "Clic gauche : Ouvrir le sélecteur de fond d'écran.\nClic droit : Définir un fond d'écran aléatoire.",
|
||||
"set-power-profile": "{profile} profil d'alimentation",
|
||||
"start-screen-recording": "Enregistreur d'écran",
|
||||
"stop-screen-recording": "Enregistreur d'écran",
|
||||
"switch-to-dark-mode": "Mode sombre",
|
||||
"switch-to-light-mode": "Mode clair",
|
||||
"up": "Répertoire parent",
|
||||
"volume-at": "Volume de sortie : {volume}%",
|
||||
"wallpaper-selector": "Sélecteur de fond d'écran",
|
||||
"widget-settings": "Paramètres du widget"
|
||||
},
|
||||
"wallpaper": {
|
||||
|
||||
2455
Assets/Translations/ja.json
Normal file
@@ -46,6 +46,11 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"device": {
|
||||
"default": "Standaard (weergaveapparaat)",
|
||||
"description": "Selecteer welk batterijapparaat u wilt weergeven.",
|
||||
"label": "Batterijapparaat"
|
||||
},
|
||||
"display-mode": {
|
||||
"description": "Kies hoe je wilt dat deze waarde wordt weergegeven.",
|
||||
"label": "Weergavemodus"
|
||||
@@ -97,9 +102,13 @@
|
||||
"control-center": {
|
||||
"browse-file": "Bestand kiezen",
|
||||
"browse-library": "Bibliotheek doorbladeren",
|
||||
"colorize-distro-logo": {
|
||||
"description": "Pas themakleuren toe op je distributielogo.",
|
||||
"label": "Distributielogo inkleuren"
|
||||
"color-selection": {
|
||||
"description": "Themakleuren toepassen op pictogrammen.",
|
||||
"label": "Kleur selecteren"
|
||||
},
|
||||
"enable-colorization": {
|
||||
"description": "Schakel kleuring in voor het pictogram van het controlecentrum, waarbij themakleuren worden toegepast.",
|
||||
"label": "Kleuren inschakelen"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Selecteer een pictogram uit de bibliotheek of een aangepast bestand.",
|
||||
@@ -227,14 +236,14 @@
|
||||
"description": "Toon artiest - titel in plaats van titel - artiest.",
|
||||
"label": "Toon eerst de artiest"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Toon een audiovisualizer wanneer muziek wordt afgespeeld.",
|
||||
"label": "Visualizer tonen"
|
||||
},
|
||||
"show-progress-ring": {
|
||||
"description": "Toon een circulaire voortgangsindicator die het bestandsspoor voortgang toont.",
|
||||
"label": "Voortgangscirkel tonen"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Toon een audiovisualizer wanneer muziek wordt afgespeeld.",
|
||||
"label": "Visualizer tonen"
|
||||
},
|
||||
"use-fixed-width": {
|
||||
"description": "Indien ingeschakeld gebruikt de widget altijd de maximale breedte in plaats van zich aan te passen aan de inhoud.",
|
||||
"label": "Vaste breedte gebruiken"
|
||||
@@ -271,9 +280,6 @@
|
||||
}
|
||||
},
|
||||
"system-monitor": {
|
||||
"cpu-critical-threshold": {
|
||||
"label": "CPU-gebruik (Kritiek)"
|
||||
},
|
||||
"cpu-temperature": {
|
||||
"description": "Toon CPU-temperatuur indien beschikbaar.",
|
||||
"label": "CPU-temperatuur"
|
||||
@@ -282,25 +288,10 @@
|
||||
"description": "Toon het huidige CPU-gebruik in procent.",
|
||||
"label": "CPU-gebruik"
|
||||
},
|
||||
"cpu-warning-threshold": {
|
||||
"label": "CPU-gebruik (Waarschuwing)"
|
||||
},
|
||||
"disk-critical-threshold": {
|
||||
"label": "Opslagruimte (Kritiek)"
|
||||
},
|
||||
"disk-path": {
|
||||
"description": "Selecteer welk schijfkoppelpunt u wilt monitoren.",
|
||||
"label": "Schijfpad"
|
||||
},
|
||||
"disk-warning-threshold": {
|
||||
"label": "Opslagruimte (Waarschuwing)"
|
||||
},
|
||||
"mem-critical-threshold": {
|
||||
"label": "Geheugengebruik (Kritiek)"
|
||||
},
|
||||
"mem-warning-threshold": {
|
||||
"label": "Geheugengebruik (Waarschuwing)"
|
||||
},
|
||||
"memory-percentage": {
|
||||
"description": "Toon geheugengebruik als percentage in plaats van absolute waarden.",
|
||||
"label": "Geheugen als percentage"
|
||||
@@ -316,16 +307,6 @@
|
||||
"storage-usage": {
|
||||
"description": "Toon informatie over schijfruimtegebruik.",
|
||||
"label": "Opslaggebruik"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "CPU-temperatuur (kritiek)"
|
||||
},
|
||||
"temp-warning-threshold": {
|
||||
"label": "CPU-temperatuur (Waarschuwing)"
|
||||
},
|
||||
"thresholds": {
|
||||
"description": "Drempels instellen voor systeemmetriekwaarschuwingen. Markeert wanneer overschreden.",
|
||||
"header": "Drempelinstellingen"
|
||||
}
|
||||
},
|
||||
"taskbar": {
|
||||
@@ -373,14 +354,14 @@
|
||||
"description": "Aantal tekens dat wordt weergegeven van werkruimtenamen (1-10).",
|
||||
"label": "Aantal tekens"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Werkruimten zonder vensters niet weergeven.",
|
||||
"label": "Ongebruikte verbergen"
|
||||
},
|
||||
"follow-focused-screen": {
|
||||
"description": "Werkruimten weergeven van het momenteel gefocuste scherm, in plaats van het scherm waar de balk zich bevindt.",
|
||||
"label": "Gefocust Scherm Volgen"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Werkruimten zonder vensters niet weergeven.",
|
||||
"label": "Ongebruikte verbergen"
|
||||
},
|
||||
"label-mode": {
|
||||
"description": "Kies hoe labels van werkruimten worden weergegeven.",
|
||||
"label": "Labelmodus"
|
||||
@@ -389,8 +370,8 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"battery-level": "Batterijniveau",
|
||||
"brightness": "Helderheid",
|
||||
"charge-level": "Laadniveau",
|
||||
"charging": "Opladen.",
|
||||
"charging-rate": "Laadsnelheid: {rate} W.",
|
||||
"discharging": "Ontladen.",
|
||||
@@ -412,6 +393,7 @@
|
||||
"blocked": "Geblokkeerd",
|
||||
"connect": "Verbinden",
|
||||
"connected-devices": "Verbonden apparaten",
|
||||
"connecting": "Verbinden...",
|
||||
"disabled": "Bluetooth is uitgeschakeld",
|
||||
"disconnect": "Verbreken",
|
||||
"enable-message": "Schakel Bluetooth in om beschikbare apparaten te zien.",
|
||||
@@ -426,6 +408,19 @@
|
||||
"panel": {
|
||||
"week": "Week"
|
||||
},
|
||||
"timer": {
|
||||
"countdown": "Aftellen",
|
||||
"duration": "Duur",
|
||||
"hours": "h",
|
||||
"minutes": "m",
|
||||
"pause": "Pauze",
|
||||
"reset": "Resetten",
|
||||
"seconds": "s",
|
||||
"start": "Begin",
|
||||
"stopwatch": "Stopwatch",
|
||||
"timer": "Timer",
|
||||
"title": "Timer"
|
||||
},
|
||||
"weather": {
|
||||
"loading": "Weer laden…"
|
||||
}
|
||||
@@ -518,14 +513,20 @@
|
||||
"no-notifications": "Geen meldingen",
|
||||
"title": "Meldingen"
|
||||
},
|
||||
"range": {
|
||||
"all": "Alle",
|
||||
"earlier": "Eerder",
|
||||
"today": "Vandaag",
|
||||
"yesterday": "Gisteren"
|
||||
},
|
||||
"time": {
|
||||
"now": "nu",
|
||||
"diffM": "1 minuut geleden",
|
||||
"diffMM": "{diff} minuten geleden",
|
||||
"diffD": "1 dag geleden",
|
||||
"diffDD": "{diff} dagen geleden",
|
||||
"diffH": "1 uur geleden",
|
||||
"diffHH": "{diff} uur geleden",
|
||||
"diffD": "1 dag geleden",
|
||||
"diffDD": "{diff} dagen geleden"
|
||||
"diffM": "1 minuut geleden",
|
||||
"diffMM": "{diff} minuten geleden",
|
||||
"now": "nu"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@@ -545,6 +546,7 @@
|
||||
},
|
||||
"colors": {
|
||||
"error": "Fout",
|
||||
"none": "Geen",
|
||||
"onSurface": "Op oppervlak",
|
||||
"primary": "Primair",
|
||||
"secondary": "Secundair",
|
||||
@@ -706,7 +708,7 @@
|
||||
"enabled": "Bluetooth"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Klik om Bluetooth-apparaten te beheren"
|
||||
"action": "Bluetooth"
|
||||
}
|
||||
},
|
||||
"keepAwake": {
|
||||
@@ -715,7 +717,7 @@
|
||||
"enabled": "Wakker houden"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Klik om de 'wakker houden'-modus te schakelen"
|
||||
"action": "Wakker houden"
|
||||
}
|
||||
},
|
||||
"nightLight": {
|
||||
@@ -725,7 +727,7 @@
|
||||
"forced": "Nachtlicht"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Linker muisknop: door Nachtlicht-modi bladeren\nRechter muisknop: Instellingen openen"
|
||||
"action": "Nachtlicht"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -734,7 +736,7 @@
|
||||
"enabled": "Meldingen"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Linker muisknop: meldingsgeschiedenis openen\nRechter muisknop: 'Niet storen' schakelen"
|
||||
"action": "Meldingen"
|
||||
}
|
||||
},
|
||||
"powerProfile": {
|
||||
@@ -742,7 +744,7 @@
|
||||
"unavailable": "Energieprofiel"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Klik om door energieprofielen te bladeren",
|
||||
"action": "Energieprofiel",
|
||||
"disabled": "Installeer power-profiles-daemon om energieprofielen te gebruiken"
|
||||
}
|
||||
},
|
||||
@@ -752,13 +754,13 @@
|
||||
"stopped": "Opnemen"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Klik om schermopname te starten/stoppen"
|
||||
"action": "Schermrecorder"
|
||||
}
|
||||
},
|
||||
"wallpaperSelector": {
|
||||
"label": "Achtergrond",
|
||||
"tooltip": {
|
||||
"action": "Linker muisknop: achtergrondkiezer openen\nRechter muisknop: willekeurige achtergrond instellen"
|
||||
"action": "Achtergrondkiezer"
|
||||
}
|
||||
},
|
||||
"wifi": {
|
||||
@@ -768,7 +770,7 @@
|
||||
"wifi": "Wi-Fi"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Klik om Wi-Fi-verbindingen te beheren"
|
||||
"action": "Wi-Fi"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -794,6 +796,8 @@
|
||||
},
|
||||
"noctalia": {
|
||||
"download-latest": "Nieuwste release downloaden",
|
||||
"git-commit": "Git commit:",
|
||||
"git-commit-loading": "Laden...",
|
||||
"installed-version": "Geïnstalleerde versie:",
|
||||
"latest-version": "Nieuwste versie:",
|
||||
"section": {
|
||||
@@ -1040,15 +1044,27 @@
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"compositors": {
|
||||
"description": "Theming van compositor.",
|
||||
"label": "Compositors",
|
||||
"niri": {
|
||||
"description": "Schrijf {filepath}. Vereist niri v25.11+",
|
||||
"description-missing": "Vereist dat {app} is geïnstalleerd."
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"description": "Aanvullende configuratie-opties.",
|
||||
"label": "Diversen",
|
||||
"description": "Maak uw eigen sjablonen",
|
||||
"label": "Geavanceerd",
|
||||
"user-templates": {
|
||||
"description": "Schakel door de gebruiker gedefinieerde Matugen-configuratie in. Er wordt een sjabloonbestand aangemaakt op ~/.config/noctalia/user-templates.toml bij de eerste keer inschakelen.",
|
||||
"label": "Gebruikerssjablonen"
|
||||
"description": "Alleen inschakelen als u weet wat u doet, raadpleeg onze online documentatie",
|
||||
"label": "Gebruikerssjablonen inschakelen"
|
||||
}
|
||||
},
|
||||
"programs": {
|
||||
"cava": {
|
||||
"description": "Schrijf {filepath}.",
|
||||
"description-missing": "Vereist dat {app} is geïnstalleerd."
|
||||
},
|
||||
"code": {
|
||||
"description": "Schrijf {filepath}. Het Hyprluna-thema moet handmatig worden geïnstalleerd en geactiveerd.",
|
||||
"description-missing": "Geen Code-client gedetecteerd. Installeer VSCode of VSCodium."
|
||||
@@ -1075,10 +1091,6 @@
|
||||
"description": "Schrijf {filepath}.",
|
||||
"description-missing": "Vereist dat {app} is geïnstalleerd."
|
||||
},
|
||||
"cava": {
|
||||
"description": "Schrijf {filepath}.",
|
||||
"description-missing": "Vereist dat {app} is geïnstalleerd."
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Schrijf {filepath} en herlaad.",
|
||||
"description-missing": "Vereist dat {app} is geïnstalleerd."
|
||||
@@ -1262,9 +1274,11 @@
|
||||
},
|
||||
"temperature": {
|
||||
"day": "Dag",
|
||||
"day-description": "Regelt de kleurtemperatuur overdag.",
|
||||
"description": "Stel de kleurwarmte voor nacht en dag in.",
|
||||
"label": "Kleurtemperatuur",
|
||||
"night": "Nacht"
|
||||
"night": "Nacht",
|
||||
"night-description": "Regelt de kleurtemperatuur 's nachts."
|
||||
}
|
||||
},
|
||||
"title": "Beeldscherm"
|
||||
@@ -1368,7 +1382,8 @@
|
||||
"description": "Bewerk je gebruikersgegevens en avatar.",
|
||||
"label": "Profiel"
|
||||
},
|
||||
"select-avatar": "Avatarafbeelding selecteren"
|
||||
"select-avatar": "Avatarafbeelding selecteren",
|
||||
"tooltip": "Bladeren naar avatarafbeelding"
|
||||
},
|
||||
"screen-corners": {
|
||||
"radius": {
|
||||
@@ -1446,6 +1461,10 @@
|
||||
"description": "Gebruik een aangepaste prefix om applicaties te starten in plaats van de standaardmethode.",
|
||||
"label": "Aangepaste startprefix inschakelen"
|
||||
},
|
||||
"grid-view": {
|
||||
"description": "Items in een raster weergeven in plaats van een lijst.",
|
||||
"label": "Rasterweergave"
|
||||
},
|
||||
"position": {
|
||||
"description": "Kies waar het launcher-paneel verschijnt.",
|
||||
"label": "Positie"
|
||||
@@ -1470,6 +1489,20 @@
|
||||
"title": "Launcher"
|
||||
},
|
||||
"location": {
|
||||
"calendar": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "Kaarten organiseren en in-/uitschakelen in het kalenderpaneel.",
|
||||
"label": "Kalenderkaarten"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"label": "Kalenderkop"
|
||||
},
|
||||
"month": {
|
||||
"label": "Kalendermaand"
|
||||
}
|
||||
},
|
||||
"date-time": {
|
||||
"12hour-format": {
|
||||
"description": "Toont tijd in 12-uurs formaat op het vergrendelscherm en in de kalender. De klok op de balk heeft zijn eigen instellingen.",
|
||||
@@ -1671,7 +1704,29 @@
|
||||
"label": "Algemeen"
|
||||
}
|
||||
},
|
||||
"title": "On-screenweergave"
|
||||
"title": "On-screenweergave",
|
||||
"types": {
|
||||
"brightness": {
|
||||
"description": "Toon het OSD wanneer de schermhelderheid verandert.",
|
||||
"label": "Helderheid"
|
||||
},
|
||||
"input-volume": {
|
||||
"description": "Toon het OSD wanneer het microfoonvolume verandert.",
|
||||
"label": "Invoervolume"
|
||||
},
|
||||
"lockkey": {
|
||||
"description": "Toon het OSD wanneer Caps Lock, Num Lock of Scroll Lock wordt omgeschakeld.",
|
||||
"label": "Vergrendeltoetsen"
|
||||
},
|
||||
"section": {
|
||||
"description": "Selecteer de gebeurtenissen die de OSD activeren. Als er geen gebeurtenissen zijn geselecteerd, activeren alle beschikbare gebeurtenissen de OSD.",
|
||||
"label": "OSD triggergebeurtenissen"
|
||||
},
|
||||
"volume": {
|
||||
"description": "Toon het OSD wanneer het uitvoervolume verandert.",
|
||||
"label": "Uitvoervolume"
|
||||
}
|
||||
}
|
||||
},
|
||||
"screen-recorder": {
|
||||
"audio": {
|
||||
@@ -1806,6 +1861,12 @@
|
||||
"memory-section": {
|
||||
"label": "Geheugengebruik"
|
||||
},
|
||||
"network-section": {
|
||||
"label": "Netwerk"
|
||||
},
|
||||
"polling-interval": {
|
||||
"label": "Peilingsinterval"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "Kritieke drempel"
|
||||
},
|
||||
@@ -1816,7 +1877,7 @@
|
||||
"label": "CPU-temperatuur"
|
||||
},
|
||||
"thresholds-section": {
|
||||
"description": "Stel drempelwaarden in voor systeemstatistieken; waarden die hierboven liggen worden gemarkeerd.",
|
||||
"description": "Pas de waarschuwing/kritieke drempels en de pollingsintervallen voor elke systeemmetriek aan.",
|
||||
"label": "Drempels"
|
||||
},
|
||||
"title": "Systeemmonitor",
|
||||
@@ -2048,6 +2109,11 @@
|
||||
"unavailable": "Klembordgeschiedenis niet beschikbaar",
|
||||
"unavailable-desc": "De toepassing 'cliphist' is niet geïnstalleerd. Installeer deze om functies voor klembordgeschiedenis te gebruiken."
|
||||
},
|
||||
"dark-mode": {
|
||||
"dark-mode": "Donkere modus",
|
||||
"enabled": "Ingeschakeld",
|
||||
"light-mode": "Lichte modus"
|
||||
},
|
||||
"do-not-disturb": {
|
||||
"disabled": "\"Niet storen\" uitgeschakeld",
|
||||
"disabled-desc": "Alle meldingen worden weergegeven.",
|
||||
@@ -2128,64 +2194,70 @@
|
||||
"tooltips": {
|
||||
"add-widget": "Widget toevoegen",
|
||||
"bluetooth-devices": "Bluetooth-apparaten",
|
||||
"brightness-at": "Helderheid: {brightness}%\nKlik met rechts voor instellingen.\nScroll om de helderheid aan te passen.",
|
||||
"cancel-timer": "Timer annuleren",
|
||||
"brightness-at": "Helderheid: {brightness}%",
|
||||
"cancel-timer": "Timer",
|
||||
"clear-history": "Geschiedenis wissen",
|
||||
"click-to-start-recording": "Klik om met opnemen te beginnen",
|
||||
"click-to-stop-recording": "Klik om te stoppen met opnemen",
|
||||
"close": "Sluiten",
|
||||
"connect-disconnect-devices": "Links klikken om te verbinden. Rechts klikken om te vergeten.",
|
||||
"click-to-start-recording": "Schermrecorder (opname starten)",
|
||||
"click-to-stop-recording": "Schermrecorder (opname stoppen)",
|
||||
"close": "Sluiten-knop",
|
||||
"connect-disconnect-devices": "Bluetooth-apparaat",
|
||||
"delete-notification": "Melding verwijderen",
|
||||
"disable-keep-awake": "\"Wakker houden\" uitschakelen",
|
||||
"do-not-disturb-disabled": "\"Niet storen\" uitgeschakeld",
|
||||
"do-not-disturb-enabled": "\"Niet storen\" ingeschakeld",
|
||||
"enable-keep-awake": "\"Wakker houden\" inschakelen",
|
||||
"disable-keep-awake": "Wakker houden",
|
||||
"do-not-disturb-disabled": "Niet storen",
|
||||
"do-not-disturb-enabled": "Niet storen",
|
||||
"enable-keep-awake": "Wakker houden",
|
||||
"forget-network": "Netwerk vergeten",
|
||||
"home": "Home",
|
||||
"input-muted": "Invoermicrofoon dempen in-/uitschakelen",
|
||||
"grid-view": "Rasterweergave",
|
||||
"hidden-files-hide": "Verborgen bestanden",
|
||||
"hidden-files-show": "Verborgen bestanden",
|
||||
"home": "Home-map",
|
||||
"input-muted": "Microfoon",
|
||||
"keep-awake": "Wakker houden",
|
||||
"keyboard-layout": "{layout}-toetsenbordindeling",
|
||||
"manage-vpn": "VPN-verbindingen beheren",
|
||||
"manage-wifi": "Wi-Fi beheren",
|
||||
"microphone-volume-at": "Microfoonvolume {volume}%\nLinks klikken voor instellingen. Rechts klikken om te dempen.\nScroll om het volume aan te passen.",
|
||||
"move-to-center-section": "Verplaatsen naar middelste sectie",
|
||||
"move-to-left-section": "Verplaatsen naar linker sectie",
|
||||
"move-to-right-section": "Verplaatsen naar rechter sectie",
|
||||
"next-media": "Volgend media-item",
|
||||
"list-view": "Lijstweergave",
|
||||
"manage-vpn": "VPN-verbindingen",
|
||||
"manage-wifi": "Wi-Fi",
|
||||
"microphone-volume-at": "Microfoonvolume: {volume}%",
|
||||
"move-to-center-section": "Middelste sectie",
|
||||
"move-to-left-section": "Linker sectie",
|
||||
"move-to-right-section": "Rechter sectie",
|
||||
"next-media": "Volgende track",
|
||||
"next-month": "Volgende maand",
|
||||
"night-light-disabled": "Nachtlicht is uitgeschakeld.\nLinks klikken om de modus te doorlopen.\nRechts klikken voor instellingen.",
|
||||
"night-light-enabled": "Nachtlicht is ingeschakeld.\nLinks klikken om de modus te doorlopen.\nRechts klikken voor instellingen.",
|
||||
"night-light-forced": "Nachtlicht is geforceerd.\nLinks klikken om de modus te doorlopen.\nRechts klikken voor instellingen.",
|
||||
"night-light-not-installed": "Nachtlicht is niet beschikbaar.\nwlsunset is niet geïnstalleerd.",
|
||||
"noctalia-performance-disabled": "Noctalia-prestatiemodus is uitgeschakeld.\nLinks klikken om in te schakelen.",
|
||||
"noctalia-performance-enabled": "Noctalia-prestatiemodus is ingeschakeld.\nLinks klikken om uit te schakelen.",
|
||||
"open-control-center": "Bedieningscentrum openen",
|
||||
"open-notification-history-disable-dnd": "Meldingsgeschiedenis openen\nKlik met rechts om \"Niet storen\" uit te schakelen.",
|
||||
"open-notification-history-enable-dnd": "Meldingsgeschiedenis openen\nKlik met rechts om \"Niet storen\" in te schakelen.",
|
||||
"open-settings": "Instellingen openen",
|
||||
"open-tray-dropdown": "Systeemvakmenu openen",
|
||||
"open-wallpaper-selector": "Achtergrondkiezer openen",
|
||||
"output-muted": "Uitvoervolume dempen in-/uitschakelen",
|
||||
"pause": "Pauzeren",
|
||||
"play": "Afspelen",
|
||||
"power-profile": "'{profile}'-energieprofiel",
|
||||
"previous-media": "Vorig media-item",
|
||||
"night-light-disabled": "Nachtlicht",
|
||||
"night-light-enabled": "Nachtlicht",
|
||||
"night-light-forced": "Nachtlicht",
|
||||
"night-light-not-installed": "Nachtlicht (niet beschikbaar)",
|
||||
"noctalia-performance-disabled": "Noctalia-prestatiemodus",
|
||||
"noctalia-performance-enabled": "Noctalia-prestatiemodus",
|
||||
"open-control-center": "Bedieningscentrum",
|
||||
"open-notification-history-disable-dnd": "Meldingsgeschiedenis",
|
||||
"open-notification-history-enable-dnd": "Meldingsgeschiedenis",
|
||||
"open-settings": "Instellingen",
|
||||
"open-tray-dropdown": "Systeemvak",
|
||||
"open-wallpaper-selector": "Achtergrondkiezer",
|
||||
"output-muted": "Audio-uitvoer",
|
||||
"pause": "Pauze-knop",
|
||||
"play": "Afspelen-knop",
|
||||
"power-profile": "{profile} energieprofiel",
|
||||
"previous-media": "Vorige track",
|
||||
"previous-month": "Vorige maand",
|
||||
"refresh": "Verversen",
|
||||
"refresh-devices": "Apparaten verversen",
|
||||
"refresh-wallhaven": "Wallhaven-resultaten verversen",
|
||||
"refresh-wallpaper-list": "Achtergrondlijst verversen",
|
||||
"remove-widget": "Widget verwijderen",
|
||||
"screen-recorder-not-installed": "Schermrecorder is niet geïnstalleerd",
|
||||
"screen-recorder-not-installed": "Schermrecorder (niet geïnstalleerd)",
|
||||
"search": "Zoeken",
|
||||
"search-close": "Zoeken sluiten",
|
||||
"session-menu": "Sessiemenu",
|
||||
"set-power-profile": "\"{profile}\"-energieprofiel instellen",
|
||||
"start-screen-recording": "Schermopname starten",
|
||||
"stop-screen-recording": "Schermopname stoppen",
|
||||
"switch-to-dark-mode": "Overschakelen naar donkere modus",
|
||||
"switch-to-light-mode": "Overschakelen naar lichte modus",
|
||||
"up": "Omhoog",
|
||||
"volume-at": "Uitvoervolume {volume}%\nLinks klikken voor instellingen. Rechts klikken om te dempen.\nScroll om het volume aan te passen.",
|
||||
"wallpaper-selector": "Linker muisknop: achtergrondkiezer openen.\nRechter muisknop: willekeurige achtergrond instellen.",
|
||||
"set-power-profile": "{profile} energieprofiel",
|
||||
"start-screen-recording": "Schermrecorder",
|
||||
"stop-screen-recording": "Schermrecorder",
|
||||
"switch-to-dark-mode": "Donkere modus",
|
||||
"switch-to-light-mode": "Lichte modus",
|
||||
"up": "Bovenliggende map",
|
||||
"volume-at": "Uitvoervolume: {volume}%",
|
||||
"wallpaper-selector": "Achtergrondkiezer",
|
||||
"widget-settings": "Widget-instellingen"
|
||||
},
|
||||
"wallpaper": {
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"device": {
|
||||
"default": "Padrão (Dispositivo de Exibição)",
|
||||
"description": "Selecione qual dispositivo de bateria exibir.",
|
||||
"label": "Dispositivo de bateria"
|
||||
},
|
||||
"display-mode": {
|
||||
"description": "Escolha como você gostaria que este valor aparecesse.",
|
||||
"label": "Modo de exibição"
|
||||
@@ -97,9 +102,13 @@
|
||||
"control-center": {
|
||||
"browse-file": "Navegar por Arquivo",
|
||||
"browse-library": "Navegar na Biblioteca",
|
||||
"colorize-distro-logo": {
|
||||
"description": "Aplicar as cores do tema ao logotipo da sua distribuição.",
|
||||
"label": "Colorir logo da distribuição"
|
||||
"color-selection": {
|
||||
"description": "Aplica as cores do tema aos ícones.",
|
||||
"label": "Selecionar cor"
|
||||
},
|
||||
"enable-colorization": {
|
||||
"description": "Ativa a colorização para o ícone do centro de controlo, aplicando as cores do tema.",
|
||||
"label": "Ativar colorização"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Selecione um ícone da biblioteca ou um arquivo personalizado.",
|
||||
@@ -227,14 +236,14 @@
|
||||
"description": "Exibir artista - título em vez de título - artista.",
|
||||
"label": "Mostrar o artista primeiro"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Exibir um visualizador de áudio quando música está sendo reproduzida.",
|
||||
"label": "Mostrar visualizador"
|
||||
},
|
||||
"show-progress-ring": {
|
||||
"description": "Exibir um indicador de progresso circular mostrando o progresso da faixa.",
|
||||
"label": "Mostrar anel de progresso"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Exibir um visualizador de áudio quando música está sendo reproduzida.",
|
||||
"label": "Mostrar visualizador"
|
||||
},
|
||||
"use-fixed-width": {
|
||||
"description": "Quando ativado, o widget sempre usará a largura máxima em vez de ajustar dinamicamente ao conteúdo.",
|
||||
"label": "Usar Largura Fixa"
|
||||
@@ -271,9 +280,6 @@
|
||||
}
|
||||
},
|
||||
"system-monitor": {
|
||||
"cpu-critical-threshold": {
|
||||
"label": "CPU Usage (Critical)"
|
||||
},
|
||||
"cpu-temperature": {
|
||||
"description": "Mostrar leituras de temperatura da CPU se disponíveis.",
|
||||
"label": "Temperatura da CPU"
|
||||
@@ -282,25 +288,10 @@
|
||||
"description": "Exibir o percentual atual de uso da CPU.",
|
||||
"label": "Uso da CPU"
|
||||
},
|
||||
"cpu-warning-threshold": {
|
||||
"label": "CPU Usage (Warning)"
|
||||
},
|
||||
"disk-critical-threshold": {
|
||||
"label": "Storage Space (Critical)"
|
||||
},
|
||||
"disk-path": {
|
||||
"description": "Selecione qual ponto de montagem de disco monitorar.",
|
||||
"label": "Caminho do disco"
|
||||
},
|
||||
"disk-warning-threshold": {
|
||||
"label": "Storage Space (Warning)"
|
||||
},
|
||||
"mem-critical-threshold": {
|
||||
"label": "Memory Usage (Critical)"
|
||||
},
|
||||
"mem-warning-threshold": {
|
||||
"label": "Memory Usage (Warning)"
|
||||
},
|
||||
"memory-percentage": {
|
||||
"description": "Mostrar o uso de memória como percentual em vez de valores absolutos.",
|
||||
"label": "Memória como percentual"
|
||||
@@ -316,16 +307,6 @@
|
||||
"storage-usage": {
|
||||
"description": "Mostrar informações de uso do espaço em disco.",
|
||||
"label": "Uso de armazenamento"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "CPU Temperature (Critical)"
|
||||
},
|
||||
"temp-warning-threshold": {
|
||||
"label": "CPU Temperature (Warning)"
|
||||
},
|
||||
"thresholds": {
|
||||
"description": "Estabelece limites para alertas de métricas do sistema. Destaca quando superados.",
|
||||
"header": "Configuração de Limiares"
|
||||
}
|
||||
},
|
||||
"taskbar": {
|
||||
@@ -373,14 +354,14 @@
|
||||
"description": "Número de caracteres a exibir dos nomes de espaços de trabalho (1-10).",
|
||||
"label": "Número de caracteres"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Não exibir áreas de trabalho sem janelas.",
|
||||
"label": "Ocultar desocupados"
|
||||
},
|
||||
"follow-focused-screen": {
|
||||
"description": "Exibir áreas de trabalho da tela atualmente em foco, em vez da tela onde a barra está localizada.",
|
||||
"label": "Seguir Tela em Foco"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Não exibir áreas de trabalho sem janelas.",
|
||||
"label": "Ocultar desocupados"
|
||||
},
|
||||
"label-mode": {
|
||||
"description": "Escolher como os rótulos de espaço de trabalho são exibidos.",
|
||||
"label": "Modo de rótulo"
|
||||
@@ -389,8 +370,8 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"battery-level": "Nível da bateria",
|
||||
"brightness": "Brilho",
|
||||
"charge-level": "Nível de carga",
|
||||
"charging": "Carregando.",
|
||||
"charging-rate": "Taxa de carregamento: {rate} W.",
|
||||
"discharging": "Descarregando.",
|
||||
@@ -412,6 +393,7 @@
|
||||
"blocked": "Bloqueado",
|
||||
"connect": "Conectar",
|
||||
"connected-devices": "Dispositivos conectados",
|
||||
"connecting": "Conectando...",
|
||||
"disabled": "O Bluetooth está desativado",
|
||||
"disconnect": "Desconectar",
|
||||
"enable-message": "Ative o Bluetooth para ver os dispositivos disponíveis.",
|
||||
@@ -426,6 +408,19 @@
|
||||
"panel": {
|
||||
"week": "Semana"
|
||||
},
|
||||
"timer": {
|
||||
"countdown": "Contagem regressiva",
|
||||
"duration": "Duração",
|
||||
"hours": "h",
|
||||
"minutes": "m",
|
||||
"pause": "Pausar",
|
||||
"reset": "Reiniciar",
|
||||
"seconds": "s",
|
||||
"start": "Começar",
|
||||
"stopwatch": "Cronômetro",
|
||||
"timer": "Temporizador",
|
||||
"title": "Temporizador"
|
||||
},
|
||||
"weather": {
|
||||
"loading": "Carregando o clima…"
|
||||
}
|
||||
@@ -518,14 +513,20 @@
|
||||
"no-notifications": "Nenhuma notificação",
|
||||
"title": "Notificações"
|
||||
},
|
||||
"range": {
|
||||
"all": "Todas",
|
||||
"earlier": "Mais antigas",
|
||||
"today": "Hoje",
|
||||
"yesterday": "Ontem"
|
||||
},
|
||||
"time": {
|
||||
"now": "agora",
|
||||
"diffM": "há 1 minuto",
|
||||
"diffMM": "há {diff} minutos",
|
||||
"diffD": "há 1 dia",
|
||||
"diffDD": "há {diff} dias",
|
||||
"diffH": "há 1 hora",
|
||||
"diffHH": "há {diff} horas",
|
||||
"diffD": "há 1 dia",
|
||||
"diffDD": "há {diff} dias"
|
||||
"diffM": "há 1 minuto",
|
||||
"diffMM": "há {diff} minutos",
|
||||
"now": "agora"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@@ -545,6 +546,7 @@
|
||||
},
|
||||
"colors": {
|
||||
"error": "Erro",
|
||||
"none": "Nenhum",
|
||||
"onSurface": "Na Superfície",
|
||||
"primary": "Primário",
|
||||
"secondary": "Secundário",
|
||||
@@ -706,7 +708,7 @@
|
||||
"enabled": "Bluetooth"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Clique para gerenciar dispositivos Bluetooth"
|
||||
"action": "Bluetooth"
|
||||
}
|
||||
},
|
||||
"keepAwake": {
|
||||
@@ -715,7 +717,7 @@
|
||||
"enabled": "Manter acordado"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Clique para alternar o modo Manter acordado"
|
||||
"action": "Manter acordado"
|
||||
}
|
||||
},
|
||||
"nightLight": {
|
||||
@@ -725,7 +727,7 @@
|
||||
"forced": "Luz noturna"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Clique para alternar o modo Luz noturna\nClique direito: Abrir configurações"
|
||||
"action": "Luz noturna"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -734,7 +736,7 @@
|
||||
"enabled": "Notificações"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Clique esquerdo: Abrir histórico de notificações\nClique direito: Alternar Não perturbar"
|
||||
"action": "Notificações"
|
||||
}
|
||||
},
|
||||
"powerProfile": {
|
||||
@@ -742,7 +744,7 @@
|
||||
"unavailable": "Perfil de energia"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Clique para alternar o perfil de energia",
|
||||
"action": "Perfil de energia",
|
||||
"disabled": "Instale power-profiles-daemon para usar perfis de energia"
|
||||
}
|
||||
},
|
||||
@@ -752,13 +754,13 @@
|
||||
"stopped": "Gravar"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Clique para iniciar/parar a gravação da tela"
|
||||
"action": "Gravador de tela"
|
||||
}
|
||||
},
|
||||
"wallpaperSelector": {
|
||||
"label": "Papel de parede",
|
||||
"tooltip": {
|
||||
"action": "Clique esquerdo: Abrir seletor de papel de parede\nClique direito: Definir papel de parede aleatório"
|
||||
"action": "Seletor de papel de parede"
|
||||
}
|
||||
},
|
||||
"wifi": {
|
||||
@@ -768,7 +770,7 @@
|
||||
"wifi": "Wi-Fi"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Clique para gerenciar conexões Wi-Fi"
|
||||
"action": "Wi-Fi"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -794,6 +796,8 @@
|
||||
},
|
||||
"noctalia": {
|
||||
"download-latest": "Baixar a versão mais recente",
|
||||
"git-commit": "Commit Git:",
|
||||
"git-commit-loading": "Carregando...",
|
||||
"installed-version": "Versão instalada:",
|
||||
"latest-version": "Última versão:",
|
||||
"section": {
|
||||
@@ -1040,15 +1044,27 @@
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"compositors": {
|
||||
"description": "Tematização do compositor.",
|
||||
"label": "Compositores",
|
||||
"niri": {
|
||||
"description": "Escrever {filepath}. Requer Niri v25.11+",
|
||||
"description-missing": "Requer que o {app} esteja instalado."
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"description": "Opções de configuração adicionais.",
|
||||
"label": "Diversos",
|
||||
"description": "Crie seus próprios modelos",
|
||||
"label": "Avançado",
|
||||
"user-templates": {
|
||||
"description": "Ativa a configuração do Matugen definida pelo usuário. Um arquivo de modelo será criado em ~/.config/noctalia/user-templates.toml na primeira ativação",
|
||||
"label": "Modelos do usuário"
|
||||
"description": "Ative apenas se souber o que está fazendo, consulte nossa documentação online",
|
||||
"label": "Ativar modelos do usuário"
|
||||
}
|
||||
},
|
||||
"programs": {
|
||||
"cava": {
|
||||
"description": "Escreva em {filepath}.",
|
||||
"description-missing": "Requer que o {app} esteja instalado."
|
||||
},
|
||||
"code": {
|
||||
"description": "Escreva em {filepath}. O tema Hyprluna precisa ser instalado e ativado manualmente.",
|
||||
"description-missing": "Nenhum cliente Code detectado. Instale VSCode ou VSCodium."
|
||||
@@ -1075,10 +1091,6 @@
|
||||
"description": "Escreva em {filepath}.",
|
||||
"description-missing": "Requer que o {app} esteja instalado."
|
||||
},
|
||||
"cava": {
|
||||
"description": "Escreva em {filepath}.",
|
||||
"description-missing": "Requer que o {app} esteja instalado."
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Escrever {filepath} e recarregar",
|
||||
"description-missing": "Requer que o {app} esteja instalado"
|
||||
@@ -1262,9 +1274,11 @@
|
||||
},
|
||||
"temperature": {
|
||||
"day": "Dia",
|
||||
"day-description": "Controla a temperatura durante o dia.",
|
||||
"description": "Defina o quão quente a cor será durante a noite e o dia.",
|
||||
"label": "Temperatura da cor",
|
||||
"night": "Noite"
|
||||
"night": "Noite",
|
||||
"night-description": "Controla a temperatura durante a noite."
|
||||
}
|
||||
},
|
||||
"title": "Tela"
|
||||
@@ -1368,7 +1382,8 @@
|
||||
"description": "Edite os detalhes do seu usuário e avatar.",
|
||||
"label": "Perfil"
|
||||
},
|
||||
"select-avatar": "Selecionar imagem de avatar"
|
||||
"select-avatar": "Selecionar imagem de avatar",
|
||||
"tooltip": "Procurar imagem de avatar"
|
||||
},
|
||||
"screen-corners": {
|
||||
"radius": {
|
||||
@@ -1446,6 +1461,10 @@
|
||||
"description": "Usar um prefixo personalizado para inicializar aplicativos em vez do método padrão.",
|
||||
"label": "Habilitar prefixo de inicialização personalizado"
|
||||
},
|
||||
"grid-view": {
|
||||
"description": "Exibir itens em uma grade em vez de uma lista.",
|
||||
"label": "Visualização em grade"
|
||||
},
|
||||
"position": {
|
||||
"description": "Escolha onde o painel do lançador aparece.",
|
||||
"label": "Posição"
|
||||
@@ -1470,6 +1489,20 @@
|
||||
"title": "Lançador"
|
||||
},
|
||||
"location": {
|
||||
"calendar": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "Organize e ative/desative cartões no painel do calendário.",
|
||||
"label": "Cartões de calendário"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"label": "Cabeçalho do Calendário"
|
||||
},
|
||||
"month": {
|
||||
"label": "Mês do calendário"
|
||||
}
|
||||
},
|
||||
"date-time": {
|
||||
"12hour-format": {
|
||||
"description": "Exibe a hora no formato de 12 horas na tela de bloqueio e no calendário. O relógio da barra tem suas próprias configurações.",
|
||||
@@ -1671,7 +1704,29 @@
|
||||
"label": "Geral"
|
||||
}
|
||||
},
|
||||
"title": "Exibição na tela"
|
||||
"title": "Exibição na tela",
|
||||
"types": {
|
||||
"brightness": {
|
||||
"description": "Mostrar o OSD quando o brilho da tela mudar.",
|
||||
"label": "Brilho"
|
||||
},
|
||||
"input-volume": {
|
||||
"description": "Mostrar o OSD quando o volume do microfone mudar.",
|
||||
"label": "Volume de entrada"
|
||||
},
|
||||
"lockkey": {
|
||||
"description": "Mostrar o OSD quando Caps Lock, Num Lock ou Scroll Lock forem alternadas.",
|
||||
"label": "Teclas de bloqueio"
|
||||
},
|
||||
"section": {
|
||||
"description": "Selecione os eventos que acionam o OSD. Se nenhum evento for selecionado, todos os eventos disponíveis acionarão o OSD.",
|
||||
"label": "Eventos de disparo OSD"
|
||||
},
|
||||
"volume": {
|
||||
"description": "Mostrar o OSD quando o volume de saída de áudio mudar.",
|
||||
"label": "Volume de saída"
|
||||
}
|
||||
}
|
||||
},
|
||||
"screen-recorder": {
|
||||
"audio": {
|
||||
@@ -1806,6 +1861,12 @@
|
||||
"memory-section": {
|
||||
"label": "Uso de memória"
|
||||
},
|
||||
"network-section": {
|
||||
"label": "Rede"
|
||||
},
|
||||
"polling-interval": {
|
||||
"label": "Intervalo de pesquisa"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "Limite crítico"
|
||||
},
|
||||
@@ -1816,7 +1877,7 @@
|
||||
"label": "Temperatura da CPU"
|
||||
},
|
||||
"thresholds-section": {
|
||||
"description": "Defina limiares para métricas do sistema; valores acima serão destacados.",
|
||||
"description": "Ajuste os limites de aviso/crítico e os intervalos de consulta para cada métrica do sistema.",
|
||||
"label": "Limiares"
|
||||
},
|
||||
"title": "Monitor do Sistema",
|
||||
@@ -2048,6 +2109,11 @@
|
||||
"unavailable": "Histórico da área de transferência indisponível",
|
||||
"unavailable-desc": "O aplicativo 'cliphist' não está instalado. Por favor, instale-o para usar os recursos do histórico da área de transferência."
|
||||
},
|
||||
"dark-mode": {
|
||||
"dark-mode": "Modo escuro",
|
||||
"enabled": "Ativado",
|
||||
"light-mode": "Modo claro"
|
||||
},
|
||||
"do-not-disturb": {
|
||||
"disabled": "'Não perturbe' desativado",
|
||||
"disabled-desc": "Mostrando todas as notificações.",
|
||||
@@ -2128,64 +2194,70 @@
|
||||
"tooltips": {
|
||||
"add-widget": "Adicionar widget",
|
||||
"bluetooth-devices": "Dispositivos Bluetooth",
|
||||
"brightness-at": "Brilho: {brightness}%\nClique direito para configurações.\nRole para modificar o brilho.",
|
||||
"cancel-timer": "Cancelar temporizador",
|
||||
"brightness-at": "Brilho: {brightness}%",
|
||||
"cancel-timer": "Temporizador",
|
||||
"clear-history": "Limpar histórico",
|
||||
"click-to-start-recording": "Clique para iniciar a gravação",
|
||||
"click-to-stop-recording": "Clique para parar a gravação",
|
||||
"close": "Fechar",
|
||||
"connect-disconnect-devices": "Clique esquerdo para conectar. Clique direito para esquecer.",
|
||||
"click-to-start-recording": "Gravador de tela (iniciar gravação)",
|
||||
"click-to-stop-recording": "Gravador de tela (parar gravação)",
|
||||
"close": "Botão fechar",
|
||||
"connect-disconnect-devices": "Dispositivo Bluetooth",
|
||||
"delete-notification": "Excluir notificação",
|
||||
"disable-keep-awake": "Clique para desativar 'manter acordado'.\nRole para ajustar o tempo limite.",
|
||||
"do-not-disturb-disabled": "'Não perturbe' desativado",
|
||||
"do-not-disturb-enabled": "'Não perturbe' ativado",
|
||||
"enable-keep-awake": "Clique para ativar 'manter acordado'.\nRole para ajustar o tempo limite.",
|
||||
"disable-keep-awake": "Manter acordado",
|
||||
"do-not-disturb-disabled": "Não perturbe",
|
||||
"do-not-disturb-enabled": "Não perturbe",
|
||||
"enable-keep-awake": "Manter acordado",
|
||||
"forget-network": "Esquecer rede",
|
||||
"home": "Início",
|
||||
"input-muted": "Silenciar entrada de áudio",
|
||||
"grid-view": "Visualização em grade",
|
||||
"hidden-files-hide": "Arquivos ocultos",
|
||||
"hidden-files-show": "Arquivos ocultos",
|
||||
"home": "Diretório home",
|
||||
"input-muted": "Microfone",
|
||||
"keep-awake": "Manter acordado",
|
||||
"keyboard-layout": "Layout de teclado {layout}",
|
||||
"manage-vpn": "Gerenciar conexões VPN",
|
||||
"manage-wifi": "Gerenciar Wi-Fi",
|
||||
"microphone-volume-at": "Volume do microfone em {volume}%.\nClique esquerdo para configurações. Clique direito para ativar/desativar o mudo.\nRole para modificar o volume.",
|
||||
"move-to-center-section": "Mover para a seção central",
|
||||
"move-to-left-section": "Mover para a seção esquerda",
|
||||
"move-to-right-section": "Mover para a seção direita",
|
||||
"next-media": "Próxima mídia",
|
||||
"list-view": "Visualização em lista",
|
||||
"manage-vpn": "Conexões VPN",
|
||||
"manage-wifi": "Wi-Fi",
|
||||
"microphone-volume-at": "Volume do microfone: {volume}%",
|
||||
"move-to-center-section": "Seção central",
|
||||
"move-to-left-section": "Seção esquerda",
|
||||
"move-to-right-section": "Seção direita",
|
||||
"next-media": "Próxima faixa",
|
||||
"next-month": "Próximo mês",
|
||||
"night-light-disabled": "Luz noturna desativada.\nClique esquerdo para alternar o modo.\nClique direito para acessar as configurações.",
|
||||
"night-light-enabled": "Luz noturna ativada.\nClique esquerdo para alternar o modo.\nClique direito para acessar as configurações.",
|
||||
"night-light-forced": "Luz noturna forçada.\nClique esquerdo para alternar o modo.\nClique direito para acessar as configurações.",
|
||||
"night-light-not-installed": "Luz noturna não disponível.\nwlsunset não está instalado.",
|
||||
"noctalia-performance-disabled": "O modo de desempenho Noctalia está desativado.\nClique esquerdo para ativar.",
|
||||
"noctalia-performance-enabled": "O modo de desempenho Noctalia está ativado.\nClique esquerdo para desativar.",
|
||||
"open-control-center": "Abrir central de controle",
|
||||
"open-notification-history-disable-dnd": "Abrir histórico de notificações\nClique direito para desativar \"Não perturbe\".",
|
||||
"open-notification-history-enable-dnd": "Abrir histórico de notificações\nClique direito para ativar \"Não perturbe\".",
|
||||
"open-settings": "Abrir configurações",
|
||||
"open-tray-dropdown": "Abrir menu suspenso da bandeja",
|
||||
"open-wallpaper-selector": "Abrir seletor de papel de parede",
|
||||
"output-muted": "Silenciar saída de áudio",
|
||||
"pause": "Pausar",
|
||||
"play": "Reproduzir",
|
||||
"power-profile": "Perfil de energia '{profile}'",
|
||||
"previous-media": "Mídia anterior",
|
||||
"night-light-disabled": "Luz noturna",
|
||||
"night-light-enabled": "Luz noturna",
|
||||
"night-light-forced": "Luz noturna",
|
||||
"night-light-not-installed": "Luz noturna (não disponível)",
|
||||
"noctalia-performance-disabled": "Modo de desempenho Noctalia",
|
||||
"noctalia-performance-enabled": "Modo de desempenho Noctalia",
|
||||
"open-control-center": "Central de controle",
|
||||
"open-notification-history-disable-dnd": "Histórico de notificações",
|
||||
"open-notification-history-enable-dnd": "Histórico de notificações",
|
||||
"open-settings": "Configurações",
|
||||
"open-tray-dropdown": "Bandeja do sistema",
|
||||
"open-wallpaper-selector": "Seletor de papel de parede",
|
||||
"output-muted": "Saída de áudio",
|
||||
"pause": "Botão pausar",
|
||||
"play": "Botão reproduzir",
|
||||
"power-profile": "{profile} perfil de energia",
|
||||
"previous-media": "Faixa anterior",
|
||||
"previous-month": "Mês anterior",
|
||||
"refresh": "Atualizar",
|
||||
"refresh-devices": "Atualizar dispositivos",
|
||||
"refresh-wallhaven": "Atualizar resultados do Wallhaven",
|
||||
"refresh-wallpaper-list": "Atualizar lista de papéis de parede",
|
||||
"remove-widget": "Remover widget",
|
||||
"screen-recorder-not-installed": "O gravador de tela não está instalado",
|
||||
"screen-recorder-not-installed": "Gravador de tela (não instalado)",
|
||||
"search": "Pesquisar",
|
||||
"search-close": "Fechar pesquisa",
|
||||
"session-menu": "Menu da Sessão",
|
||||
"set-power-profile": "Definir perfil de energia \"{profile}\"",
|
||||
"start-screen-recording": "Iniciar gravação de tela",
|
||||
"stop-screen-recording": "Parar gravação de tela",
|
||||
"switch-to-dark-mode": "Mudar para o modo escuro",
|
||||
"switch-to-light-mode": "Mudar para o modo claro",
|
||||
"up": "Acima",
|
||||
"volume-at": "Volume de saída em {volume}%.\nClique esquerdo para configurações. Clique direito para alternar o mudo.\nRole para modificar o volume.",
|
||||
"wallpaper-selector": "Clique esquerdo: Abrir seletor de papel de parede.\nClique direito: Definir papel de parede aleatório.",
|
||||
"set-power-profile": "{profile} perfil de energia",
|
||||
"start-screen-recording": "Gravador de tela",
|
||||
"stop-screen-recording": "Gravador de tela",
|
||||
"switch-to-dark-mode": "Modo escuro",
|
||||
"switch-to-light-mode": "Modo claro",
|
||||
"up": "Diretório superior",
|
||||
"volume-at": "Volume de saída: {volume}%",
|
||||
"wallpaper-selector": "Seletor de papel de parede",
|
||||
"widget-settings": "Configurações do widget"
|
||||
},
|
||||
"wallpaper": {
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"device": {
|
||||
"default": "Устройство отображения по умолчанию",
|
||||
"description": "Выберите, какое устройство с батареей отображать.",
|
||||
"label": "Батарейное устройство"
|
||||
},
|
||||
"display-mode": {
|
||||
"description": "Выберите, как это значение должно отображаться.",
|
||||
"label": "Режим отображения"
|
||||
@@ -97,9 +102,13 @@
|
||||
"control-center": {
|
||||
"browse-file": "Обзор файла",
|
||||
"browse-library": "Обзор библиотеки",
|
||||
"colorize-distro-logo": {
|
||||
"description": "Применить цвета темы к логотипу вашего дистрибутива.",
|
||||
"label": "Раскрасить логотип дистрибутива"
|
||||
"color-selection": {
|
||||
"description": "Применяет цвета темы к значкам.",
|
||||
"label": "Выбор цвета"
|
||||
},
|
||||
"enable-colorization": {
|
||||
"description": "Включает окрашивание для значка центра управления, применяя цвета темы.",
|
||||
"label": "Включить окрашивание"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Выберите иконку из библиотеки или пользовательский файл.",
|
||||
@@ -227,14 +236,14 @@
|
||||
"description": "Исполнитель - название вместо название - исполнитель.",
|
||||
"label": "Сначала исполнитель"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Отображать аудиовизуализатор при воспроизведении музыки.",
|
||||
"label": "Показывать визуализатор"
|
||||
},
|
||||
"show-progress-ring": {
|
||||
"description": "Отображать круговой индикатор прогресса воспроизведения трека.",
|
||||
"label": "Показывать кольцо прогресса"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Отображать аудиовизуализатор при воспроизведении музыки.",
|
||||
"label": "Показывать визуализатор"
|
||||
},
|
||||
"use-fixed-width": {
|
||||
"description": "Если включено, виджет всегда будет использовать максимальную ширину вместо динамической подстройки под содержимое.",
|
||||
"label": "Использовать фиксированную ширину"
|
||||
@@ -271,9 +280,6 @@
|
||||
}
|
||||
},
|
||||
"system-monitor": {
|
||||
"cpu-critical-threshold": {
|
||||
"label": "Использование CPU (Критично)"
|
||||
},
|
||||
"cpu-temperature": {
|
||||
"description": "Показывать показания температуры процессора, если доступны.",
|
||||
"label": "Температура CPU"
|
||||
@@ -282,25 +288,10 @@
|
||||
"description": "Отображать текущий процент использования CPU.",
|
||||
"label": "Использование CPU"
|
||||
},
|
||||
"cpu-warning-threshold": {
|
||||
"label": "Использование CPU (Предупреждение)"
|
||||
},
|
||||
"disk-critical-threshold": {
|
||||
"label": "Место на диске (Критично)"
|
||||
},
|
||||
"disk-path": {
|
||||
"description": "Выберите точку монтирования диска для мониторинга.",
|
||||
"label": "Путь к диску"
|
||||
},
|
||||
"disk-warning-threshold": {
|
||||
"label": "Место на диске (Предупреждение)"
|
||||
},
|
||||
"mem-critical-threshold": {
|
||||
"label": "Использование памяти (Критично)"
|
||||
},
|
||||
"mem-warning-threshold": {
|
||||
"label": "Использование памяти (Предупреждение)"
|
||||
},
|
||||
"memory-percentage": {
|
||||
"description": "Показывать использование памяти в процентах вместо абсолютных значений.",
|
||||
"label": "Память в процентах"
|
||||
@@ -316,16 +307,6 @@
|
||||
"storage-usage": {
|
||||
"description": "Показывать информацию об использовании дискового пространства.",
|
||||
"label": "Использование хранилища"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "Температура CPU: (Критично)"
|
||||
},
|
||||
"temp-warning-threshold": {
|
||||
"label": "Температура CPU (Внимание)"
|
||||
},
|
||||
"thresholds": {
|
||||
"description": "Установите пороги для оповещений системных метрик. Выделяет при превышении.",
|
||||
"header": "Пороговые значения"
|
||||
}
|
||||
},
|
||||
"taskbar": {
|
||||
@@ -373,14 +354,14 @@
|
||||
"description": "Количество символов для отображения из имен рабочих пространств (1-10).",
|
||||
"label": "Количество символов"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Не отображать рабочие пространства без окон.",
|
||||
"label": "Скрыть незанятые"
|
||||
},
|
||||
"follow-focused-screen": {
|
||||
"description": "Отображать рабочие пространства с текущего активного экрана, а не с экрана, на котором расположена панель.",
|
||||
"label": "Следовать за Активным Экраном"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Не отображать рабочие пространства без окон.",
|
||||
"label": "Скрыть незанятые"
|
||||
},
|
||||
"label-mode": {
|
||||
"description": "Выберите, как отображаются метки рабочих пространств.",
|
||||
"label": "Режим метки"
|
||||
@@ -389,8 +370,8 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"battery-level": "Уровень заряда батареи",
|
||||
"brightness": "Яркость",
|
||||
"charge-level": "Уровень заряда",
|
||||
"charging": "Зарядка.",
|
||||
"charging-rate": "Скорость зарядки: {rate} Вт.",
|
||||
"discharging": "Разрядка.",
|
||||
@@ -412,6 +393,7 @@
|
||||
"blocked": "Заблокировано",
|
||||
"connect": "Подключить",
|
||||
"connected-devices": "Подключенные устройства",
|
||||
"connecting": "Подключение…",
|
||||
"disabled": "Bluetooth отключен",
|
||||
"disconnect": "Отключить",
|
||||
"enable-message": "Включите Bluetooth, чтобы увидеть доступные устройства.",
|
||||
@@ -426,6 +408,19 @@
|
||||
"panel": {
|
||||
"week": "Неделя"
|
||||
},
|
||||
"timer": {
|
||||
"countdown": "Обратный отсчёт",
|
||||
"duration": "Продолжительность",
|
||||
"hours": "ч",
|
||||
"minutes": "м",
|
||||
"pause": "Пауза",
|
||||
"reset": "Сброс",
|
||||
"seconds": "с",
|
||||
"start": "Начать",
|
||||
"stopwatch": "Секундомер",
|
||||
"timer": "Таймер",
|
||||
"title": "Таймер"
|
||||
},
|
||||
"weather": {
|
||||
"loading": "Загрузка погоды…"
|
||||
}
|
||||
@@ -518,14 +513,20 @@
|
||||
"no-notifications": "Нет уведомлений",
|
||||
"title": "Уведомления"
|
||||
},
|
||||
"range": {
|
||||
"all": "Все",
|
||||
"earlier": "Ранее",
|
||||
"today": "Сегодня",
|
||||
"yesterday": "Вчера"
|
||||
},
|
||||
"time": {
|
||||
"now": "сейчас",
|
||||
"diffM": "1 минуту назад",
|
||||
"diffMM": "{diff} минут назад",
|
||||
"diffD": "1 день назад",
|
||||
"diffDD": "{diff} дней назад",
|
||||
"diffH": "1 час назад",
|
||||
"diffHH": "{diff} часов назад",
|
||||
"diffD": "1 день назад",
|
||||
"diffDD": "{diff} дней назад"
|
||||
"diffM": "1 минуту назад",
|
||||
"diffMM": "{diff} минут назад",
|
||||
"now": "сейчас"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@@ -545,6 +546,7 @@
|
||||
},
|
||||
"colors": {
|
||||
"error": "Ошибка",
|
||||
"none": "Ничего",
|
||||
"onSurface": "На поверхности",
|
||||
"primary": "Основной",
|
||||
"secondary": "Второстепенный",
|
||||
@@ -706,7 +708,7 @@
|
||||
"enabled": "Bluetooth"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Нажмите, чтобы управлять устройствами Bluetooth"
|
||||
"action": "Bluetooth"
|
||||
}
|
||||
},
|
||||
"keepAwake": {
|
||||
@@ -715,7 +717,7 @@
|
||||
"enabled": "Не засыпать"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Нажмите, чтобы переключить режим 'Не засыпать'"
|
||||
"action": "Не засыпать"
|
||||
}
|
||||
},
|
||||
"nightLight": {
|
||||
@@ -725,7 +727,7 @@
|
||||
"forced": "Ночной свет"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Нажмите, чтобы переключить режим Ночного света\nПравая кнопка мыши: Открыть настройки"
|
||||
"action": "Ночной свет"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -734,7 +736,7 @@
|
||||
"enabled": "Уведомления"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Левая кнопка мыши: Открыть историю уведомлений\nПравая кнопка мыши: Переключить 'Не беспокоить'"
|
||||
"action": "Уведомления"
|
||||
}
|
||||
},
|
||||
"powerProfile": {
|
||||
@@ -742,7 +744,7 @@
|
||||
"unavailable": "Профиль питания"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Нажмите, чтобы переключить профиль питания",
|
||||
"action": "Профиль питания",
|
||||
"disabled": "Установите power-profiles-daemon, чтобы использовать профили питания"
|
||||
}
|
||||
},
|
||||
@@ -752,13 +754,13 @@
|
||||
"stopped": "Записать"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Нажмите, чтобы начать/остановить запись экрана"
|
||||
"action": "Запись экрана"
|
||||
}
|
||||
},
|
||||
"wallpaperSelector": {
|
||||
"label": "Обои",
|
||||
"tooltip": {
|
||||
"action": "Левая кнопка мыши: Открыть выбор обоев\nПравая кнопка мыши: Установить случайные обои"
|
||||
"action": "Выбор обоев"
|
||||
}
|
||||
},
|
||||
"wifi": {
|
||||
@@ -768,7 +770,7 @@
|
||||
"wifi": "Wi-Fi"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Нажмите, чтобы управлять Wi-Fi подключениями"
|
||||
"action": "Wi-Fi"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -794,6 +796,8 @@
|
||||
},
|
||||
"noctalia": {
|
||||
"download-latest": "Скачать последний выпуск",
|
||||
"git-commit": "Git коммит:",
|
||||
"git-commit-loading": "Загрузка...",
|
||||
"installed-version": "Установленная версия:",
|
||||
"latest-version": "Последняя версия:",
|
||||
"section": {
|
||||
@@ -1040,15 +1044,27 @@
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"compositors": {
|
||||
"description": "Оформление композитора.",
|
||||
"label": "Компоновщики",
|
||||
"niri": {
|
||||
"description": "Записать {filepath}. Требуется niri v25.11+",
|
||||
"description-missing": "Требуется установка {app}"
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"description": "Дополнительные параметры конфигурации.",
|
||||
"label": "Разное",
|
||||
"description": "Создайте свои собственные шаблоны",
|
||||
"label": "Дополнительно",
|
||||
"user-templates": {
|
||||
"description": "Включить пользовательскую конфигурацию Matugen. Файл шаблона будет создан в ~/.config/noctalia/user-templates.toml при первом включении.",
|
||||
"label": "Пользовательские шаблоны"
|
||||
"description": "Включайте только если вы знаете, что делаете, обратитесь к нашей онлайн-документации",
|
||||
"label": "Включить пользовательские шаблоны"
|
||||
}
|
||||
},
|
||||
"programs": {
|
||||
"cava": {
|
||||
"description": "Записать {filepath}.",
|
||||
"description-missing": "Требуется установка {app}"
|
||||
},
|
||||
"code": {
|
||||
"description": "Записать {filepath}. Тему Hyprluna нужно установить и активировать вручную.",
|
||||
"description-missing": "Клиент Code не обнаружен. Установите VSCode или VSCodium."
|
||||
@@ -1075,10 +1091,6 @@
|
||||
"description": "Записать {filepath}.",
|
||||
"description-missing": "Требуется установка {app}"
|
||||
},
|
||||
"cava": {
|
||||
"description": "Записать {filepath}.",
|
||||
"description-missing": "Требуется установка {app}"
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Записать {filepath} и перезагрузить",
|
||||
"description-missing": "Требуется установка {app}"
|
||||
@@ -1262,9 +1274,11 @@
|
||||
},
|
||||
"temperature": {
|
||||
"day": "День",
|
||||
"day-description": "Управляет цветовой температурой в дневное время.",
|
||||
"description": "Установите цветовую температуру для ночного и дневного времени.",
|
||||
"label": "Цветовая температура",
|
||||
"night": "Ночь"
|
||||
"night": "Ночь",
|
||||
"night-description": "Управляет цветовой температурой в ночное время."
|
||||
}
|
||||
},
|
||||
"title": "Дисплей"
|
||||
@@ -1368,7 +1382,8 @@
|
||||
"description": "Редактируйте данные пользователя и аватар.",
|
||||
"label": "Профиль"
|
||||
},
|
||||
"select-avatar": "Выбрать изображение аватара"
|
||||
"select-avatar": "Выбрать изображение аватара",
|
||||
"tooltip": "Выбрать изображение аватара"
|
||||
},
|
||||
"screen-corners": {
|
||||
"radius": {
|
||||
@@ -1446,6 +1461,10 @@
|
||||
"description": "Использовать пользовательский префикс для запуска приложений вместо метода по умолчанию.",
|
||||
"label": "Включить пользовательский префикс запуска"
|
||||
},
|
||||
"grid-view": {
|
||||
"description": "Показывать элементы в виде сетки вместо списка.",
|
||||
"label": "Вид сетки"
|
||||
},
|
||||
"position": {
|
||||
"description": "Выберите, где появляется панель запуска.",
|
||||
"label": "Положение"
|
||||
@@ -1470,6 +1489,20 @@
|
||||
"title": "Запуск"
|
||||
},
|
||||
"location": {
|
||||
"calendar": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "Организуйте и включайте/выключайте карточки в панели календаря.",
|
||||
"label": "Карточки календаря"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"label": "Заголовок календаря"
|
||||
},
|
||||
"month": {
|
||||
"label": "Календарный месяц"
|
||||
}
|
||||
},
|
||||
"date-time": {
|
||||
"12hour-format": {
|
||||
"description": "Отображает время в 12-часовом формате на экране блокировки и в календаре. Часы на панели имеют собственные настройки.",
|
||||
@@ -1671,7 +1704,29 @@
|
||||
"label": "Общие"
|
||||
}
|
||||
},
|
||||
"title": "Экранное отображение (OSD)"
|
||||
"title": "Экранное отображение (OSD)",
|
||||
"types": {
|
||||
"brightness": {
|
||||
"description": "Показывать OSD при изменении яркости экрана.",
|
||||
"label": "Яркость"
|
||||
},
|
||||
"input-volume": {
|
||||
"description": "Показывать OSD при изменении громкости микрофона.",
|
||||
"label": "Входная громкость"
|
||||
},
|
||||
"lockkey": {
|
||||
"description": "Показывать OSD при переключении клавиш Caps Lock, Num Lock или Scroll Lock.",
|
||||
"label": "Клавиши блокировки"
|
||||
},
|
||||
"section": {
|
||||
"description": "Выберите события, которые должны запускать экранное меню (OSD). Если события не выбраны, экранное меню будет запускаться при любом доступном событии.",
|
||||
"label": "События, запускающие экранное меню"
|
||||
},
|
||||
"volume": {
|
||||
"description": "Показывать OSD при изменении громкости аудиовыхода.",
|
||||
"label": "Выходная громкость"
|
||||
}
|
||||
}
|
||||
},
|
||||
"screen-recorder": {
|
||||
"audio": {
|
||||
@@ -1806,6 +1861,12 @@
|
||||
"memory-section": {
|
||||
"label": "Использование памяти"
|
||||
},
|
||||
"network-section": {
|
||||
"label": "Сеть"
|
||||
},
|
||||
"polling-interval": {
|
||||
"label": "Интервал опроса"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "Критический порог"
|
||||
},
|
||||
@@ -1816,7 +1877,7 @@
|
||||
"label": "Температура ЦП"
|
||||
},
|
||||
"thresholds-section": {
|
||||
"description": "Установите пороги для системных метрик; значения выше будут выделены.",
|
||||
"description": "Настройте пороги предупреждения/критические пороги и интервалы опроса для каждой системной метрики.",
|
||||
"label": "Пороги"
|
||||
},
|
||||
"title": "Системный монитор",
|
||||
@@ -2048,6 +2109,11 @@
|
||||
"unavailable": "История буфера обмена недоступна",
|
||||
"unavailable-desc": "Приложение 'cliphist' не установлено. Пожалуйста, установите его, чтобы использовать функции истории буфера обмена."
|
||||
},
|
||||
"dark-mode": {
|
||||
"dark-mode": "Тёмный режим",
|
||||
"enabled": "Включен",
|
||||
"light-mode": "Светлый режим"
|
||||
},
|
||||
"do-not-disturb": {
|
||||
"disabled": "Режим 'Не беспокоить' отключен",
|
||||
"disabled-desc": "Отображаются все уведомления.",
|
||||
@@ -2128,64 +2194,70 @@
|
||||
"tooltips": {
|
||||
"add-widget": "Добавить виджет",
|
||||
"bluetooth-devices": "Устройства Bluetooth",
|
||||
"brightness-at": "Яркость: {brightness}%\nПравый клик для настроек.\nПрокрутка для изменения яркости.",
|
||||
"cancel-timer": "Отменить таймер",
|
||||
"brightness-at": "Яркость: {brightness}%",
|
||||
"cancel-timer": "Таймер",
|
||||
"clear-history": "Очистить историю",
|
||||
"click-to-start-recording": "Нажмите, чтобы начать запись",
|
||||
"click-to-stop-recording": "Нажмите, чтобы остановить запись",
|
||||
"close": "Закрыть",
|
||||
"connect-disconnect-devices": "Левый клик для подключения. Правый клик, чтобы забыть.",
|
||||
"click-to-start-recording": "Запись экрана (начать запись)",
|
||||
"click-to-stop-recording": "Запись экрана (остановить запись)",
|
||||
"close": "Кнопка закрыть",
|
||||
"connect-disconnect-devices": "Устройство Bluetooth",
|
||||
"delete-notification": "Удалить уведомление",
|
||||
"disable-keep-awake": "Нажмите, чтобы отключить режим 'Не засыпать'.\nПрокрутка для настройки таймаута.",
|
||||
"do-not-disturb-disabled": "Режим 'Не беспокоить' отключен",
|
||||
"do-not-disturb-enabled": "Режим 'Не беспокоить' включен",
|
||||
"enable-keep-awake": "Нажмите, чтобы включить режим 'Не засыпать'.\nПрокрутка для настройки таймаута.",
|
||||
"disable-keep-awake": "Не засыпать",
|
||||
"do-not-disturb-disabled": "Не беспокоить",
|
||||
"do-not-disturb-enabled": "Не беспокоить",
|
||||
"enable-keep-awake": "Не засыпать",
|
||||
"forget-network": "Забыть сеть",
|
||||
"home": "Домой",
|
||||
"input-muted": "Переключить заглушение ввода",
|
||||
"grid-view": "Вид сеткой",
|
||||
"hidden-files-hide": "Скрытые файлы",
|
||||
"hidden-files-show": "Скрытые файлы",
|
||||
"home": "Домашний каталог",
|
||||
"input-muted": "Микрофон",
|
||||
"keep-awake": "Не засыпать",
|
||||
"keyboard-layout": "Раскладка клавиатуры {layout}",
|
||||
"manage-vpn": "Управлять VPN-подключениями",
|
||||
"manage-wifi": "Управление Wi-Fi",
|
||||
"microphone-volume-at": "Громкость микрофона {volume}%\nЛевый клик для настроек. Правый клик для переключения заглушения.\nПрокрутка для изменения громкости.",
|
||||
"move-to-center-section": "Переместить в центральную секцию",
|
||||
"move-to-left-section": "Переместить в левую секцию",
|
||||
"move-to-right-section": "Переместить в правую секцию",
|
||||
"next-media": "Следующее медиа",
|
||||
"list-view": "Вид списком",
|
||||
"manage-vpn": "VPN-подключения",
|
||||
"manage-wifi": "Wi-Fi",
|
||||
"microphone-volume-at": "Громкость микрофона: {volume}%",
|
||||
"move-to-center-section": "Центральная секция",
|
||||
"move-to-left-section": "Левая секция",
|
||||
"move-to-right-section": "Правая секция",
|
||||
"next-media": "Следующий трек",
|
||||
"next-month": "Следующий месяц",
|
||||
"night-light-disabled": "Ночной свет отключен.\nЛевый клик для переключения режима.\nПравый клик для доступа к настройкам.",
|
||||
"night-light-enabled": "Ночной свет включен.\nЛевый клик для переключения режима.\nПравый клик для доступа к настройкам.",
|
||||
"night-light-forced": "Ночной свет принудительно включен.\nЛевый клик для переключения режима.\nПравый клик для доступа к настройкам.",
|
||||
"night-light-not-installed": "Ночной свет недоступен.\nwlsunset не установлен.",
|
||||
"noctalia-performance-disabled": "Режим производительности Noctalia отключен.\nЛевый клик для включения.",
|
||||
"noctalia-performance-enabled": "Режим производительности Noctalia включен.\nЛевый клик для отключения.",
|
||||
"open-control-center": "Открыть центр управления",
|
||||
"open-notification-history-disable-dnd": "Открыть историю уведомлений\nПравый клик, чтобы отключить 'Не беспокоить'.",
|
||||
"open-notification-history-enable-dnd": "Открыть историю уведомлений\nПравый клик, чтобы включить 'Не беспокоить'.",
|
||||
"open-settings": "Открыть настройки",
|
||||
"open-tray-dropdown": "Открыть выпадающий список трея",
|
||||
"open-wallpaper-selector": "Открыть выбор обоев",
|
||||
"output-muted": "Переключить заглушение вывода",
|
||||
"pause": "Пауза",
|
||||
"play": "Воспроизвести",
|
||||
"power-profile": "Профиль питания '{profile}'",
|
||||
"previous-media": "Предыдущее медиа",
|
||||
"night-light-disabled": "Ночной свет",
|
||||
"night-light-enabled": "Ночной свет",
|
||||
"night-light-forced": "Ночной свет",
|
||||
"night-light-not-installed": "Ночной свет (недоступен)",
|
||||
"noctalia-performance-disabled": "Режим производительности Noctalia",
|
||||
"noctalia-performance-enabled": "Режим производительности Noctalia",
|
||||
"open-control-center": "Центр управления",
|
||||
"open-notification-history-disable-dnd": "История уведомлений",
|
||||
"open-notification-history-enable-dnd": "История уведомлений",
|
||||
"open-settings": "Настройки",
|
||||
"open-tray-dropdown": "Системный трей",
|
||||
"open-wallpaper-selector": "Выбор обоев",
|
||||
"output-muted": "Аудиовыход",
|
||||
"pause": "Кнопка пауза",
|
||||
"play": "Кнопка воспроизвести",
|
||||
"power-profile": "{profile} профиль питания",
|
||||
"previous-media": "Предыдущий трек",
|
||||
"previous-month": "Предыдущий месяц",
|
||||
"refresh": "Обновить",
|
||||
"refresh-devices": "Обновить устройства",
|
||||
"refresh-wallhaven": "Обновить результаты Wallhaven",
|
||||
"refresh-wallpaper-list": "Обновить список обоев",
|
||||
"remove-widget": "Удалить виджет",
|
||||
"screen-recorder-not-installed": "Запись экрана не установлена",
|
||||
"screen-recorder-not-installed": "Запись экрана (не установлена)",
|
||||
"search": "Поиск",
|
||||
"search-close": "Закрыть поиск",
|
||||
"session-menu": "Меню сеанса",
|
||||
"set-power-profile": "Установить профиль питания \"{profile}\"",
|
||||
"start-screen-recording": "Начать запись экрана",
|
||||
"stop-screen-recording": "Остановить запись экрана",
|
||||
"switch-to-dark-mode": "Переключить на темный режим",
|
||||
"switch-to-light-mode": "Переключить на светлый режим",
|
||||
"up": "Вверх",
|
||||
"volume-at": "Громкость вывода {volume}%\nЛевый клик для настроек. Правый клик для переключения заглушения.\nПрокрутка для изменения громкости.",
|
||||
"wallpaper-selector": "Левый клик: Открыть выбор обоев.\nПравый клик: Установить случайные обои.",
|
||||
"set-power-profile": "{profile} профиль питания",
|
||||
"start-screen-recording": "Запись экрана",
|
||||
"stop-screen-recording": "Запись экрана",
|
||||
"switch-to-dark-mode": "Темный режим",
|
||||
"switch-to-light-mode": "Светлый режим",
|
||||
"up": "Родительский каталог",
|
||||
"volume-at": "Громкость вывода: {volume}%",
|
||||
"wallpaper-selector": "Выбор обоев",
|
||||
"widget-settings": "Настройки виджета"
|
||||
},
|
||||
"wallpaper": {
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"device": {
|
||||
"default": "Varsayılan (Görüntü Aygıtı)",
|
||||
"description": "Görüntülenecek pil cihazını seçin.",
|
||||
"label": "Pil cihazı"
|
||||
},
|
||||
"display-mode": {
|
||||
"description": "Bu değerin nasıl görünmesini istediğinizi seçin.",
|
||||
"label": "Görüntüleme modu"
|
||||
@@ -97,9 +102,13 @@
|
||||
"control-center": {
|
||||
"browse-file": "Dosyaya Göz At",
|
||||
"browse-library": "Kütüphaneye Göz At",
|
||||
"colorize-distro-logo": {
|
||||
"description": "Dağıtım logonuza tema renklerini uygula.",
|
||||
"label": "Dağıtım logosunu renklendir"
|
||||
"color-selection": {
|
||||
"description": "Simgeye tema renklerini uygular.",
|
||||
"label": "Renk Seç"
|
||||
},
|
||||
"enable-colorization": {
|
||||
"description": "Kontrol merkezi simgesi için renklendirmeyi etkinleştirir, tema renklerini uygular.",
|
||||
"label": "Renklendirmeyi Etkinleştir"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Kütüphaneden veya özel bir dosyadan bir ikon seçin.",
|
||||
@@ -227,14 +236,14 @@
|
||||
"description": "Sanatçı - başlık yerine başlık - sanatçı olarak göster.",
|
||||
"label": "Önce sanatçıyı göster"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Müzik çalarken bir ses görselleştirici göster.",
|
||||
"label": "Görselleştiriciyi göster"
|
||||
},
|
||||
"show-progress-ring": {
|
||||
"description": "Parça ilerlemesini gösteren dairesel bir ilerleme göstergesi gösterin.",
|
||||
"label": "İlerleme halkası göster"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Müzik çalarken bir ses görselleştirici göster.",
|
||||
"label": "Görselleştiriciyi göster"
|
||||
},
|
||||
"use-fixed-width": {
|
||||
"description": "Etkinleştirildiğinde, widget dinamik olarak içerik göre ayarlamak yerine her zaman maksimum genişliği kullanır.",
|
||||
"label": "Sabit Genişlik Kullan"
|
||||
@@ -271,9 +280,6 @@
|
||||
}
|
||||
},
|
||||
"system-monitor": {
|
||||
"cpu-critical-threshold": {
|
||||
"label": "CPU Kullanımı (Kritik)"
|
||||
},
|
||||
"cpu-temperature": {
|
||||
"description": "Mevcut CPU sıcaklık okumalarını gösterilir.",
|
||||
"label": "CPU sıcaklığı"
|
||||
@@ -282,25 +288,10 @@
|
||||
"description": "Mevcut CPU kullanım yüzdesini göster.",
|
||||
"label": "CPU kullanımı"
|
||||
},
|
||||
"cpu-warning-threshold": {
|
||||
"label": "CPU Kullanımı (Uyarı)"
|
||||
},
|
||||
"disk-critical-threshold": {
|
||||
"label": "Depolama Alanı (Kritik)"
|
||||
},
|
||||
"disk-path": {
|
||||
"description": "İzlenecek disk bağlama noktasını seçin.",
|
||||
"label": "Disk yolu"
|
||||
},
|
||||
"disk-warning-threshold": {
|
||||
"label": "Depolama Alanı (Uyarı)"
|
||||
},
|
||||
"mem-critical-threshold": {
|
||||
"label": "Bellek Kullanımı (Kritik)"
|
||||
},
|
||||
"mem-warning-threshold": {
|
||||
"label": "Bellek Kullanımı (Uyarı)"
|
||||
},
|
||||
"memory-percentage": {
|
||||
"description": "Mutlak değerler yerine bellek kullanımını yüzde olarak göster.",
|
||||
"label": "Bellek yüzde olarak"
|
||||
@@ -316,16 +307,6 @@
|
||||
"storage-usage": {
|
||||
"description": "Disk alanı kullanım bilgilerini göster.",
|
||||
"label": "Depolama kullanımı"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "CPU Sıcaklığı (Kritik)"
|
||||
},
|
||||
"temp-warning-threshold": {
|
||||
"label": "CPU Sıcaklığı (Uyarı)"
|
||||
},
|
||||
"thresholds": {
|
||||
"description": "Sistem metrik eşiklerini ayarlayın. Aşıldığında vurgular.",
|
||||
"header": "Eşik Ayarları"
|
||||
}
|
||||
},
|
||||
"taskbar": {
|
||||
@@ -373,14 +354,14 @@
|
||||
"description": "Çalışma alanı adlarından gösterilecek karakter sayısı (1-10).",
|
||||
"label": "Karakter sayısı"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Penceresi olmayan çalışma alanlarını gösterme.",
|
||||
"label": "Dolu olmayanları gizle"
|
||||
},
|
||||
"follow-focused-screen": {
|
||||
"description": "Çubuğun bulunduğu ekran yerine, şu anda odaklanmış ekrandaki çalışma alanlarını göster.",
|
||||
"label": "Odaklanmış Ekranı Takip Et"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Penceresi olmayan çalışma alanlarını gösterme.",
|
||||
"label": "Dolu olmayanları gizle"
|
||||
},
|
||||
"label-mode": {
|
||||
"description": "Çalışma alanı etiketlerinin nasıl gösterileceğini seçin.",
|
||||
"label": "Etiket Modu"
|
||||
@@ -389,8 +370,8 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"battery-level": "Pil seviyesi",
|
||||
"brightness": "Parlaklık",
|
||||
"charge-level": "Şarj seviyesi",
|
||||
"charging": "Şarj oluyor.",
|
||||
"charging-rate": "Şarj oranı: {rate} W.",
|
||||
"discharging": "Deşarj oluyor.",
|
||||
@@ -412,6 +393,7 @@
|
||||
"blocked": "Engellendi",
|
||||
"connect": "Bağlan",
|
||||
"connected-devices": "Bağlı cihazlar",
|
||||
"connecting": "Bağlanılıyor…",
|
||||
"disabled": "Bluetooth devre dışı",
|
||||
"disconnect": "Bağlantıyı Kes",
|
||||
"enable-message": "Mevcut cihazları görmek için Bluetooth'u etkinleştirin.",
|
||||
@@ -426,6 +408,19 @@
|
||||
"panel": {
|
||||
"week": "Hafta"
|
||||
},
|
||||
"timer": {
|
||||
"countdown": "Geri sayım",
|
||||
"duration": "Süre",
|
||||
"hours": "h",
|
||||
"minutes": "m",
|
||||
"pause": "Duraklat",
|
||||
"reset": "Sıfırla",
|
||||
"seconds": "s",
|
||||
"start": "Başla",
|
||||
"stopwatch": "Kronometre",
|
||||
"timer": "Zamanlayıcı",
|
||||
"title": "Zamanlayıcı"
|
||||
},
|
||||
"weather": {
|
||||
"loading": "Hava durumu yükleniyor..."
|
||||
}
|
||||
@@ -518,14 +513,20 @@
|
||||
"no-notifications": "Bildirim yok",
|
||||
"title": "Bildirimler"
|
||||
},
|
||||
"range": {
|
||||
"all": "Tümü",
|
||||
"earlier": "Öncekiler",
|
||||
"today": "Bugün",
|
||||
"yesterday": "Dün"
|
||||
},
|
||||
"time": {
|
||||
"now": "şimdi",
|
||||
"diffM": "1 dakika önce",
|
||||
"diffMM": "{diff} dakika önce",
|
||||
"diffD": "1 gün önce",
|
||||
"diffDD": "{diff} gün önce",
|
||||
"diffH": "1 saat önce",
|
||||
"diffHH": "{diff} saat önce",
|
||||
"diffD": "1 gün önce",
|
||||
"diffDD": "{diff} gün önce"
|
||||
"diffM": "1 dakika önce",
|
||||
"diffMM": "{diff} dakika önce",
|
||||
"now": "şimdi"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@@ -545,6 +546,7 @@
|
||||
},
|
||||
"colors": {
|
||||
"error": "Hata",
|
||||
"none": "Hiçbiri",
|
||||
"onSurface": "Üstünde",
|
||||
"primary": "Birincil",
|
||||
"secondary": "İkincil",
|
||||
@@ -706,7 +708,7 @@
|
||||
"enabled": "Bluetooth"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Tıklayarak Bluetooth cihazlarını yönet"
|
||||
"action": "Bluetooth"
|
||||
}
|
||||
},
|
||||
"keepAwake": {
|
||||
@@ -715,7 +717,7 @@
|
||||
"enabled": "Uyanık Tut"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Tıklayarak uyanık kalma modunu aç/kapat"
|
||||
"action": "Uyanık Tut"
|
||||
}
|
||||
},
|
||||
"nightLight": {
|
||||
@@ -725,7 +727,7 @@
|
||||
"forced": "Gece Işığı"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Tıklayarak gece ışığı modunu değiştir\nSağ tık: Ayarları aç"
|
||||
"action": "Gece ışığı"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -734,7 +736,7 @@
|
||||
"enabled": "Bildirimler"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Sol tık: Bildirim geçmişini aç\nSağ tık: Rahatsız etmeyi aç/kapat"
|
||||
"action": "Bildirimler"
|
||||
}
|
||||
},
|
||||
"powerProfile": {
|
||||
@@ -742,7 +744,7 @@
|
||||
"unavailable": "Güç Profili"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Tıklayarak güç profilini değiştir",
|
||||
"action": "Güç profili",
|
||||
"disabled": "Güç profillerini kullanmak için power-profiles-daemon kur"
|
||||
}
|
||||
},
|
||||
@@ -752,13 +754,13 @@
|
||||
"stopped": "Kaydet"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Tıklayarak ekran kaydını başlat/durdur"
|
||||
"action": "Ekran kaydedici"
|
||||
}
|
||||
},
|
||||
"wallpaperSelector": {
|
||||
"label": "Duvar Kağıdı",
|
||||
"tooltip": {
|
||||
"action": "Sol tık: Duvar kağıdı seçiciyi aç\nSağ tık: Rastgele duvar kağıdı ayarla"
|
||||
"action": "Duvar kağıdı seçici"
|
||||
}
|
||||
},
|
||||
"wifi": {
|
||||
@@ -768,7 +770,7 @@
|
||||
"wifi": "Wi-Fi"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Tıklayarak Wi-Fi bağlantılarını yönet"
|
||||
"action": "Wi-Fi"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -794,6 +796,8 @@
|
||||
},
|
||||
"noctalia": {
|
||||
"download-latest": "En son sürümü indir",
|
||||
"git-commit": "Git commit:",
|
||||
"git-commit-loading": "Yükleniyor...",
|
||||
"installed-version": "Yüklü sürüm:",
|
||||
"latest-version": "En son sürüm:",
|
||||
"section": {
|
||||
@@ -1040,15 +1044,27 @@
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"compositors": {
|
||||
"description": "Birleştirici temalandırma.",
|
||||
"label": "Birleştiriciler",
|
||||
"niri": {
|
||||
"description": "{filepath} dosyasına yaz. niri v25.11+ gerektirir",
|
||||
"description-missing": "{app} yüklü olmalıdır"
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"description": "Ek yapılandırma seçenekleri.",
|
||||
"label": "Çeşitli",
|
||||
"description": "Kendi şablonlarınızı oluşturun",
|
||||
"label": "Gelişmiş",
|
||||
"user-templates": {
|
||||
"description": "Kullanıcı tanımlı Matugen yapılandırmasını etkinleştir. İlk etkinleştirmede ~/.config/noctalia/user-templates.toml dosyası oluşturulacaktır",
|
||||
"label": "Kullanıcı şablonları"
|
||||
"description": "Yalnızca ne yaptığınızı biliyorsanız etkinleştirin, çevrimiçi belgelerimize bakın",
|
||||
"label": "Kullanıcı şablonlarını etkinleştir"
|
||||
}
|
||||
},
|
||||
"programs": {
|
||||
"cava": {
|
||||
"description": "{filepath} dosyasına yaz.",
|
||||
"description-missing": "Kurulum için {app} gereklidir"
|
||||
},
|
||||
"code": {
|
||||
"description": "{filepath} dosyasına yaz. Hyprluna temasının kurulu ve manuel olarak etkinleştirilmiş olması gerekir.",
|
||||
"description-missing": "Code istemcisi tespit edilmedi. VSCode veya VSCodium kurun."
|
||||
@@ -1075,10 +1091,6 @@
|
||||
"description": "{filepath} dosyasına yaz.",
|
||||
"description-missing": "Kurulum için {app} gereklidir"
|
||||
},
|
||||
"cava": {
|
||||
"description": "{filepath} dosyasına yaz.",
|
||||
"description-missing": "Kurulum için {app} gereklidir"
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "{filepath} dosyasına yaz ve yeniden yükle",
|
||||
"description-missing": "Kurulum için {app} gereklidir"
|
||||
@@ -1262,9 +1274,11 @@
|
||||
},
|
||||
"temperature": {
|
||||
"day": "Gündüz",
|
||||
"day-description": "Gündüz saatlerindeki renk sıcaklığını kontrol eder.",
|
||||
"description": "Gece ve gündüz için renk sıcaklığını ayarlayın.",
|
||||
"label": "Renk sıcaklığı",
|
||||
"night": "Gece"
|
||||
"night": "Gece",
|
||||
"night-description": "Gece saatlerindeki renk sıcaklığını kontrol eder."
|
||||
}
|
||||
},
|
||||
"title": "Görüntü"
|
||||
@@ -1368,7 +1382,8 @@
|
||||
"description": "Kullanıcı detaylarınızı ve avatarınızı düzenleyin.",
|
||||
"label": "Profil"
|
||||
},
|
||||
"select-avatar": "Avatar görüntüsü seç"
|
||||
"select-avatar": "Avatar görüntüsü seç",
|
||||
"tooltip": "Avatar resmi ara"
|
||||
},
|
||||
"screen-corners": {
|
||||
"radius": {
|
||||
@@ -1446,6 +1461,10 @@
|
||||
"description": "Uygulamaları başlatmak için varsayılan yöntem yerine özel bir ön ek kullanın.",
|
||||
"label": "Özel başlatma önekini etkinleştir"
|
||||
},
|
||||
"grid-view": {
|
||||
"description": "Öğeleri liste yerine ızgara düzeninde görüntüle.",
|
||||
"label": "Izgara görünümü"
|
||||
},
|
||||
"position": {
|
||||
"description": "Başlatıcı panelinin nerede görüneceğini seçin.",
|
||||
"label": "Konum"
|
||||
@@ -1470,6 +1489,20 @@
|
||||
"title": "Başlatıcı"
|
||||
},
|
||||
"location": {
|
||||
"calendar": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "Takvim panelinde kartları düzenleyin ve etkinleştirin/devre dışı bırakın.",
|
||||
"label": "Takvim kartları"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"label": "Takvim Başlığı"
|
||||
},
|
||||
"month": {
|
||||
"label": "Takvim Ayı"
|
||||
}
|
||||
},
|
||||
"date-time": {
|
||||
"12hour-format": {
|
||||
"description": "Zamanı kilit ekranında ve takvimde 12 saatlik formatta gösterir. Çubuk saatinin kendi ayarları vardır.",
|
||||
@@ -1671,7 +1704,29 @@
|
||||
"label": "Genel"
|
||||
}
|
||||
},
|
||||
"title": "Ekran Görüntüsü"
|
||||
"title": "Ekran Görüntüsü",
|
||||
"types": {
|
||||
"brightness": {
|
||||
"description": "Ekran parlaklığı değiştiğinde OSD'yi göster.",
|
||||
"label": "Parlaklık"
|
||||
},
|
||||
"input-volume": {
|
||||
"description": "Mikrofon ses düzeyi değiştiğinde OSD'yi göster.",
|
||||
"label": "Giriş sesi"
|
||||
},
|
||||
"lockkey": {
|
||||
"description": "Caps Lock, Num Lock veya Scroll Lock değiştirildiğinde OSD'yi göster.",
|
||||
"label": "Kilit tuşları"
|
||||
},
|
||||
"section": {
|
||||
"description": "OSD'yi tetikleyecek olayları seçin. Hiçbir olay seçilmezse, mevcut tüm olaylar OSD'yi tetikleyecektir.",
|
||||
"label": "OSD tetikleme olayları"
|
||||
},
|
||||
"volume": {
|
||||
"description": "Ses çıkış düzeyi değiştiğinde OSD'yi göster.",
|
||||
"label": "Çıkış sesi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"screen-recorder": {
|
||||
"audio": {
|
||||
@@ -1806,6 +1861,12 @@
|
||||
"memory-section": {
|
||||
"label": "Bellek Kullanımı"
|
||||
},
|
||||
"network-section": {
|
||||
"label": "Ağ"
|
||||
},
|
||||
"polling-interval": {
|
||||
"label": "Yoklama aralığı"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "Kritik Eşiği"
|
||||
},
|
||||
@@ -1816,7 +1877,7 @@
|
||||
"label": "CPU Sıcaklığı"
|
||||
},
|
||||
"thresholds-section": {
|
||||
"description": "Sistem metrikleri için eşikler ayarlayın; bu değerlerin üzerindekiler vurgulanacaktır.",
|
||||
"description": "Her sistem metriği için uyarı/kritik eşiklerini ve yoklama aralıklarını ayarlayın.",
|
||||
"label": "Eşikler"
|
||||
},
|
||||
"title": "Sistem İzleme",
|
||||
@@ -2048,6 +2109,11 @@
|
||||
"unavailable": "Panoya geçmişi kullanılamıyor",
|
||||
"unavailable-desc": "'cliphist' uygulaması kurulu değil. Lütfen panoya geçmişi özelliklerini kullanmak için kurun."
|
||||
},
|
||||
"dark-mode": {
|
||||
"dark-mode": "Karanlık mod",
|
||||
"enabled": "Etkin",
|
||||
"light-mode": "Aydınlık mod"
|
||||
},
|
||||
"do-not-disturb": {
|
||||
"disabled": "'Rahatsız etme' devre dışı",
|
||||
"disabled-desc": "Tüm bildirimler gösteriliyor.",
|
||||
@@ -2128,64 +2194,70 @@
|
||||
"tooltips": {
|
||||
"add-widget": "Widget ekle",
|
||||
"bluetooth-devices": "Bluetooth cihazları",
|
||||
"brightness-at": "Parlaklık: %{brightness}\nAyarlar için sağ tık.\nParlaklığı değiştirmek için kaydırın.",
|
||||
"cancel-timer": "Zamanlayıcıyı iptal et",
|
||||
"brightness-at": "Parlaklık: %{brightness}",
|
||||
"cancel-timer": "Zamanlayıcı",
|
||||
"clear-history": "Geçmişi temizle",
|
||||
"click-to-start-recording": "Kaydı başlatmak için tıklayın",
|
||||
"click-to-stop-recording": "Kaydı durdurmak için tıklayın",
|
||||
"close": "Kapat",
|
||||
"connect-disconnect-devices": "Bağlanmak için sol tık. Unutmak için sağ tık.",
|
||||
"click-to-start-recording": "Ekran kaydedici (kaydı başlat)",
|
||||
"click-to-stop-recording": "Ekran kaydedici (kaydı durdur)",
|
||||
"close": "Kapat düğmesi",
|
||||
"connect-disconnect-devices": "Bluetooth cihazı",
|
||||
"delete-notification": "Bildiriyi sil",
|
||||
"disable-keep-awake": "Uyanık kalmayı devre dışı bırakmak için tıklayın.\nZaman aşımını ayarlamak için kaydırın.",
|
||||
"do-not-disturb-disabled": "'Rahatsız etme' devre dışı",
|
||||
"do-not-disturb-enabled": "'Rahatsız etme' etkin",
|
||||
"enable-keep-awake": "Uyanık kalmayı etkinleştirmek için tıklayın.\nZaman aşımını ayarlamak için kaydırın.",
|
||||
"disable-keep-awake": "Uyanık kal",
|
||||
"do-not-disturb-disabled": "Rahatsız etme",
|
||||
"do-not-disturb-enabled": "Rahatsız etme",
|
||||
"enable-keep-awake": "Uyanık kal",
|
||||
"forget-network": "Ağı unut",
|
||||
"home": "Ana Sayfa",
|
||||
"input-muted": "Giriş sessizliğini değiştir",
|
||||
"grid-view": "Izgara görünümü",
|
||||
"hidden-files-hide": "Gizli dosyalar",
|
||||
"hidden-files-show": "Gizli dosyalar",
|
||||
"home": "Ana dizin",
|
||||
"input-muted": "Mikrofon",
|
||||
"keep-awake": "Uyanık kal",
|
||||
"keyboard-layout": "{layout} klavye düzeni",
|
||||
"manage-vpn": "VPN bağlantılarını yönet",
|
||||
"manage-wifi": "Wi-Fi yönet",
|
||||
"microphone-volume-at": "Mikrofon sesi %{volume}\nAyarlar için sol tık. Sessize almak için sağ tık.\nSesi değiştirmek için kaydırın.",
|
||||
"move-to-center-section": "Orta bölüme taşı",
|
||||
"move-to-left-section": "Sol bölüme taşı",
|
||||
"move-to-right-section": "Sağ bölüme taşı",
|
||||
"next-media": "Sonraki medya",
|
||||
"list-view": "Liste görünümü",
|
||||
"manage-vpn": "VPN bağlantıları",
|
||||
"manage-wifi": "Wi-Fi",
|
||||
"microphone-volume-at": "Mikrofon sesi: %{volume}",
|
||||
"move-to-center-section": "Orta bölüm",
|
||||
"move-to-left-section": "Sol bölüm",
|
||||
"move-to-right-section": "Sağ bölüm",
|
||||
"next-media": "Sonraki parça",
|
||||
"next-month": "Sonraki ay",
|
||||
"night-light-disabled": "Gece ışığı devre dışı.\nMod arasında geçiş yapmak için sol tık.\nAyarlara erişmek için sağ tık.",
|
||||
"night-light-enabled": "Gece ışığı etkin.\nMod arasında geçiş yapmak için sol tık.\nAyarlara erişmek için sağ tık.",
|
||||
"night-light-forced": "Gece ışığı zorla.\nMod arasında geçiş yapmak için sol tık.\nAyarlara erişmek için sağ tık.",
|
||||
"night-light-not-installed": "Gece ışığı mevcut değil.\nwlsunset yüklü değil.",
|
||||
"noctalia-performance-disabled": "Noctalia performans modu devre dışı.\nEtkinleştirmek için sol tıklayın.",
|
||||
"noctalia-performance-enabled": "Noctalia performans modu etkin.\nDevre dışı bırakmak için sol tıklayın.",
|
||||
"open-control-center": "Kontrol merkezini aç",
|
||||
"open-notification-history-disable-dnd": "Bildirim geçmişini aç\n\"Rahatsız etmeyi\" devre dışı bırakmak için sağ tık.",
|
||||
"open-notification-history-enable-dnd": "Bildirim geçmişini aç\n\"Rahatsız etmeyi\" etkinleştirmek için sağ tık.",
|
||||
"open-settings": "Ayarları aç",
|
||||
"open-tray-dropdown": "Tepsi açılır menüsünü aç",
|
||||
"open-wallpaper-selector": "Duvar kağıdı seçiciyi aç",
|
||||
"output-muted": "Çıkış sessizliğini değiştir",
|
||||
"pause": "Duraklat",
|
||||
"play": "Oynat",
|
||||
"power-profile": "'{profile}' güç profili",
|
||||
"previous-media": "Önceki medya",
|
||||
"night-light-disabled": "Gece ışığı",
|
||||
"night-light-enabled": "Gece ışığı",
|
||||
"night-light-forced": "Gece ışığı",
|
||||
"night-light-not-installed": "Gece ışığı (mevcut değil)",
|
||||
"noctalia-performance-disabled": "Noctalia performans modu",
|
||||
"noctalia-performance-enabled": "Noctalia performans modu",
|
||||
"open-control-center": "Kontrol merkezi",
|
||||
"open-notification-history-disable-dnd": "Bildirim geçmişi",
|
||||
"open-notification-history-enable-dnd": "Bildirim geçmişi",
|
||||
"open-settings": "Ayarlar",
|
||||
"open-tray-dropdown": "Sistem tepsisı",
|
||||
"open-wallpaper-selector": "Duvar kağıdı seçici",
|
||||
"output-muted": "Ses çıkışı",
|
||||
"pause": "Duraklat düğmesi",
|
||||
"play": "Oynat düğmesi",
|
||||
"power-profile": "{profile} güç profili",
|
||||
"previous-media": "Önceki parça",
|
||||
"previous-month": "Önceki ay",
|
||||
"refresh": "Yenile",
|
||||
"refresh-devices": "Cihazları yenile",
|
||||
"refresh-wallhaven": "Wallhaven sonuçlarını yenile",
|
||||
"refresh-wallpaper-list": "Duvar kağıdı listesini yenile",
|
||||
"remove-widget": "Widget kaldır",
|
||||
"screen-recorder-not-installed": "Ekran kaydedici yüklü değil",
|
||||
"screen-recorder-not-installed": "Ekran kaydedici (yüklü değil)",
|
||||
"search": "Ara",
|
||||
"search-close": "Aramayı kapat",
|
||||
"session-menu": "Oturum Menüsü",
|
||||
"set-power-profile": "\"{profile}\" güç profilini ayarla",
|
||||
"start-screen-recording": "Ekran kaydını başlat",
|
||||
"stop-screen-recording": "Ekran kaydını durdur",
|
||||
"switch-to-dark-mode": "Koyu moda geç",
|
||||
"switch-to-light-mode": "Açık moda geç",
|
||||
"up": "Yukarı",
|
||||
"volume-at": "Çıkış sesi %{volume}\nAyarlar için sol tık. Sessize almak için sağ tık.\nSesi değiştirmek için kaydırın.",
|
||||
"wallpaper-selector": "Sol tık: Duvar kağıdı seçiciyi aç.\nSağ tık: Rastgele duvar kağıdı ayarla.",
|
||||
"set-power-profile": "{profile} güç profili",
|
||||
"start-screen-recording": "Ekran kaydedici",
|
||||
"stop-screen-recording": "Ekran kaydedici",
|
||||
"switch-to-dark-mode": "Koyu mod",
|
||||
"switch-to-light-mode": "Açık mod",
|
||||
"up": "Üst dizin",
|
||||
"volume-at": "Çıkış sesi: %{volume}",
|
||||
"wallpaper-selector": "Duvar kağıdı seçici",
|
||||
"widget-settings": "Widget ayarları"
|
||||
},
|
||||
"wallpaper": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"authentication": {
|
||||
"error": "Помилка автентифікації",
|
||||
"failed": "Помилка автентифікації"
|
||||
"failed": "Невдала автентифікація"
|
||||
},
|
||||
"bar": {
|
||||
"widget-settings": {
|
||||
@@ -23,8 +23,8 @@
|
||||
"label": "Режим прокрутки"
|
||||
},
|
||||
"show-app-icon": {
|
||||
"description": "Відображати значок програми біля заголовка вікна.",
|
||||
"label": "Показувати значок програми"
|
||||
"description": "Відображати значок застосунка біля заголовка вікна.",
|
||||
"label": "Показувати значок застосунка"
|
||||
},
|
||||
"use-fixed-width": {
|
||||
"description": "Коли увімкнено, віджет завжди використовуватиме максимальну ширину замість динамічного налаштування до вмісту.",
|
||||
@@ -46,6 +46,11 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"device": {
|
||||
"default": "Пристрій відображення за замовчуванням",
|
||||
"description": "Виберіть пристрій з акумулятором для відображення.",
|
||||
"label": "Пристрій живлення від батареї"
|
||||
},
|
||||
"display-mode": {
|
||||
"description": "Виберіть, як ви хочете, щоб це значення відображалося.",
|
||||
"label": "Режим відображення"
|
||||
@@ -97,9 +102,13 @@
|
||||
"control-center": {
|
||||
"browse-file": "Огляд файлу",
|
||||
"browse-library": "Огляд бібліотеки",
|
||||
"colorize-distro-logo": {
|
||||
"description": "Застосувати кольори теми до логотипа вашого дистрибутива.",
|
||||
"label": "Розфарбовувати логотип дистрибутива"
|
||||
"color-selection": {
|
||||
"description": "Застосовує кольори теми до іконок.",
|
||||
"label": "Вибір кольору"
|
||||
},
|
||||
"enable-colorization": {
|
||||
"description": "Вмикає розфарбовування для іконки центру керування, застосовуючи кольори теми.",
|
||||
"label": "Увімкнути розфарбовування"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Вибрати значок з бібліотеки або власний файл.",
|
||||
@@ -227,14 +236,14 @@
|
||||
"description": "Відображати виконавець - назва замість назва - виконавець.",
|
||||
"label": "Показувати спочатку виконавця"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Відображати аудіовізуалізатор під час відтворення музики.",
|
||||
"label": "Показувати візуалізатор"
|
||||
},
|
||||
"show-progress-ring": {
|
||||
"description": "Відображати круговий індикатор прогресу, що показує просування треку.",
|
||||
"label": "Показувати кільце прогресу"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "Відображати аудіовізуалізатор під час відтворення музики.",
|
||||
"label": "Показувати візуалізатор"
|
||||
},
|
||||
"use-fixed-width": {
|
||||
"description": "Коли увімкнено, віджет завжди використовуватиме максимальну ширину замість динамічного налаштування до вмісту.",
|
||||
"label": "Використовувати фіксовану ширину"
|
||||
@@ -271,9 +280,6 @@
|
||||
}
|
||||
},
|
||||
"system-monitor": {
|
||||
"cpu-critical-threshold": {
|
||||
"label": "Використання ЦП (Критичний)"
|
||||
},
|
||||
"cpu-temperature": {
|
||||
"description": "Показувати показники температури ЦП, якщо доступно.",
|
||||
"label": "Температура ЦП"
|
||||
@@ -282,25 +288,10 @@
|
||||
"description": "Відображати поточний відсоток використання ЦП.",
|
||||
"label": "Використання ЦП"
|
||||
},
|
||||
"cpu-warning-threshold": {
|
||||
"label": "Використання ЦП (Попередження)"
|
||||
},
|
||||
"disk-critical-threshold": {
|
||||
"label": "Місце на диску (Критичний)"
|
||||
},
|
||||
"disk-path": {
|
||||
"description": "Виберіть точку монтування диска для моніторингу.",
|
||||
"label": "Шлях до диска"
|
||||
},
|
||||
"disk-warning-threshold": {
|
||||
"label": "Місце на диску (Попередження)"
|
||||
},
|
||||
"mem-critical-threshold": {
|
||||
"label": "Використання пам'яті (Критичний)"
|
||||
},
|
||||
"mem-warning-threshold": {
|
||||
"label": "Використання пам'яті (Попередження)"
|
||||
},
|
||||
"memory-percentage": {
|
||||
"description": "Показувати використання пам'яті у відсотках замість абсолютних значень.",
|
||||
"label": "Пам'ять у відсотках"
|
||||
@@ -310,22 +301,12 @@
|
||||
"label": "Використання пам'яті"
|
||||
},
|
||||
"network-traffic": {
|
||||
"description": "Відображати швидкість завантаження та вивантаження в мережі.",
|
||||
"description": "Відображати швидкість прийому та передачі даних.",
|
||||
"label": "Мережевий трафік"
|
||||
},
|
||||
"storage-usage": {
|
||||
"description": "Показувати інформацію про використання дискового простору.",
|
||||
"label": "Використання сховища"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "Температура ЦП (Критичний)"
|
||||
},
|
||||
"temp-warning-threshold": {
|
||||
"label": "Температура ЦП (Попередження)"
|
||||
},
|
||||
"thresholds": {
|
||||
"description": "Встановіть пороги для оповіщень системних метрик. Виділяє при перевищенні.",
|
||||
"header": "Налаштування порогів"
|
||||
}
|
||||
},
|
||||
"taskbar": {
|
||||
@@ -338,11 +319,11 @@
|
||||
"label": "Режим приховування"
|
||||
},
|
||||
"only-active-workspaces": {
|
||||
"description": "Показувати тільки програми з активних робочих просторів.",
|
||||
"description": "Показувати тільки застосунки з активних робочих просторів.",
|
||||
"label": "Тільки з активних робочих просторів"
|
||||
},
|
||||
"only-same-output": {
|
||||
"description": "Показувати тільки програми з виходу, де розташована панель.",
|
||||
"description": "Показувати тільки застосунки з виходу, де розташована панель.",
|
||||
"label": "Тільки з того ж виходу"
|
||||
}
|
||||
},
|
||||
@@ -359,7 +340,7 @@
|
||||
},
|
||||
"drawer-enabled": {
|
||||
"description": "Коли увімкнено, не закріплені елементи трея відображаються на панелі ящика. Коли вимкнено, всі елементи трея відображаються в рядку.",
|
||||
"label": "Увімкнути ящик"
|
||||
"label": "Увімкнути висувну панель"
|
||||
}
|
||||
},
|
||||
"volume": {
|
||||
@@ -373,14 +354,14 @@
|
||||
"description": "Кількість символів для відображення з назв робочих просторів (1-10).",
|
||||
"label": "Кількість символів"
|
||||
},
|
||||
"follow-focused-screen": {
|
||||
"description": "Відображати робочі простори з поточного активного екрана, а не з екрана, на якому розташована панель.",
|
||||
"label": "Слідувати за активним eкраном"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "Не відображати робочі простори без вікон.",
|
||||
"label": "Приховати незайняті"
|
||||
},
|
||||
"follow-focused-screen": {
|
||||
"description": "Відображати робочі простори з поточного активного екрана, а не з екрана, на якому розташована панель.",
|
||||
"label": "Слідувати за Активним Екраном"
|
||||
},
|
||||
"label-mode": {
|
||||
"description": "Виберіть, як відображаються мітки робочих просторів.",
|
||||
"label": "Режим міток"
|
||||
@@ -389,22 +370,22 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"battery-level": "Рівень заряду акумулятора",
|
||||
"brightness": "Яскравість",
|
||||
"charge-level": "Рівень заряду",
|
||||
"charging": "Зарядка.",
|
||||
"charging": "Заряджається",
|
||||
"charging-rate": "Швидкість зарядки: {rate} Вт.",
|
||||
"discharging": "Розрядка.",
|
||||
"discharging": "Розряджається",
|
||||
"discharging-rate": "Швидкість розрядки: {rate} Вт.",
|
||||
"health": "Здоров'я: {percent}%",
|
||||
"idle": "Бездіяльність.",
|
||||
"health": "Стан: {percent}%",
|
||||
"idle": "Простій",
|
||||
"inhibit-idle-description": "Підтримує систему активною.",
|
||||
"inhibit-idle-label": "Не давати заснути",
|
||||
"no-battery-detected": "Батарею не виявлено.",
|
||||
"panel-title": "Батарея",
|
||||
"plugged-in": "Підключено.",
|
||||
"plugged-in": "Підключено",
|
||||
"power-profile": "Профіль живлення",
|
||||
"time-left": "Залишилось часу: {time}.",
|
||||
"time-until-full": "Час до повного заряду: {time}."
|
||||
"time-left": "Залишилося: {time}",
|
||||
"time-until-full": "До повного заряду: {time}"
|
||||
},
|
||||
"bluetooth": {
|
||||
"panel": {
|
||||
@@ -412,11 +393,12 @@
|
||||
"blocked": "Заблоковано",
|
||||
"connect": "Підключити",
|
||||
"connected-devices": "Підключені пристрої",
|
||||
"connecting": "Підключення…",
|
||||
"disabled": "Bluetooth вимкнено",
|
||||
"disconnect": "Відключити",
|
||||
"enable-message": "Увімкніть Bluetooth, щоб побачити доступні пристрої.",
|
||||
"known-devices": "Відомі пристрої",
|
||||
"pairing": "Спарювання...",
|
||||
"pairing": "Створення пари...",
|
||||
"pairing-mode": "Переконайтеся, що ваш пристрій у режимі з'єднання.",
|
||||
"scanning": "Сканування пристроїв...",
|
||||
"title": "Bluetooth"
|
||||
@@ -426,6 +408,19 @@
|
||||
"panel": {
|
||||
"week": "Тиждень"
|
||||
},
|
||||
"timer": {
|
||||
"countdown": "Зворотний відлік",
|
||||
"duration": "Тривалість",
|
||||
"hours": "г",
|
||||
"minutes": "м",
|
||||
"pause": "Пауза",
|
||||
"reset": "Скинути",
|
||||
"seconds": "с",
|
||||
"start": "Почати",
|
||||
"stopwatch": "Секундомір",
|
||||
"timer": "Таймер",
|
||||
"title": "Таймер"
|
||||
},
|
||||
"weather": {
|
||||
"loading": "Завантаження погоди…"
|
||||
}
|
||||
@@ -447,7 +442,7 @@
|
||||
"version": "Версія {version}"
|
||||
},
|
||||
"subtitle": {
|
||||
"fresh": "Дякуємо, що встановили Noctalia! Ось що містить цей білд.",
|
||||
"fresh": "Дякуємо, що встановили Noctalia! Ось що містить цей збірка.",
|
||||
"updated": "Оновлено з {previousVersion}"
|
||||
},
|
||||
"title": "Що нового у {version}",
|
||||
@@ -479,7 +474,7 @@
|
||||
"open-mixer": "Аудіомікшер",
|
||||
"open-settings": "Відкрити налаштування",
|
||||
"pause": "Пауза",
|
||||
"play": "Грати",
|
||||
"play": "Відтворити",
|
||||
"previous": "Попередній",
|
||||
"random-wallpaper": "Випадкові шпалери",
|
||||
"toggle-mute": "Увімкнути/вимкнути звук",
|
||||
@@ -495,7 +490,7 @@
|
||||
},
|
||||
"general": {
|
||||
"no-results": "Немає результатів",
|
||||
"no-summary": "Немає резюме",
|
||||
"no-summary": "Немає опису",
|
||||
"unknown": "Невідомо"
|
||||
},
|
||||
"launcher": {
|
||||
@@ -518,20 +513,26 @@
|
||||
"no-notifications": "Немає сповіщень",
|
||||
"title": "Сповіщення"
|
||||
},
|
||||
"range": {
|
||||
"all": "Усі",
|
||||
"earlier": "Раніше",
|
||||
"today": "Сьогодні",
|
||||
"yesterday": "Учора"
|
||||
},
|
||||
"time": {
|
||||
"now": "зараз",
|
||||
"diffM": "1 хвилину тому",
|
||||
"diffMM": "{diff} хвилин тому",
|
||||
"diffD": "1 день тому",
|
||||
"diffDD": "{diff} днів тому",
|
||||
"diffH": "1 годину тому",
|
||||
"diffHH": "{diff} годин тому",
|
||||
"diffD": "1 день тому",
|
||||
"diffDD": "{diff} днів тому"
|
||||
"diffM": "1 хвилину тому",
|
||||
"diffMM": "{diff} хвилин тому",
|
||||
"now": "зараз"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"bar": {
|
||||
"density": {
|
||||
"comfortable": "Зручний",
|
||||
"comfortable": "Просторий",
|
||||
"compact": "Компактний",
|
||||
"default": "Стандартний",
|
||||
"mini": "Міні"
|
||||
@@ -545,6 +546,7 @@
|
||||
},
|
||||
"colors": {
|
||||
"error": "Помилка",
|
||||
"none": "Жоден",
|
||||
"onSurface": "На поверхні",
|
||||
"primary": "Основний",
|
||||
"secondary": "Вторинний",
|
||||
@@ -665,7 +667,7 @@
|
||||
"placeholders": {
|
||||
"cancel": "Скасувати",
|
||||
"command-example": "echo \"Привіт, Світ\"",
|
||||
"enter-command": "Введіть команду для виконання (програма або власний скрипт)",
|
||||
"enter-command": "Введіть команду для виконання (застосунок або власний скрипт)",
|
||||
"enter-text-to-collapse": "напр., 'нічого не відтворюється'. Використовуйте /regex/ для шаблонів.",
|
||||
"enter-tooltip": "Введіть підказку",
|
||||
"enter-width-pixels": "Введіть ширину в пікселях",
|
||||
@@ -679,7 +681,7 @@
|
||||
"test": "Тест"
|
||||
},
|
||||
"plugins": {
|
||||
"applications": "Програми",
|
||||
"applications": "Застосунки",
|
||||
"calculator": "Калькулятор",
|
||||
"calculator-description": "Калькулятор - обчислення математичних виразів",
|
||||
"calculator-enter-expression": "Введіть математичний вираз",
|
||||
@@ -706,16 +708,16 @@
|
||||
"enabled": "Bluetooth"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Клацніть для керування пристроями Bluetooth"
|
||||
"action": "Bluetooth"
|
||||
}
|
||||
},
|
||||
"keepAwake": {
|
||||
"label": {
|
||||
"disabled": "Не спати",
|
||||
"enabled": "Не спати"
|
||||
"disabled": "Не давати заснути",
|
||||
"enabled": "Не давати заснути"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Клацніть для перемикання режиму неспання"
|
||||
"action": "Заборона сну"
|
||||
}
|
||||
},
|
||||
"nightLight": {
|
||||
@@ -725,7 +727,7 @@
|
||||
"forced": "Нічне світло"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Клацніть для циклічної зміни режиму нічного світла\nПравий клік: Відкрити налаштування"
|
||||
"action": "Нічне світло"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -734,7 +736,7 @@
|
||||
"enabled": "Сповіщення"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Лівий клік: Відкрити історію сповіщень\nПравий клік: Перемкнути \"Не турбувати\""
|
||||
"action": "Сповіщення"
|
||||
}
|
||||
},
|
||||
"powerProfile": {
|
||||
@@ -742,23 +744,23 @@
|
||||
"unavailable": "Профіль живлення"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Клацніть для циклічної зміни профілю живлення",
|
||||
"action": "Профіль живлення",
|
||||
"disabled": "Встановіть power-profiles-daemon для використання профілів живлення"
|
||||
}
|
||||
},
|
||||
"screenRecorder": {
|
||||
"label": {
|
||||
"recording": "Зупинити",
|
||||
"stopped": "Записати"
|
||||
"stopped": "Запис"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Клацніть для початку/зупинки запису екрана"
|
||||
"action": "Запис екрана"
|
||||
}
|
||||
},
|
||||
"wallpaperSelector": {
|
||||
"label": "Шпалери",
|
||||
"tooltip": {
|
||||
"action": "Лівий клік: Відкрити вибір шпалер\nПравий клік: Встановити випадкові шпалери"
|
||||
"action": "Вибір шпалер"
|
||||
}
|
||||
},
|
||||
"wifi": {
|
||||
@@ -768,7 +770,7 @@
|
||||
"wifi": "Wi-Fi"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "Клацніть для керування підключеннями Wi-Fi"
|
||||
"action": "Wi-Fi"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -787,13 +789,15 @@
|
||||
"about": {
|
||||
"contributors": {
|
||||
"section": {
|
||||
"description": "Вітаємо нашого {count} <b>чудового</b> учасника!",
|
||||
"description_plural": "Вітаємо наших {count} <b>чудових</b> учасників!",
|
||||
"description": "Подяка нашому {count} <b>чудовому</b> учаснику!",
|
||||
"description_plural": "Подяка нашим {count} <b>чудовим</b> учасникам!",
|
||||
"label": "Учасники"
|
||||
}
|
||||
},
|
||||
"noctalia": {
|
||||
"download-latest": "Завантажити останній реліз",
|
||||
"git-commit": "Git коміт:",
|
||||
"git-commit-loading": "Завантаження...",
|
||||
"installed-version": "Встановлена версія:",
|
||||
"latest-version": "Остання версія:",
|
||||
"section": {
|
||||
@@ -802,7 +806,7 @@
|
||||
}
|
||||
},
|
||||
"support": "Підтримати нас",
|
||||
"title": "Про програму"
|
||||
"title": "Про Noctalia"
|
||||
},
|
||||
"audio": {
|
||||
"devices": {
|
||||
@@ -820,15 +824,15 @@
|
||||
}
|
||||
},
|
||||
"external-mixer": {
|
||||
"description": "Введіть команду або шлях до програми для запуску при активації функції зовнішнього аудіомікшера.",
|
||||
"description": "Введіть команду або шлях до застосунку для запуску при активації функції зовнішнього аудіомікшера.",
|
||||
"label": "Команда зовнішнього аудіомікшера",
|
||||
"placeholder": "pwvucontrol || pavucontrol"
|
||||
},
|
||||
"media": {
|
||||
"excluded-player": {
|
||||
"description": "Додайте ключові слова для плеєрів, які система має ігнорувати. Кожне ключове слово на новому рядку.",
|
||||
"label": "Виключений плеєр",
|
||||
"placeholder": "введіть підрядок та натисніть +"
|
||||
"label": "Ігнорований плеєр",
|
||||
"placeholder": "введіть частину назви та натисніть +"
|
||||
},
|
||||
"frame-rate": {
|
||||
"description": "Вищі значення плавніші, але споживають більше ресурсів.",
|
||||
@@ -848,7 +852,7 @@
|
||||
"label": "Прокрутка назви"
|
||||
},
|
||||
"section": {
|
||||
"description": "Встановіть бажані та ігноровані медіапрограми.",
|
||||
"description": "Встановіть бажані та ігноровані медіазастосунки.",
|
||||
"label": "Медіаплеєри"
|
||||
},
|
||||
"visualizer-quality": {
|
||||
@@ -888,7 +892,7 @@
|
||||
},
|
||||
"volume-overdrive": {
|
||||
"description": "Дозволити підвищення гучності понад 100%. Може не підтримуватись усім обладнанням.",
|
||||
"label": "Дозволити перегучність"
|
||||
"label": "Гучність понад 100%"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -907,8 +911,8 @@
|
||||
"label": "Щільність панелі"
|
||||
},
|
||||
"floating": {
|
||||
"description": "Відображати панель як плаваючу 'таблетку'. Примітка: Це перемістить кути екрана до країв.",
|
||||
"label": "Плаваюча панель"
|
||||
"description": "Відображати панель як плаваючу 'капсулу'. Примітка: Це перемістить кути екрана до країв.",
|
||||
"label": "Плаваюча панель (Острівець)"
|
||||
},
|
||||
"margins": {
|
||||
"description": "Налаштуйте поля навколо плаваючої панелі.",
|
||||
@@ -961,7 +965,7 @@
|
||||
"color-source": {
|
||||
"matugen-scheme-type": {
|
||||
"description": {
|
||||
"scheme-content": "Виводить кольори, що тісно збігаються з базовим зображенням",
|
||||
"scheme-content": "Генерує кольори, що тісно збігаються з базовим зображенням",
|
||||
"scheme-expressive": "Яскрава палітра з грайливою насиченістю",
|
||||
"scheme-fidelity": "Високоточна палітра, що зберігає вихідні відтінки",
|
||||
"scheme-fruit-salad": "Барвистий мікс яскравих контрастних акцентів",
|
||||
@@ -1040,20 +1044,32 @@
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"compositors": {
|
||||
"description": "Оформлення композитора.",
|
||||
"label": "Композитори",
|
||||
"niri": {
|
||||
"description": "Записати {filepath}. Потребує niri v25.11+",
|
||||
"description-missing": "Потрібно встановити {app}"
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"description": "Додаткові параметри конфігурації.",
|
||||
"label": "Різне",
|
||||
"description": "Створіть власні шаблони",
|
||||
"label": "Розширено",
|
||||
"user-templates": {
|
||||
"description": "Увімкнути визначений користувачем конфіг Matugen. Файл шаблону буде створено за адресою ~/.config/noctalia/user-templates.toml при першому увімкненні",
|
||||
"label": "Користувацькі шаблони"
|
||||
"description": "Увімкніть лише якщо ви знаєте, що робите, зверніться до нашої онлайн-документації",
|
||||
"label": "Увімкнути користувацькі шаблони"
|
||||
}
|
||||
},
|
||||
"programs": {
|
||||
"cava": {
|
||||
"description": "Записати {filepath}.",
|
||||
"description-missing": "Потрібна установка {app}"
|
||||
},
|
||||
"code": {
|
||||
"description": "Записати {filepath}. Тему Hyprluna потрібно встановити та активувати вручну.",
|
||||
"description-missing": "Клієнт Code не виявлено. Встановіть VSCode або VSCodium."
|
||||
},
|
||||
"description": "Оформлення окремих програм.",
|
||||
"description": "Оформлення окремих застосункiв.",
|
||||
"discord": {
|
||||
"description": "Записати {filepath} для {client}. Тему Hyprluna потрібно активувати вручну.",
|
||||
"description-missing": "Клієнт Discord не виявлено. Встановіть vencord, vesktop, webcord, armcord, equibop, lightcord або dorion."
|
||||
@@ -1062,7 +1078,7 @@
|
||||
"description": "Записати {filepath} та перезавантажити",
|
||||
"description-missing": "Потрібна установка {app}"
|
||||
},
|
||||
"label": "Програми",
|
||||
"label": "Застосунки",
|
||||
"pywalfox": {
|
||||
"description": "Записати {filepath} та запустити pywalfox update",
|
||||
"description-missing": "Потрібна установка {app}"
|
||||
@@ -1075,10 +1091,6 @@
|
||||
"description": "Записати {filepath}.",
|
||||
"description-missing": "Потрібна установка {app}"
|
||||
},
|
||||
"cava": {
|
||||
"description": "Записати {filepath}.",
|
||||
"description-missing": "Потрібна установка {app}"
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Записати {filepath} та перезавантажити",
|
||||
"description-missing": "Потрібна установка {app}"
|
||||
@@ -1089,7 +1101,7 @@
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"description": "Застосовувати кольори до зовнішніх програм.",
|
||||
"description": "Застосовувати кольори до зовнішніх застосункiв.",
|
||||
"label": "Шаблони"
|
||||
},
|
||||
"terminal": {
|
||||
@@ -1112,7 +1124,7 @@
|
||||
},
|
||||
"label": "Термінал",
|
||||
"wezterm": {
|
||||
"description": "Запишіть {filepath} та перезавантажте",
|
||||
"description": "Записати {filepath} та перезавантажити",
|
||||
"description-missing": "Потрібно встановити {app}"
|
||||
}
|
||||
},
|
||||
@@ -1262,9 +1274,11 @@
|
||||
},
|
||||
"temperature": {
|
||||
"day": "День",
|
||||
"day-description": "Керує колірною температурою вдень.",
|
||||
"description": "Встановіть теплоту кольору для нічного та денного часу.",
|
||||
"label": "Колірна температура",
|
||||
"night": "Ніч"
|
||||
"night": "Ніч",
|
||||
"night-description": "Керує колірною температурою вночі."
|
||||
}
|
||||
},
|
||||
"title": "Дисплей"
|
||||
@@ -1280,14 +1294,14 @@
|
||||
"label": "Радіус заокруглення"
|
||||
},
|
||||
"colorize-icons": {
|
||||
"description": "Застосувати кольори теми до значків програм у доці (тільки неактивні програми).",
|
||||
"description": "Застосувати кольори теми до значків застосункiв у доці (тільки неактивні застосунки).",
|
||||
"label": "Розфарбувати значки"
|
||||
},
|
||||
"display": {
|
||||
"always-visible": "Завжди видимий",
|
||||
"auto-hide": "Автоприховування",
|
||||
"description": "Виберіть, як поводиться док.",
|
||||
"exclusive": "Ексклюзивний",
|
||||
"exclusive": "Винятковий",
|
||||
"label": "Відображення"
|
||||
},
|
||||
"floating-distance": {
|
||||
@@ -1309,8 +1323,8 @@
|
||||
},
|
||||
"monitors": {
|
||||
"only-same-output": {
|
||||
"description": "Показувати тільки програми з виходу, де розташований док.",
|
||||
"label": "Тільки програми з того ж виходу"
|
||||
"description": "Показувати тільки застосунки з виходу, де розташований док.",
|
||||
"label": "Тільки застосунки з того ж виходу"
|
||||
},
|
||||
"section": {
|
||||
"description": "Показувати док на певних моніторах. За замовчуванням на всіх, якщо не вибрано.",
|
||||
@@ -1327,7 +1341,7 @@
|
||||
"placeholder": "Виберіть стандартний шрифт...",
|
||||
"scale": {
|
||||
"description": "Збільшити або зменшити розмір звичайного тексту.",
|
||||
"label": "Розмір стандартного шрифту"
|
||||
"label": "Масштаб стандартного шрифту"
|
||||
},
|
||||
"search-placeholder": "Пошук шрифту..."
|
||||
},
|
||||
@@ -1349,26 +1363,27 @@
|
||||
},
|
||||
"language": {
|
||||
"section": {
|
||||
"description": "Виберіть бажану мову програми.",
|
||||
"description": "Виберіть бажану мову застосунку.",
|
||||
"label": "Мова"
|
||||
},
|
||||
"select": {
|
||||
"auto-detect": "Автоматично",
|
||||
"description": "Виберіть мову інтерфейсу програми.",
|
||||
"label": "Мова програми"
|
||||
"description": "Виберіть мову інтерфейсу застосунку.",
|
||||
"label": "Мова застосунку"
|
||||
}
|
||||
},
|
||||
"launch-setup-wizard": "Запустити майстер налаштування",
|
||||
"profile": {
|
||||
"picture": {
|
||||
"description": "Ваше фото профілю, що відображається в інтерфейсі.",
|
||||
"label": "Фото профілю {user}"
|
||||
"label": "Фото профілю користувача {user}"
|
||||
},
|
||||
"section": {
|
||||
"description": "Редагуйте дані користувача та аватар.",
|
||||
"label": "Профіль"
|
||||
},
|
||||
"select-avatar": "Вибрати зображення аватара"
|
||||
"select-avatar": "Вибрати зображення аватара",
|
||||
"tooltip": "Переглянути зображення аватара"
|
||||
},
|
||||
"screen-corners": {
|
||||
"radius": {
|
||||
@@ -1386,7 +1401,7 @@
|
||||
},
|
||||
"solid-black": {
|
||||
"description": "Використовувати суцільний чорний колір замість кольору фону панелі.",
|
||||
"label": "Суцільно чорні кути"
|
||||
"label": "Суцільні чорні кути"
|
||||
}
|
||||
},
|
||||
"title": "Загальні"
|
||||
@@ -1394,7 +1409,7 @@
|
||||
"hooks": {
|
||||
"info": {
|
||||
"command-info": {
|
||||
"description": "• Команди виконуються через оболонку (sh -c)\n• Команди запускаються у фоні (відокремлено)\n• Кнопки тестування виконують з поточними значеннями",
|
||||
"description": "• Команди виконуються через оболонку (sh -c)\n• Команди запускаються у фоні (відокремлено)\n• Кнопки тестування виконують команди з поточними значеннями",
|
||||
"label": "Інформація про команди хуків"
|
||||
},
|
||||
"parameters": {
|
||||
@@ -1427,35 +1442,39 @@
|
||||
"launcher": {
|
||||
"settings": {
|
||||
"background-opacity": {
|
||||
"description": "Налаштуйте непрозорість фону запускача.",
|
||||
"description": "Налаштуйте непрозорість фону лаунчера.",
|
||||
"label": "Непрозорість фону"
|
||||
},
|
||||
"clip-preview": {
|
||||
"description": "Показувати попередній перегляд вмісту буфера обміну при використанні команди >clip.",
|
||||
"label": "Увімкнути попередній перегляд буфера обміну"
|
||||
"label": "Попередній перегляд буфера обміну"
|
||||
},
|
||||
"clipboard-history": {
|
||||
"description": "Отримати доступ до раніше скопійованих елементів із запускача.",
|
||||
"description": "Отримати доступ до раніше скопійованих елементів із лаунчера.",
|
||||
"label": "Увімкнути історію буфера обміну"
|
||||
},
|
||||
"custom-launch-prefix": {
|
||||
"description": "Додати префікс до команд власним запускачем (напр., 'runapp' для інтеграції з systemd).",
|
||||
"label": "Власний префікс запуску"
|
||||
"description": "Додати префікс до команд запуску лаунчером (напр., 'runapp' для інтеграції з systemd).",
|
||||
"label": "Користувацький префікс запуску"
|
||||
},
|
||||
"custom-launch-prefix-enabled": {
|
||||
"description": "Використовувати власний префікс для запуску програм замість стандартного методу.",
|
||||
"label": "Увімкнути власний префікс запуску"
|
||||
"description": "Використовувати власний префікс для запуску застосунків замість стандартного методу.",
|
||||
"label": "Увімкнути користувацький префікс запуску"
|
||||
},
|
||||
"grid-view": {
|
||||
"description": "Показувати елементи у вигляді сітки замість списку.",
|
||||
"label": "Режим сітки"
|
||||
},
|
||||
"position": {
|
||||
"description": "Виберіть, де з'являється панель запускача.",
|
||||
"description": "Виберіть, де з'являється панель лаунчера.",
|
||||
"label": "Положення"
|
||||
},
|
||||
"section": {
|
||||
"description": "Налаштуйте поведінку та зовнішній вигляд запускача.",
|
||||
"description": "Налаштуйте поведінку та зовнішній вигляд лаунчера.",
|
||||
"label": "Зовнішній вигляд"
|
||||
},
|
||||
"sort-by-usage": {
|
||||
"description": "Коли увімкнено, часто запускані програми з'являються першими в списку.",
|
||||
"description": "Коли увімкнено, часто використовувані застосунки з'являються першими в списку.",
|
||||
"label": "Сортувати за використанням"
|
||||
},
|
||||
"terminal-command": {
|
||||
@@ -1463,13 +1482,27 @@
|
||||
"label": "Команда терміналу"
|
||||
},
|
||||
"use-app2unit": {
|
||||
"description": "Використовує альтернативний метод запуску для кращого управління процесами програм і запобігання проблемам.",
|
||||
"label": "Використовувати App2Unit для запуску програм"
|
||||
"description": "Використовує альтернативний метод запуску для кращого керування процесами застосунків і запобігання проблемам.",
|
||||
"label": "Використовувати App2Unit для запуску застосунків"
|
||||
}
|
||||
},
|
||||
"title": "Запускач"
|
||||
"title": "Лаунчер"
|
||||
},
|
||||
"location": {
|
||||
"calendar": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "Організуйте та вмикайте/вимикайте картки на панелі календаря.",
|
||||
"label": "Календарні картки"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"label": "Заголовок календаря"
|
||||
},
|
||||
"month": {
|
||||
"label": "Місяць календаря"
|
||||
}
|
||||
},
|
||||
"date-time": {
|
||||
"12hour-format": {
|
||||
"description": "Відображати час у 12-годинному форматі на екрані блокування та в календарі. Годинник на панелі має власні налаштування.",
|
||||
@@ -1575,8 +1608,8 @@
|
||||
},
|
||||
"reset": "Скинути тривалість очікування",
|
||||
"respect-expire": {
|
||||
"description": "Використовувати час закінчення, встановлений у сповіщенні.",
|
||||
"label": "Дотримуватись часу закінчення"
|
||||
"description": "Використовувати тривалість показу, встановлений у сповіщенні.",
|
||||
"label": "Враховувати тривалість показу"
|
||||
},
|
||||
"section": {
|
||||
"description": "Налаштуйте, як довго сповіщення залишаються видимими залежно від рівня терміновості.",
|
||||
@@ -1626,8 +1659,8 @@
|
||||
"label": "Розкладка клавіатури"
|
||||
},
|
||||
"section": {
|
||||
"description": "Налаштуйте зовнішній вигляд і поведінку спливаючих сповіщень.",
|
||||
"label": "Тост"
|
||||
"description": "Налаштуйте зовнішній вигляд і поведінку спливаючих повідомлень.",
|
||||
"label": "Спливаючі повідомлення"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1671,7 +1704,29 @@
|
||||
"label": "Загальні"
|
||||
}
|
||||
},
|
||||
"title": "Екранна індикація"
|
||||
"title": "Екранна індикація",
|
||||
"types": {
|
||||
"brightness": {
|
||||
"description": "Показувати OSD, коли змінюється яскравість екрана.",
|
||||
"label": "Яскравість"
|
||||
},
|
||||
"input-volume": {
|
||||
"description": "Показувати OSD, коли змінюється гучність мікрофона.",
|
||||
"label": "Вхідна гучність"
|
||||
},
|
||||
"lockkey": {
|
||||
"description": "Показувати OSD, коли перемикаються Caps Lock, Num Lock або Scroll Lock.",
|
||||
"label": "Клавіші блокування"
|
||||
},
|
||||
"section": {
|
||||
"description": "Виберіть події, які запускають екранне меню. Якщо жодну подію не вибрано, екранне меню запускатиметься всіма доступними подіями.",
|
||||
"label": "Події, що запускають OSD"
|
||||
},
|
||||
"volume": {
|
||||
"description": "Показувати OSD, коли змінюється гучність аудіовиходу.",
|
||||
"label": "Вихідна гучність"
|
||||
}
|
||||
}
|
||||
},
|
||||
"screen-recorder": {
|
||||
"audio": {
|
||||
@@ -1806,6 +1861,12 @@
|
||||
"memory-section": {
|
||||
"label": "Використання пам'яті"
|
||||
},
|
||||
"network-section": {
|
||||
"label": "Мережа"
|
||||
},
|
||||
"polling-interval": {
|
||||
"label": "Інтервал опитування"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "Критичний поріг"
|
||||
},
|
||||
@@ -1816,7 +1877,7 @@
|
||||
"label": "Температура ЦП"
|
||||
},
|
||||
"thresholds-section": {
|
||||
"description": "Встановіть пороги для системних метрик; значення вище будуть підсвічені.",
|
||||
"description": "Налаштуйте пороги попередження/критичні пороги та інтервали опитування для кожного системного показника.",
|
||||
"label": "Пороги"
|
||||
},
|
||||
"title": "Системний монітор",
|
||||
@@ -1865,7 +1926,7 @@
|
||||
"label": "Прив'язувати панелі до країв"
|
||||
},
|
||||
"panels-overlay": {
|
||||
"description": "Забезпечує видимість панелей і панелі завдань, навіть поверх повноекранних програм.",
|
||||
"description": "Забезпечує видимість панелей і панелі завдань, навіть поверх повноекранних застосункiв.",
|
||||
"label": "Тримати панелі та панель зверху"
|
||||
},
|
||||
"scaling": {
|
||||
@@ -1927,7 +1988,7 @@
|
||||
"label": "Режим заповнення"
|
||||
},
|
||||
"section": {
|
||||
"label": "Вигляд і відчуття"
|
||||
"label": "Зовнішній вигляд"
|
||||
},
|
||||
"transition-duration": {
|
||||
"description": "Тривалість анімацій переходу в секундах.",
|
||||
@@ -1940,8 +2001,8 @@
|
||||
},
|
||||
"settings": {
|
||||
"enable-management": {
|
||||
"description": "Керувати шпалерами за допомогою Noctalia. Вимкніть, якщо надаєте перевагу іншій програмі.",
|
||||
"label": "Увімкнути управління шпалерами"
|
||||
"description": "Керувати шпалерами за допомогою Noctalia. Вимкніть, якщо надаєте перевагу іншому застосунку.",
|
||||
"label": "Увімкнути керування шпалерами"
|
||||
},
|
||||
"enable-overview": {
|
||||
"description": "Застосовує розмиті та затемнені шпалери до екрана огляду.",
|
||||
@@ -1954,7 +2015,7 @@
|
||||
},
|
||||
"hide-wallpaper-filenames": {
|
||||
"description": "Приховати назви файлів шпалер у селекторі.",
|
||||
"label": "Приховати імена файлів"
|
||||
"label": "Приховати назви файлів"
|
||||
},
|
||||
"monitor-specific": {
|
||||
"description": "Встановити різні теки шпалер для кожного монітора.",
|
||||
@@ -1962,7 +2023,7 @@
|
||||
"tooltip": "Огляд теки шпалер"
|
||||
},
|
||||
"recursive-search": {
|
||||
"description": "Також шукати шпалери в підтеках директорії шпалер.",
|
||||
"description": "Також шукати шпалери в підтеках теки шпалер.",
|
||||
"label": "Шукати в підтеках"
|
||||
},
|
||||
"section": {
|
||||
@@ -2022,7 +2083,7 @@
|
||||
"scaling-percentage": "{percentage}%",
|
||||
"signal-strength": "{signal}%",
|
||||
"unknown": "Невідомо",
|
||||
"unknown-app": "Невідома програма",
|
||||
"unknown-app": "Невідомий застосунок",
|
||||
"unknown-layout": "Невідомо",
|
||||
"unknown-version": "Невідомо",
|
||||
"uptime": "Час роботи: {uptime}",
|
||||
@@ -2038,7 +2099,7 @@
|
||||
},
|
||||
"battery": {
|
||||
"low": "Низький заряд батареї",
|
||||
"low-desc": "Батарея на {percent}%. Будь ласка, підключіть зарядний пристрій."
|
||||
"low-desc": "Рівень заряду: {percent}%. Будь ласка, підключіть зарядний пристрій."
|
||||
},
|
||||
"bluetooth": {
|
||||
"disabled": "Вимкнено",
|
||||
@@ -2046,7 +2107,12 @@
|
||||
},
|
||||
"clipboard": {
|
||||
"unavailable": "Історія буфера обміну недоступна",
|
||||
"unavailable-desc": "Програма 'cliphist' не встановлена. Будь ласка, встановіть її для використання функцій історії буфера обміну."
|
||||
"unavailable-desc": "Застосунок 'cliphist' не встановлений. Будь ласка, встановіть його для використання функцій історії буфера обміну."
|
||||
},
|
||||
"dark-mode": {
|
||||
"dark-mode": "Темний режим",
|
||||
"enabled": "Увімкнено",
|
||||
"light-mode": "Світлий режим"
|
||||
},
|
||||
"do-not-disturb": {
|
||||
"disabled": "'Не турбувати' вимкнено",
|
||||
@@ -2085,14 +2151,14 @@
|
||||
"noctalia-performance": {
|
||||
"disabled": "Режим продуктивності вимкнено.",
|
||||
"enabled": "Режим продуктивності ввімкнено.",
|
||||
"label": "Виступ Noctalia"
|
||||
"label": "Продуктивність Noctalia"
|
||||
},
|
||||
"power-profile": {
|
||||
"changed": "Профіль живлення змінено",
|
||||
"profile-name": "\"{profile}\""
|
||||
},
|
||||
"recording": {
|
||||
"failed-general": "Рекордер завершився з помилкою.",
|
||||
"failed-general": "Засіб запису завершився з помилкою.",
|
||||
"failed-gpu": "gpu-screen-recorder несподівано завершився.",
|
||||
"failed-start": "Не вдалося розпочати запис",
|
||||
"no-portals": "Портали робочого столу не запущені",
|
||||
@@ -2128,64 +2194,70 @@
|
||||
"tooltips": {
|
||||
"add-widget": "Додати віджет",
|
||||
"bluetooth-devices": "Пристрої Bluetooth",
|
||||
"brightness-at": "Яскравість: {brightness}%\nПравий клік для налаштувань.\nПрокрутка для зміни яскравості.",
|
||||
"cancel-timer": "Скасувати таймер",
|
||||
"brightness-at": "Яскравість: {brightness}%",
|
||||
"cancel-timer": "Таймер",
|
||||
"clear-history": "Очистити історію",
|
||||
"click-to-start-recording": "Клацніть для початку запису",
|
||||
"click-to-stop-recording": "Клацніть для зупинки запису",
|
||||
"close": "Закрити",
|
||||
"connect-disconnect-devices": "Лівий клік для підключення. Правий клік для забування.",
|
||||
"click-to-start-recording": "Запис екрана (почати запис)",
|
||||
"click-to-stop-recording": "Запис екрана (зупинити запис)",
|
||||
"close": "Кнопка закрити",
|
||||
"connect-disconnect-devices": "Пристрій Bluetooth",
|
||||
"delete-notification": "Видалити сповіщення",
|
||||
"disable-keep-awake": "Клацніть, щоб вимкнути режим неспання.\nПрокрутіть, щоб налаштувати тайм-аут.",
|
||||
"do-not-disturb-disabled": "'Не турбувати' вимкнено",
|
||||
"do-not-disturb-enabled": "'Не турбувати' увімкнено",
|
||||
"enable-keep-awake": "Клацніть, щоб увімкнути режим неспання.\nПрокрутіть, щоб налаштувати тайм-аут.",
|
||||
"disable-keep-awake": "Заборона сну",
|
||||
"do-not-disturb-disabled": "Не турбувати",
|
||||
"do-not-disturb-enabled": "Не турбувати",
|
||||
"enable-keep-awake": "Заборона сну",
|
||||
"forget-network": "Забути мережу",
|
||||
"home": "Додому",
|
||||
"input-muted": "Перемкнути вимкнення входу",
|
||||
"keep-awake": "Не спати",
|
||||
"grid-view": "Мережевий вигляд",
|
||||
"hidden-files-hide": "Приховані файли",
|
||||
"hidden-files-show": "Приховані файли",
|
||||
"home": "Домашній каталог",
|
||||
"input-muted": "Мікрофон",
|
||||
"keep-awake": "Заборона сну",
|
||||
"keyboard-layout": "Розкладка клавіатури {layout}",
|
||||
"manage-vpn": "Керувати підключеннями VPN",
|
||||
"manage-wifi": "Керувати Wi-Fi",
|
||||
"microphone-volume-at": "Гучність мікрофона на {volume}%\nЛівий клік для налаштувань. Правий клік для вимкнення звуку.\nПрокрутка для зміни гучності.",
|
||||
"move-to-center-section": "Перемістити в центральну секцію",
|
||||
"move-to-left-section": "Перемістити в ліву секцію",
|
||||
"move-to-right-section": "Перемістити в праву секцію",
|
||||
"next-media": "Наступне медіа",
|
||||
"list-view": "Список",
|
||||
"manage-vpn": "Підключення VPN",
|
||||
"manage-wifi": "Wi-Fi",
|
||||
"microphone-volume-at": "Гучність мікрофона: {volume}%",
|
||||
"move-to-center-section": "Центральна секція",
|
||||
"move-to-left-section": "Ліва секція",
|
||||
"move-to-right-section": "Права секція",
|
||||
"next-media": "Наступний трек",
|
||||
"next-month": "Наступний місяць",
|
||||
"night-light-disabled": "Нічне світло вимкнено.\nЛівий клік для циклічного режиму.\nПравий клік для доступу до налаштувань.",
|
||||
"night-light-enabled": "Нічне світло увімкнено.\nЛівий клік для циклічного режиму.\nПравий клік для доступу до налаштувань.",
|
||||
"night-light-forced": "Нічне світло примусово.\nЛівий клік для циклічного режиму.\nПравий клік для доступу до налаштувань.",
|
||||
"night-light-not-installed": "Нічне світло недоступне.\nwlsunset не встановлено.",
|
||||
"noctalia-performance-disabled": "Режим продуктивності Noctalia вимкнено.\nЛівий клік для увімкнення.",
|
||||
"noctalia-performance-enabled": "Режим продуктивності Noctalia увімкнено.\nЛівий клік для вимкнення.",
|
||||
"open-control-center": "Відкрити центр керування",
|
||||
"open-notification-history-disable-dnd": "Відкрити історію сповіщень\nПравий клік для вимкнення \"Не турбувати\".",
|
||||
"open-notification-history-enable-dnd": "Відкрити історію сповіщень\nПравий клік для увімкнення \"Не турбувати\".",
|
||||
"open-settings": "Відкрити налаштування",
|
||||
"open-tray-dropdown": "Відкрити спадне меню трею",
|
||||
"open-wallpaper-selector": "Відкрити вибір шпалер",
|
||||
"output-muted": "Перемкнути вимкнення виходу",
|
||||
"pause": "Пауза",
|
||||
"play": "Відтворити",
|
||||
"power-profile": "Профіль живлення '{profile}'",
|
||||
"previous-media": "Попереднє медіа",
|
||||
"night-light-disabled": "Нічне світло",
|
||||
"night-light-enabled": "Нічне світло",
|
||||
"night-light-forced": "Нічне світло",
|
||||
"night-light-not-installed": "Нічне світло (недоступне)",
|
||||
"noctalia-performance-disabled": "Режим продуктивності Noctalia",
|
||||
"noctalia-performance-enabled": "Режим продуктивності Noctalia",
|
||||
"open-control-center": "Центр керування",
|
||||
"open-notification-history-disable-dnd": "Історія сповіщень",
|
||||
"open-notification-history-enable-dnd": "Історія сповіщень",
|
||||
"open-settings": "Налаштування",
|
||||
"open-tray-dropdown": "Системний трей",
|
||||
"open-wallpaper-selector": "Вибір шпалер",
|
||||
"output-muted": "Аудіовихід",
|
||||
"pause": "Кнопка пауза",
|
||||
"play": "Кнопка відтворити",
|
||||
"power-profile": "{profile} профіль живлення",
|
||||
"previous-media": "Попередній трек",
|
||||
"previous-month": "Попередній місяць",
|
||||
"refresh": "Оновити",
|
||||
"refresh-devices": "Оновити пристрої",
|
||||
"refresh-wallhaven": "Оновити результати Wallhaven",
|
||||
"refresh-wallpaper-list": "Оновити список шпалер",
|
||||
"remove-widget": "Видалити віджет",
|
||||
"screen-recorder-not-installed": "Запис екрана не встановлено",
|
||||
"screen-recorder-not-installed": "Запис екрана (не встановлено)",
|
||||
"search": "Пошук",
|
||||
"search-close": "Закрити пошук",
|
||||
"session-menu": "Меню сеансу",
|
||||
"set-power-profile": "Встановити профіль живлення \"{profile}\"",
|
||||
"start-screen-recording": "Почати запис екрана",
|
||||
"stop-screen-recording": "Зупинити запис екрана",
|
||||
"switch-to-dark-mode": "Перемкнутися на темний режим",
|
||||
"switch-to-light-mode": "Перемкнутися на світлий режим",
|
||||
"up": "Вгору",
|
||||
"volume-at": "Вихідна гучність на {volume}%\nЛівий клік для налаштувань. Правий клік для вимкнення звуку.\nПрокрутка для зміни гучності.",
|
||||
"wallpaper-selector": "Лівий клік: Відкрити вибір шпалер.\nПравий клік: Встановити випадкові шпалери.",
|
||||
"set-power-profile": "{profile} профіль живлення",
|
||||
"start-screen-recording": "Запис екрана",
|
||||
"stop-screen-recording": "Запис екрана",
|
||||
"switch-to-dark-mode": "Темний режим",
|
||||
"switch-to-light-mode": "Світлий режим",
|
||||
"up": "Батьківський каталог",
|
||||
"volume-at": "Вихідна гучність: {volume}%",
|
||||
"wallpaper-selector": "Вибір шпалер",
|
||||
"widget-settings": "Налаштування віджета"
|
||||
},
|
||||
"wallpaper": {
|
||||
@@ -2193,7 +2265,7 @@
|
||||
"fill-modes": {
|
||||
"center": "Центр",
|
||||
"crop": "Обрізати (Заповнити)",
|
||||
"fit": "Вмістити (Містити)",
|
||||
"fit": "Вмістити (Вписати)",
|
||||
"stretch": "Розтягнути"
|
||||
},
|
||||
"no-match": "Збігів не знайдено.",
|
||||
@@ -2218,7 +2290,7 @@
|
||||
"all": "Всі",
|
||||
"label": "Фільтр контенту",
|
||||
"sfw": "SFW",
|
||||
"sketchy": "Sketchy"
|
||||
"sketchy": "Сумнівний"
|
||||
},
|
||||
"resolution": {
|
||||
"atleast": "Принаймні",
|
||||
@@ -2240,7 +2312,7 @@
|
||||
},
|
||||
"source": {
|
||||
"label": "Джерело",
|
||||
"local": "Локальний",
|
||||
"local": "Локальне",
|
||||
"wallhaven": "Wallhaven"
|
||||
},
|
||||
"title": "Вибір шпалер",
|
||||
@@ -2300,7 +2372,7 @@
|
||||
"european-date": "Європейський формат дати",
|
||||
"iso-date": "Формат дати ISO",
|
||||
"us-date": "Формат дати США",
|
||||
"weekday-date": "День тижня з датою",
|
||||
"weekday-date": "День тижня і дата",
|
||||
"weekday-month-day": "День тижня, місяць і день"
|
||||
},
|
||||
"day": {
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"device": {
|
||||
"default": "默认(显示设备)",
|
||||
"description": "选择要显示的电池设备。",
|
||||
"label": "电池设备"
|
||||
},
|
||||
"display-mode": {
|
||||
"description": "选择您希望此值显示的方式。",
|
||||
"label": "显示模式"
|
||||
@@ -97,9 +102,13 @@
|
||||
"control-center": {
|
||||
"browse-file": "浏览文件",
|
||||
"browse-library": "浏览库",
|
||||
"colorize-distro-logo": {
|
||||
"description": "将主题颜色应用到您的发行版徽标。",
|
||||
"label": "为发行版徽标着色"
|
||||
"color-selection": {
|
||||
"description": "将主题颜色应用于图标。",
|
||||
"label": "选择颜色"
|
||||
},
|
||||
"enable-colorization": {
|
||||
"description": "为控制中心图标启用着色,应用主题颜色。",
|
||||
"label": "启用着色"
|
||||
},
|
||||
"icon": {
|
||||
"description": "从库中选择图标或自定义文件。",
|
||||
@@ -227,14 +236,14 @@
|
||||
"description": "显示艺术家 - 标题,而不是标题 - 艺术家。",
|
||||
"label": "先显示艺术家"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "播放音乐时显示音频可视化器。",
|
||||
"label": "显示可视化器"
|
||||
},
|
||||
"show-progress-ring": {
|
||||
"description": "显示显示曲目进度的圆形进度指示器。",
|
||||
"label": "显示进度环"
|
||||
},
|
||||
"show-visualizer": {
|
||||
"description": "播放音乐时显示音频可视化器。",
|
||||
"label": "显示可视化器"
|
||||
},
|
||||
"use-fixed-width": {
|
||||
"description": "启用后,小部件将始终使用最大宽度,而不根据内容动态调整。",
|
||||
"label": "使用固定宽度"
|
||||
@@ -271,9 +280,6 @@
|
||||
}
|
||||
},
|
||||
"system-monitor": {
|
||||
"cpu-critical-threshold": {
|
||||
"label": "CPU 使用率(严重)"
|
||||
},
|
||||
"cpu-temperature": {
|
||||
"description": "如果可用,显示CPU温度读数。",
|
||||
"label": "CPU温度"
|
||||
@@ -282,25 +288,10 @@
|
||||
"description": "显示当前CPU使用百分比。",
|
||||
"label": "CPU使用率"
|
||||
},
|
||||
"cpu-warning-threshold": {
|
||||
"label": "CPU 使用率(警告)"
|
||||
},
|
||||
"disk-critical-threshold": {
|
||||
"label": "存储空间(严重)"
|
||||
},
|
||||
"disk-path": {
|
||||
"description": "选择要监控的磁盘挂载点。",
|
||||
"label": "磁盘路径"
|
||||
},
|
||||
"disk-warning-threshold": {
|
||||
"label": "存储空间(警告)"
|
||||
},
|
||||
"mem-critical-threshold": {
|
||||
"label": "内存使用率(严重)"
|
||||
},
|
||||
"mem-warning-threshold": {
|
||||
"label": "内存使用率(警告)"
|
||||
},
|
||||
"memory-percentage": {
|
||||
"description": "以百分比而不是绝对值显示内存使用情况。",
|
||||
"label": "内存百分比"
|
||||
@@ -316,16 +307,6 @@
|
||||
"storage-usage": {
|
||||
"description": "显示磁盘空间使用信息。",
|
||||
"label": "存储使用率"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "CPU 温度(严重)"
|
||||
},
|
||||
"temp-warning-threshold": {
|
||||
"label": "CPU 温度(警告)"
|
||||
},
|
||||
"thresholds": {
|
||||
"description": "配置系统指标提示的阈值,超出阈值时将以高亮色进行提示。",
|
||||
"header": "阈值设置"
|
||||
}
|
||||
},
|
||||
"taskbar": {
|
||||
@@ -373,14 +354,14 @@
|
||||
"description": "显示工作区名称的字符数量(1-10)。",
|
||||
"label": "字符数量"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "不显示没有窗口的工作区。",
|
||||
"label": "隐藏未占用"
|
||||
},
|
||||
"follow-focused-screen": {
|
||||
"description": "显示当前焦点屏幕的工作区,而不是任务栏所在屏幕的工作区。",
|
||||
"label": "跟随焦点屏幕"
|
||||
},
|
||||
"hide-unoccupied": {
|
||||
"description": "不显示没有窗口的工作区。",
|
||||
"label": "隐藏未占用"
|
||||
},
|
||||
"label-mode": {
|
||||
"description": "选择工作区标签的显示方式。",
|
||||
"label": "标签模式"
|
||||
@@ -389,8 +370,8 @@
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"battery-level": "电池电量",
|
||||
"brightness": "亮度",
|
||||
"charge-level": "电量",
|
||||
"charging": "正在充电。",
|
||||
"charging-rate": "充电速率:{rate} W。",
|
||||
"discharging": "正在放电。",
|
||||
@@ -412,6 +393,7 @@
|
||||
"blocked": "已阻止",
|
||||
"connect": "连接",
|
||||
"connected-devices": "已连接设备",
|
||||
"connecting": "正在连接…",
|
||||
"disabled": "蓝牙已禁用",
|
||||
"disconnect": "断开",
|
||||
"enable-message": "启用蓝牙以查看可用设备。",
|
||||
@@ -426,6 +408,19 @@
|
||||
"panel": {
|
||||
"week": "周"
|
||||
},
|
||||
"timer": {
|
||||
"countdown": "倒计时",
|
||||
"duration": "时长",
|
||||
"hours": "h",
|
||||
"minutes": "米",
|
||||
"pause": "暂停",
|
||||
"reset": "重置",
|
||||
"seconds": "s",
|
||||
"start": "开始",
|
||||
"stopwatch": "秒表",
|
||||
"timer": "计时器",
|
||||
"title": "计时器"
|
||||
},
|
||||
"weather": {
|
||||
"loading": "正在加载天气…"
|
||||
}
|
||||
@@ -518,14 +513,20 @@
|
||||
"no-notifications": "无通知",
|
||||
"title": "通知"
|
||||
},
|
||||
"range": {
|
||||
"all": "全部",
|
||||
"earlier": "更早",
|
||||
"today": "今天",
|
||||
"yesterday": "昨天"
|
||||
},
|
||||
"time": {
|
||||
"now": "现在",
|
||||
"diffM": "1 分钟前",
|
||||
"diffMM": "{diff} 分钟前",
|
||||
"diffD": "1 天前",
|
||||
"diffDD": "{diff} 天前",
|
||||
"diffH": "1 小时前",
|
||||
"diffHH": "{diff} 小时前",
|
||||
"diffD": "1 天前",
|
||||
"diffDD": "{diff} 天前"
|
||||
"diffM": "1 分钟前",
|
||||
"diffMM": "{diff} 分钟前",
|
||||
"now": "现在"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@@ -545,6 +546,7 @@
|
||||
},
|
||||
"colors": {
|
||||
"error": "错误色",
|
||||
"none": "无",
|
||||
"onSurface": "表层对应色",
|
||||
"primary": "主要色",
|
||||
"secondary": "次要色",
|
||||
@@ -706,7 +708,7 @@
|
||||
"enabled": "蓝牙"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "点击管理蓝牙设备"
|
||||
"action": "蓝牙"
|
||||
}
|
||||
},
|
||||
"keepAwake": {
|
||||
@@ -715,7 +717,7 @@
|
||||
"enabled": "保持唤醒"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "点击切换保持唤醒模式"
|
||||
"action": "保持唤醒"
|
||||
}
|
||||
},
|
||||
"nightLight": {
|
||||
@@ -725,7 +727,7 @@
|
||||
"forced": "夜间模式"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "点击切换夜间模式\n右键:打开设置"
|
||||
"action": "夜间模式"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -734,7 +736,7 @@
|
||||
"enabled": "通知"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "左键:打开通知历史\n右键:切换勿扰模式"
|
||||
"action": "通知"
|
||||
}
|
||||
},
|
||||
"powerProfile": {
|
||||
@@ -742,7 +744,7 @@
|
||||
"unavailable": "电源模式"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "点击切换电源模式",
|
||||
"action": "电源模式",
|
||||
"disabled": "安装 power-profiles-daemon 以使用电源模式"
|
||||
}
|
||||
},
|
||||
@@ -752,13 +754,13 @@
|
||||
"stopped": "录制"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "点击开始/停止屏幕录制"
|
||||
"action": "屏幕录制器"
|
||||
}
|
||||
},
|
||||
"wallpaperSelector": {
|
||||
"label": "壁纸",
|
||||
"tooltip": {
|
||||
"action": "左键:打开壁纸选择器\n右键:设置随机壁纸"
|
||||
"action": "壁纸选择器"
|
||||
}
|
||||
},
|
||||
"wifi": {
|
||||
@@ -768,7 +770,7 @@
|
||||
"wifi": "Wi-Fi"
|
||||
},
|
||||
"tooltip": {
|
||||
"action": "点击管理 Wi-Fi 连接"
|
||||
"action": "Wi-Fi"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -794,6 +796,8 @@
|
||||
},
|
||||
"noctalia": {
|
||||
"download-latest": "下载最新版本",
|
||||
"git-commit": "Git提交:",
|
||||
"git-commit-loading": "加载中...",
|
||||
"installed-version": "已安装版本:",
|
||||
"latest-version": "最新版本:",
|
||||
"section": {
|
||||
@@ -1040,15 +1044,27 @@
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"compositors": {
|
||||
"description": "合成器主题。",
|
||||
"label": "合成器",
|
||||
"niri": {
|
||||
"description": "写入 {filepath}。需要 niri v25.11+",
|
||||
"description-missing": "需要安装 {app}"
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"description": "其他配置选项。",
|
||||
"label": "杂项",
|
||||
"description": "创建您自己的模板",
|
||||
"label": "高级",
|
||||
"user-templates": {
|
||||
"description": "启用用户定义的 Matugen 配置。首次启用时将在 ~/.config/noctalia/user-templates.toml 创建模板文件",
|
||||
"label": "用户模板"
|
||||
"description": "仅在您知道自己在做什么时启用,请参阅我们的在线文档",
|
||||
"label": "启用用户模板"
|
||||
}
|
||||
},
|
||||
"programs": {
|
||||
"cava": {
|
||||
"description": "写入 {filepath}。",
|
||||
"description-missing": "需要安装 {app}"
|
||||
},
|
||||
"code": {
|
||||
"description": "写入 {filepath}。Hyprluna 主题需要手动安装和激活。",
|
||||
"description-missing": "未检测到 Code 客户端。请安装 VSCode 或 VSCodium。"
|
||||
@@ -1075,10 +1091,6 @@
|
||||
"description": "写入 {filepath}。",
|
||||
"description-missing": "需要安装 {app}"
|
||||
},
|
||||
"cava": {
|
||||
"description": "写入 {filepath}。",
|
||||
"description-missing": "需要安装 {app}"
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "写入 {filepath} 并重新加载",
|
||||
"description-missing": "需要安装 {app}"
|
||||
@@ -1262,9 +1274,11 @@
|
||||
},
|
||||
"temperature": {
|
||||
"day": "白天",
|
||||
"day-description": "控制白天的色温。",
|
||||
"description": "设置夜间和白天的颜色温暖度。",
|
||||
"label": "色温",
|
||||
"night": "夜间"
|
||||
"night": "夜间",
|
||||
"night-description": "控制夜间的色温。"
|
||||
}
|
||||
},
|
||||
"title": "显示"
|
||||
@@ -1368,7 +1382,8 @@
|
||||
"description": "编辑您的用户详细信息和头像。",
|
||||
"label": "个人资料"
|
||||
},
|
||||
"select-avatar": "选择头像图片"
|
||||
"select-avatar": "选择头像图片",
|
||||
"tooltip": "浏览头像图片"
|
||||
},
|
||||
"screen-corners": {
|
||||
"radius": {
|
||||
@@ -1446,6 +1461,10 @@
|
||||
"description": "使用自定义前缀启动应用程序,而不是默认方法。",
|
||||
"label": "启用自定义启动前缀"
|
||||
},
|
||||
"grid-view": {
|
||||
"description": "以网格布局而非列表显示项目。",
|
||||
"label": "网格视图"
|
||||
},
|
||||
"position": {
|
||||
"description": "选择启动器面板出现的位置。",
|
||||
"label": "位置"
|
||||
@@ -1470,6 +1489,20 @@
|
||||
"title": "启动器"
|
||||
},
|
||||
"location": {
|
||||
"calendar": {
|
||||
"cards": {
|
||||
"section": {
|
||||
"description": "在日历面板中组织和启用/禁用卡片。",
|
||||
"label": "日历卡"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"label": "日历标题"
|
||||
},
|
||||
"month": {
|
||||
"label": "日历月"
|
||||
}
|
||||
},
|
||||
"date-time": {
|
||||
"12hour-format": {
|
||||
"description": "在锁屏和日历上以 12 小时制格式显示时间。状态栏时钟有其自己的设置。",
|
||||
@@ -1671,7 +1704,29 @@
|
||||
"label": "常规"
|
||||
}
|
||||
},
|
||||
"title": "屏显菜单"
|
||||
"title": "屏显菜单",
|
||||
"types": {
|
||||
"brightness": {
|
||||
"description": "当屏幕亮度发生变化时显示 OSD。",
|
||||
"label": "亮度"
|
||||
},
|
||||
"input-volume": {
|
||||
"description": "当麦克风音量发生变化时显示 OSD。",
|
||||
"label": "输入音量"
|
||||
},
|
||||
"lockkey": {
|
||||
"description": "当切换大写锁定、数字锁定或滚动锁定时显示 OSD。",
|
||||
"label": "锁定键"
|
||||
},
|
||||
"section": {
|
||||
"description": "选择触发OSD的事件。如果未选择任何事件,则所有可用事件都将触发OSD。",
|
||||
"label": "OSD触发事件"
|
||||
},
|
||||
"volume": {
|
||||
"description": "当音频输出音量发生变化时显示 OSD。",
|
||||
"label": "输出音量"
|
||||
}
|
||||
}
|
||||
},
|
||||
"screen-recorder": {
|
||||
"audio": {
|
||||
@@ -1806,6 +1861,12 @@
|
||||
"memory-section": {
|
||||
"label": "内存使用率"
|
||||
},
|
||||
"network-section": {
|
||||
"label": "网络"
|
||||
},
|
||||
"polling-interval": {
|
||||
"label": "轮询间隔"
|
||||
},
|
||||
"temp-critical-threshold": {
|
||||
"label": "严重阈值"
|
||||
},
|
||||
@@ -1816,7 +1877,7 @@
|
||||
"label": "CPU 温度"
|
||||
},
|
||||
"thresholds-section": {
|
||||
"description": "设置各项系统测量指标的阈值,超过该值将以高亮色进行提示。",
|
||||
"description": "为每个系统指标调整警告/严重阈值和轮询间隔。",
|
||||
"label": "阈值"
|
||||
},
|
||||
"title": "系统监视器",
|
||||
@@ -2048,6 +2109,11 @@
|
||||
"unavailable": "剪贴板历史记录不可用",
|
||||
"unavailable-desc": "未安装 'cliphist' 应用程序。请安装它以使用剪贴板历史记录功能。"
|
||||
},
|
||||
"dark-mode": {
|
||||
"dark-mode": "深色模式",
|
||||
"enabled": "已启用",
|
||||
"light-mode": "浅色模式"
|
||||
},
|
||||
"do-not-disturb": {
|
||||
"disabled": "'勿扰模式'已禁用",
|
||||
"disabled-desc": "显示所有通知。",
|
||||
@@ -2128,64 +2194,70 @@
|
||||
"tooltips": {
|
||||
"add-widget": "添加小部件",
|
||||
"bluetooth-devices": "蓝牙设备",
|
||||
"brightness-at": "亮度:{brightness}%\n右键点击进入设置。\n滚动调整亮度。",
|
||||
"cancel-timer": "取消计时器",
|
||||
"brightness-at": "亮度:{brightness}%",
|
||||
"cancel-timer": "计时器",
|
||||
"clear-history": "清除历史记录",
|
||||
"click-to-start-recording": "点击开始录制",
|
||||
"click-to-stop-recording": "点击停止录制",
|
||||
"close": "关闭",
|
||||
"connect-disconnect-devices": "左键点击连接。右键点击忘记。",
|
||||
"click-to-start-recording": "屏幕录制器(开始录制)",
|
||||
"click-to-stop-recording": "屏幕录制器(停止录制)",
|
||||
"close": "关闭按钮",
|
||||
"connect-disconnect-devices": "蓝牙设备",
|
||||
"delete-notification": "删除通知",
|
||||
"disable-keep-awake": "单击以禁用保持唤醒。\n滚动以调整超时。",
|
||||
"do-not-disturb-disabled": "'勿扰模式'已禁用",
|
||||
"do-not-disturb-enabled": "'勿扰模式'已启用",
|
||||
"enable-keep-awake": "单击以启用保持唤醒。\n滚动以调整超时。",
|
||||
"disable-keep-awake": "保持唤醒",
|
||||
"do-not-disturb-disabled": "勿扰模式",
|
||||
"do-not-disturb-enabled": "勿扰模式",
|
||||
"enable-keep-awake": "保持唤醒",
|
||||
"forget-network": "忘记网络",
|
||||
"grid-view": "网格视图",
|
||||
"hidden-files-hide": "隐藏文件",
|
||||
"hidden-files-show": "隐藏文件",
|
||||
"home": "主目录",
|
||||
"input-muted": "静音输入设备",
|
||||
"input-muted": "麦克风",
|
||||
"keep-awake": "保持唤醒",
|
||||
"keyboard-layout": "{layout} 键盘布局",
|
||||
"manage-vpn": "管理 VPN 连接",
|
||||
"manage-wifi": "管理 Wi-Fi",
|
||||
"microphone-volume-at": "麦克风音量 {volume}%\n左键点击进入设置。右键点击切换静音。\n滚动滚轮调节音量。",
|
||||
"move-to-center-section": "移动到中央部分",
|
||||
"move-to-left-section": "移动到左侧部分",
|
||||
"move-to-right-section": "移动到右侧部分",
|
||||
"next-media": "下一媒体",
|
||||
"list-view": "列表视图",
|
||||
"manage-vpn": "VPN 连接",
|
||||
"manage-wifi": "Wi-Fi",
|
||||
"microphone-volume-at": "麦克风音量:{volume}%",
|
||||
"move-to-center-section": "中央部分",
|
||||
"move-to-left-section": "左侧部分",
|
||||
"move-to-right-section": "右侧部分",
|
||||
"next-media": "下一首",
|
||||
"next-month": "下个月",
|
||||
"night-light-disabled": "夜间模式已禁用。\n左键点击循环模式。\n右键点击访问设置。",
|
||||
"night-light-enabled": "夜间模式已启用。\n左键点击循环模式。\n右键点击访问设置。",
|
||||
"night-light-forced": "夜间模式已强制启用。\n左键点击循环模式。\n右键点击访问设置。",
|
||||
"night-light-not-installed": "夜间模式不可用。\nwlsunset 未安装。",
|
||||
"noctalia-performance-disabled": "Noctalia 性能模式已禁用。\n左键点击以启用。",
|
||||
"noctalia-performance-enabled": "Noctalia 性能模式已启用。\n左键点击以禁用。",
|
||||
"open-control-center": "打开控制中心",
|
||||
"open-notification-history-disable-dnd": "打开通知历史记录\n右键点击禁用\"勿扰模式\"。",
|
||||
"open-notification-history-enable-dnd": "打开通知历史记录\n右键点击启用\"勿扰模式\"。",
|
||||
"open-settings": "打开设置",
|
||||
"open-tray-dropdown": "打开系统托盘下拉菜单",
|
||||
"open-wallpaper-selector": "打开壁纸选择器",
|
||||
"output-muted": "静音输出设备",
|
||||
"pause": "暂停",
|
||||
"play": "播放",
|
||||
"power-profile": "'{profile}' 电源模式",
|
||||
"previous-media": "上一媒体",
|
||||
"night-light-disabled": "夜间模式",
|
||||
"night-light-enabled": "夜间模式",
|
||||
"night-light-forced": "夜间模式",
|
||||
"night-light-not-installed": "夜间模式(不可用)",
|
||||
"noctalia-performance-disabled": "Noctalia 性能模式",
|
||||
"noctalia-performance-enabled": "Noctalia 性能模式",
|
||||
"open-control-center": "控制中心",
|
||||
"open-notification-history-disable-dnd": "通知历史记录",
|
||||
"open-notification-history-enable-dnd": "通知历史记录",
|
||||
"open-settings": "设置",
|
||||
"open-tray-dropdown": "系统托盘",
|
||||
"open-wallpaper-selector": "壁纸选择器",
|
||||
"output-muted": "音频输出",
|
||||
"pause": "暂停按钮",
|
||||
"play": "播放按钮",
|
||||
"power-profile": "{profile} 电源模式",
|
||||
"previous-media": "上一首",
|
||||
"previous-month": "上个月",
|
||||
"refresh": "刷新",
|
||||
"refresh-devices": "刷新设备",
|
||||
"refresh-wallhaven": "刷新 Wallhaven 结果",
|
||||
"refresh-wallpaper-list": "刷新壁纸列表",
|
||||
"remove-widget": "移除小部件",
|
||||
"screen-recorder-not-installed": "屏幕录制器未安装",
|
||||
"screen-recorder-not-installed": "屏幕录制器(未安装)",
|
||||
"search": "搜索",
|
||||
"search-close": "关闭搜索",
|
||||
"session-menu": "会话菜单",
|
||||
"set-power-profile": "设置\"{profile}\"电源模式",
|
||||
"start-screen-recording": "开始屏幕录制",
|
||||
"stop-screen-recording": "停止屏幕录制",
|
||||
"switch-to-dark-mode": "切换到深色模式",
|
||||
"switch-to-light-mode": "切换到浅色模式",
|
||||
"up": "向上",
|
||||
"volume-at": "输出音量 {volume}%\n左键点击进入设置。右键点击切换静音。\n滚动滚轮调节音量。",
|
||||
"wallpaper-selector": "左键点击:打开壁纸选择器。\n右键点击:设置随机壁纸。",
|
||||
"set-power-profile": "{profile} 电源模式",
|
||||
"start-screen-recording": "屏幕录制器",
|
||||
"stop-screen-recording": "屏幕录制器",
|
||||
"switch-to-dark-mode": "深色模式",
|
||||
"switch-to-light-mode": "浅色模式",
|
||||
"up": "上级目录",
|
||||
"volume-at": "输出音量:{volume}%",
|
||||
"wallpaper-selector": "壁纸选择器",
|
||||
"widget-settings": "小部件设置"
|
||||
},
|
||||
"wallpaper": {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"settingsVersion": 23,
|
||||
"setupCompleted": false,
|
||||
"settingsVersion": 25,
|
||||
"bar": {
|
||||
"position": "top",
|
||||
"backgroundOpacity": 1,
|
||||
@@ -79,8 +78,8 @@
|
||||
"allowPanelsOnScreenWithoutBar": true
|
||||
},
|
||||
"ui": {
|
||||
"fontDefault": "Roboto",
|
||||
"fontFixed": "DejaVu Sans Mono",
|
||||
"fontDefault": "",
|
||||
"fontFixed": "",
|
||||
"fontDefaultScale": 1,
|
||||
"fontFixedScale": 1,
|
||||
"tooltipsEnabled": true,
|
||||
@@ -100,6 +99,26 @@
|
||||
"analogClockInCalendar": false,
|
||||
"firstDayOfWeek": -1
|
||||
},
|
||||
"calendar": {
|
||||
"cards": [
|
||||
{
|
||||
"enabled": true,
|
||||
"id": "banner-card"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"id": "calendar-card"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"id": "timer-card"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"id": "weather-card"
|
||||
}
|
||||
]
|
||||
},
|
||||
"screenRecorder": {
|
||||
"directory": "",
|
||||
"frameRate": 60,
|
||||
@@ -115,10 +134,10 @@
|
||||
"enabled": true,
|
||||
"overviewEnabled": false,
|
||||
"directory": "",
|
||||
"monitorDirectories": [],
|
||||
"enableMultiMonitorDirectories": false,
|
||||
"recursiveSearch": false,
|
||||
"setWallpaperOnAllMonitors": true,
|
||||
"defaultWallpaper": "",
|
||||
"fillMode": "crop",
|
||||
"fillColor": "#000000",
|
||||
"randomEnabled": false,
|
||||
@@ -126,7 +145,6 @@
|
||||
"transitionDuration": 1500,
|
||||
"transitionType": "random",
|
||||
"transitionEdgeSmoothness": 0.05,
|
||||
"monitors": [],
|
||||
"panelPosition": "follow_bar",
|
||||
"hideWallpaperFilenames": false,
|
||||
"useWallhaven": false,
|
||||
@@ -148,7 +166,8 @@
|
||||
"sortByMostUsed": true,
|
||||
"terminalCommand": "xterm -e",
|
||||
"customLaunchPrefixEnabled": false,
|
||||
"customLaunchPrefix": ""
|
||||
"customLaunchPrefix": "",
|
||||
"viewMode": "list"
|
||||
},
|
||||
"controlCenter": {
|
||||
"position": "close_to_bar_button",
|
||||
@@ -214,13 +233,18 @@
|
||||
"memCriticalThreshold": 90,
|
||||
"diskWarningThreshold": 80,
|
||||
"diskCriticalThreshold": 90,
|
||||
"cpuPollingInterval": 3000,
|
||||
"tempPollingInterval": 3000,
|
||||
"memPollingInterval": 3000,
|
||||
"diskPollingInterval": 3000,
|
||||
"networkPollingInterval": 3000,
|
||||
"useCustomColors": false,
|
||||
"warningColor": "",
|
||||
"criticalColor": ""
|
||||
},
|
||||
"dock": {
|
||||
"enabled": true,
|
||||
"displayMode": "always_visible",
|
||||
"displayMode": "auto_hide",
|
||||
"backgroundOpacity": 1,
|
||||
"radiusRatio": 0.1,
|
||||
"floatingRatio": 1,
|
||||
@@ -280,10 +304,15 @@
|
||||
"osd": {
|
||||
"enabled": true,
|
||||
"location": "top_right",
|
||||
"monitors": [],
|
||||
"autoHideMs": 2000,
|
||||
"overlayLayer": true,
|
||||
"backgroundOpacity": 1
|
||||
"backgroundOpacity": 1,
|
||||
"enabledTypes": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"monitors": []
|
||||
},
|
||||
"audio": {
|
||||
"volumeStep": 5,
|
||||
@@ -327,6 +356,9 @@
|
||||
"code": false,
|
||||
"spicetify": false,
|
||||
"telegram": false,
|
||||
"cava": false,
|
||||
"emacs": false,
|
||||
"niri": false,
|
||||
"enableUserTemplates": false
|
||||
},
|
||||
"nightLight": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
if [ "$#" -ne 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}" >&2
|
||||
echo "Usage: $0 {kitty|ghostty|foot|alacritty|wezterm|fuzzel|walker|pywalfox|cava|niri}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -252,6 +252,29 @@ cava)
|
||||
fi
|
||||
;;
|
||||
|
||||
niri)
|
||||
echo "🎨 Applying 'noctalia' theme to niri..."
|
||||
CONFIG_FILE="$HOME/.config/niri/config.kdl"
|
||||
INCLUDE_LINE='include "./noctalia.kdl"'
|
||||
|
||||
# Check if the config file exists.
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "Config file not found, creating $CONFIG_FILE..."
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
echo "$INCLUDE_LINE" >"$CONFIG_FILE"
|
||||
echo "Created new config file with noctalia theme."
|
||||
else
|
||||
# Check if include line already exists
|
||||
if grep -qF "$INCLUDE_LINE" "$CONFIG_FILE"; then
|
||||
echo "Theme already included, skipping modification."
|
||||
else
|
||||
# Add the include line to the end of the file
|
||||
echo "$INCLUDE_LINE" >>"$CONFIG_FILE"
|
||||
echo "✅ Added noctalia theme include to config."
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
# Handle unknown application names.
|
||||
echo "Error: Unknown application '$APP_NAME'." >&2
|
||||
|
||||
48
CREDITS.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Credits
|
||||
|
||||
Noctalia Shell is made possible by the incredible work of many open-source projects and contributors.
|
||||
|
||||
## Design & Branding
|
||||
|
||||
- **MrDowntempo** - Creator of the Noctalia Owl and moon logo
|
||||
- **[SaberJ2X](https://www.reddit.com/user/SaberJ64/)** - Creator of Talia, the Noctalia mascot
|
||||
|
||||
## Core Framework
|
||||
|
||||
- **[Quickshell](https://github.com/outfoxxed/quickshell)** - The Qt/QML-based Wayland shell framework that powers Noctalia
|
||||
|
||||
## Runtime Dependencies
|
||||
|
||||
### System Integration
|
||||
- **[brightnessctl](https://github.com/Hummer12007/brightnessctl)** - Screen brightness control
|
||||
- **[wlsunset](https://sr.ht/~kennylevinsen/wlsunset/)** - Night light and blue light filter support
|
||||
- **[wl-clipboard](https://github.com/bugaevc/wl-clipboard)** - Wayland clipboard utilities
|
||||
- **[ddcutil](https://www.ddcutil.com/)** - External display brightness control
|
||||
- **[power-profiles-daemon](https://gitlab.freedesktop.org/upower/power-profiles-daemon)** - Power profile management
|
||||
|
||||
### Media & Audio
|
||||
- **[gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/about/)** - Hardware-accelerated screen recording
|
||||
- **[Cava](https://github.com/karlstav/cava)** - Audio visualizer component
|
||||
|
||||
### Theming & Appearance
|
||||
- **[Matugen](https://github.com/InioX/matugen)** - Material You color scheme generation from wallpapers
|
||||
|
||||
### Utilities
|
||||
- **[cliphist](https://github.com/sentriz/cliphist)** - Clipboard history support
|
||||
|
||||
## Audio Assets
|
||||
|
||||
- **[DrNI on Freesound](https://freesound.org/people/DrNI/sounds/34562/)** - Notification sound effect
|
||||
|
||||
|
||||
## Special Thanks
|
||||
|
||||
- The **Wayland** community for building the future of Linux desktop graphics
|
||||
- The **Niri**, **Hyprland**, **Sway**, and **MangoWC** teams for their excellent Wayland compositors
|
||||
- All the contributors and users who have helped make Noctalia better
|
||||
|
||||
## License
|
||||
|
||||
Noctalia Shell is licensed under the MIT License. See [LICENSE](LICENSE) for details.
|
||||
|
||||
Each dependency listed above is governed by its own respective license. Please refer to their individual projects for licensing information.
|
||||
@@ -49,7 +49,7 @@ Singleton {
|
||||
readonly property color white: "#ffffff"
|
||||
|
||||
// --------------------------------
|
||||
// Default colors: RosePine
|
||||
// Default colors: Rose Pine
|
||||
QtObject {
|
||||
id: defaultColors
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ Singleton {
|
||||
"microphone": "microphone",
|
||||
"microphone-mute": "microphone-off",
|
||||
"volume-mute": "volume-off",
|
||||
"volume-x": "volume-3",
|
||||
"volume-zero": "volume-3",
|
||||
"volume-low": "volume-2",
|
||||
"volume-high": "volume",
|
||||
|
||||
44
Commons/Migrations/Migration26.qml
Normal file
@@ -0,0 +1,44 @@
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
// Migrate from version < 26 to version 26
|
||||
// Replaces old calendar-card and banner-card with calendar-header-card and calendar-month-card
|
||||
function migrate(adapter, logger) {
|
||||
logger.i("Settings", "Migrating settings to v26");
|
||||
|
||||
// Replace old calendar-card and banner-card with calendar-header-card and calendar-month-card
|
||||
if (adapter.calendar !== undefined && adapter.calendar.cards !== undefined) {
|
||||
const oldCards = adapter.calendar.cards;
|
||||
const newCards = [];
|
||||
let anyCalendarEnabled = false;
|
||||
|
||||
// Check if any calendar-related card was enabled
|
||||
for (var i = 0; i < oldCards.length; i++) {
|
||||
const card = oldCards[i];
|
||||
if ((card.id === "banner-card" || card.id === "calendar-card") && card.enabled) {
|
||||
anyCalendarEnabled = true;
|
||||
} else if (card.id !== "banner-card" && card.id !== "calendar-card") {
|
||||
// Keep other cards as-is (timer, weather)
|
||||
newCards.push(card);
|
||||
}
|
||||
}
|
||||
|
||||
// Add new split cards at the beginning (enabled if any old calendar card was enabled)
|
||||
newCards.unshift({
|
||||
"id": "calendar-month-card",
|
||||
"enabled": anyCalendarEnabled
|
||||
});
|
||||
newCards.unshift({
|
||||
"id": "calendar-header-card",
|
||||
"enabled": anyCalendarEnabled
|
||||
});
|
||||
|
||||
adapter.calendar.cards = newCards;
|
||||
logger.i("Settings", "Replaced old calendar cards with calendar-header-card + calendar-month-card");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
15
Commons/Migrations/MigrationRegistry.qml
Normal file
@@ -0,0 +1,15 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
// Map of version number to migration component
|
||||
readonly property var migrations: ({
|
||||
26: migration26Component
|
||||
})
|
||||
|
||||
// Migration components
|
||||
property Component migration26Component: Migration26 {}
|
||||
}
|
||||
@@ -5,33 +5,36 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import "../Helpers/QtObj2JS.js" as QtObj2JS
|
||||
import qs.Commons
|
||||
import qs.Commons.Migrations
|
||||
import qs.Modules.OSD
|
||||
import qs.Services.UI
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Used to access via Settings.data.xxx.yyy
|
||||
readonly property alias data: adapter
|
||||
property bool isLoaded: false
|
||||
property bool directoriesCreated: false
|
||||
property int settingsVersion: 23
|
||||
property bool isDebug: Quickshell.env("NOCTALIA_DEBUG") === "1"
|
||||
property bool shouldOpenSetupWizard: false
|
||||
|
||||
// Define our app directories
|
||||
// Default config directory: ~/.config/noctalia
|
||||
// Default cache directory: ~/.cache/noctalia
|
||||
property string shellName: "noctalia"
|
||||
property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/"
|
||||
property string cacheDir: Quickshell.env("NOCTALIA_CACHE_DIR") || (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env("HOME") + "/.cache") + "/" + shellName + "/"
|
||||
property string cacheDirImages: cacheDir + "images/"
|
||||
property string cacheDirImagesWallpapers: cacheDir + "images/wallpapers/"
|
||||
property string cacheDirImagesNotifications: cacheDir + "images/notifications/"
|
||||
property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE") || (configDir + "settings.json")
|
||||
|
||||
property string defaultLocation: "Tokyo"
|
||||
property string defaultAvatar: Quickshell.env("HOME") + "/.face"
|
||||
property string defaultVideosDirectory: Quickshell.env("HOME") + "/Videos"
|
||||
property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers"
|
||||
/*
|
||||
Shell directories.
|
||||
- Default config directory: ~/.config/noctalia
|
||||
- Default cache directory: ~/.cache/noctalia
|
||||
*/
|
||||
readonly property alias data: adapter // Used to access via Settings.data.xxx.yyy
|
||||
readonly property int settingsVersion: 26
|
||||
readonly property bool isDebug: Quickshell.env("NOCTALIA_DEBUG") === "1"
|
||||
readonly property string shellName: "noctalia"
|
||||
readonly property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/"
|
||||
readonly property string cacheDir: Quickshell.env("NOCTALIA_CACHE_DIR") || (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env("HOME") + "/.cache") + "/" + shellName + "/"
|
||||
readonly property string cacheDirImages: cacheDir + "images/"
|
||||
readonly property string cacheDirImagesWallpapers: cacheDir + "images/wallpapers/"
|
||||
readonly property string cacheDirImagesNotifications: cacheDir + "images/notifications/"
|
||||
readonly property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE") || (configDir + "settings.json")
|
||||
readonly property string defaultLocation: "Tokyo"
|
||||
readonly property string defaultAvatar: Quickshell.env("HOME") + "/.face"
|
||||
readonly property string defaultVideosDirectory: Quickshell.env("HOME") + "/Videos"
|
||||
readonly property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers"
|
||||
|
||||
// Signal emitted when settings are loaded after startupcale changes
|
||||
signal settingsLoaded
|
||||
@@ -62,7 +65,8 @@ Singleton {
|
||||
adapter.general.avatarImage = defaultAvatar;
|
||||
adapter.screenRecorder.directory = defaultVideosDirectory;
|
||||
adapter.wallpaper.directory = defaultWallpapersDirectory;
|
||||
adapter.wallpaper.defaultWallpaper = Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png";
|
||||
adapter.ui.fontDefault = Qt.application.font.family;
|
||||
adapter.ui.fontFixed = "monospace";
|
||||
|
||||
// Set the adapter to the settingsFileView to trigger the real settings load
|
||||
settingsFileView.adapter = adapter;
|
||||
@@ -73,7 +77,7 @@ Singleton {
|
||||
Timer {
|
||||
id: saveTimer
|
||||
running: false
|
||||
interval: 1000
|
||||
interval: 500
|
||||
onTriggered: {
|
||||
root.saveImmediate();
|
||||
}
|
||||
@@ -97,9 +101,13 @@ Singleton {
|
||||
if (!isLoaded) {
|
||||
Logger.i("Settings", "Settings loaded");
|
||||
|
||||
// -----------------
|
||||
// Run versioned migrations from MigrationRegistry
|
||||
runVersionedMigrations();
|
||||
|
||||
upgradeSettingsData();
|
||||
validateMonitorConfigurations();
|
||||
isLoaded = true;
|
||||
|
||||
root.isLoaded = true;
|
||||
|
||||
// Emit the signal
|
||||
root.settingsLoaded();
|
||||
@@ -112,10 +120,14 @@ Singleton {
|
||||
if (error.toString().includes("No such file") || error === 2) {
|
||||
// File doesn't exist, create it with default values
|
||||
writeAdapter();
|
||||
|
||||
// Also write to fallback if set
|
||||
if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) {
|
||||
settingsFallbackFileView.writeAdapter();
|
||||
}
|
||||
|
||||
// We started without settings, we should open the setupWizard
|
||||
root.shouldOpenSetupWizard = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,17 +140,17 @@ Singleton {
|
||||
printErrors: false
|
||||
watchChanges: false
|
||||
}
|
||||
|
||||
JsonAdapter {
|
||||
id: adapter
|
||||
|
||||
property int settingsVersion: root.settingsVersion
|
||||
property bool setupCompleted: false
|
||||
|
||||
// bar
|
||||
property JsonObject bar: JsonObject {
|
||||
property string position: "top" // "top", "bottom", "left", or "right"
|
||||
property real backgroundOpacity: 1.0
|
||||
property list<string> monitors: []
|
||||
property list<string> monitors: [] // holds bar visibility per monitor
|
||||
property string density: "default" // "compact", "default", "comfortable"
|
||||
property bool showCapsule: true
|
||||
property real capsuleOpacity: 1.0
|
||||
@@ -159,7 +171,13 @@ Singleton {
|
||||
widgets: JsonObject {
|
||||
property list<var> left: [
|
||||
{
|
||||
"id": "ControlCenter"
|
||||
"icon": "rocket",
|
||||
"id": "CustomButton",
|
||||
"leftClickExec": "qs -c noctalia-shell ipc call launcher toggle"
|
||||
},
|
||||
{
|
||||
"id": "Clock",
|
||||
"usePrimaryColor": false
|
||||
},
|
||||
{
|
||||
"id": "SystemMonitor"
|
||||
@@ -196,7 +214,7 @@ Singleton {
|
||||
"id": "Brightness"
|
||||
},
|
||||
{
|
||||
"id": "Clock"
|
||||
"id": "ControlCenter"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -226,8 +244,8 @@ Singleton {
|
||||
|
||||
// ui
|
||||
property JsonObject ui: JsonObject {
|
||||
property string fontDefault: "Roboto"
|
||||
property string fontFixed: "DejaVu Sans Mono"
|
||||
property string fontDefault: ""
|
||||
property string fontFixed: ""
|
||||
property real fontDefaultScale: 1.0
|
||||
property real fontFixedScale: 1.0
|
||||
property bool tooltipsEnabled: true
|
||||
@@ -250,6 +268,28 @@ Singleton {
|
||||
property int firstDayOfWeek: -1 // -1 = auto (use locale), 0 = Sunday, 1 = Monday, 6 = Saturday
|
||||
}
|
||||
|
||||
// calendar
|
||||
property JsonObject calendar: JsonObject {
|
||||
property list<var> cards: [
|
||||
{
|
||||
"id": "calendar-header-card",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "calendar-month-card",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "timer-card",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "weather-card",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// screen recorder
|
||||
property JsonObject screenRecorder: JsonObject {
|
||||
property string directory: ""
|
||||
@@ -268,10 +308,10 @@ Singleton {
|
||||
property bool enabled: true
|
||||
property bool overviewEnabled: false
|
||||
property string directory: ""
|
||||
property list<var> monitorDirectories: []
|
||||
property bool enableMultiMonitorDirectories: false
|
||||
property bool recursiveSearch: false
|
||||
property bool setWallpaperOnAllMonitors: true
|
||||
property string defaultWallpaper: ""
|
||||
property string fillMode: "crop"
|
||||
property color fillColor: "#000000"
|
||||
property bool randomEnabled: false
|
||||
@@ -279,7 +319,6 @@ Singleton {
|
||||
property int transitionDuration: 1500 // 1500 ms
|
||||
property string transitionType: "random"
|
||||
property real transitionEdgeSmoothness: 0.05
|
||||
property list<var> monitors: []
|
||||
property string panelPosition: "follow_bar"
|
||||
property bool hideWallpaperFilenames: false
|
||||
// Wallhaven settings
|
||||
@@ -306,6 +345,8 @@ Singleton {
|
||||
property string terminalCommand: "xterm -e"
|
||||
property bool customLaunchPrefixEnabled: false
|
||||
property string customLaunchPrefix: ""
|
||||
// View mode: "list" or "grid"
|
||||
property string viewMode: "list"
|
||||
}
|
||||
|
||||
// control center
|
||||
@@ -377,6 +418,11 @@ Singleton {
|
||||
property int memCriticalThreshold: 90
|
||||
property int diskWarningThreshold: 80
|
||||
property int diskCriticalThreshold: 90
|
||||
property int cpuPollingInterval: 3000
|
||||
property int tempPollingInterval: 3000
|
||||
property int memPollingInterval: 3000
|
||||
property int diskPollingInterval: 3000
|
||||
property int networkPollingInterval: 3000
|
||||
property bool useCustomColors: false
|
||||
property string warningColor: ""
|
||||
property string criticalColor: ""
|
||||
@@ -385,13 +431,13 @@ Singleton {
|
||||
// dock
|
||||
property JsonObject dock: JsonObject {
|
||||
property bool enabled: true
|
||||
property string displayMode: "always_visible" // "always_visible", "auto_hide", "exclusive"
|
||||
property string displayMode: "auto_hide" // "always_visible", "auto_hide", "exclusive"
|
||||
property real backgroundOpacity: 1.0
|
||||
property real radiusRatio: 0.1
|
||||
property real floatingRatio: 1.0
|
||||
property real size: 1
|
||||
property bool onlySameOutput: true
|
||||
property list<string> monitors: []
|
||||
property list<string> monitors: [] // holds dock visibility per monitor
|
||||
// Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop")
|
||||
property list<string> pinnedApps: []
|
||||
property bool colorizeIcons: false
|
||||
@@ -439,7 +485,7 @@ Singleton {
|
||||
// notifications
|
||||
property JsonObject notifications: JsonObject {
|
||||
property bool enabled: true
|
||||
property list<string> monitors: []
|
||||
property list<string> monitors: [] // holds notifications visibility per monitor
|
||||
property string location: "top_right"
|
||||
property bool overlayLayer: true
|
||||
property real backgroundOpacity: 1.0
|
||||
@@ -454,10 +500,11 @@ Singleton {
|
||||
property JsonObject osd: JsonObject {
|
||||
property bool enabled: true
|
||||
property string location: "top_right"
|
||||
property list<string> monitors: []
|
||||
property int autoHideMs: 2000
|
||||
property bool overlayLayer: true
|
||||
property real backgroundOpacity: 1.0
|
||||
property list<var> enabledTypes: [OSD.Type.Volume, OSD.Type.InputVolume, OSD.Type.Brightness]
|
||||
property list<string> monitors: [] // holds osd visibility per monitor
|
||||
}
|
||||
|
||||
// audio
|
||||
@@ -509,6 +556,8 @@ Singleton {
|
||||
property bool spicetify: false
|
||||
property bool telegram: false
|
||||
property bool cava: false
|
||||
property bool emacs: false
|
||||
property bool niri: false
|
||||
property bool enableUserTemplates: false
|
||||
}
|
||||
|
||||
@@ -584,436 +633,42 @@ Singleton {
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Function to validate monitor configurations
|
||||
function validateMonitorConfigurations() {
|
||||
var availableScreenNames = [];
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
availableScreenNames.push(Quickshell.screens[i].name);
|
||||
}
|
||||
// Run versioned migrations using MigrationRegistry
|
||||
function runVersionedMigrations() {
|
||||
const currentVersion = adapter.settingsVersion;
|
||||
const migrations = MigrationRegistry.migrations;
|
||||
|
||||
Logger.d("Settings", "Available monitors: [" + availableScreenNames.join(", ") + "]");
|
||||
Logger.d("Settings", "Configured bar monitors: [" + adapter.bar.monitors.join(", ") + "]");
|
||||
// Get all migration versions and sort them
|
||||
const versions = Object.keys(migrations).map(v => parseInt(v)).sort((a, b) => a - b);
|
||||
|
||||
// Check bar monitors
|
||||
if (adapter.bar.monitors.length > 0) {
|
||||
var hasValidBarMonitor = false;
|
||||
for (var j = 0; j < adapter.bar.monitors.length; j++) {
|
||||
if (availableScreenNames.includes(adapter.bar.monitors[j])) {
|
||||
hasValidBarMonitor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasValidBarMonitor) {
|
||||
Logger.w("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens");
|
||||
adapter.bar.monitors = [];
|
||||
} else
|
||||
// Run migrations in order for versions newer than current
|
||||
for (var i = 0; i < versions.length; i++) {
|
||||
const version = versions[i];
|
||||
|
||||
//Logger.i("Settings", "Found valid bar monitors, keeping configuration")
|
||||
{}
|
||||
} else
|
||||
if (currentVersion < version) {
|
||||
// Create migration instance and run it
|
||||
const migrationComponent = migrations[version];
|
||||
const migration = migrationComponent.createObject(root);
|
||||
|
||||
//Logger.i("Settings", "Bar monitor list is empty, will show on all available screens")
|
||||
{}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// If the settings structure has changed, ensure
|
||||
// backward compatibility by upgrading the settings
|
||||
function upgradeSettingsData() {
|
||||
// Wait for BarWidgetRegistry to be ready
|
||||
if (!BarWidgetRegistry.widgets || Object.keys(BarWidgetRegistry.widgets).length === 0) {
|
||||
Logger.w("Settings", "BarWidgetRegistry not ready, deferring upgrade");
|
||||
Qt.callLater(upgradeSettingsData);
|
||||
return;
|
||||
}
|
||||
|
||||
const sections = ["left", "center", "right"];
|
||||
|
||||
// -----------------
|
||||
// 1st. convert old widget id to new id
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s];
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i];
|
||||
|
||||
switch (widget.id) {
|
||||
case "DarkModeToggle":
|
||||
widget.id = "DarkMode";
|
||||
break;
|
||||
case "PowerToggle":
|
||||
widget.id = "SessionMenu";
|
||||
break;
|
||||
case "ScreenRecorderIndicator":
|
||||
widget.id = "ScreenRecorder";
|
||||
break;
|
||||
case "SidePanelToggle":
|
||||
widget.id = "ControlCenter";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// 2nd. remove any non existing widget type
|
||||
var removedWidget = false;
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s];
|
||||
const widgets = adapter.bar.widgets[sectionName];
|
||||
// Iterate backward through the widgets array, so it does not break when removing a widget
|
||||
for (var i = widgets.length - 1; i >= 0; i--) {
|
||||
var widget = widgets[i];
|
||||
if (!BarWidgetRegistry.hasWidget(widget.id)) {
|
||||
Logger.w(`Settings`, `Deleted invalid widget ${widget.id}`);
|
||||
widgets.splice(i, 1);
|
||||
removedWidget = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// 3nd. upgrade widget settings
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s];
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i];
|
||||
|
||||
// Check if widget registry supports user settings, if it does not, then there is nothing to do
|
||||
const reg = BarWidgetRegistry.widgetMetadata[widget.id];
|
||||
if ((reg === undefined) || (reg.allowUserSettings === undefined) || !reg.allowUserSettings) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (upgradeWidget(widget)) {
|
||||
Logger.d("Settings", `Upgraded ${widget.id} widget:`, JSON.stringify(widget));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// 4th. safety check
|
||||
// if a widget was deleted, ensure we still have a control center
|
||||
if (removedWidget) {
|
||||
var gotControlCenter = false;
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s];
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i];
|
||||
if (widget.id === "ControlCenter") {
|
||||
gotControlCenter = true;
|
||||
break;
|
||||
if (migration && typeof migration.migrate === "function") {
|
||||
const success = migration.migrate(adapter, Logger);
|
||||
if (!success) {
|
||||
Logger.e("Settings", "Migration to v" + version + " failed");
|
||||
}
|
||||
} else {
|
||||
Logger.e("Settings", "Invalid migration for v" + version);
|
||||
}
|
||||
}
|
||||
|
||||
if (!gotControlCenter) {
|
||||
//const obj = JSON.parse('{"id": "ControlCenter"}');
|
||||
adapter.bar.widgets["right"].push(({
|
||||
"id": "ControlCenter"
|
||||
}));
|
||||
Logger.w("Settings", "Added a ControlCenter widget to the right section");
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// 5th. Migrate Discord templates (version 20 → 21)
|
||||
// Consolidate individual discord_* properties into unified discord property
|
||||
if (adapter.settingsVersion < 21) {
|
||||
// Read raw JSON file to access properties not in adapter schema
|
||||
try {
|
||||
var rawJson = settingsFileView.text();
|
||||
|
||||
if (rawJson) {
|
||||
var parsed = JSON.parse(rawJson);
|
||||
var anyDiscordEnabled = false;
|
||||
|
||||
// Check if any Discord client was enabled
|
||||
const discordClients = ["discord_vesktop", "discord_webcord", "discord_armcord", "discord_equibop", "discord_lightcord", "discord_dorion", "discord_vencord"];
|
||||
|
||||
if (parsed.templates) {
|
||||
for (var i = 0; i < discordClients.length; i++) {
|
||||
if (parsed.templates[discordClients[i]]) {
|
||||
anyDiscordEnabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set unified discord property
|
||||
adapter.templates.discord = anyDiscordEnabled;
|
||||
|
||||
Logger.i("Settings", "Migrated Discord templates to unified 'discord' property (enabled:", anyDiscordEnabled + ")");
|
||||
// Clean up migration instance
|
||||
if (migration) {
|
||||
migration.destroy();
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.w("Settings", "Failed to read raw JSON for Discord migration:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// 6th. Migrate panel background opacity (version 21 → 22)
|
||||
// Move appLauncher.backgroundOpacity to ui.panelBackgroundOpacity
|
||||
if (adapter.settingsVersion < 22) {
|
||||
// Read raw JSON file to access properties not in adapter schema
|
||||
try {
|
||||
var rawJson = settingsFileView.text();
|
||||
|
||||
if (rawJson) {
|
||||
var parsed = JSON.parse(rawJson);
|
||||
if (parsed.appLauncher && parsed.appLauncher.backgroundOpacity !== undefined) {
|
||||
var oldOpacity = parsed.appLauncher.backgroundOpacity;
|
||||
if (adapter.ui) {
|
||||
adapter.ui.panelBackgroundOpacity = oldOpacity;
|
||||
Logger.i("Settings", "Migrated appLauncher.backgroundOpacity to ui.panelBackgroundOpacity (value:", oldOpacity + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.w("Settings", "Failed to read raw JSON for migration:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// 7th. Migrate dim desktop settings (version 22 → 23)
|
||||
// If dimDesktop is enabled, set dimmerOpacity to 0.8 if it's not already set or is 0
|
||||
// Then remove dimDesktop property as it's no longer needed
|
||||
if (adapter.settingsVersion < 23) {
|
||||
// Read raw JSON file to access dimDesktop property
|
||||
try {
|
||||
var rawJson = settingsFileView.text();
|
||||
|
||||
if (rawJson) {
|
||||
var parsed = JSON.parse(rawJson);
|
||||
if (parsed.general && parsed.general.dimDesktop === true) {
|
||||
// Check if dimmerOpacity exists in raw JSON (not adapter default)
|
||||
var dimmerOpacityInJson = parsed.general.dimmerOpacity;
|
||||
|
||||
// If dimmerOpacity wasn't explicitly set in JSON or was 0, set it to 0.8 (80% dimming)
|
||||
if (dimmerOpacityInJson === undefined || dimmerOpacityInJson === 0) {
|
||||
adapter.general.dimmerOpacity = 0.8;
|
||||
Logger.i("Settings", "Migrated dimDesktop=true: set dimmerOpacity to 0.8 (80% dimming)");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.w("Settings", "Failed to read raw JSON for dimDesktop migration:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// 8th. Migrate ShellState-related files from old cache files to ShellState
|
||||
// This consolidates migrations that were previously in individual service files
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
migrateShellStateFiles();
|
||||
} else {
|
||||
// Wait for ShellState to be ready
|
||||
Qt.callLater(() => {
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
migrateShellStateFiles();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Migrate old cache files to ShellState
|
||||
function migrateShellStateFiles() {
|
||||
// Migrate display.json → ShellState (CompositorService)
|
||||
migrateDisplayFile();
|
||||
|
||||
// Migrate notifications-state.json → ShellState (NotificationService)
|
||||
migrateNotificationsStateFile();
|
||||
|
||||
// Migrate changelog-state.json → ShellState (UpdateService)
|
||||
migrateChangelogStateFile();
|
||||
|
||||
// Migrate color-schemes-list.json → ShellState (SchemeDownloader)
|
||||
migrateColorSchemesListFile();
|
||||
|
||||
// Migrate wallpaper paths from Settings → ShellState (WallpaperService)
|
||||
migrateWallpaperPaths();
|
||||
}
|
||||
|
||||
function migrateDisplayFile() {
|
||||
// Check if ShellState already has display data
|
||||
const cached = ShellState.getDisplay();
|
||||
if (cached && Object.keys(cached).length > 0) {
|
||||
return; // Already migrated
|
||||
}
|
||||
|
||||
const oldDisplayPath = cacheDir + "display.json";
|
||||
const migrationFileView = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
FileView {
|
||||
id: migrationView
|
||||
path: "${oldDisplayPath}"
|
||||
printErrors: false
|
||||
adapter: JsonAdapter {
|
||||
property var displays: ({})
|
||||
}
|
||||
onLoaded: {
|
||||
if (adapter.displays && Object.keys(adapter.displays).length > 0) {
|
||||
ShellState.setDisplay(adapter.displays);
|
||||
Logger.i("Settings", "Migrated display.json to ShellState");
|
||||
}
|
||||
migrationView.destroy();
|
||||
}
|
||||
onLoadFailed: {
|
||||
migrationView.destroy();
|
||||
}
|
||||
}
|
||||
`, root, "displayMigrationView");
|
||||
}
|
||||
|
||||
function migrateNotificationsStateFile() {
|
||||
// Check if ShellState already has notifications state
|
||||
const cached = ShellState.getNotificationsState();
|
||||
if (cached && cached.lastSeenTs && cached.lastSeenTs > 0) {
|
||||
return; // Already migrated
|
||||
}
|
||||
|
||||
// Also check Settings for lastSeenTs
|
||||
if (adapter.notifications && adapter.notifications.lastSeenTs) {
|
||||
ShellState.setNotificationsState({
|
||||
lastSeenTs: adapter.notifications.lastSeenTs
|
||||
});
|
||||
Logger.i("Settings", "Migrated notifications lastSeenTs from Settings to ShellState");
|
||||
return;
|
||||
}
|
||||
|
||||
const oldStatePath = cacheDir + "notifications-state.json";
|
||||
const migrationFileView = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
FileView {
|
||||
id: migrationView
|
||||
path: "${oldStatePath}"
|
||||
printErrors: false
|
||||
adapter: JsonAdapter {
|
||||
property real lastSeenTs: 0
|
||||
}
|
||||
onLoaded: {
|
||||
if (adapter.lastSeenTs && adapter.lastSeenTs > 0) {
|
||||
ShellState.setNotificationsState({
|
||||
lastSeenTs: adapter.lastSeenTs
|
||||
});
|
||||
Logger.i("Settings", "Migrated notifications-state.json to ShellState");
|
||||
}
|
||||
migrationView.destroy();
|
||||
}
|
||||
onLoadFailed: {
|
||||
migrationView.destroy();
|
||||
}
|
||||
}
|
||||
`, root, "notificationsMigrationView");
|
||||
}
|
||||
|
||||
function migrateChangelogStateFile() {
|
||||
// Check if ShellState already has changelog state
|
||||
const cached = ShellState.getChangelogState();
|
||||
if (cached && cached.lastSeenVersion && cached.lastSeenVersion !== "") {
|
||||
return; // Already migrated
|
||||
}
|
||||
|
||||
// Also check Settings for lastSeenVersion
|
||||
if (adapter.changelog && adapter.changelog.lastSeenVersion) {
|
||||
ShellState.setChangelogState({
|
||||
lastSeenVersion: adapter.changelog.lastSeenVersion
|
||||
});
|
||||
Logger.i("Settings", "Migrated changelog lastSeenVersion from Settings to ShellState");
|
||||
return;
|
||||
}
|
||||
|
||||
const oldChangelogPath = cacheDir + "changelog-state.json";
|
||||
const migrationFileView = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
FileView {
|
||||
id: migrationView
|
||||
path: "${oldChangelogPath}"
|
||||
printErrors: false
|
||||
adapter: JsonAdapter {
|
||||
property string lastSeenVersion: ""
|
||||
}
|
||||
onLoaded: {
|
||||
if (adapter.lastSeenVersion && adapter.lastSeenVersion !== "") {
|
||||
ShellState.setChangelogState({
|
||||
lastSeenVersion: adapter.lastSeenVersion
|
||||
});
|
||||
Logger.i("Settings", "Migrated changelog-state.json to ShellState");
|
||||
}
|
||||
migrationView.destroy();
|
||||
}
|
||||
onLoadFailed: {
|
||||
migrationView.destroy();
|
||||
}
|
||||
}
|
||||
`, root, "changelogMigrationView");
|
||||
}
|
||||
|
||||
function migrateColorSchemesListFile() {
|
||||
// Check if ShellState already has color schemes list
|
||||
const cached = ShellState.getColorSchemesList();
|
||||
if (cached && cached.schemes && cached.schemes.length > 0) {
|
||||
return; // Already migrated
|
||||
}
|
||||
|
||||
const oldSchemesPath = cacheDir + "color-schemes-list.json";
|
||||
const migrationFileView = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
FileView {
|
||||
id: migrationView
|
||||
path: "${oldSchemesPath}"
|
||||
printErrors: false
|
||||
adapter: JsonAdapter {
|
||||
property var schemes: []
|
||||
property real timestamp: 0
|
||||
}
|
||||
onLoaded: {
|
||||
if (adapter.schemes && adapter.schemes.length > 0) {
|
||||
ShellState.setColorSchemesList({
|
||||
schemes: adapter.schemes,
|
||||
timestamp: adapter.timestamp || 0
|
||||
});
|
||||
Logger.i("Settings", "Migrated color-schemes-list.json to ShellState");
|
||||
}
|
||||
migrationView.destroy();
|
||||
}
|
||||
onLoadFailed: {
|
||||
migrationView.destroy();
|
||||
}
|
||||
}
|
||||
`, root, "schemesMigrationView");
|
||||
}
|
||||
|
||||
function migrateWallpaperPaths() {
|
||||
// Check if ShellState already has wallpaper paths
|
||||
const cached = ShellState.getWallpapers();
|
||||
if (cached && Object.keys(cached).length > 0) {
|
||||
return; // Already migrated
|
||||
}
|
||||
|
||||
// Migrate from Settings wallpaper.monitors
|
||||
var monitors = adapter.wallpaper.monitors || [];
|
||||
if (monitors.length > 0) {
|
||||
var wallpapers = {};
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
if (monitors[i].name && monitors[i].wallpaper) {
|
||||
wallpapers[monitors[i].name] = monitors[i].wallpaper;
|
||||
}
|
||||
}
|
||||
if (Object.keys(wallpapers).length > 0) {
|
||||
ShellState.setWallpapers(wallpapers);
|
||||
Logger.i("Settings", "Migrated wallpaper paths from Settings to ShellState");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Function to clean up deprecated user/custom bar widgets settings
|
||||
function upgradeWidget(widget) {
|
||||
// Backup the widget definition before altering
|
||||
const widgetBefore = JSON.stringify(widget);
|
||||
@@ -1047,4 +702,79 @@ Singleton {
|
||||
const widgetAfter = JSON.stringify(widget);
|
||||
return (widgetAfter !== widgetBefore);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// If the settings structure has changed, ensure
|
||||
// backward compatibility by upgrading the settings
|
||||
function upgradeSettingsData() {
|
||||
// Wait for BarWidgetRegistry to be ready
|
||||
if (!BarWidgetRegistry.widgets || Object.keys(BarWidgetRegistry.widgets).length === 0) {
|
||||
Logger.w("Settings", "BarWidgetRegistry not ready, deferring upgrade");
|
||||
Qt.callLater(upgradeSettingsData);
|
||||
return;
|
||||
}
|
||||
|
||||
const sections = ["left", "center", "right"];
|
||||
|
||||
// -----------------
|
||||
// 1. remove any non existing widget type
|
||||
var removedWidget = false;
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s];
|
||||
const widgets = adapter.bar.widgets[sectionName];
|
||||
// Iterate backward through the widgets array, so it does not break when removing a widget
|
||||
for (var i = widgets.length - 1; i >= 0; i--) {
|
||||
var widget = widgets[i];
|
||||
if (!BarWidgetRegistry.hasWidget(widget.id)) {
|
||||
Logger.w(`Settings`, `Deleted invalid widget ${widget.id}`);
|
||||
widgets.splice(i, 1);
|
||||
removedWidget = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// 2. upgrade user widget settings
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s];
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i];
|
||||
|
||||
// Check if widget registry supports user settings, if it does not, then there is nothing to do
|
||||
const reg = BarWidgetRegistry.widgetMetadata[widget.id];
|
||||
if ((reg === undefined) || (reg.allowUserSettings === undefined) || !reg.allowUserSettings) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (upgradeWidget(widget)) {
|
||||
Logger.d("Settings", `Upgraded ${widget.id} widget:`, JSON.stringify(widget));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// 3. safety check
|
||||
// if a widget was deleted, ensure we still have a control center
|
||||
if (removedWidget) {
|
||||
var gotControlCenter = false;
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s];
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i];
|
||||
if (widget.id === "ControlCenter") {
|
||||
gotControlCenter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!gotControlCenter) {
|
||||
//const obj = JSON.parse('{"id": "ControlCenter"}');
|
||||
adapter.bar.widgets["right"].push(({
|
||||
"id": "ControlCenter"
|
||||
}));
|
||||
Logger.w("Settings", "Added a ControlCenter widget to the right section");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "../Helpers/QtObj2JS.js" as QtObj2JS
|
||||
import qs.Services.Power
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
|
||||
// Centralized shell state management for small cache files
|
||||
Singleton {
|
||||
@@ -57,9 +61,6 @@ Singleton {
|
||||
schemes: [],
|
||||
timestamp: 0
|
||||
})
|
||||
|
||||
// WallpaperService: current wallpapers per screen
|
||||
property var wallpapers: ({})
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
@@ -82,7 +83,7 @@ Singleton {
|
||||
// Debounced save timer
|
||||
Timer {
|
||||
id: saveTimer
|
||||
interval: 300
|
||||
interval: 500
|
||||
onTriggered: performSave()
|
||||
}
|
||||
|
||||
@@ -170,13 +171,29 @@ Singleton {
|
||||
};
|
||||
}
|
||||
|
||||
// Wallpapers (WallpaperService)
|
||||
function setWallpapers(wallpapersData) {
|
||||
adapter.wallpapers = wallpapersData;
|
||||
save();
|
||||
}
|
||||
// -----------------------------------------------------
|
||||
function buildStateSnapshot() {
|
||||
try {
|
||||
const settingsData = QtObj2JS.qtObjectToPlainObject(Settings.data);
|
||||
const shellStateData = ShellState?.data ? QtObj2JS.qtObjectToPlainObject(ShellState.data) || {} : {};
|
||||
|
||||
function getWallpapers() {
|
||||
return adapter.wallpapers || {};
|
||||
return {
|
||||
settings: settingsData,
|
||||
state: {
|
||||
doNotDisturb: NotificationService.doNotDisturb,
|
||||
noctaliaPerformanceMode: PowerProfileService.noctaliaPerformanceMode,
|
||||
barVisible: BarService.isVisible,
|
||||
wallpapers: WallpaperService.currentWallpapers || {},
|
||||
// -------------
|
||||
display: shellStateData.display || {},
|
||||
notificationsState: shellStateData.notificationsState || {},
|
||||
changelogState: shellStateData.changelogState || {},
|
||||
colorSchemesList: shellStateData.colorSchemesList || {}
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
Logger.e("Settings", "Failed to build state snapshot:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import QtQuick
|
||||
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -15,6 +16,16 @@ Singleton {
|
||||
return Math.floor(root.now / 1000);
|
||||
}
|
||||
|
||||
// Timer state (for countdown/stopwatch)
|
||||
property bool timerRunning: false
|
||||
property bool timerStopwatchMode: false
|
||||
property int timerRemainingSeconds: 0
|
||||
property int timerTotalSeconds: 0
|
||||
property int timerElapsedSeconds: 0
|
||||
property bool timerSoundPlaying: false
|
||||
property int timerStartTimestamp: 0 // Unix timestamp when timer was started
|
||||
property int timerPausedAt: 0 // Value when paused (for resuming)
|
||||
|
||||
Timer {
|
||||
id: updateTimer
|
||||
interval: 1000
|
||||
@@ -25,6 +36,20 @@ Singleton {
|
||||
var newTime = new Date();
|
||||
root.now = newTime;
|
||||
|
||||
// Update timer if running
|
||||
if (root.timerRunning && root.timerStartTimestamp > 0) {
|
||||
const elapsedSinceStart = root.timestamp - root.timerStartTimestamp;
|
||||
|
||||
if (root.timerStopwatchMode) {
|
||||
root.timerElapsedSeconds = root.timerPausedAt + elapsedSinceStart;
|
||||
} else {
|
||||
root.timerRemainingSeconds = root.timerTotalSeconds - elapsedSinceStart;
|
||||
if (root.timerRemainingSeconds <= 0) {
|
||||
root.timerOnFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust next interval to sync with the start of the next second
|
||||
var msIntoSecond = newTime.getMilliseconds();
|
||||
if (msIntoSecond > 100) {
|
||||
@@ -119,4 +144,64 @@ Singleton {
|
||||
"diff": Math.floor(diff / 86400000)
|
||||
});
|
||||
}
|
||||
|
||||
// Timer functions
|
||||
function timerStart() {
|
||||
if (root.timerStopwatchMode) {
|
||||
root.timerRunning = true;
|
||||
root.timerStartTimestamp = root.timestamp;
|
||||
root.timerPausedAt = root.timerElapsedSeconds;
|
||||
} else {
|
||||
if (root.timerRemainingSeconds <= 0) {
|
||||
return;
|
||||
}
|
||||
root.timerRunning = true;
|
||||
root.timerTotalSeconds = root.timerRemainingSeconds;
|
||||
root.timerStartTimestamp = root.timestamp;
|
||||
root.timerPausedAt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function timerPause() {
|
||||
if (root.timerRunning) {
|
||||
// Save current state
|
||||
if (root.timerStopwatchMode) {
|
||||
root.timerPausedAt = root.timerElapsedSeconds;
|
||||
} else {
|
||||
root.timerPausedAt = root.timerRemainingSeconds;
|
||||
}
|
||||
}
|
||||
root.timerRunning = false;
|
||||
root.timerStartTimestamp = 0;
|
||||
// Stop any repeating notification sound when pausing
|
||||
SoundService.stopSound("alarm-beep.wav");
|
||||
root.timerSoundPlaying = false;
|
||||
}
|
||||
|
||||
function timerReset() {
|
||||
root.timerRunning = false;
|
||||
root.timerStartTimestamp = 0;
|
||||
if (root.timerStopwatchMode) {
|
||||
root.timerElapsedSeconds = 0;
|
||||
root.timerPausedAt = 0;
|
||||
} else {
|
||||
root.timerRemainingSeconds = 0;
|
||||
root.timerTotalSeconds = 0;
|
||||
root.timerPausedAt = 0;
|
||||
}
|
||||
// Stop any repeating notification sound
|
||||
SoundService.stopSound("alarm-beep.wav");
|
||||
root.timerSoundPlaying = false;
|
||||
}
|
||||
|
||||
function timerOnFinished() {
|
||||
root.timerRunning = false;
|
||||
root.timerRemainingSeconds = 0;
|
||||
// Play notification sound with repeat at lower volume
|
||||
root.timerSoundPlaying = true;
|
||||
SoundService.playSound("alarm-beep.wav", {
|
||||
repeat: true,
|
||||
volume: 0.3
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ Item {
|
||||
property bool oppositeDirection: false
|
||||
property bool hovered: false
|
||||
property bool rotateText: false
|
||||
property color customBackgroundColor: Qt.rgba(0, 0, 0, 0)
|
||||
property color customTextIconColor: Qt.rgba(0, 0, 0, 0)
|
||||
property color customBackgroundColor: Color.transparent
|
||||
property color customTextIconColor: Color.transparent
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
|
||||
|
||||
@@ -20,8 +20,10 @@ Item {
|
||||
property bool forceClose: false
|
||||
property bool oppositeDirection: false
|
||||
property bool hovered: false
|
||||
property color customBackgroundColor: Qt.rgba(0, 0, 0, 0)
|
||||
property color customTextIconColor: Qt.rgba(0, 0, 0, 0)
|
||||
property color customBackgroundColor: Color.transparent
|
||||
property color customTextIconColor: Color.transparent
|
||||
|
||||
readonly property bool collapseToIcon: forceClose && !forceOpen
|
||||
|
||||
// Effective shown state (true if hovered/animated open or forced)
|
||||
readonly property bool revealed: !forceClose && (forceOpen || showPill)
|
||||
@@ -44,6 +46,10 @@ Item {
|
||||
readonly property int pillOverlap: Math.round(Style.capsuleHeight * 0.5)
|
||||
readonly property int pillMaxWidth: Math.max(1, Math.round(textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap))
|
||||
|
||||
// Always prioritize hover color, then the custom one and finally the fallback color
|
||||
readonly property color bgColor: hovered ? Color.mHover : (customBackgroundColor.a > 0) ? customBackgroundColor : Style.capsuleColor
|
||||
readonly property color fgColor: hovered ? Color.mOnHover : (customTextIconColor.a > 0) ? customTextIconColor : Color.mOnSurface
|
||||
|
||||
readonly property real iconSize: {
|
||||
switch (root.density) {
|
||||
case "compact":
|
||||
@@ -62,7 +68,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
width: pillHeight + Math.max(0, pill.width - pillOverlap)
|
||||
width: collapseToIcon ? pillHeight : pillHeight + Math.max(0, pill.width - pillOverlap)
|
||||
height: pillHeight
|
||||
|
||||
Connections {
|
||||
@@ -77,17 +83,17 @@ Item {
|
||||
// Unified background for the entire pill area to avoid overlapping opacity
|
||||
Rectangle {
|
||||
id: pillBackground
|
||||
width: root.width
|
||||
width: collapseToIcon ? pillHeight : root.width
|
||||
height: pillHeight
|
||||
radius: halfPillHeight
|
||||
color: hovered ? (customBackgroundColor.a > 0 ? Qt.lighter(customBackgroundColor, 1.1) : Color.mHover) : (customBackgroundColor.a > 0 ? customBackgroundColor : Style.capsuleColor)
|
||||
color: root.bgColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
readonly property int halfPillHeight: Math.round(pillHeight * 0.5)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
@@ -131,7 +137,7 @@ Item {
|
||||
pointSize: textSize
|
||||
applyUiScale: false
|
||||
font.weight: Style.fontWeightBold
|
||||
color: hovered ? (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnHover) : (customTextIconColor.a > 0 ? customTextIconColor : (forceOpen ? Color.mOnSurface : Color.mPrimary))
|
||||
color: root.fgColor
|
||||
visible: revealed
|
||||
}
|
||||
|
||||
@@ -145,7 +151,7 @@ Item {
|
||||
Behavior on opacity {
|
||||
enabled: showAnim.running || hideAnim.running
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
@@ -165,7 +171,7 @@ Item {
|
||||
icon: root.icon
|
||||
pointSize: iconSize
|
||||
applyUiScale: false
|
||||
color: hovered ? (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnHover) : (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnSurface)
|
||||
color: root.fgColor
|
||||
// Center horizontally
|
||||
x: (iconCircle.width - width) / 2
|
||||
// Center vertically accounting for font metrics
|
||||
@@ -189,7 +195,7 @@ Item {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Style.animationNormal
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
onStarted: {
|
||||
@@ -230,7 +236,7 @@ Item {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Style.animationNormal
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
onStopped: {
|
||||
@@ -257,7 +263,7 @@ Item {
|
||||
onEntered: {
|
||||
hovered = true;
|
||||
root.entered();
|
||||
TooltipService.show(pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong);
|
||||
TooltipService.show(pill, root.tooltipText, BarService.getTooltipDirection(), (forceOpen || forceClose) ? Style.tooltipDelay : Style.tooltipDelayLong);
|
||||
if (forceClose) {
|
||||
return;
|
||||
}
|
||||
@@ -286,6 +292,8 @@ Item {
|
||||
}
|
||||
|
||||
function show() {
|
||||
if (collapseToIcon)
|
||||
return;
|
||||
if (!showPill) {
|
||||
shouldAnimateHide = autoHide;
|
||||
showAnim.start();
|
||||
@@ -296,6 +304,8 @@ Item {
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (collapseToIcon)
|
||||
return;
|
||||
if (forceOpen) {
|
||||
return;
|
||||
}
|
||||
@@ -306,6 +316,8 @@ Item {
|
||||
}
|
||||
|
||||
function showDelayed() {
|
||||
if (collapseToIcon)
|
||||
return;
|
||||
if (!showPill) {
|
||||
shouldAnimateHide = autoHide;
|
||||
showTimer.start();
|
||||
|
||||
@@ -21,19 +21,10 @@ Item {
|
||||
property bool oppositeDirection: false
|
||||
property bool hovered: false
|
||||
property bool rotateText: false
|
||||
property color customBackgroundColor: Qt.rgba(0, 0, 0, 0)
|
||||
property color customTextIconColor: Qt.rgba(0, 0, 0, 0)
|
||||
property color customBackgroundColor: Color.transparent
|
||||
property color customTextIconColor: Color.transparent
|
||||
|
||||
// Bar position detection for pill direction
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
|
||||
|
||||
// Determine pill direction based on section position
|
||||
readonly property bool openDownward: oppositeDirection
|
||||
readonly property bool openUpward: !oppositeDirection
|
||||
|
||||
// Effective shown state (true if animated open or forced, but not if force closed)
|
||||
readonly property bool revealed: !forceClose && (forceOpen || showPill)
|
||||
readonly property bool collapseToIcon: forceClose && !forceOpen
|
||||
|
||||
signal shown
|
||||
signal hidden
|
||||
@@ -50,11 +41,23 @@ Item {
|
||||
|
||||
// Sizing logic for vertical bars
|
||||
readonly property int buttonSize: Style.capsuleHeight
|
||||
readonly property int halfButtonSize: Math.round(buttonSize * 0.5)
|
||||
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))
|
||||
|
||||
// Determine pill direction based on section position
|
||||
readonly property bool openDownward: oppositeDirection
|
||||
readonly property bool openUpward: !oppositeDirection
|
||||
|
||||
// Effective shown state (true if animated open or forced, but not if force closed)
|
||||
readonly property bool revealed: !forceClose && (forceOpen || showPill)
|
||||
|
||||
// Always prioritize hover color, then the custom one and finally the fallback color
|
||||
readonly property color bgColor: hovered ? Color.mHover : (customBackgroundColor.a > 0) ? customBackgroundColor : Style.capsuleColor
|
||||
readonly property color fgColor: hovered ? Color.mOnHover : (customTextIconColor.a > 0) ? customTextIconColor : Color.mOnSurface
|
||||
|
||||
readonly property real iconSize: {
|
||||
switch (root.density) {
|
||||
case "compact":
|
||||
@@ -75,7 +78,7 @@ Item {
|
||||
|
||||
// For vertical bars: width is just icon size, height includes pill space
|
||||
width: buttonSize
|
||||
height: revealed ? (buttonSize + maxPillHeight - pillOverlap) : buttonSize
|
||||
height: collapseToIcon ? buttonSize : (revealed ? (buttonSize + maxPillHeight - pillOverlap) : buttonSize)
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
@@ -90,15 +93,13 @@ Item {
|
||||
Rectangle {
|
||||
id: pillBackground
|
||||
width: buttonSize
|
||||
height: revealed ? (buttonSize + maxPillHeight - pillOverlap) : buttonSize
|
||||
height: collapseToIcon ? buttonSize : (revealed ? (buttonSize + maxPillHeight - pillOverlap) : buttonSize)
|
||||
radius: halfButtonSize
|
||||
color: hovered ? (customBackgroundColor.a > 0 ? Qt.lighter(customBackgroundColor, 1.1) : Color.mHover) : (customBackgroundColor.a > 0 ? customBackgroundColor : Style.capsuleColor)
|
||||
|
||||
readonly property int halfButtonSize: Math.round(buttonSize * 0.5)
|
||||
color: root.bgColor
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
@@ -117,8 +118,6 @@ Item {
|
||||
opacity: revealed ? Style.opacityFull : Style.opacityNone
|
||||
color: Color.transparent // Make pill background transparent to avoid double opacity
|
||||
|
||||
readonly property int halfButtonSize: Math.round(buttonSize * 0.5)
|
||||
|
||||
// Radius logic for vertical expansion - rounded on the side that connects to icon
|
||||
topLeftRadius: openUpward ? halfButtonSize : 0
|
||||
bottomLeftRadius: openDownward ? halfButtonSize : 0
|
||||
@@ -140,7 +139,7 @@ Item {
|
||||
font.weight: Style.fontWeightMedium
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: hovered ? (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnHover) : (customTextIconColor.a > 0 ? customTextIconColor : (forceOpen ? Color.mOnSurface : Color.mPrimary))
|
||||
color: root.fgColor
|
||||
visible: revealed
|
||||
|
||||
function getVerticalCenterOffset() {
|
||||
@@ -165,7 +164,7 @@ Item {
|
||||
Behavior on opacity {
|
||||
enabled: showAnim.running || hideAnim.running
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
@@ -187,7 +186,7 @@ Item {
|
||||
icon: root.icon
|
||||
pointSize: iconSize
|
||||
applyUiScale: false
|
||||
color: hovered ? (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnHover) : (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnSurface)
|
||||
color: root.fgColor
|
||||
// Center horizontally
|
||||
x: (iconCircle.width - width) / 2
|
||||
// Center vertically accounting for font metrics
|
||||
@@ -219,7 +218,7 @@ Item {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Style.animationNormal
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
onStarted: {
|
||||
@@ -268,7 +267,7 @@ Item {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Style.animationNormal
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
onStopped: {
|
||||
@@ -295,7 +294,7 @@ Item {
|
||||
onEntered: {
|
||||
hovered = true;
|
||||
root.entered();
|
||||
TooltipService.show(pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong);
|
||||
TooltipService.show(pill, root.tooltipText, BarService.getTooltipDirection(), (forceOpen || forceClose) ? Style.tooltipDelay : Style.tooltipDelayLong);
|
||||
if (forceClose) {
|
||||
return;
|
||||
}
|
||||
@@ -324,6 +323,8 @@ Item {
|
||||
}
|
||||
|
||||
function show() {
|
||||
if (collapseToIcon)
|
||||
return;
|
||||
if (!showPill) {
|
||||
shouldAnimateHide = autoHide;
|
||||
showAnim.start();
|
||||
@@ -334,6 +335,8 @@ Item {
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (collapseToIcon)
|
||||
return;
|
||||
if (forceOpen) {
|
||||
return;
|
||||
}
|
||||
@@ -344,6 +347,8 @@ Item {
|
||||
}
|
||||
|
||||
function showDelayed() {
|
||||
if (collapseToIcon)
|
||||
return;
|
||||
if (!showPill) {
|
||||
shouldAnimateHide = autoHide;
|
||||
showTimer.start();
|
||||
|
||||
@@ -447,9 +447,9 @@ Item {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(root, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,9 +150,9 @@ Item {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(root, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
} else {
|
||||
const types = ["linear", "mirrored", "wave"];
|
||||
|
||||
@@ -6,6 +6,7 @@ import Quickshell.Services.UPower
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Networking
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
@@ -34,55 +35,128 @@ Item {
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode
|
||||
readonly property real warningThreshold: widgetSettings.warningThreshold !== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold
|
||||
readonly property bool isLowBattery: !charging && percent <= warningThreshold
|
||||
// Only show low battery warning if device is ready (prevents false positive during initialization)
|
||||
readonly property bool isLowBattery: isReady && !charging && percent <= warningThreshold
|
||||
|
||||
// Test mode
|
||||
readonly property bool testMode: false
|
||||
readonly property int testPercent: 15
|
||||
readonly property int testPercent: 35
|
||||
readonly property bool testCharging: false
|
||||
|
||||
// Main properties
|
||||
readonly property var battery: UPower.displayDevice
|
||||
readonly property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent)
|
||||
readonly property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0)
|
||||
readonly property string deviceNativePath: widgetSettings.deviceNativePath || ""
|
||||
|
||||
function findBatteryDevice(nativePath) {
|
||||
if (!nativePath || !UPower.devices) {
|
||||
return UPower.displayDevice;
|
||||
}
|
||||
var devices = UPower.devices.values || [];
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
var device = devices[i];
|
||||
if (device && device.nativePath === nativePath && device.type !== UPowerDeviceType.LinePower && device.percentage !== undefined) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return UPower.displayDevice;
|
||||
}
|
||||
|
||||
function findBluetoothDevice(nativePath) {
|
||||
if (!nativePath || !BluetoothService.devices) {
|
||||
return null;
|
||||
}
|
||||
var macMatch = nativePath.match(/([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2})/);
|
||||
if (!macMatch) {
|
||||
return null;
|
||||
}
|
||||
var macAddress = macMatch[1].toUpperCase();
|
||||
var devices = BluetoothService.devices.values || [];
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
var device = devices[i];
|
||||
if (device && device.address && device.address.toUpperCase() === macAddress) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
readonly property var battery: findBatteryDevice(deviceNativePath)
|
||||
readonly property var bluetoothDevice: deviceNativePath ? findBluetoothDevice(deviceNativePath) : null
|
||||
readonly property bool hasBluetoothBattery: bluetoothDevice && bluetoothDevice.batteryAvailable && bluetoothDevice.battery !== undefined
|
||||
readonly property bool isBluetoothConnected: bluetoothDevice && bluetoothDevice.connected === true
|
||||
|
||||
property bool initializationComplete: false
|
||||
Timer {
|
||||
interval: 500
|
||||
running: true
|
||||
onTriggered: root.initializationComplete = true
|
||||
}
|
||||
|
||||
readonly property bool isDevicePresent: {
|
||||
if (testMode)
|
||||
return true;
|
||||
if (deviceNativePath) {
|
||||
if (bluetoothDevice) {
|
||||
return isBluetoothConnected;
|
||||
}
|
||||
if (battery && battery.nativePath === deviceNativePath) {
|
||||
if (battery.type === UPowerDeviceType.Battery && battery.isPresent !== undefined) {
|
||||
return battery.isPresent;
|
||||
}
|
||||
return battery.ready && battery.percentage !== undefined && (battery.percentage > 0 || battery.state === UPowerDeviceState.Charging);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (battery) {
|
||||
return (battery.type === UPowerDeviceType.Battery && battery.isPresent !== undefined) ? battery.isPresent : (battery.ready && battery.percentage !== undefined);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property bool isReady: testMode ? true : (initializationComplete && battery && battery.ready && isDevicePresent && (battery.percentage !== undefined || hasBluetoothBattery))
|
||||
readonly property real percent: testMode ? testPercent : (isReady ? (hasBluetoothBattery ? (bluetoothDevice.battery * 100) : (battery.percentage * 100)) : 0)
|
||||
readonly property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false)
|
||||
property bool hasNotifiedLowBattery: false
|
||||
|
||||
implicitWidth: pill.width
|
||||
implicitHeight: pill.height
|
||||
|
||||
// Helper to evaluate and possibly notify
|
||||
function maybeNotify(percent, charging) {
|
||||
// Only notify once we are a below threshold
|
||||
if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) {
|
||||
root.hasNotifiedLowBattery = true;
|
||||
function maybeNotify(currentPercent, isCharging) {
|
||||
if (!isCharging && !hasNotifiedLowBattery && currentPercent <= warningThreshold) {
|
||||
hasNotifiedLowBattery = true;
|
||||
ToastService.showWarning(I18n.tr("toast.battery.low"), I18n.tr("toast.battery.low-desc", {
|
||||
"percent": Math.round(percent)
|
||||
"percent": Math.round(currentPercent)
|
||||
}));
|
||||
} else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) {
|
||||
// Reset when charging starts or when battery recovers 5% above threshold
|
||||
root.hasNotifiedLowBattery = false;
|
||||
} else if (hasNotifiedLowBattery && (isCharging || currentPercent > warningThreshold + 5)) {
|
||||
hasNotifiedLowBattery = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for battery changes
|
||||
Connections {
|
||||
target: UPower.displayDevice
|
||||
function onPercentageChanged() {
|
||||
var currentPercent = UPower.displayDevice.percentage * 100;
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
|
||||
root.maybeNotify(currentPercent, isCharging);
|
||||
}
|
||||
function getCurrentPercent() {
|
||||
return hasBluetoothBattery ? (bluetoothDevice.battery * 100) : (battery ? battery.percentage * 100 : 0);
|
||||
}
|
||||
|
||||
function onStateChanged() {
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
|
||||
// Reset notification flag when charging starts
|
||||
if (isCharging) {
|
||||
root.hasNotifiedLowBattery = false;
|
||||
Connections {
|
||||
target: battery
|
||||
function onPercentageChanged() {
|
||||
if (battery) {
|
||||
maybeNotify(getCurrentPercent(), battery.state === UPowerDeviceState.Charging);
|
||||
}
|
||||
}
|
||||
function onStateChanged() {
|
||||
if (battery) {
|
||||
if (battery.state === UPowerDeviceState.Charging) {
|
||||
hasNotifiedLowBattery = false;
|
||||
}
|
||||
maybeNotify(getCurrentPercent(), battery.state === UPowerDeviceState.Charging);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: bluetoothDevice
|
||||
function onBatteryChanged() {
|
||||
if (bluetoothDevice && hasBluetoothBattery) {
|
||||
maybeNotify(bluetoothDevice.battery * 100, battery ? battery.state === UPowerDeviceState.Charging : false);
|
||||
}
|
||||
// Also re-evaluate maybeNotify, as state might have changed
|
||||
var currentPercent = UPower.displayDevice.percentage * 100;
|
||||
root.maybeNotify(currentPercent, isCharging);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,10 +193,10 @@ Item {
|
||||
text: (isReady || testMode) ? Math.round(percent) : "-"
|
||||
suffix: "%"
|
||||
autoHide: false
|
||||
forceOpen: isReady && (testMode || battery.isLaptopBattery) && displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide" || !isReady || (!testMode && !battery.isLaptopBattery)
|
||||
customBackgroundColor: isLowBattery ? Color.mError : Qt.rgba(0, 0, 0, 0)
|
||||
customTextIconColor: isLowBattery ? Color.mOnError : charging ? Color.mPrimary : Qt.rgba(0, 0, 0, 0)
|
||||
forceOpen: isReady && displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide" || (initializationComplete && !isReady)
|
||||
customBackgroundColor: !initializationComplete ? Color.transparent : (charging ? Color.mPrimary : (isLowBattery ? Color.mError : Color.transparent))
|
||||
customTextIconColor: !initializationComplete ? Color.transparent : (charging ? Color.mOnPrimary : (isLowBattery ? Color.mOnError : Color.transparent))
|
||||
|
||||
tooltipText: {
|
||||
let lines = [];
|
||||
@@ -130,7 +204,7 @@ Item {
|
||||
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(12345)}.`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
if (!isReady || !battery.isLaptopBattery) {
|
||||
if (!isReady || !isDevicePresent) {
|
||||
return I18n.tr("battery.no-battery-detected");
|
||||
}
|
||||
if (battery.timeToEmpty > 0) {
|
||||
@@ -173,9 +247,9 @@ Item {
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(pill, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,14 +87,14 @@ Item {
|
||||
}
|
||||
autoHide: false
|
||||
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
|
||||
forceClose: isBarVertical || root.displayMode === "alwaysHide" || BluetoothService.connectedDevices.length === 0
|
||||
forceClose: isBarVertical || root.displayMode === "alwaysHide" || text === ""
|
||||
onClicked: PanelService.getPanel("bluetoothPanel", screen)?.toggle(this)
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(pill, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
tooltipText: {
|
||||
|
||||
@@ -47,6 +47,8 @@ Item {
|
||||
function getIcon() {
|
||||
var monitor = getMonitor();
|
||||
var brightness = monitor ? monitor.brightness : 0;
|
||||
if (brightness <= 0.001)
|
||||
return "sun-off";
|
||||
return brightness <= 0.5 ? "brightness-low" : "brightness-high";
|
||||
}
|
||||
|
||||
@@ -148,9 +150,9 @@ Item {
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(pill, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
if (action === "open-calendar") {
|
||||
PanelService.getPanel("calendarPanel", screen)?.toggle(root);
|
||||
PanelService.getPanel("clockPanel", screen)?.toggle(root);
|
||||
} else if (action === "widget-settings") {
|
||||
BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings);
|
||||
}
|
||||
@@ -154,7 +154,7 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onEntered: {
|
||||
if (!PanelService.getPanel("calendarPanel", screen)?.active) {
|
||||
if (!PanelService.getPanel("clockPanel", screen)?.active) {
|
||||
TooltipService.show(root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection());
|
||||
}
|
||||
}
|
||||
@@ -166,12 +166,12 @@ Rectangle {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(root, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
} else {
|
||||
PanelService.getPanel("calendarPanel", screen)?.toggle(this);
|
||||
PanelService.getPanel("clockPanel", screen)?.toggle(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,13 +34,51 @@ NIconButton {
|
||||
readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon
|
||||
readonly property bool useDistroLogo: (widgetSettings.useDistroLogo !== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
|
||||
readonly property string customIconPath: widgetSettings.customIconPath || ""
|
||||
readonly property bool colorizeDistroLogo: {
|
||||
if (widgetSettings.colorizeDistroLogo !== undefined)
|
||||
return widgetSettings.colorizeDistroLogo;
|
||||
return widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false;
|
||||
readonly property bool enableColorization: widgetSettings.enableColorization || false
|
||||
|
||||
readonly property string colorizeSystemIcon: {
|
||||
if (widgetSettings.colorizeSystemIcon !== undefined)
|
||||
return widgetSettings.colorizeSystemIcon;
|
||||
return widgetMetadata.colorizeSystemIcon !== undefined ? widgetMetadata.colorizeSystemIcon : "none";
|
||||
}
|
||||
|
||||
// If we have a custom path or distro logo, don't use the theme icon.
|
||||
readonly property bool isColorizing: enableColorization && colorizeSystemIcon !== "none"
|
||||
|
||||
readonly property color iconColor: {
|
||||
if (!isColorizing)
|
||||
return Color.mOnSurface;
|
||||
switch (colorizeSystemIcon) {
|
||||
case "primary":
|
||||
return Color.mPrimary;
|
||||
case "secondary":
|
||||
return Color.mSecondary;
|
||||
case "tertiary":
|
||||
return Color.mTertiary;
|
||||
case "error":
|
||||
return Color.mError;
|
||||
default:
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
}
|
||||
readonly property color iconHoverColor: {
|
||||
if (!isColorizing)
|
||||
return Color.mOnHover;
|
||||
switch (colorizeSystemIcon) {
|
||||
case "primary":
|
||||
return Qt.darker(Color.mPrimary, 1.2);
|
||||
case "secondary":
|
||||
return Qt.darker(Color.mSecondary, 1.2);
|
||||
case "tertiary":
|
||||
return Qt.darker(Color.mTertiary, 1.2);
|
||||
case "error":
|
||||
return Qt.darker(Color.mError, 1.2);
|
||||
default:
|
||||
return Color.mOnHover;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a custom path and not using distro logo, use the theme icon.
|
||||
// If using distro logo, don't use theme icon.
|
||||
icon: (customIconPath === "" && !useDistroLogo) ? customIcon : ""
|
||||
tooltipText: I18n.tr("tooltips.open-control-center")
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
@@ -48,8 +86,9 @@ NIconButton {
|
||||
applyUiScale: false
|
||||
density: Settings.data.bar.density
|
||||
colorBg: Style.capsuleColor
|
||||
colorFg: Color.mOnSurface
|
||||
colorFg: iconColor
|
||||
colorBgHover: useDistroLogo ? Color.mSurfaceVariant : Color.mHover
|
||||
colorFgHover: iconHoverColor
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: useDistroLogo ? Color.mHover : Color.transparent
|
||||
|
||||
@@ -102,9 +141,9 @@ NIconButton {
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(root, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
onMiddleClicked: PanelService.getPanel("launcherPanel", screen)?.toggle()
|
||||
@@ -115,18 +154,18 @@ NIconButton {
|
||||
width: root.width * 0.8
|
||||
height: width
|
||||
source: {
|
||||
if (customIconPath !== "")
|
||||
return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath;
|
||||
if (useDistroLogo)
|
||||
return HostService.osLogo;
|
||||
if (customIconPath !== "")
|
||||
return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath;
|
||||
return "";
|
||||
}
|
||||
visible: source !== ""
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
layer.enabled: useDistroLogo && colorizeDistroLogo
|
||||
layer.enabled: isColorizing && (useDistroLogo || customIconPath !== "")
|
||||
layer.effect: ShaderEffect {
|
||||
property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant
|
||||
property color targetColor: isColorizing ? iconColor : (Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant)
|
||||
property real colorizeMode: 2.0
|
||||
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
|
||||
|
||||
@@ -15,7 +15,7 @@ NIconButton {
|
||||
baseSize: Style.capsuleHeight
|
||||
applyUiScale: false
|
||||
colorBg: Style.capsuleColor
|
||||
colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
onClicked: Settings.data.colorSchemes.darkMode = !Settings.data.colorSchemes.darkMode
|
||||
|
||||
@@ -82,9 +82,9 @@ Item {
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(pill, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,9 +80,9 @@ Rectangle {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(root, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ Item {
|
||||
readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : widgetMetadata.scrollingMode
|
||||
readonly property bool showProgressRing: (widgetSettings.showProgressRing !== undefined) ? widgetSettings.showProgressRing : widgetMetadata.showProgressRing
|
||||
|
||||
// Private constants for element sizes
|
||||
readonly property int _iconOnlySize: Math.round(18 * scaling)
|
||||
readonly property int _artAndProgressSize: Math.round(21 * scaling)
|
||||
|
||||
// Maximum widget width with user settings support
|
||||
readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen ? screen.width * 0.06 : 0)
|
||||
readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth
|
||||
@@ -68,7 +72,7 @@ Item {
|
||||
|
||||
// Hide conditions
|
||||
readonly property bool shouldHideIdle: ((hideMode === "idle") || hideWhenIdle) && !MediaService.isPlaying
|
||||
readonly property bool isEmptyForHideMode: (!hasActivePlayer) && (hideMode === "hidden" || hideMode === "transparent")
|
||||
readonly property bool isEmptyForHideMode: (!hasActivePlayer) && (hideMode === "hidden")
|
||||
|
||||
implicitHeight: visible ? (isVerticalBar ? ((shouldHideIdle || isEmptyForHideMode) ? 0 : calculatedVerticalDimension()) : Style.capsuleHeight) : 0
|
||||
implicitWidth: visible ? (isVerticalBar ? ((shouldHideIdle || isEmptyForHideMode) ? 0 : calculatedVerticalDimension()) : ((shouldHideIdle || isEmptyForHideMode) ? 0 : dynamicWidth)) : 0
|
||||
@@ -111,15 +115,17 @@ Item {
|
||||
function calculateContentWidth() {
|
||||
// Calculate the actual content width based on visible elements
|
||||
var contentWidth = 0;
|
||||
var margins = Style.marginS * scaling * 2; // Left and right margins
|
||||
|
||||
// Icon or album art width
|
||||
if (!hasActivePlayer || !showAlbumArt) {
|
||||
// Icon width
|
||||
contentWidth += Math.round(18 * scaling);
|
||||
// Icon, progress ring, or album art width
|
||||
if (!hasActivePlayer || (!showAlbumArt && !showProgressRing)) {
|
||||
// Icon width only
|
||||
contentWidth += _iconOnlySize;
|
||||
} else if (showProgressRing && hasActivePlayer) {
|
||||
// Progress ring width (same as album art width to maintain consistent sizing)
|
||||
contentWidth += _artAndProgressSize;
|
||||
} else if (showAlbumArt && hasActivePlayer) {
|
||||
// Album art width
|
||||
contentWidth += 21 * scaling;
|
||||
contentWidth += _artAndProgressSize;
|
||||
}
|
||||
|
||||
// Spacing between icon/art and text; only if there is text
|
||||
@@ -133,25 +139,26 @@ Item {
|
||||
contentWidth += Style.marginXXS * 2;
|
||||
}
|
||||
|
||||
// Add container margins
|
||||
contentWidth += margins;
|
||||
|
||||
return Math.ceil(contentWidth);
|
||||
}
|
||||
|
||||
// Dynamic width: adapt to content but respect maximum width setting
|
||||
readonly property real dynamicWidth: {
|
||||
var contentWidth = calculateContentWidth();
|
||||
// For vertical bars, there are no horizontal margins to add
|
||||
var margins = isVerticalBar ? 0 : (Style.marginS * scaling * 2);
|
||||
var totalWidth = contentWidth + margins;
|
||||
|
||||
// If using fixed width mode, always use maxWidth
|
||||
if (useFixedWidth) {
|
||||
return maxWidth;
|
||||
}
|
||||
// Otherwise, adapt to content
|
||||
// If there's no active player, the widget should be compact
|
||||
if (!hasActivePlayer) {
|
||||
// Keep compact when no active player
|
||||
return calculateContentWidth();
|
||||
return totalWidth;
|
||||
}
|
||||
// Use content width but don't exceed user-set maximum width
|
||||
return Math.min(calculateContentWidth(), maxWidth);
|
||||
// Adapt to content but don't exceed user-set maximum width
|
||||
return Math.min(totalWidth, maxWidth);
|
||||
}
|
||||
|
||||
// A hidden text element to safely measure the full title width
|
||||
@@ -309,33 +316,36 @@ Item {
|
||||
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeL * scaling
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: !hasActivePlayer || (!showAlbumArt && !trackArt.visible)
|
||||
Layout.preferredWidth: _iconOnlySize
|
||||
Layout.preferredHeight: _iconOnlySize
|
||||
visible: !hasActivePlayer || (!showAlbumArt && !showProgressRing)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showAlbumArt && hasActivePlayer
|
||||
spacing: 0
|
||||
|
||||
// Progress circle (independent of album art)
|
||||
Item {
|
||||
Layout.preferredWidth: Math.round(21 * scaling)
|
||||
Layout.preferredHeight: Math.round(21 * scaling)
|
||||
Layout.preferredWidth: (hasActivePlayer && (showProgressRing || showAlbumArt)) ? _artAndProgressSize : 0
|
||||
Layout.preferredHeight: (hasActivePlayer && (showProgressRing || showAlbumArt)) ? _artAndProgressSize : 0
|
||||
Layout.minimumWidth: (hasActivePlayer && showProgressRing) ? _artAndProgressSize : 0
|
||||
Layout.minimumHeight: (hasActivePlayer && showProgressRing) ? _artAndProgressSize : 0
|
||||
Layout.maximumWidth: (hasActivePlayer && (showProgressRing || showAlbumArt)) ? _artAndProgressSize : 0
|
||||
Layout.maximumHeight: (hasActivePlayer && (showProgressRing || showAlbumArt)) ? _artAndProgressSize : 0
|
||||
Layout.fillWidth: false
|
||||
Layout.fillHeight: false
|
||||
visible: hasActivePlayer && (showProgressRing || showAlbumArt) // Show container when there's active player and either feature is enabled
|
||||
|
||||
// Background for progress circle
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: Color.transparent
|
||||
}
|
||||
|
||||
// Progress circle
|
||||
// Progress circle - always available when showProgressRing is true
|
||||
Canvas {
|
||||
id: progressCanvas
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0 // Align exactly with parent to avoid clipping
|
||||
visible: showProgressRing // Control visibility with setting
|
||||
z: 0 // Behind the album art
|
||||
visible: hasActivePlayer && showProgressRing // Only show when progress ring is enabled
|
||||
z: 0 // Behind the album art or icon
|
||||
|
||||
// Calculate progress ratio: 0 to 1
|
||||
property real progressRatio: {
|
||||
@@ -351,23 +361,28 @@ Item {
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
// Check if width/height are valid before calculating radius
|
||||
if (width <= 0 || height <= 0) {
|
||||
return; // Skip drawing if dimensions are invalid
|
||||
}
|
||||
|
||||
var centerX = width / 2;
|
||||
var centerY = height / 2;
|
||||
var radius = Math.min(width, height) / 2 - (1.25 * scaling); // Larger radius, accounting for line width to approach edge
|
||||
var radius = Math.max(0, Math.min(width, height) / 2 - (1.25 * scaling)); // Larger radius, accounting for line width to approach edge
|
||||
|
||||
ctx.reset();
|
||||
|
||||
// Background circle (full track, not played yet)
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
|
||||
ctx.lineWidth = 3 * scaling; // Thicker line width based on scaling property
|
||||
ctx.lineWidth = 2.5 * scaling; // Thicker line width based on scaling property
|
||||
ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.4); // More opaque for better visibility
|
||||
ctx.stroke();
|
||||
|
||||
// Progress arc (played portion)
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progressRatio * 2 * Math.PI);
|
||||
ctx.lineWidth = 3 * scaling; // Thicker line width based on scaling property
|
||||
ctx.lineWidth = 2.5 * scaling; // Thicker line width based on scaling property
|
||||
ctx.strokeStyle = Color.mPrimary; // Use primary color for progress
|
||||
ctx.lineCap = "round";
|
||||
ctx.stroke();
|
||||
@@ -385,16 +400,45 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
NImageCircled {
|
||||
id: trackArt
|
||||
// Property to track mPrimary color changes and trigger repaint
|
||||
Item {
|
||||
id: colorTrackerHorizontal
|
||||
property color currentColor: Color.mPrimary
|
||||
onCurrentColorChanged: progressCanvas.requestPaint()
|
||||
}
|
||||
|
||||
// Album art or icon - only show album art when enabled and player is active
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: showProgressRing ? (2.5 * scaling) : 0.5 // Make album art smaller only when progress ring is visible, scaled with widget
|
||||
imagePath: MediaService.trackArtUrl
|
||||
fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play"
|
||||
fallbackIconSize: 10
|
||||
borderWidth: 0
|
||||
border.color: Color.transparent
|
||||
z: 1 // In front of the progress circle
|
||||
anchors.margins: showProgressRing ? (3 * scaling) : 0.5 // Adjusted to align with progress circle better
|
||||
|
||||
NImageRounded {
|
||||
id: trackArt
|
||||
anchors.fill: parent
|
||||
anchors.margins: showProgressRing ? 0 : -1 * scaling // Add negative margin to make album art larger when no progress ring
|
||||
radius: width * 0.5
|
||||
visible: showAlbumArt && hasActivePlayer
|
||||
imagePath: MediaService.trackArtUrl
|
||||
fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play"
|
||||
fallbackIconSize: showProgressRing ? 10 : 12 // Larger fallback icon when no progress ring
|
||||
borderWidth: 0
|
||||
borderColor: Color.transparent
|
||||
z: 1 // In front of the progress circle
|
||||
}
|
||||
|
||||
// Fallback icon when no album art or album art not shown
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc"
|
||||
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
|
||||
pointSize: (showAlbumArt || showProgressRing) ? 8 * scaling : 12 * scaling // Smaller when inside album art circle or progress ring, larger when alone
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: (!showAlbumArt && hasActivePlayer) && showProgressRing
|
||||
z: 1 // In front of the progress circle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,10 +447,10 @@ Item {
|
||||
id: titleContainer
|
||||
Layout.preferredWidth: {
|
||||
// Calculate available width based on other elements in the row
|
||||
var iconWidth = (windowIcon.visible ? (18 * scaling + Style.marginS * scaling) : 0);
|
||||
var albumArtWidth = (hasActivePlayer && showAlbumArt ? (21 * scaling + Style.marginS * scaling) : 0);
|
||||
var iconWidth = (windowIcon.visible ? (_iconOnlySize + Style.marginS * scaling) : 0);
|
||||
var artWidth = (hasActivePlayer && (showAlbumArt || showProgressRing) ? (_artAndProgressSize + Style.marginS * scaling) : 0);
|
||||
var totalMargins = Style.marginXXS * 2;
|
||||
var availableWidth = mainContainer.width - iconWidth - albumArtWidth - totalMargins;
|
||||
var availableWidth = mainContainer.width - iconWidth - artWidth - totalMargins;
|
||||
return Math.max(20, availableWidth);
|
||||
}
|
||||
Layout.maximumWidth: Layout.preferredWidth
|
||||
@@ -572,55 +616,67 @@ Item {
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
// Check if width/height are valid before calculating radius
|
||||
if (width <= 0 || height <= 0) {
|
||||
return; // Skip drawing if dimensions are invalid
|
||||
}
|
||||
|
||||
var centerX = width / 2;
|
||||
var centerY = height / 2;
|
||||
// Align with mediaMini radius which is circular in vertical mode
|
||||
var radius = Math.min(width, height) / 2 - 4; // Position ring near the outer edge of background
|
||||
var radius = Math.max(0, Math.min(width, height) / 2 - 4); // Position ring near the outer edge of background
|
||||
|
||||
ctx.reset();
|
||||
|
||||
// Background circle (full track, not played yet)
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
|
||||
ctx.lineWidth = 1.5 * scaling; // Line width based on scaling property, thinner for vertical layout
|
||||
ctx.lineWidth = 2.5 * scaling; // Line width based on scaling property, thinner for vertical layout
|
||||
ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.4); // More opaque for better visibility
|
||||
ctx.stroke();
|
||||
|
||||
// Progress arc (played portion)
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progressRatio * 2 * Math.PI);
|
||||
ctx.lineWidth = 1.5 * scaling; // Line width based on scaling property, thinner for vertical layout
|
||||
ctx.lineWidth = 2.5 * scaling; // Line width based on scaling property, thinner for vertical layout
|
||||
ctx.strokeStyle = Color.mPrimary; // Use primary color for progress
|
||||
ctx.lineCap = "round";
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical layout for left/right bars - icon only
|
||||
// Vertical layout for left/right bars - icon or album art
|
||||
Item {
|
||||
id: verticalLayout
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Style.marginM * 2
|
||||
height: parent.height - Style.marginM * 2
|
||||
width: showProgressRing ? (Style.baseWidgetSize * 0.5 * scaling) : (calculatedVerticalDimension() - 4 * scaling)
|
||||
height: width
|
||||
visible: isVerticalBar
|
||||
z: 1 // Above the visualizer
|
||||
z: 1 // Above the visualizer and progress ring
|
||||
|
||||
// Media icon
|
||||
Item {
|
||||
width: Style.baseWidgetSize * 0.5
|
||||
height: width
|
||||
// Album Art
|
||||
NImageRounded {
|
||||
anchors.fill: parent
|
||||
visible: showAlbumArt && hasActivePlayer
|
||||
radius: width * 0.5
|
||||
imagePath: MediaService.trackArtUrl
|
||||
fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play"
|
||||
fallbackIconSize: 12
|
||||
borderWidth: 0
|
||||
}
|
||||
|
||||
// Media icon (fallback)
|
||||
NIcon {
|
||||
id: mediaIconVertical
|
||||
anchors.centerIn: parent
|
||||
|
||||
NIcon {
|
||||
id: mediaIconVertical
|
||||
anchors.fill: parent
|
||||
icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc"
|
||||
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeL * scaling
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
z: 1 // In front of the progress circle
|
||||
}
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
visible: !showAlbumArt || !hasActivePlayer
|
||||
icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc"
|
||||
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeM * scaling
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,6 +691,13 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Property to track mPrimary color changes and trigger repaint for vertical canvas
|
||||
Item {
|
||||
id: colorTrackerVertical
|
||||
property color currentColor: Color.mPrimary
|
||||
onCurrentColorChanged: progressCanvasVertical.requestPaint()
|
||||
}
|
||||
|
||||
// Mouse area for hover detection
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
@@ -652,9 +715,9 @@ Item {
|
||||
TooltipService.hide();
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(mediaMini, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(mediaMini, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
if (hasActivePlayer && MediaService.canGoPrevious) {
|
||||
|
||||
@@ -169,9 +169,9 @@ Item {
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(pill, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
onMiddleClicked: root.openExternalMixer()
|
||||
|
||||
@@ -9,7 +9,7 @@ import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
NIconButton {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
@@ -33,6 +33,9 @@ NIconButton {
|
||||
readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge !== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge
|
||||
readonly property bool hideWhenZero: (widgetSettings.hideWhenZero !== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero
|
||||
|
||||
implicitWidth: pill.width
|
||||
implicitHeight: pill.height
|
||||
|
||||
function computeUnreadCount() {
|
||||
var since = NotificationService.lastSeenTs;
|
||||
var count = 0;
|
||||
@@ -46,17 +49,6 @@ NIconButton {
|
||||
return count;
|
||||
}
|
||||
|
||||
baseSize: Style.capsuleHeight
|
||||
applyUiScale: false
|
||||
density: Settings.data.bar.density
|
||||
icon: NotificationService.doNotDisturb ? "bell-off" : "bell"
|
||||
tooltipText: NotificationService.doNotDisturb ? I18n.tr("tooltips.open-notification-history-disable-dnd") : I18n.tr("tooltips.open-notification-history-enable-dnd")
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
colorBg: Style.capsuleColor
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
NPopupContextMenu {
|
||||
id: contextMenu
|
||||
|
||||
@@ -94,37 +86,79 @@ NIconButton {
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
var panel = PanelService.getPanel("notificationHistoryPanel", screen);
|
||||
panel?.toggle(this);
|
||||
}
|
||||
BarPill {
|
||||
id: pill
|
||||
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(root, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
property string currentNotif
|
||||
Connections {
|
||||
target: NotificationService.activeList
|
||||
function onCountChanged() {
|
||||
// keep current text a bit longer for the animation
|
||||
if (NotificationService.activeList.count > 0) {
|
||||
var notif = NotificationService.activeList.get(0)
|
||||
var summary = notif.summary.trim()
|
||||
var body = notif.body.trim()
|
||||
pill.currentNotif = `${summary}: ${body}`.replace(/\n/g, " ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 2
|
||||
anchors.topMargin: 1
|
||||
z: 2
|
||||
active: showUnreadBadge && (!hideWhenZero || computeUnreadCount() > 0)
|
||||
sourceComponent: Rectangle {
|
||||
id: badge
|
||||
readonly property int count: computeUnreadCount()
|
||||
height: 8
|
||||
width: height
|
||||
radius: height / 2
|
||||
color: Color.mError
|
||||
border.color: Color.mSurface
|
||||
border.width: Style.borderS
|
||||
visible: count > 0 || !hideWhenZero
|
||||
Component.onCompleted: {
|
||||
function dismiss(notificationId) {
|
||||
if (Settings.data.notifications?.location == "bar") {
|
||||
NotificationService.dismissActiveNotification(notificationId)
|
||||
}
|
||||
}
|
||||
NotificationService.animateAndRemove.connect(dismiss);
|
||||
}
|
||||
|
||||
|
||||
screen: root.screen
|
||||
density: Settings.data.bar.density
|
||||
oppositeDirection: BarService.getPillDirection(root)
|
||||
icon: NotificationService.doNotDisturb ? "bell-off" : "bell"
|
||||
tooltipText: NotificationService.doNotDisturb ? I18n.tr("tooltips.open-notification-history-disable-dnd") : I18n.tr("tooltips.open-notification-history-enable-dnd")
|
||||
|
||||
text: currentNotif
|
||||
forceOpen: Settings.data.notifications?.location == "bar" && NotificationService.activeList.count > 0
|
||||
// prevent open via mouse over
|
||||
forceClose: NotificationService.activeList.count == 0
|
||||
|
||||
opacity: NotificationService.doNotDisturb || computeUnreadCount() > 0 ? 100 : 0
|
||||
|
||||
onClicked: {
|
||||
var panel = PanelService.getPanel("notificationHistoryPanel", screen);
|
||||
panel?.toggle(this);
|
||||
}
|
||||
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(root, pos.x, pos.y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loader {
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 2
|
||||
anchors.topMargin: 1
|
||||
z: 2
|
||||
active: showUnreadBadge && (!hideWhenZero || computeUnreadCount() > 0)
|
||||
sourceComponent: Rectangle {
|
||||
id: badge
|
||||
readonly property int count: computeUnreadCount()
|
||||
height: 8
|
||||
width: height
|
||||
radius: height / 2
|
||||
color: Color.mError
|
||||
border.color: Color.mSurface
|
||||
border.width: Style.borderS
|
||||
visible: count > 0 || !hideWhenZero
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,9 +85,9 @@ NIconButton {
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(root, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,9 +143,9 @@ Rectangle {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(root, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ Rectangle {
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
|
||||
readonly property string density: Settings.data.bar.density
|
||||
readonly property real itemSize: (density === "compact") ? Style.capsuleHeight * 0.9 : Style.capsuleHeight * 0.8
|
||||
|
||||
@@ -43,22 +44,26 @@ Rectangle {
|
||||
// Context menu state
|
||||
property var selectedWindow: null
|
||||
property string selectedAppName: ""
|
||||
property int modelUpdateTrigger: 0 // Dummy property to force model re-evaluation
|
||||
|
||||
NPopupContextMenu {
|
||||
id: contextMenu
|
||||
model: {
|
||||
// Reference modelUpdateTrigger to make binding reactive
|
||||
const _ = root.modelUpdateTrigger;
|
||||
|
||||
var items = [];
|
||||
if (selectedWindow) {
|
||||
if (root.selectedWindow) {
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.activate-app", {
|
||||
"app": selectedAppName
|
||||
"app": root.selectedAppName
|
||||
}),
|
||||
"action": "activate",
|
||||
"icon": "focus"
|
||||
});
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.close-app", {
|
||||
"app": selectedAppName
|
||||
"app": root.selectedAppName
|
||||
}),
|
||||
"action": "close",
|
||||
"icon": "x"
|
||||
@@ -160,7 +165,7 @@ Rectangle {
|
||||
required property var modelData
|
||||
property ShellScreen screen: root.screen
|
||||
|
||||
visible: (!onlySameOutput || modelData.output == screen.name) && (!onlyActiveWorkspaces || CompositorService.getActiveWorkspaces().map(function (ws) {
|
||||
visible: (!onlySameOutput || modelData.output === screen?.name) && (!onlyActiveWorkspaces || CompositorService.getActiveWorkspaces().map(function (ws) {
|
||||
return ws.id;
|
||||
}).includes(modelData.workspaceId))
|
||||
|
||||
@@ -203,6 +208,7 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
preventStealing: true
|
||||
|
||||
onPressed: function (mouse) {
|
||||
if (!taskbarItem.modelData)
|
||||
@@ -213,16 +219,25 @@ Rectangle {
|
||||
} catch (error) {
|
||||
Logger.e("Taskbar", "Failed to activate toplevel: " + error);
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: function (mouse) {
|
||||
if (!taskbarItem.modelData)
|
||||
return;
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
mouse.accepted = true;
|
||||
TooltipService.hide();
|
||||
root.selectedWindow = taskbarItem.modelData;
|
||||
root.selectedAppName = CompositorService.getCleanAppName(taskbarItem.modelData.appId, taskbarItem.modelData.title);
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
const pos = BarService.getContextMenuPosition(taskbarItem, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(taskbarItem, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
|
||||
// Store position and size for timer callback
|
||||
const globalPos = taskbarItem.mapToItem(root, 0, 0);
|
||||
contextMenuOpenTimer.globalX = globalPos.x;
|
||||
contextMenuOpenTimer.globalY = globalPos.y;
|
||||
contextMenuOpenTimer.itemWidth = taskbarItem.width;
|
||||
contextMenuOpenTimer.itemHeight = taskbarItem.height;
|
||||
contextMenuOpenTimer.restart();
|
||||
}
|
||||
}
|
||||
onEntered: TooltipService.show(taskbarItem, taskbarItem.modelData.title || taskbarItem.modelData.appId || "Unknown app.", BarService.getTooltipDirection())
|
||||
@@ -231,4 +246,69 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: contextMenuOpenTimer
|
||||
interval: 10
|
||||
repeat: false
|
||||
property real globalX: 0
|
||||
property real globalY: 0
|
||||
property real itemWidth: 0
|
||||
property real itemHeight: 0
|
||||
|
||||
onTriggered: {
|
||||
// Directly build and set model as a new array (bypass binding issues)
|
||||
var items = [];
|
||||
if (root.selectedWindow) {
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.activate-app", {
|
||||
"app": root.selectedAppName
|
||||
}),
|
||||
"action": "activate",
|
||||
"icon": "focus"
|
||||
});
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.close-app", {
|
||||
"app": root.selectedAppName
|
||||
}),
|
||||
"action": "close",
|
||||
"icon": "x"
|
||||
});
|
||||
}
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.widget-settings"),
|
||||
"action": "widget-settings",
|
||||
"icon": "settings"
|
||||
});
|
||||
|
||||
// Set the model directly
|
||||
contextMenu.model = items;
|
||||
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.open();
|
||||
|
||||
// Calculate menu position
|
||||
let menuX, menuY;
|
||||
if (root.barPosition === "top") {
|
||||
menuX = globalX + (itemWidth / 2) - (contextMenu.implicitWidth / 2);
|
||||
menuY = Style.barHeight + Style.marginS;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
const menuHeight = 12 + contextMenu.model.length * contextMenu.itemHeight;
|
||||
menuX = globalX + (itemWidth / 2) - (contextMenu.implicitWidth / 2);
|
||||
menuY = -menuHeight - Style.marginS;
|
||||
} else if (root.barPosition === "left") {
|
||||
menuX = Style.barHeight + Style.marginS;
|
||||
menuY = globalY + (itemHeight / 2) - (contextMenu.implicitHeight / 2);
|
||||
} else {
|
||||
// right
|
||||
menuX = -contextMenu.implicitWidth - Style.marginS;
|
||||
menuY = globalY + (itemHeight / 2) - (contextMenu.implicitHeight / 2);
|
||||
}
|
||||
|
||||
contextMenu.openAtItem(root, menuX, menuY);
|
||||
popupMenuWindow.contentItem = contextMenu;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ Item {
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
|
||||
readonly property string density: Settings.data.bar.density
|
||||
readonly property real itemSize: (density === "compact") ? Style.capsuleHeight * 0.9 : Style.capsuleHeight * 0.8
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
@@ -53,6 +54,7 @@ Item {
|
||||
// Context menu state
|
||||
property var selectedWindow: null
|
||||
property string selectedAppName: ""
|
||||
property int modelUpdateTrigger: 0 // Dummy property to force model re-evaluation
|
||||
|
||||
function refreshWorkspaces() {
|
||||
localWorkspaces.clear();
|
||||
@@ -74,6 +76,7 @@ Item {
|
||||
|
||||
localWorkspaces.append(workspaceData);
|
||||
}
|
||||
|
||||
updateWorkspaceFocus();
|
||||
}
|
||||
|
||||
@@ -167,18 +170,21 @@ Item {
|
||||
id: contextMenu
|
||||
|
||||
model: {
|
||||
// Reference modelUpdateTrigger to make binding reactive
|
||||
const _ = root.modelUpdateTrigger;
|
||||
|
||||
var items = [];
|
||||
if (selectedWindow) {
|
||||
if (root.selectedWindow) {
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.activate-app", {
|
||||
"app": selectedAppName
|
||||
"app": root.selectedAppName
|
||||
}),
|
||||
"action": "activate",
|
||||
"icon": "focus"
|
||||
});
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.close-app", {
|
||||
"app": selectedAppName
|
||||
"app": root.selectedAppName
|
||||
}),
|
||||
"action": "close",
|
||||
"icon": "x"
|
||||
@@ -273,21 +279,28 @@ Item {
|
||||
enabled: !hasWindows
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
preventStealing: true
|
||||
onPressed: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
CompositorService.switchToWorkspace(workspaceModel);
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
TooltipService.hide();
|
||||
root.selectedWindow = "";
|
||||
root.selectedAppName = "";
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
const pos = BarService.getContextMenuPosition(container, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(container, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
onReleased: mouse => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
mouse.accepted = true;
|
||||
TooltipService.hide();
|
||||
root.selectedWindow = "";
|
||||
root.selectedAppName = "";
|
||||
|
||||
// Store position and size for timer callback
|
||||
const globalPos = container.mapToItem(root, 0, 0);
|
||||
contextMenuOpenTimer1.globalX = globalPos.x;
|
||||
contextMenuOpenTimer1.globalY = globalPos.y;
|
||||
contextMenuOpenTimer1.itemWidth = container.width;
|
||||
contextMenuOpenTimer1.itemHeight = container.height;
|
||||
contextMenuOpenTimer1.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Flow {
|
||||
@@ -359,6 +372,7 @@ Item {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
preventStealing: true
|
||||
|
||||
onPressed: mouse => {
|
||||
if (!model) {
|
||||
@@ -367,18 +381,29 @@ Item {
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
CompositorService.focusWindow(model);
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
TooltipService.hide();
|
||||
root.selectedWindow = model;
|
||||
root.selectedAppName = CompositorService.getCleanAppName(model.appId, model.title);
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
const pos = BarService.getContextMenuPosition(taskbarItem, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(taskbarItem, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: mouse => {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
mouse.accepted = true;
|
||||
TooltipService.hide();
|
||||
root.selectedWindow = model;
|
||||
root.selectedAppName = CompositorService.getCleanAppName(model.appId, model.title);
|
||||
|
||||
// Store position and size for timer callback
|
||||
const globalPos = taskbarItem.mapToItem(root, 0, 0);
|
||||
contextMenuOpenTimer2.globalX = globalPos.x;
|
||||
contextMenuOpenTimer2.globalY = globalPos.y;
|
||||
contextMenuOpenTimer2.itemWidth = taskbarItem.width;
|
||||
contextMenuOpenTimer2.itemHeight = taskbarItem.height;
|
||||
contextMenuOpenTimer2.restart();
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
taskbarItem.itemHovered = true;
|
||||
TooltipService.show(taskbarItem, model.title || model.appId || "Unknown app.", BarService.getTooltipDirection());
|
||||
@@ -533,4 +558,82 @@ Item {
|
||||
delegate: workspaceRepeaterDelegate
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: contextMenuOpenTimer1
|
||||
interval: 10
|
||||
repeat: false
|
||||
property real globalX: 0
|
||||
property real globalY: 0
|
||||
property real itemWidth: 0
|
||||
property real itemHeight: 0
|
||||
onTriggered: openContextMenu(globalX, globalY, itemWidth, itemHeight)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: contextMenuOpenTimer2
|
||||
interval: 10
|
||||
repeat: false
|
||||
property real globalX: 0
|
||||
property real globalY: 0
|
||||
property real itemWidth: 0
|
||||
property real itemHeight: 0
|
||||
onTriggered: openContextMenu(globalX, globalY, itemWidth, itemHeight)
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
function openContextMenu(globalX, globalY, itemWidth, itemHeight) {
|
||||
// Directly build and set model as a new array (bypass binding issues)
|
||||
var items = [];
|
||||
if (root.selectedWindow) {
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.activate-app", {
|
||||
"app": root.selectedAppName
|
||||
}),
|
||||
"action": "activate",
|
||||
"icon": "focus"
|
||||
});
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.close-app", {
|
||||
"app": root.selectedAppName
|
||||
}),
|
||||
"action": "close",
|
||||
"icon": "x"
|
||||
});
|
||||
}
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.widget-settings"),
|
||||
"action": "widget-settings",
|
||||
"icon": "settings"
|
||||
});
|
||||
|
||||
// Set the model directly
|
||||
contextMenu.model = items;
|
||||
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.open();
|
||||
|
||||
// Calculate menu position
|
||||
let menuX, menuY;
|
||||
if (root.barPosition === "top") {
|
||||
menuX = globalX + (itemWidth / 2) - (contextMenu.implicitWidth / 2);
|
||||
menuY = Style.barHeight + Style.marginS;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
const menuHeight = 12 + contextMenu.model.length * contextMenu.itemHeight;
|
||||
menuX = globalX + (itemWidth / 2) - (contextMenu.implicitWidth / 2);
|
||||
menuY = -menuHeight - Style.marginS;
|
||||
} else if (root.barPosition === "left") {
|
||||
menuX = Style.barHeight + Style.marginS;
|
||||
menuY = globalY + (itemHeight / 2) - (contextMenu.implicitHeight / 2);
|
||||
} else {
|
||||
// right
|
||||
menuX = -contextMenu.implicitWidth - Style.marginS;
|
||||
menuY = globalY + (itemHeight / 2) - (contextMenu.implicitHeight / 2);
|
||||
}
|
||||
|
||||
contextMenu.openAtItem(root, menuX, menuY);
|
||||
popupMenuWindow.contentItem = contextMenu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,10 +161,14 @@ Rectangle {
|
||||
}
|
||||
//Logger.d("Tray", "wildCardMatch - Input str:", str, "rule:", rule)
|
||||
|
||||
// Escape all special regex characters in the rule
|
||||
let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
// Convert '*' to '.*' for wildcard matching
|
||||
let pattern = escapedRule.replace(/\\\*/g, '.*');
|
||||
// First, convert '*' to a placeholder to preserve it, then escape other special regex characters
|
||||
// Use a unique placeholder that won't appear in normal strings
|
||||
const placeholder = '\uE000'; // Private use character
|
||||
let processedRule = rule.replace(/\*/g, placeholder);
|
||||
// Escape all special regex characters (but placeholder won't match this)
|
||||
let escapedRule = processedRule.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
||||
// Convert placeholder back to '.*' for wildcard matching
|
||||
let pattern = escapedRule.replace(new RegExp(placeholder, 'g'), '.*');
|
||||
// Add ^ and $ to match the entire string
|
||||
pattern = '^' + pattern + '$';
|
||||
|
||||
|
||||
@@ -124,9 +124,9 @@ Item {
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(pill, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
tooltipText: {
|
||||
|
||||
@@ -153,12 +153,9 @@ Item {
|
||||
// Get the shared popup menu window for this screen
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
// Calculate position using centralized helper (with center-based positioning)
|
||||
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
|
||||
// Show the context menu inside the popup window
|
||||
contextMenu.openAtItem(pill, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(pill, pos.x, pos.y);
|
||||
}
|
||||
}
|
||||
onMiddleClicked: root.openExternalMixer()
|
||||
|
||||
@@ -57,9 +57,9 @@ NIconButton {
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(root, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,14 +109,14 @@ Item {
|
||||
}
|
||||
autoHide: false
|
||||
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
|
||||
forceClose: isBarVertical || root.displayMode === "alwaysHide" || !pill.text
|
||||
forceClose: isBarVertical || root.displayMode === "alwaysHide" || text === ""
|
||||
onClicked: PanelService.getPanel("wifiPanel", screen)?.toggle(this)
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(pill, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
tooltipText: {
|
||||
|
||||
@@ -272,9 +272,9 @@ Item {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
const pos = BarService.getContextMenuPosition(workspaceBackground, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(workspaceBackground, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,28 +11,80 @@ NBox {
|
||||
|
||||
property real localOutputVolume: 0
|
||||
property bool localOutputVolumeChanging: false
|
||||
property int lastSinkId: -1
|
||||
|
||||
property real localInputVolume: 0
|
||||
property bool localInputVolumeChanging: false
|
||||
property int lastSourceId: -1
|
||||
|
||||
Component.onCompleted: {
|
||||
var vol = AudioService.volume;
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
var inputVol = AudioService.inputVolume;
|
||||
localInputVolume = (inputVol !== undefined && !isNaN(inputVol)) ? inputVol : 0;
|
||||
if (AudioService.sink) {
|
||||
lastSinkId = AudioService.sink.id;
|
||||
}
|
||||
if (AudioService.source) {
|
||||
lastSourceId = AudioService.source.id;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset local volume when device changes - use current device's volume
|
||||
Connections {
|
||||
target: AudioService
|
||||
function onSinkChanged() {
|
||||
if (AudioService.sink) {
|
||||
const newSinkId = AudioService.sink.id;
|
||||
if (newSinkId !== lastSinkId) {
|
||||
lastSinkId = newSinkId;
|
||||
// Immediately set local volume to current device's volume
|
||||
var vol = AudioService.volume;
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
} else {
|
||||
lastSinkId = -1;
|
||||
localOutputVolume = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AudioService
|
||||
function onSourceChanged() {
|
||||
if (AudioService.source) {
|
||||
const newSourceId = AudioService.source.id;
|
||||
if (newSourceId !== lastSourceId) {
|
||||
lastSourceId = newSourceId;
|
||||
// Immediately set local volume to current device's volume
|
||||
var vol = AudioService.inputVolume;
|
||||
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
} else {
|
||||
lastSourceId = -1;
|
||||
localInputVolume = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Timer to debounce volume changes
|
||||
// Only sync if the device hasn't changed (check by comparing IDs)
|
||||
Timer {
|
||||
interval: 100
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localOutputVolume);
|
||||
// Only sync if sink hasn't changed
|
||||
if (AudioService.sink && AudioService.sink.id === lastSinkId) {
|
||||
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localOutputVolume);
|
||||
}
|
||||
}
|
||||
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
|
||||
AudioService.setInputVolume(localInputVolume);
|
||||
// Only sync if source hasn't changed
|
||||
if (AudioService.source && AudioService.source.id === lastSourceId) {
|
||||
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
|
||||
AudioService.setInputVolume(localInputVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +93,7 @@ NBox {
|
||||
Connections {
|
||||
target: AudioService
|
||||
function onVolumeChanged() {
|
||||
if (!localOutputVolumeChanging) {
|
||||
if (!localOutputVolumeChanging && AudioService.sink && AudioService.sink.id === lastSinkId) {
|
||||
var vol = AudioService.volume;
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
@@ -51,7 +103,7 @@ NBox {
|
||||
Connections {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localOutputVolumeChanging) {
|
||||
if (!localOutputVolumeChanging && AudioService.sink && AudioService.sink.id === lastSinkId) {
|
||||
var vol = AudioService.volume;
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
@@ -61,7 +113,7 @@ NBox {
|
||||
Connections {
|
||||
target: AudioService
|
||||
function onInputVolumeChanged() {
|
||||
if (!localInputVolumeChanging) {
|
||||
if (!localInputVolumeChanging && AudioService.source && AudioService.source.id === lastSourceId) {
|
||||
var vol = AudioService.inputVolume;
|
||||
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
@@ -71,7 +123,7 @@ NBox {
|
||||
Connections {
|
||||
target: AudioService.source?.audio ? AudioService.source?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localInputVolumeChanging) {
|
||||
if (!localInputVolumeChanging && AudioService.source && AudioService.source.id === lastSourceId) {
|
||||
var vol = AudioService.inputVolume;
|
||||
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
132
Modules/Cards/CalendarHeaderCard.qml
Normal file
@@ -0,0 +1,132 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Location
|
||||
import qs.Widgets
|
||||
|
||||
// Calendar header with date, month/year, location, and clock
|
||||
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)
|
||||
radius: Style.radiusL
|
||||
color: Color.mPrimary
|
||||
|
||||
// Internal state
|
||||
readonly property var now: Time.now
|
||||
readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null)
|
||||
|
||||
// Expose current month/year for potential synchronization with CalendarMonthCard
|
||||
readonly property int currentMonth: now.getMonth()
|
||||
readonly property int currentYear: now.getFullYear()
|
||||
|
||||
ColumnLayout {
|
||||
id: capsuleColumn
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: Style.marginM
|
||||
anchors.bottomMargin: Style.marginM
|
||||
anchors.rightMargin: clockLoader.width + (Style.marginXL * 2)
|
||||
anchors.leftMargin: Style.marginXL
|
||||
spacing: 0
|
||||
|
||||
// Combined layout for date, month year, location and time-zone
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
height: 60 * Style.uiScaleRatio
|
||||
clip: true
|
||||
spacing: Style.marginS
|
||||
|
||||
// Today day number
|
||||
NText {
|
||||
Layout.preferredWidth: implicitWidth
|
||||
elide: Text.ElideNone
|
||||
clip: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
text: root.now.getDate()
|
||||
pointSize: Style.fontSizeXXXL * 1.5
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
|
||||
// Month, year, location
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.bottomMargin: Style.marginXXS
|
||||
Layout.topMargin: -Style.marginXXS
|
||||
spacing: -Style.marginXS
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.locale.monthName(root.currentMonth, Locale.LongFormat).toUpperCase()
|
||||
pointSize: Style.fontSizeXL * 1.1
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnPrimary
|
||||
Layout.alignment: Qt.AlignBaseline
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${root.currentYear}`
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Qt.alpha(Color.mOnPrimary, 0.7)
|
||||
Layout.alignment: Qt.AlignBaseline
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
|
||||
NText {
|
||||
text: {
|
||||
if (!Settings.data.location.weatherEnabled)
|
||||
return "";
|
||||
if (!root.weatherReady)
|
||||
return I18n.tr("calendar.weather.loading");
|
||||
const chunks = Settings.data.location.name.split(",");
|
||||
return chunks[0];
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: Color.mOnPrimary
|
||||
Layout.maximumWidth: 150
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
NText {
|
||||
text: root.weatherReady ? ` (${LocationService.data.weather.timezone_abbreviation})` : ""
|
||||
pointSize: Style.fontSizeXS
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: Qt.alpha(Color.mOnPrimary, 0.7)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analog/Digital clock
|
||||
NClock {
|
||||
id: clockLoader
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.marginXL
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clockStyle: Settings.data.location.analogClockInCalendar ? "analog" : "digital"
|
||||
progressColor: Color.mOnPrimary
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
now: root.now
|
||||
}
|
||||
}
|
||||
396
Modules/Cards/CalendarMonthCard.qml
Normal file
@@ -0,0 +1,396 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Location
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
// Calendar month grid with navigation
|
||||
NBox {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: calendarContent.implicitHeight + Style.marginM * 2
|
||||
|
||||
// Internal state - independent from header
|
||||
readonly property var now: Time.now
|
||||
property int calendarMonth: now.getMonth()
|
||||
property int calendarYear: now.getFullYear()
|
||||
readonly property int firstDayOfWeek: Settings.data.location.firstDayOfWeek === -1 ? I18n.locale.firstDayOfWeek : Settings.data.location.firstDayOfWeek
|
||||
|
||||
// Helper function to calculate ISO week number
|
||||
function getISOWeekNumber(date) {
|
||||
const target = new Date(date.valueOf());
|
||||
const dayNr = (date.getDay() + 6) % 7;
|
||||
target.setDate(target.getDate() - dayNr + 3);
|
||||
const firstThursday = new Date(target.getFullYear(), 0, 4);
|
||||
const diff = target - firstThursday;
|
||||
const oneWeek = 1000 * 60 * 60 * 24 * 7;
|
||||
const weekNumber = 1 + Math.round(diff / oneWeek);
|
||||
return weekNumber;
|
||||
}
|
||||
|
||||
// Helper function to check if an event is all-day
|
||||
function isAllDayEvent(event) {
|
||||
const duration = event.end - event.start;
|
||||
const startDate = new Date(event.start * 1000);
|
||||
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0;
|
||||
return duration === 86400 && isAtMidnight;
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: calendarContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginS
|
||||
|
||||
// Navigation row
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "chevron-left"
|
||||
onClicked: {
|
||||
let newDate = new Date(root.calendarYear, root.calendarMonth - 1, 1);
|
||||
root.calendarYear = newDate.getFullYear();
|
||||
root.calendarMonth = newDate.getMonth();
|
||||
const now = new Date();
|
||||
const monthStart = new Date(root.calendarYear, root.calendarMonth, 1);
|
||||
const monthEnd = new Date(root.calendarYear, root.calendarMonth + 1, 0);
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "calendar"
|
||||
onClicked: {
|
||||
root.calendarMonth = root.now.getMonth();
|
||||
root.calendarYear = root.now.getFullYear();
|
||||
CalendarService.loadEvents();
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "chevron-right"
|
||||
onClicked: {
|
||||
let newDate = new Date(root.calendarYear, root.calendarMonth + 1, 1);
|
||||
root.calendarYear = newDate.getFullYear();
|
||||
root.calendarMonth = newDate.getMonth();
|
||||
const now = new Date();
|
||||
const monthStart = new Date(root.calendarYear, root.calendarMonth, 1);
|
||||
const monthEnd = new Date(root.calendarYear, root.calendarMonth + 1, 0);
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Day names header
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
visible: Settings.data.location.showWeekNumberInCalendar
|
||||
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
columns: 7
|
||||
rows: 1
|
||||
columnSpacing: 0
|
||||
rowSpacing: 0
|
||||
|
||||
Repeater {
|
||||
model: 7
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.fontSizeS * 2
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
let dayIndex = (root.firstDayOfWeek + index) % 7;
|
||||
const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat);
|
||||
return dayName.substring(0, 2).toUpperCase();
|
||||
}
|
||||
color: Color.mPrimary
|
||||
pointSize: Style.fontSizeS
|
||||
font.weight: Style.fontWeightBold
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calendar grid with week numbers
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
// Helper functions
|
||||
function hasEventsOnDate(year, month, day) {
|
||||
if (!CalendarService.available || CalendarService.events.length === 0)
|
||||
return false;
|
||||
const targetDate = new Date(year, month, day);
|
||||
const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000;
|
||||
const targetEnd = targetStart + 86400;
|
||||
return CalendarService.events.some(event => {
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
|
||||
});
|
||||
}
|
||||
|
||||
function getEventsForDate(year, month, day) {
|
||||
if (!CalendarService.available || CalendarService.events.length === 0)
|
||||
return [];
|
||||
const targetDate = new Date(year, month, day);
|
||||
const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000);
|
||||
const targetEnd = targetStart + 86400;
|
||||
return CalendarService.events.filter(event => {
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
|
||||
});
|
||||
}
|
||||
|
||||
function isMultiDayEvent(event) {
|
||||
if (root.isAllDayEvent(event)) {
|
||||
return false;
|
||||
}
|
||||
const startDate = new Date(event.start * 1000);
|
||||
const endDate = new Date(event.end * 1000);
|
||||
const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
|
||||
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
return startDateOnly.getTime() !== endDateOnly.getTime();
|
||||
}
|
||||
|
||||
function getEventColor(event, isToday) {
|
||||
if (isMultiDayEvent(event)) {
|
||||
return isToday ? Color.mOnSecondary : Color.mTertiary;
|
||||
} else if (root.isAllDayEvent(event)) {
|
||||
return isToday ? Color.mOnSecondary : Color.mSecondary;
|
||||
} else {
|
||||
return isToday ? Color.mOnSecondary : Color.mPrimary;
|
||||
}
|
||||
}
|
||||
|
||||
// Week numbers column
|
||||
ColumnLayout {
|
||||
visible: Settings.data.location.showWeekNumberInCalendar
|
||||
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
|
||||
Layout.alignment: Qt.AlignTop
|
||||
spacing: Style.marginXXS
|
||||
|
||||
property var weekNumbers: {
|
||||
if (!grid.daysModel || grid.daysModel.length === 0)
|
||||
return [];
|
||||
const weeks = [];
|
||||
const numWeeks = Math.ceil(grid.daysModel.length / 7);
|
||||
for (var i = 0; i < numWeeks; i++) {
|
||||
const dayIndex = i * 7;
|
||||
if (dayIndex < grid.daysModel.length) {
|
||||
const weekDay = grid.daysModel[dayIndex];
|
||||
const date = new Date(weekDay.year, weekDay.month, weekDay.day);
|
||||
let thursday = new Date(date);
|
||||
if (root.firstDayOfWeek === 0) {
|
||||
thursday.setDate(date.getDate() + 4);
|
||||
} else if (root.firstDayOfWeek === 1) {
|
||||
thursday.setDate(date.getDate() + 3);
|
||||
} else {
|
||||
let daysToThursday = (4 - root.firstDayOfWeek + 7) % 7;
|
||||
thursday.setDate(date.getDate() + daysToThursday);
|
||||
}
|
||||
weeks.push(root.getISOWeekNumber(thursday));
|
||||
}
|
||||
}
|
||||
return weeks;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: parent.weekNumbers
|
||||
Item {
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 0.7
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 0.9
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
color: Qt.alpha(Color.mPrimary, 0.7)
|
||||
pointSize: Style.fontSizeXXS
|
||||
font.weight: Style.fontWeightMedium
|
||||
text: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calendar grid
|
||||
GridLayout {
|
||||
id: grid
|
||||
Layout.fillWidth: true
|
||||
columns: 7
|
||||
columnSpacing: Style.marginXXS
|
||||
rowSpacing: Style.marginXXS
|
||||
|
||||
property int month: root.calendarMonth
|
||||
property int year: root.calendarYear
|
||||
|
||||
property var daysModel: {
|
||||
const firstOfMonth = new Date(year, month, 1);
|
||||
const lastOfMonth = new Date(year, month + 1, 0);
|
||||
const daysInMonth = lastOfMonth.getDate();
|
||||
const firstDayOfWeek = root.firstDayOfWeek;
|
||||
const firstOfMonthDayOfWeek = firstOfMonth.getDay();
|
||||
let daysBefore = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7;
|
||||
const lastOfMonthDayOfWeek = lastOfMonth.getDay();
|
||||
const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7;
|
||||
const days = [];
|
||||
const today = new Date();
|
||||
|
||||
// Previous month days
|
||||
const prevMonth = new Date(year, month, 0);
|
||||
const prevMonthDays = prevMonth.getDate();
|
||||
for (var i = daysBefore - 1; i >= 0; i--) {
|
||||
const day = prevMonthDays - i;
|
||||
days.push({
|
||||
"day": day,
|
||||
"month": month - 1,
|
||||
"year": month === 0 ? year - 1 : year,
|
||||
"today": false,
|
||||
"currentMonth": false
|
||||
});
|
||||
}
|
||||
|
||||
// Current month days
|
||||
for (var day = 1; day <= daysInMonth; day++) {
|
||||
const date = new Date(year, month, day);
|
||||
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
|
||||
days.push({
|
||||
"day": day,
|
||||
"month": month,
|
||||
"year": year,
|
||||
"today": isToday,
|
||||
"currentMonth": true
|
||||
});
|
||||
}
|
||||
|
||||
// Next month days
|
||||
for (var i = 1; i <= daysAfter; i++) {
|
||||
days.push({
|
||||
"day": i,
|
||||
"month": month + 1,
|
||||
"year": month === 11 ? year + 1 : year,
|
||||
"today": false,
|
||||
"currentMonth": false
|
||||
});
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: grid.daysModel
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 0.9
|
||||
|
||||
Rectangle {
|
||||
width: Style.baseWidgetSize * 0.9
|
||||
height: Style.baseWidgetSize * 0.9
|
||||
anchors.centerIn: parent
|
||||
radius: Style.radiusM
|
||||
color: modelData.today ? Color.mSecondary : Color.transparent
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.day
|
||||
color: {
|
||||
if (modelData.today)
|
||||
return Color.mOnSecondary;
|
||||
if (modelData.currentMonth)
|
||||
return Color.mOnSurface;
|
||||
return Color.mOnSurfaceVariant;
|
||||
}
|
||||
opacity: modelData.currentMonth ? 1.0 : 0.4
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: modelData.today ? Style.fontWeightBold : Style.fontWeightMedium
|
||||
}
|
||||
|
||||
// Event indicator dots
|
||||
Row {
|
||||
visible: Settings.data.location.showCalendarEvents && parent.parent.parent.parent.hasEventsOnDate(modelData.year, modelData.month, modelData.day)
|
||||
spacing: 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Style.marginXS
|
||||
|
||||
Repeater {
|
||||
model: parent.parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day)
|
||||
|
||||
Rectangle {
|
||||
width: 4
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: parent.parent.parent.parent.parent.getEventColor(modelData, modelData.today)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: Settings.data.location.showCalendarEvents
|
||||
|
||||
onEntered: {
|
||||
const events = parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day);
|
||||
if (events.length > 0) {
|
||||
const summaries = events.map(event => {
|
||||
if (root.isAllDayEvent(event)) {
|
||||
return event.summary;
|
||||
} else {
|
||||
const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm";
|
||||
const start = new Date(event.start * 1000);
|
||||
const startFormatted = I18n.locale.toString(start, timeFormat);
|
||||
const end = new Date(event.end * 1000);
|
||||
const endFormatted = I18n.locale.toString(end, timeFormat);
|
||||
return `${startFormatted}-${endFormatted} ${event.summary}`;
|
||||
}
|
||||
}).join('\n');
|
||||
TooltipService.show(parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`;
|
||||
if (ProgramCheckerService.gnomeCalendarAvailable) {
|
||||
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes]);
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Modules.Panels.ControlCenter.Cards
|
||||
import qs.Modules.Panels.Settings
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
@@ -25,13 +24,14 @@ NBox {
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
NImageCircled {
|
||||
NImageRounded {
|
||||
Layout.preferredWidth: Math.round(Style.baseWidgetSize * 1.25 * Style.uiScaleRatio)
|
||||
Layout.preferredHeight: Math.round(Style.baseWidgetSize * 1.25 * Style.uiScaleRatio)
|
||||
radius: width * 0.5
|
||||
imagePath: Settings.preprocessPath(Settings.data.general.avatarImage)
|
||||
fallbackIcon: "person"
|
||||
borderColor: Color.mPrimary
|
||||
borderWidth: Style.borderM
|
||||
borderWidth: Style.borderS * 1.5
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
@@ -4,7 +4,6 @@ import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Panels.ControlCenter
|
||||
import qs.Modules.Panels.ControlCenter.Cards
|
||||
import qs.Widgets
|
||||
|
||||
RowLayout {
|
||||
@@ -14,6 +13,7 @@ RowLayout {
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.shortcutsHeight
|
||||
visible: Settings.data.controlCenter.shortcuts.left.length > 0
|
||||
|
||||
RowLayout {
|
||||
id: leftContent
|
||||
@@ -53,6 +53,7 @@ RowLayout {
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.shortcutsHeight
|
||||
visible: Settings.data.controlCenter.shortcuts.right.length > 0
|
||||
|
||||
RowLayout {
|
||||
id: rightContent
|
||||
449
Modules/Cards/TimerCard.qml
Normal file
@@ -0,0 +1,449 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
// Timer card for the Calendar panel
|
||||
NBox {
|
||||
id: root
|
||||
|
||||
implicitHeight: content.implicitHeight + (Style.marginM * 2)
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
clip: true
|
||||
|
||||
// Header
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NIcon {
|
||||
icon: isStopwatchMode ? "clock" : "hourglass"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("calendar.timer.title")
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
// Timer display (editable when not running)
|
||||
Item {
|
||||
id: timerDisplayItem
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: isRunning ? 160 * Style.uiScaleRatio : timerInput.implicitHeight
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
property string inputBuffer: ""
|
||||
property bool isEditing: false
|
||||
|
||||
// Circular progress ring (only for countdown mode when running)
|
||||
Canvas {
|
||||
id: progressRing
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
visible: !isStopwatchMode && isRunning && totalSeconds > 0
|
||||
z: -1
|
||||
|
||||
property real progressRatio: {
|
||||
if (totalSeconds <= 0)
|
||||
return 0;
|
||||
// Inverted: show remaining time (starts at 1, goes to 0)
|
||||
const ratio = remainingSeconds / totalSeconds;
|
||||
return Math.max(0, Math.min(1, ratio));
|
||||
}
|
||||
|
||||
onProgressRatioChanged: requestPaint()
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
if (width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var centerX = width / 2;
|
||||
var centerY = height / 2;
|
||||
var radius = Math.max(0, Math.min(width, height) / 2 - 6);
|
||||
|
||||
ctx.reset();
|
||||
|
||||
// Background circle (full track)
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
|
||||
ctx.lineWidth = 4;
|
||||
ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.2);
|
||||
ctx.stroke();
|
||||
|
||||
// Progress arc (elapsed portion)
|
||||
if (progressRatio > 0) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progressRatio * 2 * Math.PI);
|
||||
ctx.lineWidth = 4;
|
||||
ctx.strokeStyle = Color.mPrimary;
|
||||
ctx.lineCap = "round";
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextInput {
|
||||
id: timerInput
|
||||
anchors.centerIn: parent
|
||||
width: Math.max(implicitWidth, parent.width)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
selectByMouse: false
|
||||
cursorVisible: false
|
||||
cursorDelegate: Item {} // Empty cursor delegate to hide cursor
|
||||
readOnly: isStopwatchMode || isRunning
|
||||
enabled: !isRunning
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
|
||||
// Calculate if hours are being shown
|
||||
readonly property bool showingHours: {
|
||||
if (isStopwatchMode) {
|
||||
return elapsedSeconds >= 3600;
|
||||
}
|
||||
// In edit mode, always show hours (HH:MM:SS format)
|
||||
if (timerDisplayItem.isEditing) {
|
||||
return true;
|
||||
}
|
||||
// When not editing, only show hours if >= 1 hour
|
||||
return remainingSeconds >= 3600;
|
||||
}
|
||||
|
||||
font.pointSize: {
|
||||
if (!isRunning) {
|
||||
return Style.fontSizeXXXL;
|
||||
}
|
||||
// When running, use smaller font if hours are shown
|
||||
return showingHours ? Style.fontSizeXXL : (Style.fontSizeXXL * 1.2);
|
||||
}
|
||||
|
||||
font.weight: Style.fontWeightBold
|
||||
color: {
|
||||
if (isRunning) {
|
||||
return Color.mPrimary;
|
||||
}
|
||||
if (timerDisplayItem.isEditing) {
|
||||
return Color.mPrimary;
|
||||
}
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
|
||||
// Display formatted time, but show input buffer when editing
|
||||
text: {
|
||||
if (isStopwatchMode) {
|
||||
return formatTime(elapsedSeconds, false); // Stopwatch: only show hours if >= 1 hour
|
||||
}
|
||||
if (!timerDisplayItem.isEditing) {
|
||||
// When not editing and not running, always show hours
|
||||
// When running, only show hours if >= 1 hour
|
||||
return formatTime(remainingSeconds, isRunning);
|
||||
}
|
||||
if (timerDisplayItem.inputBuffer !== "") {
|
||||
return formatTimeFromDigits(timerDisplayItem.inputBuffer);
|
||||
}
|
||||
return formatTime(0, false);
|
||||
}
|
||||
|
||||
// Only accept digit keys
|
||||
Keys.onPressed: event => {
|
||||
if (isRunning || isStopwatchMode) {
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle backspace
|
||||
if (event.key === Qt.Key_Backspace) {
|
||||
if (timerDisplayItem.isEditing && timerDisplayItem.inputBuffer.length > 0) {
|
||||
timerDisplayItem.inputBuffer = timerDisplayItem.inputBuffer.slice(0, -1);
|
||||
if (timerDisplayItem.inputBuffer !== "") {
|
||||
parseDigitsToTime(timerDisplayItem.inputBuffer);
|
||||
} else {
|
||||
Time.timerRemainingSeconds = 0;
|
||||
}
|
||||
}
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle delete
|
||||
if (event.key === Qt.Key_Delete) {
|
||||
if (timerDisplayItem.isEditing) {
|
||||
timerDisplayItem.inputBuffer = "";
|
||||
Time.timerRemainingSeconds = 0;
|
||||
}
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow navigation keys (but don't let them modify text)
|
||||
if (event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Home || event.key === Qt.Key_End || (event.modifiers & Qt.ControlModifier) || (event.modifiers & Qt.ShiftModifier)) {
|
||||
event.accepted = false; // Let default handling work for selection
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle enter/return
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
applyTimeFromBuffer();
|
||||
timerDisplayItem.isEditing = false;
|
||||
focus = false;
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle escape
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
timerDisplayItem.inputBuffer = "";
|
||||
Time.timerRemainingSeconds = 0;
|
||||
timerDisplayItem.isEditing = false;
|
||||
focus = false;
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Only allow digits 0-9
|
||||
if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) {
|
||||
// Limit to 6 digits max
|
||||
if (timerDisplayItem.inputBuffer.length >= 6) {
|
||||
event.accepted = true; // Block if already at max
|
||||
return;
|
||||
}
|
||||
// Add the digit to the buffer
|
||||
timerDisplayItem.inputBuffer += String.fromCharCode(event.key);
|
||||
// Update the display and parse
|
||||
parseDigitsToTime(timerDisplayItem.inputBuffer);
|
||||
event.accepted = true; // We handled it
|
||||
} else {
|
||||
event.accepted = true; // Block all other keys
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
applyTimeFromBuffer();
|
||||
timerDisplayItem.isEditing = false;
|
||||
focus = false;
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
timerDisplayItem.inputBuffer = "";
|
||||
Time.timerRemainingSeconds = 0;
|
||||
timerDisplayItem.isEditing = false;
|
||||
focus = false;
|
||||
}
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus) {
|
||||
timerDisplayItem.isEditing = true;
|
||||
timerDisplayItem.inputBuffer = "";
|
||||
} else {
|
||||
applyTimeFromBuffer();
|
||||
timerDisplayItem.isEditing = false;
|
||||
timerDisplayItem.inputBuffer = "";
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: !isRunning && !isStopwatchMode
|
||||
cursorShape: enabled ? Qt.IBeamCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
if (!isRunning && !isStopwatchMode) {
|
||||
timerInput.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Control buttons
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 0
|
||||
implicitHeight: startButton.implicitHeight
|
||||
color: Color.transparent
|
||||
|
||||
NButton {
|
||||
id: startButton
|
||||
anchors.fill: parent
|
||||
text: isRunning ? I18n.tr("calendar.timer.pause") : I18n.tr("calendar.timer.start")
|
||||
icon: isRunning ? "player-pause" : "player-play"
|
||||
enabled: isStopwatchMode || remainingSeconds > 0
|
||||
onClicked: {
|
||||
if (isRunning) {
|
||||
pauseTimer();
|
||||
} else {
|
||||
startTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 0
|
||||
implicitHeight: resetButton.implicitHeight
|
||||
color: Color.transparent
|
||||
|
||||
NButton {
|
||||
id: resetButton
|
||||
anchors.fill: parent
|
||||
text: I18n.tr("calendar.timer.reset")
|
||||
icon: "refresh"
|
||||
enabled: (isStopwatchMode && (elapsedSeconds > 0 || isRunning)) || (!isStopwatchMode && (remainingSeconds > 0 || isRunning || soundPlaying))
|
||||
onClicked: {
|
||||
resetTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mode tabs (Android-style) - below buttons
|
||||
NTabBar {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: !isRunning
|
||||
currentIndex: isStopwatchMode ? 1 : 0
|
||||
onCurrentIndexChanged: {
|
||||
const newMode = currentIndex === 1;
|
||||
if (newMode !== isStopwatchMode) {
|
||||
if (isRunning) {
|
||||
pauseTimer();
|
||||
}
|
||||
// Stop any repeating notification sound when switching modes
|
||||
SoundService.stopSound("alarm-beep.wav");
|
||||
Time.timerSoundPlaying = false;
|
||||
Time.timerStopwatchMode = newMode;
|
||||
if (newMode) {
|
||||
// Reset to 0 for stopwatch
|
||||
Time.timerElapsedSeconds = 0;
|
||||
} else {
|
||||
Time.timerRemainingSeconds = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
spacing: Style.marginXS
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("calendar.timer.countdown")
|
||||
tabIndex: 0
|
||||
checked: !isStopwatchMode
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("calendar.timer.stopwatch")
|
||||
tabIndex: 1
|
||||
checked: isStopwatchMode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bind to Time for persistent timer state
|
||||
readonly property bool isRunning: Time.timerRunning
|
||||
property bool isStopwatchMode: Time.timerStopwatchMode
|
||||
readonly property int remainingSeconds: Time.timerRemainingSeconds
|
||||
readonly property int totalSeconds: Time.timerTotalSeconds
|
||||
readonly property int elapsedSeconds: Time.timerElapsedSeconds
|
||||
readonly property bool soundPlaying: Time.timerSoundPlaying
|
||||
|
||||
function formatTime(seconds, hideHoursWhenZero) {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = seconds % 60;
|
||||
|
||||
// If hideHoursWhenZero is true (when running), only show hours if > 0
|
||||
// Otherwise (when not running or editing), always show hours
|
||||
if (hideHoursWhenZero && hours === 0) {
|
||||
return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||
}
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function formatTimeFromDigits(digits) {
|
||||
// Parse digits right-to-left: last 2 = seconds, next 2 = minutes, rest = hours
|
||||
const len = digits.length;
|
||||
let seconds = 0;
|
||||
let minutes = 0;
|
||||
let hours = 0;
|
||||
|
||||
if (len > 0) {
|
||||
seconds = parseInt(digits.substring(Math.max(0, len - 2))) || 0;
|
||||
}
|
||||
if (len > 2) {
|
||||
minutes = parseInt(digits.substring(Math.max(0, len - 4), len - 2)) || 0;
|
||||
}
|
||||
if (len > 4) {
|
||||
hours = parseInt(digits.substring(0, len - 4)) || 0;
|
||||
}
|
||||
|
||||
// Clamp values
|
||||
seconds = Math.min(59, seconds);
|
||||
minutes = Math.min(59, minutes);
|
||||
hours = Math.min(99, hours);
|
||||
|
||||
// Always show HH:MM:SS format in edit mode
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function parseDigitsToTime(digits) {
|
||||
// Parse digits right-to-left: last 2 = seconds, next 2 = minutes, rest = hours
|
||||
const len = digits.length;
|
||||
let seconds = 0;
|
||||
let minutes = 0;
|
||||
let hours = 0;
|
||||
|
||||
if (len > 0) {
|
||||
seconds = parseInt(digits.substring(Math.max(0, len - 2))) || 0;
|
||||
}
|
||||
if (len > 2) {
|
||||
minutes = parseInt(digits.substring(Math.max(0, len - 4), len - 2)) || 0;
|
||||
}
|
||||
if (len > 4) {
|
||||
hours = parseInt(digits.substring(0, len - 4)) || 0;
|
||||
}
|
||||
|
||||
// Clamp values
|
||||
seconds = Math.min(59, seconds);
|
||||
minutes = Math.min(59, minutes);
|
||||
hours = Math.min(99, hours);
|
||||
|
||||
Time.timerRemainingSeconds = (hours * 3600) + (minutes * 60) + seconds;
|
||||
}
|
||||
|
||||
function applyTimeFromBuffer() {
|
||||
if (timerDisplayItem.inputBuffer !== "") {
|
||||
parseDigitsToTime(timerDisplayItem.inputBuffer);
|
||||
timerDisplayItem.inputBuffer = "";
|
||||
}
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
Time.timerStart();
|
||||
}
|
||||
|
||||
function pauseTimer() {
|
||||
Time.timerPause();
|
||||
}
|
||||
|
||||
function resetTimer() {
|
||||
Time.timerReset();
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ NBox {
|
||||
|
||||
// Weather condition detection
|
||||
readonly property int currentWeatherCode: weatherReady ? LocationService.data.weather.current_weather.weathercode : 0
|
||||
readonly property bool isRaining: testEffects === "rain" || (testEffects === "" && currentWeatherCode >= 51 && currentWeatherCode <= 67)
|
||||
readonly property bool isRaining: testEffects === "rain" || (testEffects === "" && ((currentWeatherCode >= 51 && currentWeatherCode <= 67) || (currentWeatherCode >= 80 && currentWeatherCode <= 82)))
|
||||
readonly property bool isSnowing: testEffects === "snow" || (testEffects === "" && ((currentWeatherCode >= 71 && currentWeatherCode <= 77) || (currentWeatherCode >= 85 && currentWeatherCode <= 86)))
|
||||
|
||||
visible: Settings.data.location.weatherEnabled
|
||||
@@ -84,6 +84,7 @@ Loader {
|
||||
cache: true
|
||||
smooth: true
|
||||
mipmap: false
|
||||
antialiasing: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -301,10 +302,11 @@ Loader {
|
||||
}
|
||||
}
|
||||
|
||||
NImageCircled {
|
||||
NImageRounded {
|
||||
anchors.centerIn: parent
|
||||
width: 66
|
||||
height: 66
|
||||
radius: width * 0.5
|
||||
imagePath: Settings.preprocessPath(Settings.data.general.avatarImage)
|
||||
fallbackIcon: "person"
|
||||
|
||||
@@ -344,14 +346,14 @@ Loader {
|
||||
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",
|
||||
"nl": "dddd d MMMM",
|
||||
"pt": "dddd, d 'de' MMMM",
|
||||
"zh": "yyyy年M月d日 dddd",
|
||||
"uk": "dddd, d MMMM",
|
||||
"tr": "dddd, d MMMM"
|
||||
"zh": "yyyy年M月d日 dddd"
|
||||
};
|
||||
return I18n.locale.toString(Time.now, formats[lang] || "dddd, MMMM d");
|
||||
return I18n.locale.toString(Time.now, formats[lang] || "dddd, d MMMM");
|
||||
}
|
||||
pointSize: Style.fontSizeXL
|
||||
font.weight: Font.Medium
|
||||
@@ -525,7 +527,7 @@ Loader {
|
||||
}
|
||||
Text {
|
||||
id: hibernateText
|
||||
text: I18n.tr("session-menu.hibernate")
|
||||
text: Settings.data.general.showHibernateOnLockScreen ? I18n.tr("session-menu.hibernate") : ""
|
||||
font.pointSize: buttonRowTextMeasurer.fontSize
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
@@ -631,9 +633,10 @@ Loader {
|
||||
color: Color.transparent
|
||||
clip: true
|
||||
|
||||
NImageCircled {
|
||||
NImageRounded {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
radius: width * 0.5
|
||||
imagePath: MediaService.trackArtUrl
|
||||
fallbackIcon: "disc"
|
||||
fallbackIconSize: Style.fontSizeM
|
||||
@@ -752,7 +755,7 @@ Loader {
|
||||
}
|
||||
}
|
||||
|
||||
// 3-day forecast
|
||||
// Forecast
|
||||
RowLayout {
|
||||
visible: Settings.data.location.weatherEnabled && LocationService.data.weather !== null
|
||||
Layout.preferredWidth: 260
|
||||
@@ -760,7 +763,7 @@ Loader {
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
model: 3
|
||||
model: MediaService.currentPlayer && MediaService.canPlay ? 3 : 4
|
||||
delegate: ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 3
|
||||
@@ -807,8 +810,6 @@ Loader {
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
visible: !(Settings.data.location.weatherEnabled && LocationService.data.weather !== null)
|
||||
Layout.preferredWidth: visible ? 1 : 0
|
||||
}
|
||||
|
||||
// Battery and Keyboard Layout (full mode only)
|
||||
|
||||
@@ -90,9 +90,9 @@ Item {
|
||||
backgroundColor: panelBackgroundColor
|
||||
}
|
||||
|
||||
// Calendar
|
||||
// Clock
|
||||
PanelBackground {
|
||||
panel: root.windowRoot.calendarPanelPlaceholder
|
||||
panel: root.windowRoot.clockPanelPlaceholder
|
||||
shapeContainer: backgroundsShape
|
||||
backgroundColor: panelBackgroundColor
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
@@ -13,8 +14,8 @@ import qs.Modules.Panels.Audio
|
||||
import qs.Modules.Panels.Battery
|
||||
import qs.Modules.Panels.Bluetooth
|
||||
import qs.Modules.Panels.Brightness
|
||||
import qs.Modules.Panels.Calendar
|
||||
import qs.Modules.Panels.Changelog
|
||||
import qs.Modules.Panels.Clock
|
||||
import qs.Modules.Panels.ControlCenter
|
||||
import qs.Modules.Panels.Launcher
|
||||
import qs.Modules.Panels.NotificationHistory
|
||||
@@ -24,6 +25,7 @@ import qs.Modules.Panels.SetupWizard
|
||||
import qs.Modules.Panels.Tray
|
||||
import qs.Modules.Panels.Wallpaper
|
||||
import qs.Modules.Panels.WiFi
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
|
||||
/**
|
||||
@@ -37,7 +39,7 @@ PanelWindow {
|
||||
readonly property alias batteryPanel: batteryPanel
|
||||
readonly property alias bluetoothPanel: bluetoothPanel
|
||||
readonly property alias brightnessPanel: brightnessPanel
|
||||
readonly property alias calendarPanel: calendarPanel
|
||||
readonly property alias clockPanel: clockPanel
|
||||
readonly property alias changelogPanel: changelogPanel
|
||||
readonly property alias controlCenterPanel: controlCenterPanel
|
||||
readonly property alias launcherPanel: launcherPanel
|
||||
@@ -49,22 +51,22 @@ PanelWindow {
|
||||
readonly property alias wallpaperPanel: wallpaperPanel
|
||||
readonly property alias wifiPanel: wifiPanel
|
||||
|
||||
// Expose panel placeholders for AllBackgrounds
|
||||
readonly property var audioPanelPlaceholder: audioPanel.panelPlaceholder
|
||||
readonly property var batteryPanelPlaceholder: batteryPanel.panelPlaceholder
|
||||
readonly property var bluetoothPanelPlaceholder: bluetoothPanel.panelPlaceholder
|
||||
readonly property var brightnessPanelPlaceholder: brightnessPanel.panelPlaceholder
|
||||
readonly property var calendarPanelPlaceholder: calendarPanel.panelPlaceholder
|
||||
readonly property var changelogPanelPlaceholder: changelogPanel.panelPlaceholder
|
||||
readonly property var controlCenterPanelPlaceholder: controlCenterPanel.panelPlaceholder
|
||||
readonly property var launcherPanelPlaceholder: launcherPanel.panelPlaceholder
|
||||
readonly property var notificationHistoryPanelPlaceholder: notificationHistoryPanel.panelPlaceholder
|
||||
readonly property var sessionMenuPanelPlaceholder: sessionMenuPanel.panelPlaceholder
|
||||
readonly property var settingsPanelPlaceholder: settingsPanel.panelPlaceholder
|
||||
readonly property var setupWizardPanelPlaceholder: setupWizardPanel.panelPlaceholder
|
||||
readonly property var trayDrawerPanelPlaceholder: trayDrawerPanel.panelPlaceholder
|
||||
readonly property var wallpaperPanelPlaceholder: wallpaperPanel.panelPlaceholder
|
||||
readonly property var wifiPanelPlaceholder: wifiPanel.panelPlaceholder
|
||||
// Expose panel backgrounds for AllBackgrounds
|
||||
readonly property var audioPanelPlaceholder: audioPanel.panelRegion
|
||||
readonly property var batteryPanelPlaceholder: batteryPanel.panelRegion
|
||||
readonly property var bluetoothPanelPlaceholder: bluetoothPanel.panelRegion
|
||||
readonly property var brightnessPanelPlaceholder: brightnessPanel.panelRegion
|
||||
readonly property var clockPanelPlaceholder: clockPanel.panelRegion
|
||||
readonly property var changelogPanelPlaceholder: changelogPanel.panelRegion
|
||||
readonly property var controlCenterPanelPlaceholder: controlCenterPanel.panelRegion
|
||||
readonly property var launcherPanelPlaceholder: launcherPanel.panelRegion
|
||||
readonly property var notificationHistoryPanelPlaceholder: notificationHistoryPanel.panelRegion
|
||||
readonly property var sessionMenuPanelPlaceholder: sessionMenuPanel.panelRegion
|
||||
readonly property var settingsPanelPlaceholder: settingsPanel.panelRegion
|
||||
readonly property var setupWizardPanelPlaceholder: setupWizardPanel.panelRegion
|
||||
readonly property var trayDrawerPanelPlaceholder: trayDrawerPanel.panelRegion
|
||||
readonly property var wallpaperPanelPlaceholder: wallpaperPanel.panelRegion
|
||||
readonly property var wifiPanelPlaceholder: wifiPanel.panelRegion
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("MainScreen", "Initialized for screen:", screen?.name, "- Dimensions:", screen?.width, "x", screen?.height, "- Position:", screen?.x, ",", screen?.y);
|
||||
@@ -74,7 +76,12 @@ PanelWindow {
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
WlrLayershell.namespace: "noctalia-background-" + (screen?.name || "unknown")
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore // Don't reserve space - BarExclusionZone handles that
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (!root.isPanelOpen) {
|
||||
return WlrKeyboardFocus.None;
|
||||
}
|
||||
return PanelService.openedPanel.exclusiveKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand;
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
@@ -128,7 +135,9 @@ PanelWindow {
|
||||
height: root.height
|
||||
intersection: Intersection.Xor
|
||||
|
||||
regions: [barMaskRegion]
|
||||
// Only include regions that are actually needed
|
||||
// panelRegions is handled by PanelService, bar is local to this screen
|
||||
regions: [barMaskRegion, backgroundMaskRegion]
|
||||
|
||||
// Bar region - subtract bar area from mask (only if bar should be shown on this screen)
|
||||
Region {
|
||||
@@ -142,6 +151,16 @@ PanelWindow {
|
||||
height: root.barShouldShow ? barPlaceholder.height : 0
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
|
||||
// Background region for click-to-close - reactive sizing
|
||||
Region {
|
||||
id: backgroundMaskRegion
|
||||
x: 0
|
||||
y: 0
|
||||
width: root.isPanelOpen && !isPanelClosing ? root.width : 0
|
||||
height: root.isPanelOpen && !isPanelClosing ? root.height : 0
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
@@ -162,6 +181,20 @@ PanelWindow {
|
||||
z: 0 // Behind all content
|
||||
}
|
||||
|
||||
// Background MouseArea for closing panels when clicking outside
|
||||
// Active whenever a panel is open - the mask ensures it only receives clicks when panel is open
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.isPanelOpen
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
if (PanelService.openedPanel) {
|
||||
PanelService.openedPanel.close();
|
||||
}
|
||||
}
|
||||
z: 0 // Behind panels and bar
|
||||
}
|
||||
|
||||
// ---------------------------------------
|
||||
// All panels always exist
|
||||
// ---------------------------------------
|
||||
@@ -169,90 +202,105 @@ PanelWindow {
|
||||
id: audioPanel
|
||||
objectName: "audioPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
BatteryPanel {
|
||||
id: batteryPanel
|
||||
objectName: "batteryPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
BluetoothPanel {
|
||||
id: bluetoothPanel
|
||||
objectName: "bluetoothPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
BrightnessPanel {
|
||||
id: brightnessPanel
|
||||
objectName: "brightnessPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
ControlCenterPanel {
|
||||
id: controlCenterPanel
|
||||
objectName: "controlCenterPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
ChangelogPanel {
|
||||
id: changelogPanel
|
||||
objectName: "changelogPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
CalendarPanel {
|
||||
id: calendarPanel
|
||||
objectName: "calendarPanel-" + (root.screen?.name || "unknown")
|
||||
ClockPanel {
|
||||
id: clockPanel
|
||||
objectName: "clockPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
Launcher {
|
||||
id: launcherPanel
|
||||
objectName: "launcherPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
NotificationHistoryPanel {
|
||||
id: notificationHistoryPanel
|
||||
objectName: "notificationHistoryPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
SessionMenu {
|
||||
id: sessionMenuPanel
|
||||
objectName: "sessionMenuPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
SettingsPanel {
|
||||
id: settingsPanel
|
||||
objectName: "settingsPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
SetupWizard {
|
||||
id: setupWizardPanel
|
||||
objectName: "setupWizardPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
TrayDrawerPanel {
|
||||
id: trayDrawerPanel
|
||||
objectName: "trayDrawerPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
WallpaperPanel {
|
||||
id: wallpaperPanel
|
||||
objectName: "wallpaperPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
WiFiPanel {
|
||||
id: wifiPanel
|
||||
objectName: "wifiPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
}
|
||||
|
||||
// ----------------------------------------------
|
||||
@@ -358,4 +406,183 @@ PanelWindow {
|
||||
*/
|
||||
ScreenCorners {}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Centralized Keyboard Shortcuts
|
||||
// ========================================
|
||||
// These shortcuts delegate to the opened panel's handler functions
|
||||
// Panels can implement: onEscapePressed, onTabPressed, onShiftTabPressed,
|
||||
// onUpPressed, onDownPressed, onReturnPressed
|
||||
|
||||
Shortcut {
|
||||
sequence: "Escape"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onEscapePressed) {
|
||||
PanelService.openedPanel.onEscapePressed();
|
||||
} else if (PanelService.openedPanel) {
|
||||
PanelService.openedPanel.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Tab"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onTabPressed) {
|
||||
PanelService.openedPanel.onTabPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Shift+Tab"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onShiftTabPressed) {
|
||||
PanelService.openedPanel.onShiftTabPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Up"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onUpPressed) {
|
||||
PanelService.openedPanel.onUpPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Down"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onDownPressed) {
|
||||
PanelService.openedPanel.onDownPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Return"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onReturnPressed) {
|
||||
PanelService.openedPanel.onReturnPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Left"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onLeftPressed) {
|
||||
PanelService.openedPanel.onLeftPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Right"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onRightPressed) {
|
||||
PanelService.openedPanel.onRightPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Home"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onHomePressed) {
|
||||
PanelService.openedPanel.onHomePressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "End"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onEndPressed) {
|
||||
PanelService.openedPanel.onEndPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "PgUp"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onPageUpPressed) {
|
||||
PanelService.openedPanel.onPageUpPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "PgDown"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onPageDownPressed) {
|
||||
PanelService.openedPanel.onPageDownPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Backtab"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onBackTabPressed) {
|
||||
PanelService.openedPanel.onBackTabPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+J"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlJPressed) {
|
||||
PanelService.openedPanel.onCtrlJPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+K"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlKPressed) {
|
||||
PanelService.openedPanel.onCtrlKPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+N"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlNPressed) {
|
||||
PanelService.openedPanel.onCtrlNPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+P"
|
||||
enabled: root.isPanelOpen
|
||||
onActivated: {
|
||||
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlPPressed) {
|
||||
PanelService.openedPanel.onCtrlPPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,706 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
/**
|
||||
* PanelPlaceholder - Lightweight positioning logic for panel backgrounds
|
||||
*
|
||||
* This component stays in MainScreen and provides geometry for PanelBackground rendering.
|
||||
* It contains only positioning calculations and animations, no visual content.
|
||||
* The actual panel content lives in a separate SmartPanelWindow.
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Required properties
|
||||
required property ShellScreen screen
|
||||
required property string panelName
|
||||
// Unique identifier
|
||||
|
||||
// Panel size properties
|
||||
property real preferredWidth: 700
|
||||
property real preferredHeight: 900
|
||||
property real preferredWidthRatio
|
||||
property real preferredHeightRatio
|
||||
property var buttonItem: null
|
||||
property bool forceAttachToBar: false
|
||||
|
||||
// Anchoring properties
|
||||
property bool panelAnchorHorizontalCenter: false
|
||||
property bool panelAnchorVerticalCenter: false
|
||||
property bool panelAnchorTop: false
|
||||
property bool panelAnchorBottom: false
|
||||
property bool panelAnchorLeft: false
|
||||
property bool panelAnchorRight: false
|
||||
|
||||
// Button position properties
|
||||
property bool useButtonPosition: false
|
||||
property point buttonPosition: Qt.point(0, 0)
|
||||
property int buttonWidth: 0
|
||||
property int buttonHeight: 0
|
||||
|
||||
// Edge snapping distance
|
||||
property real edgeSnapDistance: 50
|
||||
|
||||
// State tracking (controlled by SmartPanelWindow)
|
||||
property bool isPanelVisible: false
|
||||
property bool isClosing: false
|
||||
property bool opacityFadeComplete: false
|
||||
property bool sizeAnimationComplete: false
|
||||
|
||||
// Derived state: track opening transition
|
||||
readonly property bool isOpening: isPanelVisible && !isClosing && !sizeAnimationComplete
|
||||
|
||||
// Content size (set by SmartPanelWindow when content size changes)
|
||||
property real contentPreferredWidth: 0
|
||||
property real contentPreferredHeight: 0
|
||||
|
||||
// Expose panelBackground as panelItem for AllBackgrounds
|
||||
readonly property var panelItem: panelBackground
|
||||
|
||||
// Bar configuration
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
|
||||
readonly property bool barFloating: Settings.data.bar.floating
|
||||
readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
||||
readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0
|
||||
|
||||
// Helper to detect if any anchor is explicitly set
|
||||
readonly property bool hasExplicitHorizontalAnchor: panelAnchorHorizontalCenter || panelAnchorLeft || panelAnchorRight
|
||||
readonly property bool hasExplicitVerticalAnchor: panelAnchorVerticalCenter || panelAnchorTop || panelAnchorBottom
|
||||
|
||||
// Attachment properties
|
||||
readonly property bool allowAttach: Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar
|
||||
readonly property bool allowAttachToBar: {
|
||||
if (!(Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar) || Settings.data.bar.backgroundOpacity < 1.0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A panel can only be attached to a bar if there is a bar on that screen
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
var result = monitors.length === 0 || monitors.includes(root.screen?.name || "");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Effective anchor properties (depend on allowAttach)
|
||||
readonly property bool effectivePanelAnchorTop: panelAnchorTop || (useButtonPosition && barPosition === "top") || (allowAttach && !hasExplicitVerticalAnchor && barPosition === "top" && !barIsVertical)
|
||||
readonly property bool effectivePanelAnchorBottom: panelAnchorBottom || (useButtonPosition && barPosition === "bottom") || (allowAttach && !hasExplicitVerticalAnchor && barPosition === "bottom" && !barIsVertical)
|
||||
readonly property bool effectivePanelAnchorLeft: panelAnchorLeft || (useButtonPosition && barPosition === "left") || (allowAttach && !hasExplicitHorizontalAnchor && barPosition === "left" && barIsVertical)
|
||||
readonly property bool effectivePanelAnchorRight: panelAnchorRight || (useButtonPosition && barPosition === "right") || (allowAttach && !hasExplicitHorizontalAnchor && barPosition === "right" && barIsVertical)
|
||||
|
||||
// Panel dimensions and visibility
|
||||
visible: isPanelVisible
|
||||
width: parent ? parent.width : 0
|
||||
height: parent ? parent.height : 0
|
||||
|
||||
// Update position when UI scale changes
|
||||
Connections {
|
||||
target: Style
|
||||
|
||||
function onUiScaleRatioChanged() {
|
||||
if (root.isPanelVisible) {
|
||||
root.setPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public function to update content size from SmartPanelWindow
|
||||
function updateContentSize(w, h) {
|
||||
contentPreferredWidth = w;
|
||||
contentPreferredHeight = h;
|
||||
if (isPanelVisible) {
|
||||
setPosition();
|
||||
}
|
||||
}
|
||||
|
||||
// Main positioning calculation function
|
||||
function setPosition() {
|
||||
// Don't calculate position if parent dimensions aren't available yet
|
||||
if (!root.width || !root.height) {
|
||||
Logger.d("PanelPlaceholder", "Skipping setPosition - dimensions not ready:", root.width, "x", root.height, panelName);
|
||||
Qt.callLater(setPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate panel dimensions first (needed for positioning)
|
||||
var w;
|
||||
// Priority 1: Content-driven size (dynamic)
|
||||
if (contentPreferredWidth > 0) {
|
||||
w = contentPreferredWidth;
|
||||
} // Priority 2: Ratio-based size
|
||||
else if (root.preferredWidthRatio !== undefined) {
|
||||
w = Math.round(Math.max(root.width * root.preferredWidthRatio, root.preferredWidth));
|
||||
} // Priority 3: Static preferred width
|
||||
else {
|
||||
w = root.preferredWidth;
|
||||
}
|
||||
var panelWidth = Math.min(w, root.width - Style.marginL * 2);
|
||||
|
||||
var h;
|
||||
// Priority 1: Content-driven size (dynamic)
|
||||
if (contentPreferredHeight > 0) {
|
||||
h = contentPreferredHeight;
|
||||
} // Priority 2: Ratio-based size
|
||||
else if (root.preferredHeightRatio !== undefined) {
|
||||
h = Math.round(Math.max(root.height * root.preferredHeightRatio, root.preferredHeight));
|
||||
} // Priority 3: Static preferred height
|
||||
else {
|
||||
h = root.preferredHeight;
|
||||
}
|
||||
var panelHeight = Math.min(h, root.height - Style.barHeight - Style.marginL * 2);
|
||||
|
||||
// Update panelBackground target size (will be animated)
|
||||
panelBackground.targetWidth = panelWidth;
|
||||
panelBackground.targetHeight = panelHeight;
|
||||
|
||||
// Calculate position
|
||||
var calculatedX;
|
||||
var calculatedY;
|
||||
|
||||
// ===== X POSITIONING =====
|
||||
if (root.useButtonPosition && root.width > 0 && panelWidth > 0) {
|
||||
if (root.barIsVertical) {
|
||||
// For vertical bars
|
||||
if (allowAttach) {
|
||||
// Attached panels: align with bar edge (left or right side)
|
||||
if (root.barPosition === "left") {
|
||||
var leftBarEdge = root.barMarginH + Style.barHeight;
|
||||
calculatedX = leftBarEdge;
|
||||
} else {
|
||||
// right
|
||||
var rightBarEdge = root.width - root.barMarginH - Style.barHeight;
|
||||
calculatedX = rightBarEdge - panelWidth;
|
||||
}
|
||||
} else {
|
||||
// Detached panels: center on button X position
|
||||
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2;
|
||||
var minX = Style.marginL;
|
||||
var maxX = root.width - panelWidth - Style.marginL;
|
||||
|
||||
// Account for vertical bar taking up space
|
||||
if (root.barPosition === "left") {
|
||||
minX = root.barMarginH + Style.barHeight + Style.marginL;
|
||||
} else if (root.barPosition === "right") {
|
||||
maxX = root.width - root.barMarginH - Style.barHeight - panelWidth - Style.marginL;
|
||||
}
|
||||
|
||||
panelX = Math.max(minX, Math.min(panelX, maxX));
|
||||
calculatedX = panelX;
|
||||
}
|
||||
} else {
|
||||
// For horizontal bars, center panel on button X position
|
||||
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2;
|
||||
if (allowAttach) {
|
||||
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0;
|
||||
var barLeftEdge = root.barMarginH + cornerInset;
|
||||
var barRightEdge = root.width - root.barMarginH - cornerInset;
|
||||
panelX = Math.max(barLeftEdge, Math.min(panelX, barRightEdge - panelWidth));
|
||||
} else {
|
||||
panelX = Math.max(Style.marginL, Math.min(panelX, root.width - panelWidth - Style.marginL));
|
||||
}
|
||||
calculatedX = panelX;
|
||||
}
|
||||
} else {
|
||||
// Standard anchor positioning
|
||||
if (root.panelAnchorHorizontalCenter) {
|
||||
if (root.barIsVertical) {
|
||||
if (root.barPosition === "left") {
|
||||
var availableStart = root.barMarginH + Style.barHeight;
|
||||
var availableWidth = root.width - availableStart;
|
||||
calculatedX = availableStart + (availableWidth - panelWidth) / 2;
|
||||
} else if (root.barPosition === "right") {
|
||||
var availableWidth = root.width - root.barMarginH - Style.barHeight;
|
||||
calculatedX = (availableWidth - panelWidth) / 2;
|
||||
} else {
|
||||
calculatedX = (root.width - panelWidth) / 2;
|
||||
}
|
||||
} else {
|
||||
calculatedX = (root.width - panelWidth) / 2;
|
||||
}
|
||||
} else if (root.effectivePanelAnchorRight) {
|
||||
if (allowAttach && root.barIsVertical && root.barPosition === "right") {
|
||||
var rightBarEdge = root.width - root.barMarginH - Style.barHeight;
|
||||
calculatedX = rightBarEdge - panelWidth;
|
||||
} else if (allowAttach) {
|
||||
// Account for corner inset when bar is floating, horizontal, AND panel is on same edge as bar
|
||||
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom);
|
||||
if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) {
|
||||
var rightCornerInset = Style.radiusL * 2;
|
||||
calculatedX = root.width - root.barMarginH - rightCornerInset - panelWidth;
|
||||
} else {
|
||||
calculatedX = root.width - panelWidth;
|
||||
}
|
||||
} else {
|
||||
calculatedX = root.width - panelWidth - Style.marginL;
|
||||
}
|
||||
} else if (root.effectivePanelAnchorLeft) {
|
||||
if (allowAttach && root.barIsVertical && root.barPosition === "left") {
|
||||
var leftBarEdge = root.barMarginH + Style.barHeight;
|
||||
calculatedX = leftBarEdge;
|
||||
} else if (allowAttach) {
|
||||
// Account for corner inset when bar is floating, horizontal, AND panel is on same edge as bar
|
||||
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom);
|
||||
if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) {
|
||||
var leftCornerInset = Style.radiusL * 2;
|
||||
calculatedX = root.barMarginH + leftCornerInset;
|
||||
} else {
|
||||
calculatedX = 0;
|
||||
}
|
||||
} else {
|
||||
calculatedX = Style.marginL;
|
||||
}
|
||||
} else {
|
||||
// No explicit anchor: default to centering on bar
|
||||
if (root.barIsVertical) {
|
||||
if (root.barPosition === "left") {
|
||||
var availableStart = root.barMarginH + Style.barHeight;
|
||||
var availableWidth = root.width - availableStart - Style.marginL;
|
||||
calculatedX = availableStart + (availableWidth - panelWidth) / 2;
|
||||
} else {
|
||||
var availableWidth = root.width - root.barMarginH - Style.barHeight - Style.marginL;
|
||||
calculatedX = Style.marginL + (availableWidth - panelWidth) / 2;
|
||||
}
|
||||
} else {
|
||||
if (allowAttach) {
|
||||
var cornerInset = Style.radiusL + (root.barFloating ? Style.radiusL : 0);
|
||||
var barLeftEdge = root.barMarginH + cornerInset;
|
||||
var barRightEdge = root.width - root.barMarginH - cornerInset;
|
||||
var centeredX = (root.width - panelWidth) / 2;
|
||||
calculatedX = Math.max(barLeftEdge, Math.min(centeredX, barRightEdge - panelWidth));
|
||||
} else {
|
||||
calculatedX = (root.width - panelWidth) / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Edge snapping for X
|
||||
if (allowAttach && !root.barFloating && root.width > 0 && panelWidth > 0) {
|
||||
var leftEdgePos = root.barMarginH;
|
||||
if (root.barPosition === "left") {
|
||||
leftEdgePos = root.barMarginH + Style.barHeight;
|
||||
}
|
||||
|
||||
var rightEdgePos = root.width - root.barMarginH - panelWidth;
|
||||
if (root.barPosition === "right") {
|
||||
rightEdgePos = root.width - root.barMarginH - Style.barHeight - panelWidth;
|
||||
}
|
||||
|
||||
// Only snap to left edge if panel is actually meant to be at left
|
||||
var shouldSnapToLeft = root.effectivePanelAnchorLeft || (!root.hasExplicitHorizontalAnchor && root.barPosition === "left");
|
||||
// Only snap to right edge if panel is actually meant to be at right
|
||||
var shouldSnapToRight = root.effectivePanelAnchorRight || (!root.hasExplicitHorizontalAnchor && root.barPosition === "right");
|
||||
|
||||
if (shouldSnapToLeft && Math.abs(calculatedX - leftEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedX = leftEdgePos;
|
||||
} else if (shouldSnapToRight && Math.abs(calculatedX - rightEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedX = rightEdgePos;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Y POSITIONING =====
|
||||
if (root.useButtonPosition && root.height > 0 && panelHeight > 0) {
|
||||
if (root.barPosition === "top") {
|
||||
var topBarEdge = root.barMarginV + Style.barHeight;
|
||||
if (allowAttach) {
|
||||
calculatedY = topBarEdge;
|
||||
} else {
|
||||
calculatedY = topBarEdge + Style.marginM;
|
||||
}
|
||||
} else if (root.barPosition === "bottom") {
|
||||
var bottomBarEdge = root.height - root.barMarginV - Style.barHeight;
|
||||
if (allowAttach) {
|
||||
calculatedY = bottomBarEdge - panelHeight;
|
||||
} else {
|
||||
calculatedY = bottomBarEdge - panelHeight - Style.marginM;
|
||||
}
|
||||
} else if (root.barIsVertical) {
|
||||
var panelY = root.buttonPosition.y + root.buttonHeight / 2 - panelHeight / 2;
|
||||
var extraPadding = (allowAttach && root.barFloating) ? Style.radiusL : 0;
|
||||
if (allowAttach) {
|
||||
var cornerInset = extraPadding + (root.barFloating ? Style.radiusL : 0);
|
||||
var barTopEdge = root.barMarginV + cornerInset;
|
||||
var barBottomEdge = root.height - root.barMarginV - cornerInset;
|
||||
panelY = Math.max(barTopEdge, Math.min(panelY, barBottomEdge - panelHeight));
|
||||
} else {
|
||||
panelY = Math.max(Style.marginL + extraPadding, Math.min(panelY, root.height - panelHeight - Style.marginL - extraPadding));
|
||||
}
|
||||
calculatedY = panelY;
|
||||
}
|
||||
} else {
|
||||
// Standard anchor positioning
|
||||
var barOffset = 0;
|
||||
if (!allowAttach) {
|
||||
if (root.barPosition === "top") {
|
||||
barOffset = root.barMarginV + Style.barHeight + Style.marginM;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
barOffset = root.barMarginV + Style.barHeight + Style.marginM;
|
||||
}
|
||||
} else {
|
||||
if (root.effectivePanelAnchorTop && root.barPosition === "top") {
|
||||
calculatedY = root.barMarginV + Style.barHeight;
|
||||
} else if (root.effectivePanelAnchorBottom && root.barPosition === "bottom") {
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
||||
} else if (!root.hasExplicitVerticalAnchor) {
|
||||
if (root.barPosition === "top") {
|
||||
calculatedY = root.barMarginV + Style.barHeight;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (calculatedY === undefined) {
|
||||
if (root.panelAnchorVerticalCenter) {
|
||||
if (!root.barIsVertical) {
|
||||
if (root.barPosition === "top") {
|
||||
var availableStart = root.barMarginV + Style.barHeight;
|
||||
var availableHeight = root.height - availableStart;
|
||||
calculatedY = availableStart + (availableHeight - panelHeight) / 2;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
var availableHeight = root.height - root.barMarginV - Style.barHeight;
|
||||
calculatedY = (availableHeight - panelHeight) / 2;
|
||||
} else {
|
||||
calculatedY = (root.height - panelHeight) / 2;
|
||||
}
|
||||
} else {
|
||||
calculatedY = (root.height - panelHeight) / 2;
|
||||
}
|
||||
} else if (root.effectivePanelAnchorTop) {
|
||||
if (allowAttach) {
|
||||
calculatedY = 0;
|
||||
} else {
|
||||
var topBarOffset = (root.barPosition === "top") ? barOffset : 0;
|
||||
calculatedY = topBarOffset + Style.marginL;
|
||||
}
|
||||
} else if (root.effectivePanelAnchorBottom) {
|
||||
if (allowAttach) {
|
||||
calculatedY = root.height - panelHeight;
|
||||
} else {
|
||||
var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0;
|
||||
calculatedY = root.height - panelHeight - bottomBarOffset - Style.marginL;
|
||||
}
|
||||
} else {
|
||||
if (root.barIsVertical) {
|
||||
if (allowAttach) {
|
||||
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0;
|
||||
var barTopEdge = root.barMarginV + cornerInset;
|
||||
var barBottomEdge = root.height - root.barMarginV - cornerInset;
|
||||
var centeredY = (root.height - panelHeight) / 2;
|
||||
calculatedY = Math.max(barTopEdge, Math.min(centeredY, barBottomEdge - panelHeight));
|
||||
} else {
|
||||
calculatedY = (root.height - panelHeight) / 2;
|
||||
}
|
||||
} else {
|
||||
if (allowAttach && !root.barIsVertical) {
|
||||
if (root.barPosition === "top") {
|
||||
calculatedY = root.barMarginV + Style.barHeight;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
||||
}
|
||||
} else {
|
||||
if (root.barPosition === "top") {
|
||||
calculatedY = barOffset + Style.marginL;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
calculatedY = Style.marginL;
|
||||
} else {
|
||||
calculatedY = Style.marginL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Edge snapping for Y
|
||||
if (allowAttach && !root.barFloating && root.height > 0 && panelHeight > 0) {
|
||||
var topEdgePos = root.barMarginV;
|
||||
if (root.barPosition === "top") {
|
||||
topEdgePos = root.barMarginV + Style.barHeight;
|
||||
}
|
||||
|
||||
var bottomEdgePos = root.height - root.barMarginV - panelHeight;
|
||||
if (root.barPosition === "bottom") {
|
||||
bottomEdgePos = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
||||
}
|
||||
|
||||
// Only snap to top edge if panel is actually meant to be at top
|
||||
var shouldSnapToTop = root.effectivePanelAnchorTop || (!root.hasExplicitVerticalAnchor && root.barPosition === "top");
|
||||
// Only snap to bottom edge if panel is actually meant to be at bottom
|
||||
var shouldSnapToBottom = root.effectivePanelAnchorBottom || (!root.hasExplicitVerticalAnchor && root.barPosition === "bottom");
|
||||
|
||||
if (shouldSnapToTop && Math.abs(calculatedY - topEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedY = topEdgePos;
|
||||
} else if (shouldSnapToBottom && Math.abs(calculatedY - bottomEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedY = bottomEdgePos;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply calculated positions (set targets for animation)
|
||||
panelBackground.targetX = calculatedX;
|
||||
panelBackground.targetY = calculatedY;
|
||||
|
||||
Logger.d("PanelPlaceholder", "Position calculated:", calculatedX, calculatedY, panelName);
|
||||
Logger.d("PanelPlaceholder", " Panel size:", panelWidth, "x", panelHeight);
|
||||
}
|
||||
|
||||
// The panel background geometry item
|
||||
Item {
|
||||
id: panelBackground
|
||||
|
||||
// Store target dimensions (set by setPosition())
|
||||
property real targetWidth: root.preferredWidth
|
||||
property real targetHeight: root.preferredHeight
|
||||
property real targetX: 0
|
||||
property real targetY: 0
|
||||
|
||||
property var bezierCurve: [0.05, 0, 0.133, 0.06, 0.166, 0.4, 0.208, 0.82, 0.25, 1, 1, 1]
|
||||
|
||||
// Edge detection
|
||||
readonly property bool touchingLeftEdge: allowAttach && panelBackground.x <= 1
|
||||
readonly property bool touchingRightEdge: allowAttach && (panelBackground.x + panelBackground.width) >= (root.width - 1)
|
||||
readonly property bool touchingTopEdge: allowAttach && panelBackground.y <= 1
|
||||
readonly property bool touchingBottomEdge: allowAttach && (panelBackground.y + panelBackground.height) >= (root.height - 1)
|
||||
|
||||
// Bar edge detection
|
||||
readonly property bool touchingTopBar: allowAttachToBar && root.barPosition === "top" && !root.barIsVertical && Math.abs(panelBackground.y - (root.barMarginV + Style.barHeight)) <= 1
|
||||
readonly property bool touchingBottomBar: allowAttachToBar && root.barPosition === "bottom" && !root.barIsVertical && Math.abs((panelBackground.y + panelBackground.height) - (root.height - root.barMarginV - Style.barHeight)) <= 1
|
||||
readonly property bool touchingLeftBar: allowAttachToBar && root.barPosition === "left" && root.barIsVertical && Math.abs(panelBackground.x - (root.barMarginH + Style.barHeight)) <= 1
|
||||
readonly property bool touchingRightBar: allowAttachToBar && root.barPosition === "right" && root.barIsVertical && Math.abs((panelBackground.x + panelBackground.width) - (root.width - root.barMarginH - Style.barHeight)) <= 1
|
||||
|
||||
// Animation direction determination (using target position to avoid binding loops)
|
||||
readonly property bool willTouchTopBar: {
|
||||
if (!isPanelVisible)
|
||||
return false;
|
||||
if (!allowAttachToBar || root.barPosition !== "top" || root.barIsVertical)
|
||||
return false;
|
||||
var targetTopBarY = root.barMarginV + Style.barHeight;
|
||||
return Math.abs(panelBackground.targetY - targetTopBarY) <= 1;
|
||||
}
|
||||
readonly property bool willTouchBottomBar: {
|
||||
if (!isPanelVisible)
|
||||
return false;
|
||||
if (!allowAttachToBar || root.barPosition !== "bottom" || root.barIsVertical)
|
||||
return false;
|
||||
var targetBottomBarY = root.height - root.barMarginV - Style.barHeight - panelBackground.targetHeight;
|
||||
return Math.abs(panelBackground.targetY - targetBottomBarY) <= 1;
|
||||
}
|
||||
readonly property bool willTouchLeftBar: {
|
||||
if (!isPanelVisible)
|
||||
return false;
|
||||
if (!allowAttachToBar || root.barPosition !== "left" || !root.barIsVertical)
|
||||
return false;
|
||||
var targetLeftBarX = root.barMarginH + Style.barHeight;
|
||||
return Math.abs(panelBackground.targetX - targetLeftBarX) <= 1;
|
||||
}
|
||||
readonly property bool willTouchRightBar: {
|
||||
if (!isPanelVisible)
|
||||
return false;
|
||||
if (!allowAttachToBar || root.barPosition !== "right" || !root.barIsVertical)
|
||||
return false;
|
||||
var targetRightBarX = root.width - root.barMarginH - Style.barHeight - panelBackground.targetWidth;
|
||||
return Math.abs(panelBackground.targetX - targetRightBarX) <= 1;
|
||||
}
|
||||
readonly property bool willTouchTopEdge: isPanelVisible && allowAttach && panelBackground.targetY <= 1
|
||||
readonly property bool willTouchBottomEdge: isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1)
|
||||
readonly property bool willTouchLeftEdge: isPanelVisible && allowAttach && panelBackground.targetX <= 1
|
||||
readonly property bool willTouchRightEdge: isPanelVisible && allowAttach && (panelBackground.targetX + panelBackground.targetWidth) >= (root.width - 1)
|
||||
|
||||
readonly property bool isActuallyAttachedToAnyEdge: {
|
||||
if (!isPanelVisible)
|
||||
return false;
|
||||
return willTouchTopBar || willTouchBottomBar || willTouchLeftBar || willTouchRightBar || willTouchTopEdge || willTouchBottomEdge || willTouchLeftEdge || willTouchRightEdge;
|
||||
}
|
||||
|
||||
readonly property bool animateFromTop: {
|
||||
if (!isPanelVisible)
|
||||
return true;
|
||||
if (willTouchTopBar)
|
||||
return true;
|
||||
if (willTouchTopEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar)
|
||||
return true;
|
||||
if (!isActuallyAttachedToAnyEdge)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
readonly property bool animateFromBottom: {
|
||||
if (!isPanelVisible)
|
||||
return false;
|
||||
if (willTouchBottomBar)
|
||||
return true;
|
||||
if (willTouchBottomEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
readonly property bool animateFromLeft: {
|
||||
if (!isPanelVisible)
|
||||
return false;
|
||||
if (willTouchTopBar || willTouchBottomBar)
|
||||
return false;
|
||||
if (willTouchLeftBar)
|
||||
return true;
|
||||
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1;
|
||||
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1);
|
||||
if (touchingTopEdge || touchingBottomEdge)
|
||||
return false;
|
||||
if (willTouchLeftEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
readonly property bool animateFromRight: {
|
||||
if (!isPanelVisible)
|
||||
return false;
|
||||
if (willTouchTopBar || willTouchBottomBar)
|
||||
return false;
|
||||
if (willTouchRightBar)
|
||||
return true;
|
||||
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1;
|
||||
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1);
|
||||
if (touchingTopEdge || touchingBottomEdge)
|
||||
return false;
|
||||
if (willTouchRightEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property bool shouldAnimateWidth: !shouldAnimateHeight && (animateFromLeft || animateFromRight)
|
||||
readonly property bool shouldAnimateHeight: animateFromTop || animateFromBottom
|
||||
|
||||
// Track whether we're in an initial open/close state transition vs normal content resizing
|
||||
readonly property bool isStateTransition: root.isOpening || root.isClosing
|
||||
|
||||
// Current animated width/height
|
||||
readonly property real currentWidth: {
|
||||
if (isClosing && opacityFadeComplete && shouldAnimateWidth)
|
||||
return 0;
|
||||
if (isClosing || isPanelVisible)
|
||||
return targetWidth;
|
||||
return 0;
|
||||
}
|
||||
readonly property real currentHeight: {
|
||||
if (isClosing && opacityFadeComplete && shouldAnimateHeight)
|
||||
return 0;
|
||||
if (isClosing || isPanelVisible)
|
||||
return targetHeight;
|
||||
return 0;
|
||||
}
|
||||
|
||||
width: currentWidth
|
||||
height: currentHeight
|
||||
|
||||
x: {
|
||||
if (animateFromRight) {
|
||||
if (isPanelVisible || isClosing) {
|
||||
var targetRightEdge = targetX + targetWidth;
|
||||
return targetRightEdge - width;
|
||||
}
|
||||
}
|
||||
return targetX;
|
||||
}
|
||||
y: {
|
||||
if (animateFromBottom) {
|
||||
if (isPanelVisible || isClosing) {
|
||||
var targetBottomEdge = targetY + targetHeight;
|
||||
return targetBottomEdge - height;
|
||||
}
|
||||
}
|
||||
return targetY;
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
// During opening: use 0ms if not animating width, otherwise use normal duration
|
||||
// During closing: use 0ms if not animating width, otherwise use fast duration
|
||||
// During normal content resizing: always use normal duration
|
||||
duration: (root.isOpening && !panelBackground.shouldAnimateWidth) ? 0 : root.isOpening ? Style.animationNormal : (root.isClosing && !panelBackground.shouldAnimateWidth) ? 0 : root.isClosing ? Style.animationFast : Style.animationNormal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: panelBackground.bezierCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
// During opening: use 0ms if not animating height, otherwise use normal duration
|
||||
// During closing: use 0ms if not animating height, otherwise use fast duration
|
||||
// During normal content resizing: always use normal duration
|
||||
duration: (root.isOpening && !panelBackground.shouldAnimateHeight) ? 0 : root.isOpening ? Style.animationNormal : (root.isClosing && !panelBackground.shouldAnimateHeight) ? 0 : root.isClosing ? Style.animationFast : Style.animationNormal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: panelBackground.bezierCurve
|
||||
}
|
||||
}
|
||||
|
||||
// Corner states for PanelBackground to read
|
||||
property int topLeftCornerState: {
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft));
|
||||
var barTouchInverted = touchingTopBar || touchingLeftBar;
|
||||
var edgeInverted = allowAttach && (touchingLeftEdge || touchingTopEdge);
|
||||
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top");
|
||||
|
||||
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
||||
if (touchingLeftEdge && touchingTopEdge)
|
||||
return 0;
|
||||
if (touchingLeftEdge)
|
||||
return 2;
|
||||
if (touchingTopEdge)
|
||||
return 1;
|
||||
return root.barIsVertical ? 2 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
property int topRightCornerState: {
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight));
|
||||
var barTouchInverted = touchingTopBar || touchingRightBar;
|
||||
var edgeInverted = allowAttach && (touchingRightEdge || touchingTopEdge);
|
||||
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top");
|
||||
|
||||
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
||||
if (touchingRightEdge && touchingTopEdge)
|
||||
return 0;
|
||||
if (touchingRightEdge)
|
||||
return 2;
|
||||
if (touchingTopEdge)
|
||||
return 1;
|
||||
return root.barIsVertical ? 2 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
property int bottomLeftCornerState: {
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft));
|
||||
var barTouchInverted = touchingBottomBar || touchingLeftBar;
|
||||
var edgeInverted = allowAttach && (touchingLeftEdge || touchingBottomEdge);
|
||||
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom");
|
||||
|
||||
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
||||
if (touchingLeftEdge && touchingBottomEdge)
|
||||
return 0;
|
||||
if (touchingLeftEdge)
|
||||
return 2;
|
||||
if (touchingBottomEdge)
|
||||
return 1;
|
||||
return root.barIsVertical ? 2 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
property int bottomRightCornerState: {
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight));
|
||||
var barTouchInverted = touchingBottomBar || touchingRightBar;
|
||||
var edgeInverted = allowAttach && (touchingRightEdge || touchingBottomEdge);
|
||||
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom");
|
||||
|
||||
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
||||
if (touchingRightEdge && touchingBottomEdge)
|
||||
return 0;
|
||||
if (touchingRightEdge)
|
||||
return 2;
|
||||
if (touchingBottomEdge)
|
||||
return 1;
|
||||
return root.barIsVertical ? 2 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,454 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
|
||||
/**
|
||||
* SmartPanelWindow - Separate window for panel content
|
||||
*
|
||||
* This component runs in its own window, separate from MainScreen.
|
||||
* It follows the PanelPlaceholder for positioning and contains the actual panel content.
|
||||
*/
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
// Required reference to placeholder
|
||||
required property PanelPlaceholder placeholder
|
||||
|
||||
// Panel content component (set by SmartPanel wrapper)
|
||||
property Component panelContent: null
|
||||
|
||||
// Reference to the SmartPanel wrapper (for keyboard handlers)
|
||||
property var panelWrapper: null
|
||||
|
||||
// Keyboard focus
|
||||
property bool exclusiveKeyboard: true
|
||||
|
||||
// Support close with escape
|
||||
property bool closeWithEscape: true
|
||||
|
||||
// Track whether panel is open
|
||||
property bool isPanelOpen: false
|
||||
|
||||
// Track actual visibility (delayed until content is loaded and sized)
|
||||
property bool isPanelVisible: false
|
||||
|
||||
// Track size animation completion for sequential opacity animation
|
||||
property bool sizeAnimationComplete: false
|
||||
|
||||
// Track close animation state
|
||||
property bool isClosing: false
|
||||
property bool opacityFadeComplete: false
|
||||
property bool closeFinalized: false
|
||||
|
||||
// Safety: Watchdog timers
|
||||
property bool closeWatchdogActive: false
|
||||
property bool openWatchdogActive: false
|
||||
|
||||
// Signals
|
||||
signal panelOpened
|
||||
signal panelClosed
|
||||
|
||||
// Window configuration
|
||||
color: Color.transparent
|
||||
mask: null // No mask - content window is rectangular
|
||||
visible: isPanelOpen
|
||||
screen: placeholder.screen // Explicitly set screen to match placeholder
|
||||
|
||||
// Wayland layer shell configuration - fullscreen window
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
WlrLayershell.namespace: "noctalia-panel-content-" + placeholder.panelName + "-" + (placeholder.screen?.name || "unknown")
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (!root.isPanelOpen) {
|
||||
return WlrKeyboardFocus.None;
|
||||
}
|
||||
if (CompositorService.isHyprland) {
|
||||
// Exclusive focus on hyprland is too restrictive.
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
} else {
|
||||
return root.exclusiveKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand;
|
||||
}
|
||||
}
|
||||
|
||||
// Anchor to all edges to make fullscreen
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
// Margins to exclude bar area so bar remains clickable
|
||||
margins {
|
||||
top: placeholder.barPosition === "top" ? (placeholder.barMarginV + Style.barHeight) : 0
|
||||
bottom: placeholder.barPosition === "bottom" ? (placeholder.barMarginV + Style.barHeight) : 0
|
||||
left: placeholder.barPosition === "left" ? (placeholder.barMarginH + Style.barHeight) : 0
|
||||
right: placeholder.barPosition === "right" ? (placeholder.barMarginH + Style.barHeight) : 0
|
||||
}
|
||||
|
||||
// Sync state to placeholder
|
||||
onIsPanelVisibleChanged: {
|
||||
placeholder.isPanelVisible = isPanelVisible;
|
||||
}
|
||||
onIsClosingChanged: {
|
||||
placeholder.isClosing = isClosing;
|
||||
}
|
||||
onOpacityFadeCompleteChanged: {
|
||||
placeholder.opacityFadeComplete = opacityFadeComplete;
|
||||
}
|
||||
onSizeAnimationCompleteChanged: {
|
||||
placeholder.sizeAnimationComplete = sizeAnimationComplete;
|
||||
}
|
||||
|
||||
// Panel control functions
|
||||
function toggle(buttonItem, buttonName) {
|
||||
if (!isPanelOpen) {
|
||||
open(buttonItem, buttonName);
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
function open(buttonItem, buttonName) {
|
||||
if (!buttonItem && buttonName) {
|
||||
buttonItem = BarService.lookupWidget(buttonName, placeholder.screen.name);
|
||||
}
|
||||
|
||||
if (buttonItem) {
|
||||
placeholder.buttonItem = buttonItem;
|
||||
// Map button position to screen coordinates
|
||||
var buttonPos = buttonItem.mapToItem(null, 0, 0);
|
||||
placeholder.buttonPosition = Qt.point(buttonPos.x, buttonPos.y);
|
||||
placeholder.buttonWidth = buttonItem.width;
|
||||
placeholder.buttonHeight = buttonItem.height;
|
||||
placeholder.useButtonPosition = true;
|
||||
} else {
|
||||
// No button provided: reset button position mode
|
||||
placeholder.buttonItem = null;
|
||||
placeholder.useButtonPosition = false;
|
||||
}
|
||||
|
||||
// Set isPanelOpen to trigger content loading
|
||||
isPanelOpen = true;
|
||||
|
||||
// Notify PanelService
|
||||
PanelService.willOpenPanel(root);
|
||||
}
|
||||
|
||||
function close() {
|
||||
// Start close sequence: fade opacity first
|
||||
isClosing = true;
|
||||
sizeAnimationComplete = false;
|
||||
closeFinalized = false;
|
||||
|
||||
// Stop the open animation timer if it's still running
|
||||
opacityTrigger.stop();
|
||||
openWatchdogActive = false;
|
||||
openWatchdogTimer.stop();
|
||||
|
||||
// Start close watchdog timer
|
||||
closeWatchdogActive = true;
|
||||
closeWatchdogTimer.restart();
|
||||
|
||||
// If opacity is already 0, skip directly to size animation
|
||||
if (contentWrapper.opacity === 0.0) {
|
||||
opacityFadeComplete = true;
|
||||
} else {
|
||||
opacityFadeComplete = false;
|
||||
}
|
||||
|
||||
Logger.d("SmartPanelWindow", "Closing panel", placeholder.panelName);
|
||||
}
|
||||
|
||||
function finalizeClose() {
|
||||
// Prevent double-finalization
|
||||
if (root.closeFinalized) {
|
||||
Logger.w("SmartPanelWindow", "finalizeClose called but already finalized - ignoring", placeholder.panelName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Complete the close sequence after animations finish
|
||||
root.closeFinalized = true;
|
||||
root.closeWatchdogActive = false;
|
||||
closeWatchdogTimer.stop();
|
||||
|
||||
root.isPanelVisible = false;
|
||||
root.isPanelOpen = false;
|
||||
root.isClosing = false;
|
||||
root.opacityFadeComplete = false;
|
||||
PanelService.closedPanel(root);
|
||||
panelClosed();
|
||||
|
||||
Logger.d("SmartPanelWindow", "Panel close finalized", placeholder.panelName);
|
||||
}
|
||||
|
||||
// Fullscreen container for click-to-close and content
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
focus: true // Enable keyboard event handling
|
||||
|
||||
// Handle keyboard events directly via Keys handler
|
||||
Keys.onPressed: event => {
|
||||
Logger.d("SmartPanelWindow", "Key pressed:", event.key, "for panel:", placeholder.panelName);
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
panelWrapper.onEscapePressed();
|
||||
if (closeWithEscape) {
|
||||
root.close();
|
||||
event.accepted = true;
|
||||
}
|
||||
} else if (panelWrapper) {
|
||||
if (event.key === Qt.Key_Up && panelWrapper.onUpPressed) {
|
||||
panelWrapper.onUpPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down && panelWrapper.onDownPressed) {
|
||||
panelWrapper.onDownPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Left && panelWrapper.onLeftPressed) {
|
||||
panelWrapper.onLeftPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Right && panelWrapper.onRightPressed) {
|
||||
panelWrapper.onRightPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Tab && panelWrapper.onTabPressed) {
|
||||
panelWrapper.onTabPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Backtab && panelWrapper.onBackTabPressed) {
|
||||
panelWrapper.onBackTabPressed();
|
||||
event.accepted = true;
|
||||
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && panelWrapper.onReturnPressed) {
|
||||
panelWrapper.onReturnPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Home && panelWrapper.onHomePressed) {
|
||||
panelWrapper.onHomePressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_End && panelWrapper.onEndPressed) {
|
||||
panelWrapper.onEndPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_PageUp && panelWrapper.onPageUpPressed) {
|
||||
panelWrapper.onPageUpPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_PageDown && panelWrapper.onPageDownPressed) {
|
||||
panelWrapper.onPageDownPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_J && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlJPressed) {
|
||||
panelWrapper.onCtrlJPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_K && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlKPressed) {
|
||||
panelWrapper.onCtrlKPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_N && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlNPressed) {
|
||||
panelWrapper.onCtrlNPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_P && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlPPressed) {
|
||||
panelWrapper.onCtrlPPressed();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Background MouseArea for click-to-close (behind content)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.isPanelOpen && !root.isClosing
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
root.close();
|
||||
mouse.accepted = true;
|
||||
}
|
||||
z: 0
|
||||
}
|
||||
|
||||
// Content wrapper with opacity animation
|
||||
Item {
|
||||
id: contentWrapper
|
||||
// Position at placeholder location, compensating for window margins
|
||||
x: placeholder.panelItem.x - (placeholder.barPosition === "left" ? (placeholder.barMarginH + Style.barHeight) : 0)
|
||||
y: placeholder.panelItem.y - (placeholder.barPosition === "top" ? (placeholder.barMarginV + Style.barHeight) : 0)
|
||||
width: placeholder.panelItem.width
|
||||
height: placeholder.panelItem.height
|
||||
z: 1 // Above click-to-close MouseArea
|
||||
|
||||
// Opacity animation
|
||||
opacity: {
|
||||
if (isClosing)
|
||||
return 0.0;
|
||||
if (isPanelVisible && sizeAnimationComplete)
|
||||
return 1.0;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
id: opacityAnimation
|
||||
duration: root.isClosing ? Style.animationFaster : Style.animationFast
|
||||
easing.type: Easing.OutQuad
|
||||
|
||||
onRunningChanged: {
|
||||
// Safety: Zero-duration animation handling
|
||||
if (!running && duration === 0) {
|
||||
if (root.isClosing && contentWrapper.opacity === 0.0) {
|
||||
root.opacityFadeComplete = true;
|
||||
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight;
|
||||
if (shouldFinalizeNow) {
|
||||
Logger.d("SmartPanelWindow", "Zero-duration opacity + no size animation - finalizing", placeholder.panelName);
|
||||
Qt.callLater(root.finalizeClose);
|
||||
}
|
||||
} else if (root.isPanelVisible && contentWrapper.opacity === 1.0) {
|
||||
root.openWatchdogActive = false;
|
||||
openWatchdogTimer.stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// When opacity fade completes during close, trigger size animation
|
||||
if (!running && root.isClosing && contentWrapper.opacity === 0.0) {
|
||||
root.opacityFadeComplete = true;
|
||||
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight;
|
||||
if (shouldFinalizeNow) {
|
||||
Logger.d("SmartPanelWindow", "No animation - finalizing immediately", placeholder.panelName);
|
||||
Qt.callLater(root.finalizeClose);
|
||||
} else {
|
||||
Logger.d("SmartPanelWindow", "Animation will run - waiting for size animation", placeholder.panelName);
|
||||
}
|
||||
} // When opacity fade completes during open, stop watchdog
|
||||
else if (!running && root.isPanelVisible && contentWrapper.opacity === 1.0) {
|
||||
root.openWatchdogActive = false;
|
||||
openWatchdogTimer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Panel content loader
|
||||
Loader {
|
||||
id: contentLoader
|
||||
active: isPanelOpen
|
||||
anchors.fill: parent
|
||||
sourceComponent: root.panelContent
|
||||
|
||||
// When content finishes loading, trigger positioning and visibility
|
||||
onLoaded: {
|
||||
// Capture initial content-driven size if available
|
||||
if (contentLoader.item) {
|
||||
var hasWidthProp = contentLoader.item.hasOwnProperty('contentPreferredWidth');
|
||||
var hasHeightProp = contentLoader.item.hasOwnProperty('contentPreferredHeight');
|
||||
|
||||
if (hasWidthProp || hasHeightProp) {
|
||||
var initialWidth = hasWidthProp ? contentLoader.item.contentPreferredWidth : 0;
|
||||
var initialHeight = hasHeightProp ? contentLoader.item.contentPreferredHeight : 0;
|
||||
placeholder.updateContentSize(initialWidth, initialHeight);
|
||||
Logger.d("SmartPanelWindow", "Initial content size:", initialWidth, "x", initialHeight, placeholder.panelName);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate position in placeholder
|
||||
placeholder.setPosition();
|
||||
|
||||
// Make panel visible on the next frame
|
||||
Qt.callLater(function () {
|
||||
root.isPanelVisible = true;
|
||||
opacityTrigger.start();
|
||||
|
||||
// Start open watchdog timer
|
||||
root.openWatchdogActive = true;
|
||||
openWatchdogTimer.start();
|
||||
|
||||
panelOpened();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// MouseArea to prevent clicks on panel content from closing it
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
mouse.accepted = true; // Eat the click to prevent propagation to background
|
||||
}
|
||||
z: -1 // Behind content but above background click-to-close
|
||||
}
|
||||
|
||||
// Watch for changes in content-driven sizes
|
||||
Connections {
|
||||
target: contentLoader.item
|
||||
ignoreUnknownSignals: true
|
||||
|
||||
function onContentPreferredWidthChanged() {
|
||||
if (root.isPanelOpen && root.isPanelVisible && contentLoader.item) {
|
||||
placeholder.updateContentSize(contentLoader.item.contentPreferredWidth, placeholder.contentPreferredHeight);
|
||||
}
|
||||
}
|
||||
|
||||
function onContentPreferredHeightChanged() {
|
||||
if (root.isPanelOpen && root.isPanelVisible && contentLoader.item) {
|
||||
placeholder.updateContentSize(placeholder.contentPreferredWidth, contentLoader.item.contentPreferredHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Timer to trigger opacity fade at 50% of size animation
|
||||
Timer {
|
||||
id: opacityTrigger
|
||||
interval: Style.animationNormal * 0.5
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.isPanelVisible) {
|
||||
root.sizeAnimationComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Watchdog timer for open sequence
|
||||
Timer {
|
||||
id: openWatchdogTimer
|
||||
interval: Style.animationNormal * 3
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.openWatchdogActive) {
|
||||
Logger.w("SmartPanelWindow", "Open watchdog timeout - forcing panel visible state", placeholder.panelName);
|
||||
root.openWatchdogActive = false;
|
||||
if (root.isPanelOpen && !root.isPanelVisible) {
|
||||
root.isPanelVisible = true;
|
||||
root.sizeAnimationComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Watchdog timer for close sequence
|
||||
Timer {
|
||||
id: closeWatchdogTimer
|
||||
interval: Style.animationFast * 3
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.closeWatchdogActive && !root.closeFinalized) {
|
||||
Logger.w("SmartPanelWindow", "Close watchdog timeout - forcing panel close", placeholder.panelName);
|
||||
Qt.callLater(root.finalizeClose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for placeholder size animation completion to finalize close
|
||||
Connections {
|
||||
target: placeholder.panelItem
|
||||
|
||||
function onWidthChanged() {
|
||||
// When width shrinks to 0 during close and we're animating width, finalize
|
||||
if (root.isClosing && placeholder.panelItem.width === 0 && placeholder.panelItem.shouldAnimateWidth) {
|
||||
Qt.callLater(root.finalizeClose);
|
||||
}
|
||||
}
|
||||
|
||||
function onHeightChanged() {
|
||||
// When height shrinks to 0 during close and we're animating height, finalize
|
||||
if (root.isClosing && placeholder.panelItem.height === 0 && placeholder.panelItem.shouldAnimateHeight) {
|
||||
Qt.callLater(root.finalizeClose);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ Variants {
|
||||
property ListModel notificationModel: NotificationService.activeList
|
||||
|
||||
// Always create window (but with 0x0 dimensions when no notifications)
|
||||
active: true
|
||||
active: (notificationModel.count > 0 || delayTimer.running) && Settings.data.notifications?.location != "bar"
|
||||
|
||||
// Keep loader active briefly after last notification to allow animations to complete
|
||||
Timer {
|
||||
@@ -104,8 +104,8 @@ Variants {
|
||||
margins.left: isLeft ? barOffsetLeft : 0
|
||||
margins.right: isRight ? barOffsetRight : 0
|
||||
|
||||
implicitWidth: (notificationModel.count > 0 || delayTimer.running) ? notifWidth : 0
|
||||
implicitHeight: (notificationModel.count > 0 || delayTimer.running) ? (notificationStack.implicitHeight + Style.marginL) : 0
|
||||
implicitWidth: notifWidth
|
||||
implicitHeight: notificationStack.implicitHeight + Style.marginL
|
||||
|
||||
property var animateConnection: null
|
||||
|
||||
@@ -398,17 +398,16 @@ Variants {
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
|
||||
ColumnLayout {
|
||||
NImageCircled {
|
||||
Layout.preferredWidth: Math.round(40 * Style.uiScaleRatio)
|
||||
Layout.preferredHeight: Math.round(40 * Style.uiScaleRatio)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
imagePath: model.originalImage || ""
|
||||
borderColor: Color.transparent
|
||||
borderWidth: 0
|
||||
fallbackIcon: "bell"
|
||||
fallbackIconSize: 24
|
||||
}
|
||||
NImageRounded {
|
||||
Layout.preferredWidth: Math.round(40 * Style.uiScaleRatio)
|
||||
Layout.preferredHeight: Math.round(40 * Style.uiScaleRatio)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
radius: width * 0.5
|
||||
imagePath: model.originalImage || ""
|
||||
borderColor: Color.transparent
|
||||
borderWidth: 0
|
||||
fallbackIcon: "bell"
|
||||
fallbackIconSize: 24
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
@@ -5,11 +5,22 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Keyboard
|
||||
import qs.Services.Media
|
||||
import qs.Widgets
|
||||
|
||||
// Unified OSD component that displays volume, input volume, and brightness changes
|
||||
Variants {
|
||||
id: osd
|
||||
|
||||
// Do not change the order or it will break settings.
|
||||
enum Type {
|
||||
Volume,
|
||||
InputVolume,
|
||||
Brightness,
|
||||
LockKey
|
||||
}
|
||||
|
||||
model: Quickshell.screens.filter(screen => (Settings.data.osd.monitors.includes(screen.name) || Settings.data.osd.monitors.length === 0) && Settings.data.osd.enabled)
|
||||
|
||||
delegate: Loader {
|
||||
@@ -20,31 +31,39 @@ Variants {
|
||||
active: false
|
||||
|
||||
// OSD State
|
||||
property string currentOSDType: "" // "volume", "inputVolume", "brightness", or ""
|
||||
property int currentOSDType: -1 // OSD.Type enum value, -1 means none
|
||||
property bool startupComplete: false
|
||||
property real currentBrightness: 0
|
||||
|
||||
// Lock Key States
|
||||
property string lastLockKeyChanged: "" // "caps", "num", "scroll", or ""
|
||||
|
||||
// Current values (computed properties)
|
||||
readonly property real currentVolume: AudioService.volume
|
||||
readonly property bool isMuted: AudioService.muted
|
||||
readonly property real currentInputVolume: AudioService.inputVolume
|
||||
readonly property bool isInputMuted: AudioService.inputMuted
|
||||
readonly property real epsilon: 0.005
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
function getIcon() {
|
||||
switch (currentOSDType) {
|
||||
case "volume":
|
||||
case OSD.Type.Volume:
|
||||
if (isMuted)
|
||||
return "volume-mute";
|
||||
if (currentVolume <= Number.EPSILON)
|
||||
return "volume-zero";
|
||||
// Show volume-x icon when volume is effectively 0% (within rounding threshold)
|
||||
if (currentVolume < root.epsilon)
|
||||
return "volume-x";
|
||||
return currentVolume <= 0.5 ? "volume-low" : "volume-high";
|
||||
case "inputVolume":
|
||||
case OSD.Type.InputVolume:
|
||||
return isInputMuted ? "microphone-off" : "microphone";
|
||||
case "brightness":
|
||||
case OSD.Type.Brightness:
|
||||
// Show sun-off icon when brightness is effectively 0% (within rounding threshold)
|
||||
if (currentBrightness < root.epsilon)
|
||||
return "sun-off";
|
||||
return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high";
|
||||
case OSD.Type.LockKey:
|
||||
return "keyboard";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -52,28 +71,35 @@ Variants {
|
||||
|
||||
function getCurrentValue() {
|
||||
switch (currentOSDType) {
|
||||
case "volume":
|
||||
case OSD.Type.Volume:
|
||||
return isMuted ? 0 : currentVolume;
|
||||
case "inputVolume":
|
||||
case OSD.Type.InputVolume:
|
||||
return isInputMuted ? 0 : currentInputVolume;
|
||||
case "brightness":
|
||||
case OSD.Type.Brightness:
|
||||
return currentBrightness;
|
||||
case OSD.Type.LockKey:
|
||||
return 1.0; // Always show 100% when showing lock key status
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function getMaxValue() {
|
||||
if (currentOSDType === "volume" || currentOSDType === "inputVolume") {
|
||||
if (currentOSDType === OSD.Type.Volume || currentOSDType === OSD.Type.InputVolume) {
|
||||
return Settings.data.audio.volumeOverdrive ? 1.5 : 1.0;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
function getDisplayPercentage() {
|
||||
if (currentOSDType === OSD.Type.LockKey) {
|
||||
// For lock keys, return the pre-determined status text
|
||||
return lastLockKeyChanged;
|
||||
}
|
||||
|
||||
const value = getCurrentValue();
|
||||
const max = getMaxValue();
|
||||
if ((currentOSDType === "volume" || currentOSDType === "inputVolume") && Settings.data.audio.volumeOverdrive) {
|
||||
if ((currentOSDType === OSD.Type.Volume || currentOSDType === OSD.Type.InputVolume) && Settings.data.audio.volumeOverdrive) {
|
||||
const pct = Math.round(value * 100);
|
||||
return pct + "%";
|
||||
}
|
||||
@@ -82,28 +108,51 @@ Variants {
|
||||
}
|
||||
|
||||
function getProgressColor() {
|
||||
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted);
|
||||
const isMutedState = (currentOSDType === OSD.Type.Volume && isMuted) || (currentOSDType === OSD.Type.InputVolume && isInputMuted);
|
||||
if (isMutedState) {
|
||||
return Color.mError;
|
||||
}
|
||||
// When volumeOverdrive is enabled, show error color if volume is above 100%
|
||||
if ((currentOSDType === "volume" || currentOSDType === "inputVolume") && Settings.data.audio.volumeOverdrive) {
|
||||
if ((currentOSDType === OSD.Type.Volume || currentOSDType === OSD.Type.InputVolume) && Settings.data.audio.volumeOverdrive) {
|
||||
const value = getCurrentValue();
|
||||
if (value > 1.0) {
|
||||
return Color.mError;
|
||||
}
|
||||
}
|
||||
// For lock keys, use a different color to indicate the lock state
|
||||
if (currentOSDType === OSD.Type.LockKey) {
|
||||
// Check the specific lock key that was changed
|
||||
if (lastLockKeyChanged.startsWith("CAPS")) {
|
||||
return LockKeysService.capsLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
|
||||
} else if (lastLockKeyChanged.startsWith("NUM")) {
|
||||
return LockKeysService.numLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
|
||||
} else if (lastLockKeyChanged.startsWith("SCROLL")) {
|
||||
return LockKeysService.scrollLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
|
||||
}
|
||||
}
|
||||
return Color.mPrimary;
|
||||
}
|
||||
|
||||
function getIconColor() {
|
||||
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted);
|
||||
return isMutedState ? Color.mError : Color.mOnSurface;
|
||||
const isMutedState = (currentOSDType === OSD.Type.Volume && isMuted) || (currentOSDType === OSD.Type.InputVolume && isInputMuted);
|
||||
if (isMutedState)
|
||||
return Color.mError;
|
||||
|
||||
if (currentOSDType === OSD.Type.LockKey) {
|
||||
// Check the specific lock key that was changed
|
||||
if (lastLockKeyChanged.startsWith("CAPS")) {
|
||||
return LockKeysService.capsLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
|
||||
} else if (lastLockKeyChanged.startsWith("NUM")) {
|
||||
return LockKeysService.numLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
|
||||
} else if (lastLockKeyChanged.startsWith("SCROLL")) {
|
||||
return LockKeysService.scrollLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
|
||||
}
|
||||
}
|
||||
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Brightness Handling
|
||||
// ============================================================================
|
||||
function connectBrightnessMonitors() {
|
||||
for (var i = 0; i < BrightnessService.monitors.length; i++) {
|
||||
const monitor = BrightnessService.monitors[i];
|
||||
@@ -114,17 +163,28 @@ Variants {
|
||||
|
||||
function onBrightnessChanged(newBrightness) {
|
||||
currentBrightness = newBrightness;
|
||||
showOSD("brightness");
|
||||
showOSD(OSD.Type.Brightness);
|
||||
}
|
||||
|
||||
// Check if a specific OSD type is enabled
|
||||
function isTypeEnabled(type) {
|
||||
const enabledTypes = Settings.data.osd.enabledTypes || [];
|
||||
// If enabledTypes is empty, all types are enabled (backwards compatibility)
|
||||
if (enabledTypes.length === 0)
|
||||
return true;
|
||||
return enabledTypes.includes(type);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OSD Display Control
|
||||
// ============================================================================
|
||||
function showOSD(type) {
|
||||
// Ignore all OSD requests during startup period
|
||||
if (!startupComplete)
|
||||
return;
|
||||
|
||||
// Check if this OSD type is enabled
|
||||
if (!isTypeEnabled(type))
|
||||
return;
|
||||
|
||||
currentOSDType = type;
|
||||
|
||||
if (!root.active) {
|
||||
@@ -149,30 +209,52 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Signal Connections
|
||||
// ============================================================================
|
||||
|
||||
// AudioService monitoring
|
||||
Connections {
|
||||
target: AudioService
|
||||
|
||||
function onVolumeChanged() {
|
||||
showOSD("volume");
|
||||
showOSD(OSD.Type.Volume);
|
||||
}
|
||||
|
||||
function onMutedChanged() {
|
||||
showOSD("volume");
|
||||
if (AudioService.consumeOutputOSDSuppression())
|
||||
return;
|
||||
showOSD(OSD.Type.Volume);
|
||||
}
|
||||
|
||||
function onInputVolumeChanged() {
|
||||
if (AudioService.hasInput)
|
||||
showOSD("inputVolume");
|
||||
showOSD(OSD.Type.InputVolume);
|
||||
}
|
||||
|
||||
function onInputMutedChanged() {
|
||||
if (AudioService.hasInput)
|
||||
showOSD("inputVolume");
|
||||
if (!AudioService.hasInput)
|
||||
return;
|
||||
if (AudioService.consumeInputOSDSuppression())
|
||||
return;
|
||||
showOSD(OSD.Type.InputVolume);
|
||||
}
|
||||
|
||||
// Refresh OSD when device changes to ensure correct volume is displayed
|
||||
function onSinkChanged() {
|
||||
// If volume OSD is currently showing, refresh it to show new device's volume
|
||||
if (root.currentOSDType === OSD.Type.Volume) {
|
||||
Qt.callLater(() => {
|
||||
showOSD(OSD.Type.Volume);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onSourceChanged() {
|
||||
// If input volume OSD is currently showing, refresh it to show new device's volume
|
||||
if (root.currentOSDType === OSD.Type.InputVolume) {
|
||||
Qt.callLater(() => {
|
||||
showOSD(OSD.Type.InputVolume);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +266,26 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
// LockKeys monitoring with a cleaner approach
|
||||
Connections {
|
||||
target: LockKeysService
|
||||
|
||||
function onCapsLockChanged(active) {
|
||||
root.lastLockKeyChanged = active ? "CAPS ON" : "CAPS OFF";
|
||||
root.showOSD(OSD.Type.LockKey);
|
||||
}
|
||||
|
||||
function onNumLockChanged(active) {
|
||||
root.lastLockKeyChanged = active ? "NUM ON" : "NUM OFF";
|
||||
root.showOSD(OSD.Type.LockKey);
|
||||
}
|
||||
|
||||
function onScrollLockChanged(active) {
|
||||
root.lastLockKeyChanged = active ? "SCROLL ON" : "SCROLL OFF";
|
||||
root.showOSD(OSD.Type.LockKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Startup timer - connect brightness monitors and enable OSD after 2 seconds
|
||||
Timer {
|
||||
id: startupTimer
|
||||
@@ -195,9 +297,7 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Visual Component
|
||||
// ============================================================================
|
||||
sourceComponent: PanelWindow {
|
||||
id: panel
|
||||
screen: modelData
|
||||
@@ -211,10 +311,14 @@ Variants {
|
||||
readonly property bool verticalMode: location === "left" || location === "right"
|
||||
|
||||
// Dimensions
|
||||
readonly property int hWidth: Math.round(320 * Style.uiScaleRatio)
|
||||
readonly property int hHeight: Math.round(72 * Style.uiScaleRatio)
|
||||
readonly property int vWidth: Math.round(80 * Style.uiScaleRatio)
|
||||
readonly property int vHeight: Math.round(280 * Style.uiScaleRatio)
|
||||
readonly property bool isShortMode: root.currentOSDType === OSD.Type.LockKey
|
||||
readonly property int longHWidth: Math.round(320 * Style.uiScaleRatio)
|
||||
readonly property int longHHeight: Math.round(72 * Style.uiScaleRatio)
|
||||
readonly property int shortHWidth: Math.round(180 * Style.uiScaleRatio)
|
||||
readonly property int longVWidth: Math.round(80 * Style.uiScaleRatio)
|
||||
readonly property int longVHeight: Math.round(280 * Style.uiScaleRatio)
|
||||
readonly property int shortVHeight: Math.round(180 * Style.uiScaleRatio)
|
||||
|
||||
readonly property int barThickness: {
|
||||
const base = Math.max(8, Math.round(8 * Style.uiScaleRatio));
|
||||
return base % 2 === 0 ? base : base + 1;
|
||||
@@ -243,8 +347,8 @@ Variants {
|
||||
margins.left: calculateMargin(anchors.left, "left")
|
||||
margins.right: calculateMargin(anchors.right, "right")
|
||||
|
||||
implicitWidth: verticalMode ? vWidth : hWidth
|
||||
implicitHeight: verticalMode ? vHeight : hHeight
|
||||
implicitWidth: verticalMode ? longVWidth : (isShortMode ? shortHWidth : longHWidth)
|
||||
implicitHeight: verticalMode ? (isShortMode ? shortVHeight : longVHeight) : longHHeight
|
||||
color: Color.transparent
|
||||
|
||||
WlrLayershell.namespace: "noctalia-osd-" + (screen?.name || "unknown")
|
||||
@@ -284,7 +388,8 @@ Variants {
|
||||
interval: Style.animationNormal + 50
|
||||
onTriggered: {
|
||||
osdItem.visible = false;
|
||||
root.currentOSDType = "";
|
||||
root.currentOSDType = -1;
|
||||
root.lastLockKeyChanged = ""; // Reset the lock key change indicator
|
||||
root.active = false;
|
||||
}
|
||||
}
|
||||
@@ -325,7 +430,7 @@ Variants {
|
||||
spacing: Style.marginM
|
||||
clip: true
|
||||
|
||||
// TextMetrics to measure the maximum possible percentage width (150%)
|
||||
// TextMetrics to measure the maximum possible percentage width
|
||||
TextMetrics {
|
||||
id: percentageMetrics
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
@@ -334,6 +439,7 @@ Variants {
|
||||
text: "150%" // Maximum possible value with volumeOverdrive
|
||||
}
|
||||
|
||||
// Common Icon for all types
|
||||
NIcon {
|
||||
icon: root.getIcon()
|
||||
color: root.getIconColor()
|
||||
@@ -348,7 +454,22 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
// Lock Key Status Text (replaces progress bar)
|
||||
NText {
|
||||
visible: root.currentOSDType === OSD.Type.LockKey
|
||||
text: root.getDisplayPercentage()
|
||||
color: root.getProgressColor()
|
||||
pointSize: Style.fontSizeM
|
||||
family: Settings.data.ui.fontFixed
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
// Progress Bar for Volume/Brightness
|
||||
Rectangle {
|
||||
visible: root.currentOSDType !== OSD.Type.LockKey
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
height: panel.barThickness
|
||||
@@ -369,7 +490,6 @@ Variants {
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
@@ -379,7 +499,9 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
// Percentage Text for Volume/Brightness
|
||||
NText {
|
||||
visible: root.currentOSDType !== OSD.Type.LockKey
|
||||
text: root.getDisplayPercentage()
|
||||
color: Color.mOnSurface
|
||||
pointSize: Style.fontSizeS
|
||||
@@ -401,21 +523,27 @@ Variants {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Style.marginL
|
||||
anchors.bottomMargin: Style.marginL
|
||||
spacing: Style.marginS
|
||||
spacing: root.currentOSDType === OSD.Type.LockKey ? Style.marginM : Style.marginS
|
||||
clip: true
|
||||
|
||||
// Unified Text display for Percentage or Lock Status
|
||||
NText {
|
||||
text: root.getDisplayPercentage()
|
||||
color: Color.mOnSurface
|
||||
pointSize: Style.fontSizeS
|
||||
color: root.currentOSDType === OSD.Type.LockKey ? root.getProgressColor() : Color.mOnSurface
|
||||
pointSize: root.currentOSDType === OSD.Type.LockKey ? Style.fontSizeM : Style.fontSizeS
|
||||
family: Settings.data.ui.fontFixed
|
||||
font.weight: root.currentOSDType === OSD.Type.LockKey ? Style.fontWeightMedium : Style.fontWeightRegular
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(20 * Style.uiScaleRatio)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
// Only set preferredHeight for the standard case to maintain layout
|
||||
Layout.preferredHeight: root.currentOSDType === OSD.Type.LockKey ? -1 : Math.round(20 * Style.uiScaleRatio)
|
||||
}
|
||||
|
||||
// Progress Bar for Volume/Brightness
|
||||
Item {
|
||||
visible: root.currentOSDType !== OSD.Type.LockKey
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
@@ -441,7 +569,6 @@ Variants {
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
@@ -452,11 +579,12 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
// Unified Icon display
|
||||
NIcon {
|
||||
icon: root.getIcon()
|
||||
color: root.getIconColor()
|
||||
pointSize: Style.fontSizeL
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
pointSize: root.currentOSDType === OSD.Type.LockKey ? Style.fontSizeXL : Style.fontSizeL
|
||||
Layout.alignment: root.currentOSDType === OSD.Type.LockKey ? Qt.AlignHCenter : (Qt.AlignHCenter | Qt.AlignBottom)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
@@ -495,7 +623,7 @@ Variants {
|
||||
osdItem.opacity = 0;
|
||||
osdItem.scale = 0.85;
|
||||
osdItem.visible = false;
|
||||
root.currentOSDType = "";
|
||||
root.currentOSDType = -1;
|
||||
root.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,43 +13,105 @@ SmartPanel {
|
||||
|
||||
property real localOutputVolume: AudioService.volume || 0
|
||||
property bool localOutputVolumeChanging: false
|
||||
property int lastSinkId: -1
|
||||
|
||||
property real localInputVolume: AudioService.inputVolume || 0
|
||||
property bool localInputVolumeChanging: false
|
||||
property int lastSourceId: -1
|
||||
|
||||
preferredWidth: Math.round(340 * Style.uiScaleRatio)
|
||||
preferredHeight: Math.round(420 * Style.uiScaleRatio)
|
||||
|
||||
// Reset local volume when device changes - use current device's volume
|
||||
Connections {
|
||||
target: AudioService
|
||||
function onSinkChanged() {
|
||||
if (AudioService.sink) {
|
||||
const newSinkId = AudioService.sink.id;
|
||||
if (newSinkId !== lastSinkId) {
|
||||
lastSinkId = newSinkId;
|
||||
// Immediately set local volume to current device's volume
|
||||
localOutputVolume = AudioService.volume;
|
||||
}
|
||||
} else {
|
||||
lastSinkId = -1;
|
||||
localOutputVolume = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AudioService
|
||||
function onSourceChanged() {
|
||||
if (AudioService.source) {
|
||||
const newSourceId = AudioService.source.id;
|
||||
if (newSourceId !== lastSourceId) {
|
||||
lastSourceId = newSourceId;
|
||||
// Immediately set local volume to current device's volume
|
||||
localInputVolume = AudioService.inputVolume;
|
||||
}
|
||||
} else {
|
||||
lastSourceId = -1;
|
||||
localInputVolume = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connections to update local volumes when AudioService changes
|
||||
Connections {
|
||||
target: AudioService
|
||||
function onVolumeChanged() {
|
||||
if (!localOutputVolumeChanging && AudioService.sink && AudioService.sink.id === lastSinkId) {
|
||||
localOutputVolume = AudioService.volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localOutputVolumeChanging) {
|
||||
if (!localOutputVolumeChanging && AudioService.sink && AudioService.sink.id === lastSinkId) {
|
||||
localOutputVolume = AudioService.volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AudioService
|
||||
function onInputVolumeChanged() {
|
||||
if (!localInputVolumeChanging && AudioService.source && AudioService.source.id === lastSourceId) {
|
||||
localInputVolume = AudioService.inputVolume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AudioService.source?.audio ? AudioService.source?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localInputVolumeChanging) {
|
||||
if (!localInputVolumeChanging && AudioService.source && AudioService.source.id === lastSourceId) {
|
||||
localInputVolume = AudioService.inputVolume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Timer to debounce volume changes
|
||||
// Only sync if the device hasn't changed (check by comparing IDs)
|
||||
Timer {
|
||||
interval: 100
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localOutputVolume);
|
||||
// Only sync if sink hasn't changed
|
||||
if (AudioService.sink && AudioService.sink.id === lastSinkId) {
|
||||
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localOutputVolume);
|
||||
}
|
||||
}
|
||||
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
|
||||
AudioService.setInputVolume(localInputVolume);
|
||||
// Only sync if source hasn't changed
|
||||
if (AudioService.source && AudioService.source.id === lastSourceId) {
|
||||
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
|
||||
AudioService.setInputVolume(localInputVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +157,7 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.output-muted")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
AudioService.suppressOutputOSD();
|
||||
AudioService.setOutputMuted(!AudioService.muted);
|
||||
}
|
||||
}
|
||||
@@ -104,6 +167,7 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.input-muted")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
AudioService.suppressInputOSD();
|
||||
AudioService.setInputMuted(!AudioService.inputMuted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import Quickshell.Services.UPower
|
||||
import qs.Commons
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Power
|
||||
import qs.Services.Networking
|
||||
import qs.Widgets
|
||||
|
||||
SmartPanel {
|
||||
@@ -15,17 +15,133 @@ SmartPanel {
|
||||
preferredWidth: Math.round(360 * Style.uiScaleRatio)
|
||||
preferredHeight: Math.round(460 * Style.uiScaleRatio)
|
||||
|
||||
readonly property var battery: UPower.displayDevice
|
||||
readonly property bool isReady: battery && battery.ready && battery.isLaptopBattery && battery.isPresent
|
||||
readonly property int percent: isReady ? Math.round(battery.percentage * 100) : -1
|
||||
// Get device selection from Battery widget settings (check right section first, then any Battery widget)
|
||||
function getBatteryDevicePath() {
|
||||
// Check right section first (most common location for Battery widget)
|
||||
var rightWidgets = Settings.data.bar.widgets.right || [];
|
||||
for (var i = 0; i < rightWidgets.length; i++) {
|
||||
if (rightWidgets[i].id === "Battery" && rightWidgets[i].deviceNativePath) {
|
||||
return rightWidgets[i].deviceNativePath;
|
||||
}
|
||||
}
|
||||
// Check other sections
|
||||
var sections = ["left", "center"];
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
var widgets = Settings.data.bar.widgets[sections[s]] || [];
|
||||
for (var j = 0; j < widgets.length; j++) {
|
||||
if (widgets[j].id === "Battery" && widgets[j].deviceNativePath) {
|
||||
return widgets[j].deviceNativePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Helper function to find battery device by nativePath
|
||||
function findBatteryDevice(nativePath) {
|
||||
if (!nativePath || nativePath === "") {
|
||||
return UPower.displayDevice;
|
||||
}
|
||||
|
||||
if (!UPower.devices) {
|
||||
return UPower.displayDevice;
|
||||
}
|
||||
|
||||
var deviceArray = UPower.devices.values || [];
|
||||
for (var i = 0; i < deviceArray.length; i++) {
|
||||
var device = deviceArray[i];
|
||||
if (device && device.nativePath === nativePath) {
|
||||
if (device.type === UPowerDeviceType.LinePower) {
|
||||
continue;
|
||||
}
|
||||
if (device.percentage !== undefined) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
return UPower.displayDevice;
|
||||
}
|
||||
|
||||
// Helper function to find Bluetooth device by MAC address from nativePath
|
||||
function findBluetoothDevice(nativePath) {
|
||||
if (!nativePath || !BluetoothService.devices) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var macMatch = nativePath.match(/([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2})/);
|
||||
if (!macMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var macAddress = macMatch[1].toUpperCase();
|
||||
var deviceArray = BluetoothService.devices.values || [];
|
||||
|
||||
for (var i = 0; i < deviceArray.length; i++) {
|
||||
var device = deviceArray[i];
|
||||
if (device && device.address && device.address.toUpperCase() === macAddress) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
readonly property string deviceNativePath: getBatteryDevicePath()
|
||||
readonly property var battery: findBatteryDevice(deviceNativePath)
|
||||
readonly property var bluetoothDevice: deviceNativePath ? findBluetoothDevice(deviceNativePath) : null
|
||||
readonly property bool hasBluetoothBattery: bluetoothDevice && bluetoothDevice.batteryAvailable && bluetoothDevice.battery !== undefined
|
||||
readonly property bool isBluetoothConnected: bluetoothDevice && bluetoothDevice.connected !== undefined ? bluetoothDevice.connected : false
|
||||
|
||||
// Check if device is actually present/connected
|
||||
readonly property bool isDevicePresent: {
|
||||
if (deviceNativePath && deviceNativePath !== "") {
|
||||
if (bluetoothDevice) {
|
||||
return isBluetoothConnected;
|
||||
}
|
||||
if (battery && battery.nativePath === deviceNativePath) {
|
||||
if (battery.type === UPowerDeviceType.Battery && battery.isPresent !== undefined) {
|
||||
return battery.isPresent;
|
||||
}
|
||||
return battery.ready && battery.percentage !== undefined && (battery.percentage > 0 || battery.state === UPowerDeviceState.Charging);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (battery) {
|
||||
if (battery.type === UPowerDeviceType.Battery && battery.isPresent !== undefined) {
|
||||
return battery.isPresent;
|
||||
}
|
||||
return battery.ready && battery.percentage !== undefined;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property bool isReady: battery && battery.ready && isDevicePresent && (battery.percentage !== undefined || hasBluetoothBattery)
|
||||
readonly property int percent: isReady ? Math.round(hasBluetoothBattery ? (bluetoothDevice.battery * 100) : (battery.percentage * 100)) : -1
|
||||
readonly property bool charging: isReady ? battery.state === UPowerDeviceState.Charging : false
|
||||
readonly property bool healthSupported: isReady && battery.healthSupported
|
||||
readonly property bool healthAvailable: healthSupported
|
||||
readonly property bool healthAvailable: isReady && battery.healthSupported
|
||||
readonly property int healthPercent: healthAvailable ? Math.round(battery.healthPercentage) : -1
|
||||
readonly property bool powerProfileAvailable: PowerProfileService.available
|
||||
readonly property var powerProfiles: [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
||||
|
||||
function getDeviceName() {
|
||||
if (!isReady) {
|
||||
return "";
|
||||
}
|
||||
// Don't show name for laptop batteries
|
||||
if (battery && battery.isLaptopBattery) {
|
||||
return "";
|
||||
}
|
||||
if (bluetoothDevice && bluetoothDevice.name) {
|
||||
return bluetoothDevice.name;
|
||||
}
|
||||
if (battery && battery.model) {
|
||||
return battery.model;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
readonly property string deviceName: getDeviceName()
|
||||
readonly property string panelTitle: deviceName ? `${I18n.tr("battery.panel-title")} - ${deviceName}` : I18n.tr("battery.panel-title")
|
||||
|
||||
readonly property string timeText: {
|
||||
if (!isReady)
|
||||
if (!isReady || !isDevicePresent)
|
||||
return I18n.tr("battery.no-battery-detected");
|
||||
if (charging && battery.timeToFull > 0) {
|
||||
return I18n.tr("battery.time-until-full", {
|
||||
@@ -40,9 +156,6 @@ SmartPanel {
|
||||
return I18n.tr("battery.idle");
|
||||
}
|
||||
readonly property string iconName: BatteryService.getIcon(percent, charging, isReady)
|
||||
readonly property bool profilesAvailable: PowerProfileService.available
|
||||
property int profileIndex: profileToIndex(PowerProfileService.profile)
|
||||
property bool manualInhibitActive: manualInhibitorEnabled()
|
||||
|
||||
panelContent: Item {
|
||||
property real contentPreferredHeight: mainLayout.implicitHeight + Style.marginL * 2
|
||||
@@ -75,11 +188,12 @@ SmartPanel {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: I18n.tr("battery.panel-title")
|
||||
text: root.panelTitle
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
NText {
|
||||
@@ -104,6 +218,7 @@ SmartPanel {
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
height: chargeLayout.implicitHeight + Style.marginL * 2
|
||||
visible: isReady
|
||||
|
||||
ColumnLayout {
|
||||
id: chargeLayout
|
||||
@@ -117,7 +232,7 @@ SmartPanel {
|
||||
|
||||
ColumnLayout {
|
||||
NText {
|
||||
text: I18n.tr("battery.charge-level")
|
||||
text: I18n.tr("battery.battery-level")
|
||||
color: Color.mOnSurface
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
@@ -166,131 +281,6 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Power profile and idle inhibit controls
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
height: controlsLayout.implicitHeight + Style.marginM * 2
|
||||
|
||||
ColumnLayout {
|
||||
id: controlsLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
ColumnLayout {
|
||||
id: ppd
|
||||
visible: root.powerProfileAvailable
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
NIcon {
|
||||
icon: PowerProfileService.getIcon()
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mPrimary
|
||||
}
|
||||
NText {
|
||||
text: I18n.tr("battery.power-profile")
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NText {
|
||||
text: PowerProfileService.getName(profileIndex)
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 2
|
||||
stepSize: 1
|
||||
snapAlways: true
|
||||
value: profileIndex
|
||||
enabled: profilesAvailable
|
||||
onPressedChanged: (pressed, v) => {
|
||||
if (!pressed) {
|
||||
setProfileByIndex(v);
|
||||
}
|
||||
}
|
||||
onMoved: v => {
|
||||
profileIndex = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NIcon {
|
||||
icon: manualInhibitActive ? "keep-awake-on" : "keep-awake-off"
|
||||
pointSize: Style.fontSizeL
|
||||
color: manualInhibitActive ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
checked: manualInhibitActive
|
||||
label: I18n.tr("battery.inhibit-idle-label")
|
||||
description: I18n.tr("battery.inhibit-idle-description")
|
||||
onToggled: function (checked) {
|
||||
if (checked) {
|
||||
IdleInhibitorService.addManualInhibitor(null);
|
||||
} else {
|
||||
IdleInhibitorService.removeManualInhibitor();
|
||||
}
|
||||
manualInhibitActive = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function profileToIndex(p) {
|
||||
return powerProfiles.indexOf(p) ?? 1;
|
||||
}
|
||||
|
||||
function indexToProfile(idx) {
|
||||
return powerProfiles[idx] ?? PowerProfile.Balanced;
|
||||
}
|
||||
|
||||
function setProfileByIndex(idx) {
|
||||
var prof = indexToProfile(idx);
|
||||
profileIndex = idx;
|
||||
PowerProfileService.setProfile(prof);
|
||||
}
|
||||
|
||||
function manualInhibitorEnabled() {
|
||||
return IdleInhibitorService.activeInhibitors && IdleInhibitorService.activeInhibitors.indexOf("manual") >= 0;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: IdleInhibitorService
|
||||
|
||||
function onIsInhibitedChanged() {
|
||||
manualInhibitActive = manualInhibitorEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: inhibitorPoll
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: true
|
||||
onTriggered: manualInhibitActive = manualInhibitorEnabled()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PowerProfileService
|
||||
|
||||
function onProfileChanged() {
|
||||
profileIndex = profileToIndex(PowerProfileService.profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ NBox {
|
||||
text: root.label
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mSecondary
|
||||
font.weight: Style.fontWeightMedium
|
||||
font.weight: Style.fontWeightBold
|
||||
visible: root.model.length > 0
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginM
|
||||
@@ -86,7 +86,7 @@ NBox {
|
||||
NText {
|
||||
text: modelData.name || modelData.deviceName
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightMedium
|
||||
font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium
|
||||
elide: Text.ElideRight
|
||||
color: getContentColor(Color.mOnSurface)
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -21,8 +21,8 @@ SmartPanel {
|
||||
// Calculate content height based on header + devices list (or minimum for empty states)
|
||||
property real headerHeight: headerRow.implicitHeight + Style.marginM * 2
|
||||
property real devicesHeight: devicesList.implicitHeight
|
||||
property real calculatedHeight: headerHeight + devicesHeight + Style.marginL * 2 + Style.marginM
|
||||
property real contentPreferredHeight: (BluetoothService.adapter && BluetoothService.adapter.enabled) ? Math.min(root.preferredHeight, Math.max(280 * Style.uiScaleRatio, calculatedHeight)) : Math.min(root.preferredHeight, 280 * Style.uiScaleRatio)
|
||||
property real calculatedHeight: (devicesHeight !== 0) ? (headerHeight + devicesHeight + Style.marginL * 2 + Style.marginM) : (280 * Style.uiScaleRatio)
|
||||
property real contentPreferredHeight: (BluetoothService.adapter && BluetoothService.adapter.enabled) ? Math.min(root.preferredHeight, calculatedHeight) : Math.min(root.preferredHeight, 280 * Style.uiScaleRatio)
|
||||
|
||||
ColumnLayout {
|
||||
id: mainColumn
|
||||
|
||||
@@ -1,664 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Modules.Panels.ControlCenter.Cards
|
||||
import qs.Services.Location
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
SmartPanel {
|
||||
id: root
|
||||
|
||||
readonly property var now: Time.now
|
||||
|
||||
preferredWidth: Math.round(440 * Style.uiScaleRatio)
|
||||
preferredHeight: Math.round(700 * Style.uiScaleRatio)
|
||||
|
||||
// Helper function to calculate ISO week number
|
||||
function getISOWeekNumber(date) {
|
||||
const target = new Date(date.valueOf());
|
||||
const dayNr = (date.getDay() + 6) % 7;
|
||||
target.setDate(target.getDate() - dayNr + 3);
|
||||
const firstThursday = new Date(target.getFullYear(), 0, 4);
|
||||
const diff = target - firstThursday;
|
||||
const oneWeek = 1000 * 60 * 60 * 24 * 7;
|
||||
const weekNumber = 1 + Math.round(diff / oneWeek);
|
||||
return weekNumber;
|
||||
}
|
||||
|
||||
// Helper function to check if an event is all-day
|
||||
function isAllDayEvent(event) {
|
||||
const duration = event.end - event.start;
|
||||
const startDate = new Date(event.start * 1000);
|
||||
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0;
|
||||
return duration === 86400 && isAtMidnight;
|
||||
}
|
||||
|
||||
panelContent: Item {
|
||||
anchors.fill: parent
|
||||
|
||||
// Dynamic sizing properties that SmartPanel will bind to
|
||||
property real contentPreferredWidth: (Settings.data.location.showWeekNumberInCalendar ? 400 : 380) * Style.uiScaleRatio
|
||||
|
||||
// Use implicitHeight from content + margins to avoid binding loops
|
||||
property real contentPreferredHeight: content.implicitHeight + Style.marginL * 2
|
||||
|
||||
property real calendarGridHeight: {
|
||||
// Calculate number of weeks in the calendar grid
|
||||
const numWeeks = grid.daysModel ? Math.ceil(grid.daysModel.length / 7) : 5;
|
||||
|
||||
// Calendar grid height (dynamic based on number of weeks)
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
|
||||
|
||||
return numWeeks * rowHeight;
|
||||
}
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
width: parent.contentPreferredWidth - Style.marginL * 2
|
||||
spacing: Style.marginM
|
||||
|
||||
readonly property int firstDayOfWeek: Settings.data.location.firstDayOfWeek === -1 ? I18n.locale.firstDayOfWeek : Settings.data.location.firstDayOfWeek
|
||||
property bool isCurrentMonth: true
|
||||
readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null)
|
||||
|
||||
function checkIsCurrentMonth() {
|
||||
return (now.getMonth() === grid.month) && (now.getFullYear() === grid.year);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
isCurrentMonth = checkIsCurrentMonth();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Time
|
||||
function onNowChanged() {
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: I18n
|
||||
function onLanguageChanged() {
|
||||
// Force update of day names when language changes
|
||||
grid.month = grid.month;
|
||||
}
|
||||
}
|
||||
|
||||
// Banner with date/time/clock
|
||||
Rectangle {
|
||||
id: banner
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: capsuleColumn.implicitHeight + Style.marginM * 2
|
||||
radius: Style.radiusL
|
||||
color: Color.mPrimary
|
||||
|
||||
ColumnLayout {
|
||||
id: capsuleColumn
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
anchors.topMargin: Style.marginM
|
||||
anchors.bottomMargin: Style.marginM
|
||||
anchors.rightMargin: clockLoader.width + (Style.marginXL * 2)
|
||||
anchors.leftMargin: Style.marginXL
|
||||
|
||||
spacing: 0
|
||||
|
||||
// Combined layout for date, month year, locatio and time-zone
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
height: 60 * Style.uiScaleRatio
|
||||
clip: true
|
||||
spacing: Style.marginS
|
||||
|
||||
// Today day number - with simple, stable animation
|
||||
NText {
|
||||
opacity: content.isCurrentMonth ? 1.0 : 0.0
|
||||
Layout.preferredWidth: content.isCurrentMonth ? implicitWidth : 0
|
||||
elide: Text.ElideNone
|
||||
clip: true
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
text: now.getDate()
|
||||
pointSize: Style.fontSizeXXXL * 1.5
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnPrimary
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
Behavior on Layout.preferredWidth {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Month, year, location
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.bottomMargin: Style.marginXXS
|
||||
Layout.topMargin: -Style.marginXXS
|
||||
spacing: -Style.marginXS
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.locale.monthName(grid.month, Locale.LongFormat).toUpperCase()
|
||||
pointSize: Style.fontSizeXL * 1.1
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnPrimary
|
||||
Layout.alignment: Qt.AlignBaseline
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${grid.year}`
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Qt.alpha(Color.mOnPrimary, 0.7)
|
||||
Layout.alignment: Qt.AlignBaseline
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
|
||||
NText {
|
||||
text: {
|
||||
if (!Settings.data.location.weatherEnabled)
|
||||
return "";
|
||||
if (!content.weatherReady)
|
||||
return I18n.tr("calendar.weather.loading");
|
||||
const chunks = Settings.data.location.name.split(",");
|
||||
return chunks[0];
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: Color.mOnPrimary
|
||||
Layout.maximumWidth: 150
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
NText {
|
||||
text: content.weatherReady ? ` (${LocationService.data.weather.timezone_abbreviation})` : ""
|
||||
pointSize: Style.fontSizeXS
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: Qt.alpha(Color.mOnPrimary, 0.7)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer to push content left
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analog clock
|
||||
NClock {
|
||||
id: clockLoader
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.marginXL
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clockStyle: Settings.data.location.analogClockInCalendar ? "analog" : "digital"
|
||||
progressColor: Color.mOnPrimary
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
now: root.now
|
||||
}
|
||||
}
|
||||
|
||||
// Calendar itself
|
||||
NBox {
|
||||
id: calendar
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: {
|
||||
const navigationHeight = Style.baseWidgetSize; // Navigation buttons row
|
||||
const dayNamesHeight = Style.baseWidgetSize * 0.6; // Day names header row
|
||||
const innerMargins = Style.marginM * 2; // Top and bottom margins inside NBox
|
||||
const innerSpacing = Style.marginS * 2; // Spacing between nav, dayNames, and grid (2 gaps)
|
||||
return navigationHeight + dayNamesHeight + calendarGridHeight + innerMargins + innerSpacing;
|
||||
}
|
||||
|
||||
Behavior on Layout.preferredWidth {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginS
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "chevron-left"
|
||||
onClicked: {
|
||||
let newDate = new Date(grid.year, grid.month - 1, 1);
|
||||
grid.year = newDate.getFullYear();
|
||||
grid.month = newDate.getMonth();
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth();
|
||||
const now = new Date();
|
||||
const monthStart = new Date(grid.year, grid.month, 1);
|
||||
const monthEnd = new Date(grid.year, grid.month + 1, 0);
|
||||
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
|
||||
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "calendar"
|
||||
onClicked: {
|
||||
grid.month = now.getMonth();
|
||||
grid.year = now.getFullYear();
|
||||
content.isCurrentMonth = true;
|
||||
CalendarService.loadEvents();
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "chevron-right"
|
||||
onClicked: {
|
||||
let newDate = new Date(grid.year, grid.month + 1, 1);
|
||||
grid.year = newDate.getFullYear();
|
||||
grid.month = newDate.getMonth();
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth();
|
||||
const now = new Date();
|
||||
const monthStart = new Date(grid.year, grid.month, 1);
|
||||
const monthEnd = new Date(grid.year, grid.month + 1, 0);
|
||||
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
|
||||
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
visible: Settings.data.location.showWeekNumberInCalendar
|
||||
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
columns: 7
|
||||
rows: 1
|
||||
columnSpacing: 0
|
||||
rowSpacing: 0
|
||||
Repeater {
|
||||
model: 7
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.fontSizeS * 2
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
let dayIndex = (content.firstDayOfWeek + index) % 7;
|
||||
const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat);
|
||||
return dayName.substring(0, 2).toUpperCase();
|
||||
}
|
||||
color: Color.mPrimary
|
||||
pointSize: Style.fontSizeS
|
||||
font.weight: Style.fontWeightBold
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
// Helper function to check if a date has events
|
||||
function hasEventsOnDate(year, month, day) {
|
||||
if (!CalendarService.available || CalendarService.events.length === 0)
|
||||
return false;
|
||||
|
||||
const targetDate = new Date(year, month, day);
|
||||
const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000;
|
||||
const targetEnd = targetStart + 86400; // +24 hours
|
||||
|
||||
return CalendarService.events.some(event => {
|
||||
// Check if event starts or overlaps with this day
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to get events for a specific date
|
||||
function getEventsForDate(year, month, day) {
|
||||
if (!CalendarService.available || CalendarService.events.length === 0)
|
||||
return [];
|
||||
|
||||
const targetDate = new Date(year, month, day);
|
||||
const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000);
|
||||
const targetEnd = targetStart + 86400; // +24 hours
|
||||
|
||||
return CalendarService.events.filter(event => {
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to check if an event is multi-day
|
||||
function isMultiDayEvent(event) {
|
||||
if (isAllDayEvent(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const startDate = new Date(event.start * 1000);
|
||||
const endDate = new Date(event.end * 1000);
|
||||
|
||||
const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
|
||||
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
return startDateOnly.getTime() !== endDateOnly.getTime();
|
||||
}
|
||||
|
||||
// Helper function to get color for a specific event
|
||||
function getEventColor(event, isToday) {
|
||||
if (isMultiDayEvent(event)) {
|
||||
return isToday ? Color.mOnSecondary : Color.mTertiary;
|
||||
} else if (isAllDayEvent(event)) {
|
||||
return isToday ? Color.mOnSecondary : Color.mSecondary;
|
||||
} else {
|
||||
return isToday ? Color.mOnSecondary : Color.mPrimary;
|
||||
}
|
||||
}
|
||||
|
||||
// Column of week numbers
|
||||
ColumnLayout {
|
||||
visible: Settings.data.location.showWeekNumberInCalendar
|
||||
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
|
||||
Layout.preferredHeight: {
|
||||
const numWeeks = weekNumbers ? weekNumbers.length : 5;
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
|
||||
return numWeeks * rowHeight;
|
||||
}
|
||||
spacing: Style.marginXXS
|
||||
|
||||
Behavior on Layout.preferredHeight {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
property var weekNumbers: {
|
||||
if (!grid.daysModel || grid.daysModel.length === 0)
|
||||
return [];
|
||||
|
||||
const weeks = [];
|
||||
const numWeeks = Math.ceil(grid.daysModel.length / 7);
|
||||
|
||||
for (var i = 0; i < numWeeks; i++) {
|
||||
const dayIndex = i * 7;
|
||||
if (dayIndex < grid.daysModel.length) {
|
||||
const weekDay = grid.daysModel[dayIndex];
|
||||
const date = new Date(weekDay.year, weekDay.month, weekDay.day);
|
||||
|
||||
// Get Thursday of this week for ISO week calculation
|
||||
const firstDayOfWeek = content.firstDayOfWeek;
|
||||
let thursday = new Date(date);
|
||||
if (firstDayOfWeek === 0) {
|
||||
thursday.setDate(date.getDate() + 4);
|
||||
} else if (firstDayOfWeek === 1) {
|
||||
thursday.setDate(date.getDate() + 3);
|
||||
} else {
|
||||
let daysToThursday = (4 - firstDayOfWeek + 7) % 7;
|
||||
thursday.setDate(date.getDate() + daysToThursday);
|
||||
}
|
||||
|
||||
weeks.push(root.getISOWeekNumber(thursday));
|
||||
}
|
||||
}
|
||||
return weeks;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: parent.weekNumbers
|
||||
Item {
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 0.7
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 0.9
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
color: Qt.alpha(Color.mPrimary, 0.7)
|
||||
pointSize: Style.fontSizeXXS
|
||||
font.weight: Style.fontWeightMedium
|
||||
text: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: grid
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: {
|
||||
const numWeeks = daysModel ? Math.ceil(daysModel.length / 7) : 5;
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
|
||||
return numWeeks * rowHeight;
|
||||
}
|
||||
columns: 7
|
||||
columnSpacing: Style.marginXXS
|
||||
rowSpacing: Style.marginXXS
|
||||
|
||||
property int month: now.getMonth()
|
||||
property int year: now.getFullYear()
|
||||
|
||||
Behavior on Layout.preferredHeight {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate days to display
|
||||
property var daysModel: {
|
||||
const firstOfMonth = new Date(year, month, 1);
|
||||
const lastOfMonth = new Date(year, month + 1, 0);
|
||||
const daysInMonth = lastOfMonth.getDate();
|
||||
|
||||
// Get first day of week (0 = Sunday, 1 = Monday, etc.)
|
||||
const firstDayOfWeek = content.firstDayOfWeek;
|
||||
const firstOfMonthDayOfWeek = firstOfMonth.getDay();
|
||||
|
||||
// Calculate days before first of month
|
||||
let daysBefore = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7;
|
||||
|
||||
// Calculate days after last of month to complete the week
|
||||
const lastOfMonthDayOfWeek = lastOfMonth.getDay();
|
||||
const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7;
|
||||
|
||||
// Build array of day objects
|
||||
const days = [];
|
||||
const today = new Date();
|
||||
|
||||
// Previous month days
|
||||
const prevMonth = new Date(year, month, 0);
|
||||
const prevMonthDays = prevMonth.getDate();
|
||||
for (var i = daysBefore - 1; i >= 0; i--) {
|
||||
const day = prevMonthDays - i;
|
||||
const date = new Date(year, month - 1, day);
|
||||
days.push({
|
||||
"day": day,
|
||||
"month": month - 1,
|
||||
"year": month === 0 ? year - 1 : year,
|
||||
"today": false,
|
||||
"currentMonth": false
|
||||
});
|
||||
}
|
||||
|
||||
// Current month days
|
||||
for (var day = 1; day <= daysInMonth; day++) {
|
||||
const date = new Date(year, month, day);
|
||||
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
|
||||
days.push({
|
||||
"day": day,
|
||||
"month": month,
|
||||
"year": year,
|
||||
"today": isToday,
|
||||
"currentMonth": true
|
||||
});
|
||||
}
|
||||
|
||||
// Next month days (only if needed to complete the week)
|
||||
for (var i = 1; i <= daysAfter; i++) {
|
||||
days.push({
|
||||
"day": i,
|
||||
"month": month + 1,
|
||||
"year": month === 11 ? year + 1 : year,
|
||||
"today": false,
|
||||
"currentMonth": false
|
||||
});
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: grid.daysModel
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 0.9
|
||||
|
||||
Rectangle {
|
||||
width: Style.baseWidgetSize * 0.9
|
||||
height: Style.baseWidgetSize * 0.9
|
||||
anchors.centerIn: parent
|
||||
radius: Style.radiusM
|
||||
color: modelData.today ? Color.mSecondary : Color.transparent
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.day
|
||||
color: {
|
||||
if (modelData.today)
|
||||
return Color.mOnSecondary;
|
||||
if (modelData.currentMonth)
|
||||
return Color.mOnSurface;
|
||||
return Color.mOnSurfaceVariant;
|
||||
}
|
||||
opacity: modelData.currentMonth ? 1.0 : 0.4
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: modelData.today ? Style.fontWeightBold : Style.fontWeightMedium
|
||||
}
|
||||
|
||||
// Event indicator dots
|
||||
Row {
|
||||
visible: Settings.data.location.showCalendarEvents && parent.parent.parent.parent.hasEventsOnDate(modelData.year, modelData.month, modelData.day)
|
||||
spacing: 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Style.marginXS
|
||||
|
||||
Repeater {
|
||||
model: parent.parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day)
|
||||
|
||||
Rectangle {
|
||||
width: 4
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: parent.parent.parent.parent.parent.getEventColor(modelData, modelData.today)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: Settings.data.location.showCalendarEvents
|
||||
|
||||
onEntered: {
|
||||
const events = parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day);
|
||||
if (events.length > 0) {
|
||||
const summaries = events.map(event => {
|
||||
if (isAllDayEvent(event)) {
|
||||
return event.summary;
|
||||
} else {
|
||||
// Always format with '0' padding to ensure proper horizontal alignment
|
||||
const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm";
|
||||
const start = new Date(event.start * 1000);
|
||||
const startFormatted = I18n.locale.toString(start, timeFormat);
|
||||
const end = new Date(event.end * 1000);
|
||||
const endFormatted = I18n.locale.toString(end, timeFormat);
|
||||
return `${startFormatted}-${endFormatted} ${event.summary}`;
|
||||
}
|
||||
}).join('\n');
|
||||
TooltipService.show(parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`;
|
||||
if (ProgramCheckerService.gnomeCalendarAvailable) {
|
||||
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes]);
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: weatherLoader
|
||||
active: Settings.data.location.weatherEnabled && Settings.data.location.showCalendarWeather
|
||||
visible: active
|
||||
Layout.fillWidth: true
|
||||
|
||||
sourceComponent: WeatherCard {
|
||||
Layout.fillWidth: true
|
||||
forecastDays: 5
|
||||
showLocation: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
87
Modules/Panels/Clock/ClockPanel.qml
Normal file
@@ -0,0 +1,87 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Modules.Cards
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.Location
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
SmartPanel {
|
||||
id: root
|
||||
|
||||
// Calculate width based on settings
|
||||
preferredWidth: Math.round((Settings.data.location.showWeekNumberInCalendar ? 460 : 440) * Style.uiScaleRatio)
|
||||
|
||||
panelContent: Item {
|
||||
anchors.fill: parent
|
||||
|
||||
// SmartPanel uses this to calculate panel height dynamically
|
||||
readonly property real contentPreferredHeight: content.implicitHeight + (Style.marginL * 2)
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
x: Style.marginL
|
||||
y: Style.marginL
|
||||
width: parent.width - (Style.marginL * 2)
|
||||
spacing: Style.marginL
|
||||
|
||||
// All clock panel cards
|
||||
Repeater {
|
||||
model: Settings.data.calendar.cards
|
||||
Loader {
|
||||
active: modelData.enabled && (modelData.id !== "weather-card" || Settings.data.location.weatherEnabled)
|
||||
visible: active
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: {
|
||||
switch (modelData.id) {
|
||||
case "calendar-header-card":
|
||||
return calendarHeaderCard;
|
||||
case "calendar-month-card":
|
||||
return calendarMonthCard;
|
||||
case "timer-card":
|
||||
return timerCard;
|
||||
case "weather-card":
|
||||
return weatherCard;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: calendarHeaderCard
|
||||
CalendarHeaderCard {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: calendarMonthCard
|
||||
CalendarMonthCard {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: timerCard
|
||||
TimerCard {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: weatherCard
|
||||
WeatherCard {
|
||||
Layout.fillWidth: true
|
||||
forecastDays: 5
|
||||
showLocation: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Cards
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Modules.Panels.ControlCenter.Cards
|
||||
import qs.Services.Media
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
@@ -102,7 +102,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
imagePath: imageDataUrl
|
||||
visible: isImageContent && !loadingFullContent && imageDataUrl !== ""
|
||||
imageRadius: Style.radiusS
|
||||
radius: Style.radiusS
|
||||
imageFillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
|
||||
@@ -11,13 +11,30 @@ Item {
|
||||
property var launcher: null
|
||||
property bool handleSearch: false
|
||||
|
||||
property string selectedCategory: "recent"
|
||||
property bool isBrowsingMode: false
|
||||
|
||||
property var categoryIcons: ({
|
||||
"recent": "clock",
|
||||
"people": "user",
|
||||
"animals": "paw",
|
||||
"nature": "leaf",
|
||||
"food": "apple",
|
||||
"activity": "run",
|
||||
"travel": "plane",
|
||||
"objects": "home",
|
||||
"symbols": "star",
|
||||
"flags": "flag"
|
||||
})
|
||||
|
||||
property var categories: ["recent", "people", "animals", "nature", "food", "activity", "travel", "objects", "symbols", "flags"]
|
||||
|
||||
// Force update results when emoji service loads
|
||||
Connections {
|
||||
target: EmojiService
|
||||
function onLoadedChanged() {
|
||||
if (EmojiService.loaded && root.launcher) {
|
||||
// Update launcher results to refresh the UI
|
||||
root.launcher?.updateResults();
|
||||
root.launcher.updateResults();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +44,18 @@ Item {
|
||||
Logger.i("EmojiPlugin", "Initialized");
|
||||
}
|
||||
|
||||
function selectCategory(category) {
|
||||
selectedCategory = category;
|
||||
if (launcher) {
|
||||
launcher.updateResults();
|
||||
}
|
||||
}
|
||||
|
||||
function onOpened() {
|
||||
// Always reset to "recent" category when opening
|
||||
selectedCategory = "recent";
|
||||
}
|
||||
|
||||
// Check if this plugin handles the command
|
||||
function handleCommand(searchText) {
|
||||
return searchText.startsWith(">emoji");
|
||||
@@ -65,15 +94,23 @@ Item {
|
||||
];
|
||||
}
|
||||
|
||||
const query = searchText.slice(6).trim();
|
||||
const emojis = EmojiService.search(query);
|
||||
return emojis.map(formatEmojiEntry);
|
||||
var query = searchText.slice(6).trim();
|
||||
|
||||
if (query === "") {
|
||||
isBrowsingMode = true;
|
||||
var emojis = EmojiService.getEmojisByCategory(selectedCategory);
|
||||
return emojis.map(formatEmojiEntry);
|
||||
} else {
|
||||
isBrowsingMode = false;
|
||||
var emojis = EmojiService.search(query);
|
||||
return emojis.map(formatEmojiEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// Format an emoji entry for the results list
|
||||
function formatEmojiEntry(emoji) {
|
||||
let title = emoji.name;
|
||||
let description = (emoji.keywords || []).join(", ");
|
||||
let description = emoji.keywords.join(", ");
|
||||
|
||||
if (emoji.category) {
|
||||
description += " • Category: " + emoji.category;
|
||||
|
||||
@@ -13,6 +13,71 @@ import qs.Widgets
|
||||
SmartPanel {
|
||||
id: root
|
||||
|
||||
// 0 = All, 1 = Today, 2 = Yesterday, 3 = Earlier
|
||||
property int currentRange: 1 // start on Today by default
|
||||
property var rangeCounts: [0, 0, 0, 0]
|
||||
property bool groupByDate: true
|
||||
|
||||
function dateOnly(d) {
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
||||
}
|
||||
|
||||
function rangeForTimestamp(ts) {
|
||||
var dt = new Date(ts);
|
||||
var today = dateOnly(new Date());
|
||||
var thatDay = dateOnly(dt);
|
||||
|
||||
var diffMs = today - thatDay;
|
||||
var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 0)
|
||||
return 0;
|
||||
if (diffDays === 1)
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
function isInCurrentRange(ts) {
|
||||
if (currentRange === 0)
|
||||
return true;
|
||||
return rangeForTimestamp(ts) === (currentRange - 1);
|
||||
}
|
||||
|
||||
function recalcRangeCounts() {
|
||||
var m = NotificationService.historyList;
|
||||
if (!m || typeof m.count === "undefined" || m.count <= 0) {
|
||||
rangeCounts = [0, 0, 0, 0];
|
||||
return;
|
||||
}
|
||||
|
||||
var counts = [0, 0, 0, 0];
|
||||
|
||||
counts[0] = m.count;
|
||||
|
||||
for (var i = 0; i < m.count; ++i) {
|
||||
var item = m.get(i);
|
||||
if (!item || typeof item.timestamp === "undefined")
|
||||
continue;
|
||||
var r = rangeForTimestamp(item.timestamp);
|
||||
counts[r + 1] = counts[r + 1] + 1;
|
||||
}
|
||||
|
||||
rangeCounts = counts;
|
||||
}
|
||||
|
||||
function countForRange(range) {
|
||||
return rangeCounts[range] || 0;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NotificationService.historyList
|
||||
function onCountChanged() {
|
||||
recalcRangeCounts();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: recalcRangeCounts()
|
||||
|
||||
preferredWidth: Math.round(420 * Style.uiScaleRatio)
|
||||
preferredHeight: Math.round(540 * Style.uiScaleRatio)
|
||||
|
||||
@@ -82,6 +147,60 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
// Time range tabs ([All] / [Today] / [Yesterday] / [Earlier])
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginS
|
||||
implicitHeight: timeTabs.implicitHeight + (Style.marginS * 2)
|
||||
visible: NotificationService.historyList.count > 0 && root.groupByDate
|
||||
|
||||
RowLayout {
|
||||
id: timeTabs
|
||||
spacing: Style.marginXS
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
visible: NotificationService.historyList.count > 0
|
||||
|
||||
Repeater {
|
||||
model: 4
|
||||
|
||||
delegate: NButton {
|
||||
readonly property int rangeId: index
|
||||
readonly property bool isActive: root.currentRange === rangeId
|
||||
|
||||
text: {
|
||||
if (rangeId === 0)
|
||||
return I18n.tr("notifications.range.all") + " (" + root.countForRange(rangeId) + ")";
|
||||
else if (rangeId === 1)
|
||||
return I18n.tr("notifications.range.today") + " (" + root.countForRange(rangeId) + ")";
|
||||
else if (rangeId === 2)
|
||||
return I18n.tr("notifications.range.yesterday") + " (" + root.countForRange(rangeId) + ")";
|
||||
return I18n.tr("notifications.range.earlier") + " (" + root.countForRange(rangeId) + ")";
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1
|
||||
implicitHeight: Style.baseWidgetSize * 0.7
|
||||
fontSize: Style.fontSizeXS
|
||||
outlined: false
|
||||
|
||||
backgroundColor: isActive ? Color.mPrimary : (hovered ? Color.mHover : Color.transparent)
|
||||
textColor: isActive ? Color.mOnPrimary : (hovered ? Color.mOnHover : Color.mOnSurface)
|
||||
hoverColor: backgroundColor
|
||||
|
||||
Behavior on backgroundColor {
|
||||
enabled: !Settings.data.general.animationDisabled
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: root.currentRange = rangeId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state when no notifications
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
@@ -148,7 +267,8 @@ SmartPanel {
|
||||
delegate: Item {
|
||||
id: notificationDelegate
|
||||
width: parent.width
|
||||
height: contentColumn.height + (Style.marginM * 2)
|
||||
visible: root.isInCurrentRange(model.timestamp)
|
||||
height: visible ? contentColumn.height + (Style.marginM * 2) : 0
|
||||
|
||||
property string notificationId: model.id
|
||||
property bool isExpanded: scrollView.expandedId === notificationId
|
||||
@@ -197,10 +317,11 @@ SmartPanel {
|
||||
spacing: Style.marginM
|
||||
|
||||
// Icon
|
||||
NImageCircled {
|
||||
NImageRounded {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Math.round(40 * Style.uiScaleRatio)
|
||||
height: Math.round(40 * Style.uiScaleRatio)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: width * 0.5
|
||||
imagePath: model.cachedImage || model.originalImage || ""
|
||||
borderColor: Color.transparent
|
||||
borderWidth: 0
|
||||
|
||||
@@ -153,9 +153,15 @@ Popup {
|
||||
function loadWidgetSettings() {
|
||||
const source = BarWidgetRegistry.widgetSettingsMap[widgetId];
|
||||
if (source) {
|
||||
// Use setSource to pass properties at creation time
|
||||
var currentWidgetData = widgetData;
|
||||
if (sectionId && widgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[sectionId];
|
||||
if (widgets && widgetIndex < widgets.length) {
|
||||
currentWidgetData = widgets[widgetIndex];
|
||||
}
|
||||
}
|
||||
settingsLoader.setSource(source, {
|
||||
"widgetData": widgetData,
|
||||
"widgetData": currentWidgetData,
|
||||
"widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
@@ -15,14 +16,109 @@ ColumnLayout {
|
||||
// Local state
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
property int valueWarningThreshold: widgetData.warningThreshold !== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold
|
||||
property string valueDeviceNativePath: widgetData.deviceNativePath !== undefined ? widgetData.deviceNativePath : ""
|
||||
|
||||
// Build model of available battery devices
|
||||
function buildDeviceModel() {
|
||||
var model = [
|
||||
{
|
||||
"key": "",
|
||||
"name": I18n.tr("bar.widget-settings.battery.device.default")
|
||||
}
|
||||
];
|
||||
|
||||
if (!UPower.devices) {
|
||||
return model;
|
||||
}
|
||||
|
||||
var deviceArray = UPower.devices.values || [];
|
||||
for (var i = 0; i < deviceArray.length; i++) {
|
||||
var device = deviceArray[i];
|
||||
if (!device || device.type === UPowerDeviceType.LinePower) {
|
||||
continue;
|
||||
}
|
||||
var displayName = device.model || device.nativePath || "Unknown";
|
||||
model.push({
|
||||
"key": device.nativePath || "",
|
||||
"name": displayName
|
||||
});
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
readonly property int _deviceCount: (UPower.devices && UPower.devices.values) ? UPower.devices.values.length : 0
|
||||
property var deviceModel: buildDeviceModel()
|
||||
|
||||
on_DeviceCountChanged: {
|
||||
deviceModel = buildDeviceModel();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: UPower.devices
|
||||
function onValuesChanged() {
|
||||
deviceModel = buildDeviceModel();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: refreshTimer
|
||||
interval: 2000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
var currentCount = (UPower.devices && UPower.devices.values) ? UPower.devices.values.length : 0;
|
||||
if (currentCount !== root._deviceCount) {
|
||||
deviceModel = buildDeviceModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
if (widgetData && widgetData.id) {
|
||||
settings.id = widgetData.id;
|
||||
}
|
||||
settings.displayMode = valueDisplayMode;
|
||||
settings.warningThreshold = valueWarningThreshold;
|
||||
if (valueDeviceNativePath && valueDeviceNativePath !== "") {
|
||||
settings.deviceNativePath = valueDeviceNativePath;
|
||||
} else {
|
||||
delete settings.deviceNativePath;
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NComboBox {
|
||||
id: deviceComboBox
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.widget-settings.battery.device.label")
|
||||
description: I18n.tr("bar.widget-settings.battery.device.description")
|
||||
minimumWidth: 134
|
||||
model: root.deviceModel
|
||||
currentKey: root.valueDeviceNativePath
|
||||
onSelected: key => root.valueDeviceNativePath = key
|
||||
}
|
||||
|
||||
// Update currentKey when model changes to ensure selection is preserved
|
||||
Connections {
|
||||
target: root
|
||||
function onDeviceModelChanged() {
|
||||
// Force update of currentKey to trigger selection update
|
||||
deviceComboBox.currentKey = root.valueDeviceNativePath;
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: "Refresh device list"
|
||||
onClicked: deviceModel = buildDeviceModel()
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("bar.widget-settings.battery.display-mode.label")
|
||||
description: I18n.tr("bar.widget-settings.battery.display-mode.description")
|
||||
|
||||
@@ -17,14 +17,16 @@ ColumnLayout {
|
||||
property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon
|
||||
property bool valueUseDistroLogo: widgetData.useDistroLogo !== undefined ? widgetData.useDistroLogo : widgetMetadata.useDistroLogo
|
||||
property string valueCustomIconPath: widgetData.customIconPath !== undefined ? widgetData.customIconPath : ""
|
||||
property bool valueColorizeDistroLogo: widgetData.colorizeDistroLogo !== undefined ? widgetData.colorizeDistroLogo : (widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false)
|
||||
property bool valueEnableColorization: widgetData.enableColorization || false
|
||||
property string valueColorizeSystemIcon: widgetData.colorizeSystemIcon !== undefined ? widgetData.colorizeSystemIcon : (widgetMetadata.colorizeSystemIcon !== undefined ? widgetMetadata.colorizeSystemIcon : "none")
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.icon = valueIcon;
|
||||
settings.useDistroLogo = valueUseDistroLogo;
|
||||
settings.customIconPath = valueCustomIconPath;
|
||||
settings.colorizeDistroLogo = valueColorizeDistroLogo;
|
||||
settings.enableColorization = valueEnableColorization;
|
||||
settings.colorizeSystemIcon = valueColorizeSystemIcon;
|
||||
return settings;
|
||||
}
|
||||
|
||||
@@ -34,20 +36,47 @@ ColumnLayout {
|
||||
checked: valueUseDistroLogo
|
||||
onToggled: function (checked) {
|
||||
valueUseDistroLogo = checked;
|
||||
if (checked) {
|
||||
valueCustomIconPath = "";
|
||||
valueIcon = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
visible: valueUseDistroLogo
|
||||
label: I18n.tr("bar.widget-settings.control-center.colorize-distro-logo.label")
|
||||
description: I18n.tr("bar.widget-settings.control-center.colorize-distro-logo.description")
|
||||
checked: valueColorizeDistroLogo
|
||||
label: I18n.tr("bar.widget-settings.control-center.enable-colorization.label")
|
||||
description: I18n.tr("bar.widget-settings.control-center.enable-colorization.description")
|
||||
checked: valueEnableColorization
|
||||
onToggled: function (checked) {
|
||||
valueColorizeDistroLogo = checked;
|
||||
valueEnableColorization = checked;
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
visible: valueEnableColorization
|
||||
label: I18n.tr("bar.widget-settings.control-center.color-selection.label")
|
||||
description: I18n.tr("bar.widget-settings.control-center.color-selection.description")
|
||||
model: [
|
||||
{
|
||||
"name": I18n.tr("options.colors.none"),
|
||||
"key": "none"
|
||||
},
|
||||
{
|
||||
"name": I18n.tr("options.colors.primary"),
|
||||
"key": "primary"
|
||||
},
|
||||
{
|
||||
"name": I18n.tr("options.colors.secondary"),
|
||||
"key": "secondary"
|
||||
},
|
||||
{
|
||||
"name": I18n.tr("options.colors.tertiary"),
|
||||
"key": "tertiary"
|
||||
},
|
||||
{
|
||||
"name": I18n.tr("options.colors.error"),
|
||||
"key": "error"
|
||||
}
|
||||
]
|
||||
currentKey: valueColorizeSystemIcon
|
||||
onSelected: function (key) {
|
||||
valueColorizeSystemIcon = key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,19 +88,20 @@ ColumnLayout {
|
||||
description: I18n.tr("bar.widget-settings.control-center.icon.description")
|
||||
}
|
||||
|
||||
NImageCircled {
|
||||
NImageRounded {
|
||||
Layout.preferredWidth: Style.fontSizeXL * 2
|
||||
Layout.preferredHeight: Style.fontSizeXL * 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
radius: width * 0.5
|
||||
imagePath: valueCustomIconPath
|
||||
visible: valueCustomIconPath !== ""
|
||||
visible: valueCustomIconPath !== "" && !valueUseDistroLogo
|
||||
}
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: valueIcon
|
||||
pointSize: Style.fontSizeXXL * 1.5
|
||||
visible: valueIcon !== "" && valueCustomIconPath === ""
|
||||
visible: valueIcon !== "" && valueCustomIconPath === "" && !valueUseDistroLogo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
@@ -14,9 +15,136 @@ ColumnLayout {
|
||||
property string latestVersion: GitHubService.latestVersion
|
||||
property string currentVersion: UpdateService.currentVersion
|
||||
property var contributors: GitHubService.contributors
|
||||
property string commitInfo: ""
|
||||
|
||||
readonly property int topContributorsCount: 20
|
||||
readonly property bool isGitVersion: root.currentVersion.endsWith("-git")
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("AboutTab", "Component.onCompleted - Current version:", root.currentVersion);
|
||||
Logger.d("AboutTab", "Component.onCompleted - Is git version:", root.isGitVersion);
|
||||
// Only fetch commit info for -git versions
|
||||
if (root.isGitVersion) {
|
||||
// On NixOS, extract commit hash from the store path
|
||||
if (HostService.isNixOS) {
|
||||
var shellDir = Quickshell.shellDir || "";
|
||||
Logger.d("AboutTab", "Component.onCompleted - NixOS detected, shellDir:", shellDir);
|
||||
if (shellDir) {
|
||||
// Extract commit hash from path like: /nix/store/...-noctalia-shell-2025-11-30_225e6d3/share/noctalia-shell
|
||||
// Pattern matches: noctalia-shell-YYYY-MM-DD_<commit_hash>
|
||||
var match = shellDir.match(/noctalia-shell-\d{4}-\d{2}-\d{2}_([0-9a-f]{7,})/i);
|
||||
if (match && match[1]) {
|
||||
// Use first 7 characters of the commit hash
|
||||
root.commitInfo = match[1].substring(0, 7);
|
||||
Logger.d("AboutTab", "Component.onCompleted - Extracted commit from NixOS path:", root.commitInfo);
|
||||
return;
|
||||
} else {
|
||||
Logger.d("AboutTab", "Component.onCompleted - Could not extract commit from NixOS path, trying fallback");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Try to get Arch package version first (which includes commit hash)
|
||||
pacmanProcess.running = true;
|
||||
// Start fallback timer in case pacman fails to start
|
||||
gitFallbackTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: gitFallbackTimer
|
||||
interval: 500
|
||||
running: false
|
||||
onTriggered: {
|
||||
if (!root.commitInfo) {
|
||||
fetchGitCommit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: pacmanProcess
|
||||
command: ["pacman", "-Q", "noctalia-shell-git"]
|
||||
running: false
|
||||
|
||||
onStarted: {
|
||||
gitFallbackTimer.stop();
|
||||
}
|
||||
|
||||
onExited: function (exitCode) {
|
||||
gitFallbackTimer.stop();
|
||||
Logger.d("AboutTab", "pacmanProcess - Process exited with code:", exitCode);
|
||||
if (exitCode === 0) {
|
||||
var output = stdout.text.trim();
|
||||
Logger.d("AboutTab", "pacmanProcess - Output:", output);
|
||||
var match = output.match(/noctalia-shell-git\s+(.+)/);
|
||||
if (match && match[1]) {
|
||||
// For Arch packages, the version format might be like: 3.4.0.r112.g3f00bec8-1
|
||||
// Extract just the commit hash part if it exists
|
||||
var version = match[1];
|
||||
var commitMatch = version.match(/\.g([0-9a-f]{7,})/i);
|
||||
if (commitMatch && commitMatch[1]) {
|
||||
// Show short hash (first 7 characters)
|
||||
root.commitInfo = commitMatch[1].substring(0, 7);
|
||||
Logger.d("AboutTab", "pacmanProcess - Set commitInfo from Arch package:", root.commitInfo);
|
||||
return; // Successfully got commit hash from Arch package
|
||||
} else {
|
||||
// If no commit hash in version format, still try git repo
|
||||
Logger.d("AboutTab", "pacmanProcess - No commit hash in version, trying git");
|
||||
fetchGitCommit();
|
||||
}
|
||||
} else {
|
||||
// Unexpected output format, try git
|
||||
Logger.d("AboutTab", "pacmanProcess - Unexpected output format, trying git");
|
||||
fetchGitCommit();
|
||||
}
|
||||
} else {
|
||||
// If not on Arch, try to get git commit from repository
|
||||
Logger.d("AboutTab", "pacmanProcess - Package not found, trying git");
|
||||
fetchGitCommit();
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
function fetchGitCommit() {
|
||||
var shellDir = Quickshell.shellDir || "";
|
||||
Logger.d("AboutTab", "fetchGitCommit - shellDir:", shellDir);
|
||||
if (!shellDir) {
|
||||
Logger.d("AboutTab", "fetchGitCommit - Cannot determine shell directory, skipping git commit fetch");
|
||||
return;
|
||||
}
|
||||
|
||||
gitProcess.workingDirectory = shellDir;
|
||||
gitProcess.running = true;
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gitProcess
|
||||
command: ["git", "rev-parse", "--short", "HEAD"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
Logger.d("AboutTab", "gitProcess - Process exited with code:", exitCode);
|
||||
if (exitCode === 0) {
|
||||
var gitOutput = stdout.text.trim();
|
||||
Logger.d("AboutTab", "gitProcess - gitOutput:", gitOutput);
|
||||
if (gitOutput) {
|
||||
root.commitInfo = gitOutput;
|
||||
Logger.d("AboutTab", "gitProcess - Set commitInfo to:", root.commitInfo);
|
||||
}
|
||||
} else {
|
||||
Logger.d("AboutTab", "gitProcess - Git command failed. Exit code:", exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.about.noctalia.section.label")
|
||||
description: I18n.tr("settings.about.noctalia.section.description")
|
||||
@@ -52,6 +180,21 @@ ColumnLayout {
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: root.isGitVersion
|
||||
text: I18n.tr("settings.about.noctalia.git-commit")
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: root.isGitVersion
|
||||
text: root.commitInfo || I18n.tr("settings.about.noctalia.git-commit-loading")
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
font.family: root.commitInfo ? "monospace" : ""
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
}
|
||||
|
||||
// Update button
|
||||
@@ -149,92 +292,222 @@ ColumnLayout {
|
||||
enableDescriptionRichText: true
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: contributorsGrid
|
||||
|
||||
readonly property int columnsCount: 2
|
||||
|
||||
// Top 20 contributors with full cards (avoids GridView shader crashes on Qt 6.8)
|
||||
Flow {
|
||||
id: topContributorsFlow
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: cellWidth * columnsCount
|
||||
Layout.preferredHeight: {
|
||||
if (root.contributors.length === 0)
|
||||
return 0;
|
||||
Layout.preferredWidth: Math.round(Style.baseWidgetSize * 14)
|
||||
spacing: Style.marginM
|
||||
|
||||
const rows = Math.ceil(root.contributors.length / columnsCount);
|
||||
return rows * cellHeight;
|
||||
}
|
||||
cellWidth: Math.round(Style.baseWidgetSize * 7)
|
||||
cellHeight: Math.round(Style.baseWidgetSize * 2.5)
|
||||
model: root.contributors
|
||||
Repeater {
|
||||
model: Math.min(root.contributors.length, root.topContributorsCount)
|
||||
|
||||
delegate: Rectangle {
|
||||
width: contributorsGrid.cellWidth - Style.marginM
|
||||
height: contributorsGrid.cellHeight - Style.marginM
|
||||
radius: Style.radiusL
|
||||
color: contributorArea.containsMouse ? Color.mHover : Color.transparent
|
||||
delegate: Rectangle {
|
||||
width: Math.round(Style.baseWidgetSize * 6.8)
|
||||
height: Math.round(Style.baseWidgetSize * 2.3)
|
||||
radius: Style.radiusM
|
||||
color: contributorArea.containsMouse ? Color.mHover : Color.transparent
|
||||
border.width: 1
|
||||
border.color: contributorArea.containsMouse ? Color.mPrimary : Color.mOutline
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - (Style.marginS * 2)
|
||||
spacing: Style.marginM
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 2 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 2 * Style.uiScaleRatio
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
NImageCircled {
|
||||
imagePath: modelData.avatar_url || ""
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXS
|
||||
fallbackIcon: "person"
|
||||
borderColor: contributorArea.containsMouse ? Color.mOnHover : Color.mPrimary
|
||||
borderWidth: Style.borderM
|
||||
// Avatar container with rectangular design (modern, no shader issues)
|
||||
Item {
|
||||
id: wrapper
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 1.8
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 1.8
|
||||
|
||||
Behavior on borderColor {
|
||||
ColorAnimation {
|
||||
property bool isRounded: false
|
||||
|
||||
// Background and image container
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
// Simple circular image (pre-rendered, no shaders)
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: {
|
||||
// Try cached circular version first
|
||||
var username = root.contributors[index].login;
|
||||
var cached = GitHubService.cachedCircularAvatars[username];
|
||||
if (cached) {
|
||||
wrapper.isRounded = true;
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Fall back to original avatar URL
|
||||
return root.contributors[index].avatar_url || "";
|
||||
}
|
||||
fillMode: Image.PreserveAspectFit // Fit since image is already circular with transparency
|
||||
mipmap: true
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: root.contributors[index].avatar_url !== undefined && root.contributors[index].avatar_url !== ""
|
||||
opacity: status === Image.Ready ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback icon
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: !root.contributors[index].avatar_url || root.contributors[index].avatar_url === ""
|
||||
icon: "person"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: wrapper.isRounded
|
||||
anchors.fill: parent
|
||||
color: Color.transparent
|
||||
radius: width * 0.5
|
||||
border.width: Style.borderM
|
||||
border.color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
// Info column
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: root.contributors[index].login || "Unknown"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NIcon {
|
||||
icon: "git-commit"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${(root.contributors[index].contributions || 0).toString()} commits`
|
||||
pointSize: Style.fontSizeXS
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hover indicator
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: "arrow-right"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mPrimary
|
||||
opacity: contributorArea.containsMouse ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: modelData.login || "Unknown"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: (modelData.contributions || 0) + " " + ((modelData.contributions || 0) === 1 ? "commit" : "commits")
|
||||
pointSize: Style.fontSizeXS
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
MouseArea {
|
||||
id: contributorArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.contributors[index].html_url)
|
||||
Quickshell.execDetached(["xdg-open", root.contributors[index].html_url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: contributorArea
|
||||
// Remaining contributors (simple text links)
|
||||
Flow {
|
||||
id: remainingContributorsFlow
|
||||
visible: root.contributors.length > root.topContributorsCount
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: Math.round(Style.baseWidgetSize * 14)
|
||||
Layout.topMargin: Style.marginL
|
||||
spacing: Style.marginS
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData.html_url)
|
||||
Quickshell.execDetached(["xdg-open", modelData.html_url]);
|
||||
Repeater {
|
||||
model: Math.max(0, root.contributors.length - root.topContributorsCount)
|
||||
|
||||
delegate: Rectangle {
|
||||
width: nameText.implicitWidth + Style.marginM * 2
|
||||
height: nameText.implicitHeight + Style.marginS * 2
|
||||
radius: Style.radiusS
|
||||
color: nameArea.containsMouse ? Color.mHover : Color.transparent
|
||||
border.width: Style.borderS
|
||||
border.color: nameArea.containsMouse ? Color.mPrimary : Color.mOutline
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
id: nameText
|
||||
anchors.centerIn: parent
|
||||
text: root.contributors[index + root.topContributorsCount].login || "Unknown"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: nameArea.containsMouse ? Color.mOnHover : Color.mOnSurface
|
||||
font.weight: Style.fontWeightMedium
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nameArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.contributors[index + root.topContributorsCount].html_url)
|
||||
Quickshell.execDetached(["xdg-open", root.contributors[index + root.topContributorsCount].html_url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,17 @@ ColumnLayout {
|
||||
|
||||
property real localVolume: AudioService.volume
|
||||
|
||||
Connections {
|
||||
target: AudioService
|
||||
function onSinkChanged() {
|
||||
// Immediately update local volume when device changes to prevent old value from being applied
|
||||
localVolume = AudioService.volume;
|
||||
}
|
||||
function onVolumeChanged() {
|
||||
localVolume = AudioService.volume;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
function onVolumeChanged() {
|
||||
@@ -42,7 +53,8 @@ ColumnLayout {
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (Math.abs(localVolume - AudioService.volume) >= 0.01) {
|
||||
// Don't set volume if device is switching - wait for new device's volume to be read
|
||||
if (!AudioService.isSwitchingSink && Math.abs(localVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localVolume);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ ColumnLayout {
|
||||
schemeName = "Noctalia (legacy)";
|
||||
} else if (schemeName === "Tokyo-Night") {
|
||||
schemeName = "Tokyo Night";
|
||||
} else if (schemeName === "Rosepine") {
|
||||
schemeName = "Rose Pine";
|
||||
}
|
||||
|
||||
return schemeName;
|
||||
@@ -332,22 +334,17 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: !Settings.data.colorSchemes.useWallpaperColors
|
||||
|
||||
RowLayout {
|
||||
NHeader {
|
||||
label: I18n.tr("settings.color-scheme.predefined.section.label")
|
||||
description: I18n.tr("settings.color-scheme.predefined.section.description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.color-scheme.predefined.section.label")
|
||||
description: I18n.tr("settings.color-scheme.predefined.section.description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("settings.color-scheme.download.button")
|
||||
icon: "download"
|
||||
onClicked: {
|
||||
root.openDownloadPopup();
|
||||
}
|
||||
}
|
||||
NButton {
|
||||
text: I18n.tr("settings.color-scheme.download.button")
|
||||
icon: "download"
|
||||
onClicked: root.openDownloadPopup()
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
// Download popup
|
||||
@@ -579,6 +576,32 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
// Compositors
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.compositors.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.compositors.description")
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: "Niri"
|
||||
description: ProgramCheckerService.niriAvailable ? I18n.tr("settings.color-scheme.templates.compositors.niri.description", {
|
||||
"filepath": "~/.config/niri/noctalia.kdl"
|
||||
}) : I18n.tr("settings.color-scheme.templates.compositors.niri.description-missing", {
|
||||
"app": "niri"
|
||||
})
|
||||
checked: Settings.data.templates.niri
|
||||
enabled: ProgramCheckerService.niriAvailable
|
||||
opacity: ProgramCheckerService.niriAvailable ? 1.0 : 0.6
|
||||
onToggled: checked => {
|
||||
if (ProgramCheckerService.niriAvailable) {
|
||||
Settings.data.templates.niri = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Terminal Emulators
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
@@ -853,14 +876,14 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NCheckbox {
|
||||
label: "Cava"
|
||||
description: ProgramCheckerService.cavaAvailable ? I18n.tr("settings.color-scheme.templates.programs.cava.description", {
|
||||
"filepath": "~/.config/cava/themes/noctalia"
|
||||
}) : I18n.tr("settings.color-scheme.templates.programs.cava.description-missing", {
|
||||
"app": "cava"
|
||||
})
|
||||
"filepath": "~/.config/cava/themes/noctalia"
|
||||
}) : I18n.tr("settings.color-scheme.templates.programs.cava.description-missing", {
|
||||
"app": "cava"
|
||||
})
|
||||
checked: Settings.data.templates.cava
|
||||
enabled: ProgramCheckerService.cavaAvailable
|
||||
opacity: ProgramCheckerService.cavaAvailable ? 1.0 : 0.6
|
||||
@@ -871,7 +894,24 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Emacs"
|
||||
description: ProgramCheckerService.emacsAvailable ? "Doom: ~/.config/doom/themes/noctalia.el\nStandard: ~/.emacs.d/themes/noctalia.el\n\nApply manually: (load-theme 'noctalia)" : I18n.tr("settings.color-scheme.templates.programs.emacs.description-missing", {
|
||||
"app": "emacs"
|
||||
})
|
||||
checked: Settings.data.templates.emacs
|
||||
enabled: ProgramCheckerService.emacsAvailable
|
||||
opacity: ProgramCheckerService.emacsAvailable ? 1.0 : 0.6
|
||||
onToggled: checked => {
|
||||
if (ProgramCheckerService.emacsAvailable) {
|
||||
Settings.data.templates.emacs = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -241,9 +241,14 @@ Popup {
|
||||
// Rate limit hit - try to use cache if available
|
||||
downloadError = I18n.tr("settings.color-scheme.download.error.rate-limit");
|
||||
Logger.w("ColorSchemeDownload", downloadError);
|
||||
if (schemesCacheAdapter.schemes && schemesCacheAdapter.schemes.length > 0) {
|
||||
availableSchemes = schemesCacheAdapter.schemes;
|
||||
Logger.i("ColorSchemeDownload", "Using cached schemes due to rate limit");
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
const cacheData = ShellState.getColorSchemesList();
|
||||
const cachedSchemes = cacheData.schemes || [];
|
||||
if (cachedSchemes.length > 0) {
|
||||
availableSchemes = cachedSchemes;
|
||||
hasInitialData = true;
|
||||
Logger.i("ColorSchemeDownload", "Using cached schemes due to rate limit");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
downloadError = I18n.tr("settings.color-scheme.download.error.api-error", {
|
||||
@@ -740,7 +745,12 @@ Popup {
|
||||
enabled: !fetching && !downloading
|
||||
onClicked: {
|
||||
// Force refresh by clearing cache timestamp and fetching directly from API
|
||||
schemesCacheAdapter.timestamp = 0;
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
ShellState.setColorSchemesList({
|
||||
schemes: [],
|
||||
timestamp: 0
|
||||
});
|
||||
}
|
||||
// Fetch directly from API to avoid cache check delay
|
||||
fetchAvailableSchemesFromAPI();
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ ColumnLayout {
|
||||
sectionName: I18n.tr("settings.control-center.shortcuts.sectionLeft")
|
||||
sectionId: "left"
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
|
||||
maxWidgets: 5
|
||||
maxWidgets: Settings.data.controlCenter.shortcuts["right"].length > 5 ? 0 : (Settings.data.controlCenter.shortcuts["right"].length > 0 ? 5 : 10)
|
||||
widgetRegistry: ControlCenterWidgetRegistry
|
||||
widgetModel: Settings.data.controlCenter.shortcuts["left"]
|
||||
availableWidgets: availableWidgets
|
||||
@@ -266,7 +266,7 @@ ColumnLayout {
|
||||
sectionName: I18n.tr("settings.control-center.shortcuts.sectionRight")
|
||||
sectionId: "right"
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
|
||||
maxWidgets: 5
|
||||
maxWidgets: Settings.data.controlCenter.shortcuts["left"].length > 5 ? 0 : (Settings.data.controlCenter.shortcuts["left"].length > 0 ? 5 : 10)
|
||||
widgetRegistry: ControlCenterWidgetRegistry
|
||||
widgetModel: Settings.data.controlCenter.shortcuts["right"]
|
||||
availableWidgets: availableWidgets
|
||||
|
||||
@@ -232,63 +232,130 @@ ColumnLayout {
|
||||
|
||||
// Temperature
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: Settings.data.nightLight.enabled
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Night temperature
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.temperature.label")
|
||||
description: I18n.tr("settings.display.night-light.temperature.description")
|
||||
label: I18n.tr("settings.display.night-light.temperature.night")
|
||||
description: I18n.tr("settings.display.night-light.temperature.night-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: Settings.data.nightLight.enabled
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: false
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.temperature.night")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
NSlider {
|
||||
id: nightSlider
|
||||
Layout.fillWidth: true
|
||||
|
||||
NTextInput {
|
||||
text: Settings.data.nightLight.nightTemp
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onEditingFinished: {
|
||||
var nightTemp = parseInt(text);
|
||||
from: 1000
|
||||
to: 6500
|
||||
value: Settings.data.nightLight.nightTemp
|
||||
|
||||
// Clamp as the thumb moves, but do NOT change Settings here
|
||||
onValueChanged: {
|
||||
var dayTemp = parseInt(Settings.data.nightLight.dayTemp);
|
||||
if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
|
||||
// Clamp value between [1000 .. (dayTemp-500)]
|
||||
var clampedValue = Math.min(dayTemp - 500, Math.max(1000, nightTemp));
|
||||
text = Settings.data.nightLight.nightTemp = clampedValue.toString();
|
||||
var v = Math.round(value);
|
||||
|
||||
if (!isNaN(dayTemp)) {
|
||||
var maxNight = dayTemp - 500;
|
||||
v = Math.min(maxNight, Math.max(1000, v));
|
||||
} else {
|
||||
v = Math.max(1000, v);
|
||||
}
|
||||
|
||||
if (v !== value)
|
||||
value = v;
|
||||
}
|
||||
|
||||
// Only write back to Settings when the user releases the slider
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
var dayTemp = parseInt(Settings.data.nightLight.dayTemp);
|
||||
var v = Math.round(value);
|
||||
|
||||
if (!isNaN(dayTemp)) {
|
||||
var maxNight = dayTemp - 500;
|
||||
v = Math.min(maxNight, Math.max(1000, v));
|
||||
} else {
|
||||
v = Math.max(1000, v);
|
||||
}
|
||||
|
||||
Settings.data.nightLight.nightTemp = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.temperature.day")
|
||||
text: nightSlider.value + "K"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
NTextInput {
|
||||
text: Settings.data.nightLight.dayTemp
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onEditingFinished: {
|
||||
var dayTemp = parseInt(text);
|
||||
}
|
||||
|
||||
// Day temperature
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.temperature.day")
|
||||
description: I18n.tr("settings.display.night-light.temperature.day-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NSlider {
|
||||
id: daySlider
|
||||
Layout.fillWidth: true
|
||||
|
||||
from: 1000
|
||||
to: 6500
|
||||
value: Settings.data.nightLight.dayTemp
|
||||
|
||||
// Clamp as the thumb moves, but do NOT change Settings here
|
||||
onValueChanged: {
|
||||
var nightTemp = parseInt(Settings.data.nightLight.nightTemp);
|
||||
if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
|
||||
// Clamp value between [(nightTemp+500) .. 6500]
|
||||
var clampedValue = Math.max(nightTemp + 500, Math.min(6500, dayTemp));
|
||||
text = Settings.data.nightLight.dayTemp = clampedValue.toString();
|
||||
var v = Math.round(value);
|
||||
|
||||
if (!isNaN(nightTemp)) {
|
||||
var minDay = nightTemp + 500;
|
||||
v = Math.max(minDay, Math.min(6500, v));
|
||||
} else {
|
||||
v = Math.min(6500, v);
|
||||
}
|
||||
|
||||
if (v !== value)
|
||||
value = v;
|
||||
}
|
||||
|
||||
// Only write back to Settings when the user releases the slider
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
var nightTemp = parseInt(Settings.data.nightLight.nightTemp);
|
||||
var v = Math.round(value);
|
||||
|
||||
if (!isNaN(nightTemp)) {
|
||||
var minDay = nightTemp + 500;
|
||||
v = Math.max(minDay, Math.min(6500, v));
|
||||
} else {
|
||||
v = Math.min(6500, v);
|
||||
}
|
||||
|
||||
Settings.data.nightLight.dayTemp = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: daySlider.value + "K"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,10 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
|
||||
// Avatar preview
|
||||
NImageCircled {
|
||||
NImageRounded {
|
||||
Layout.preferredWidth: 88 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: width
|
||||
radius: width * 0.5
|
||||
imagePath: Settings.preprocessPath(Settings.data.general.avatarImage)
|
||||
fallbackIcon: "person"
|
||||
borderColor: Color.mPrimary
|
||||
@@ -39,7 +40,7 @@ ColumnLayout {
|
||||
text: Settings.data.general.avatarImage
|
||||
placeholderText: I18n.tr("placeholders.profile-picture-path")
|
||||
buttonIcon: "photo"
|
||||
buttonTooltip: "Browse for avatar image"
|
||||
buttonTooltip: I18n.tr("settings.general.profile.tooltip")
|
||||
onInputEditingFinished: Settings.data.general.avatarImage = text
|
||||
onButtonClicked: {
|
||||
avatarPicker.openFilePicker();
|
||||
|
||||
@@ -58,6 +58,13 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.launcher.settings.grid-view.label")
|
||||
description: I18n.tr("settings.launcher.settings.grid-view.description")
|
||||
checked: Settings.data.appLauncher.viewMode === "grid"
|
||||
onToggled: checked => Settings.data.appLauncher.viewMode = checked ? "grid" : "list"
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.launcher.settings.clipboard-history.label")
|
||||
description: I18n.tr("settings.launcher.settings.clipboard-history.description")
|
||||
|
||||
@@ -9,6 +9,89 @@ ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
|
||||
property list<var> cardsModel: []
|
||||
property list<var> cardsDefault: [
|
||||
{
|
||||
"id": "calendar-header-card",
|
||||
"text": I18n.tr("settings.location.calendar.header.label"),
|
||||
"enabled": true,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "calendar-month-card",
|
||||
"text": I18n.tr("settings.location.calendar.month.label"),
|
||||
"enabled": true,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "timer-card",
|
||||
"text": I18n.tr("calendar.timer.title"),
|
||||
"enabled": true,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "weather-card",
|
||||
"text": I18n.tr("settings.location.weather.section.label"),
|
||||
"enabled": true,
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
|
||||
function saveCards() {
|
||||
var toSave = [];
|
||||
for (var i = 0; i < cardsModel.length; i++) {
|
||||
toSave.push({
|
||||
"id": cardsModel[i].id,
|
||||
"enabled": cardsModel[i].enabled
|
||||
});
|
||||
}
|
||||
Settings.data.calendar.cards = toSave;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Starts empty
|
||||
cardsModel = [];
|
||||
|
||||
// Add the cards available in settings
|
||||
for (var i = 0; i < Settings.data.calendar.cards.length; i++) {
|
||||
const settingCard = Settings.data.calendar.cards[i];
|
||||
|
||||
for (var j = 0; j < cardsDefault.length; j++) {
|
||||
if (settingCard.id === cardsDefault[j].id) {
|
||||
var card = cardsDefault[j];
|
||||
card.enabled = settingCard.enabled;
|
||||
// Auto-disable weather card if weather is disabled
|
||||
if (card.id === "weather-card" && !Settings.data.location.weatherEnabled) {
|
||||
card.enabled = false;
|
||||
}
|
||||
cardsModel.push(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add any missing cards from default
|
||||
for (var i = 0; i < cardsDefault.length; i++) {
|
||||
var found = false;
|
||||
for (var j = 0; j < cardsModel.length; j++) {
|
||||
if (cardsModel[j].id === cardsDefault[i].id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
var card = cardsDefault[i];
|
||||
// Auto-disable weather card if weather is disabled
|
||||
if (card.id === "weather-card" && !Settings.data.location.weatherEnabled) {
|
||||
card.enabled = false;
|
||||
}
|
||||
cardsModel.push(card);
|
||||
}
|
||||
}
|
||||
|
||||
saveCards();
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.location.location.section.label")
|
||||
description: I18n.tr("settings.location.location.section.description")
|
||||
@@ -85,14 +168,6 @@ ColumnLayout {
|
||||
enabled: Settings.data.location.weatherEnabled
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.location.weather.show-in-calendar.label")
|
||||
description: I18n.tr("settings.location.weather.show-in-calendar.description")
|
||||
checked: Settings.data.location.showCalendarWeather
|
||||
onToggled: checked => Settings.data.location.showCalendarWeather = checked
|
||||
enabled: Settings.data.location.weatherEnabled
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.location.weather.show-effects.label")
|
||||
description: I18n.tr("settings.location.weather.show-effects.description")
|
||||
@@ -108,6 +183,62 @@ ColumnLayout {
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Calendar Cards Management Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.location.calendar.cards.section.label")
|
||||
description: I18n.tr("settings.location.calendar.cards.section.description")
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Settings.data.location
|
||||
function onWeatherEnabledChanged() {
|
||||
// Auto-disable weather card when weather is disabled
|
||||
var newModel = cardsModel.slice();
|
||||
for (var i = 0; i < newModel.length; i++) {
|
||||
if (newModel[i].id === "weather-card") {
|
||||
newModel[i] = Object.assign({}, newModel[i], {
|
||||
"enabled": Settings.data.location.weatherEnabled
|
||||
});
|
||||
cardsModel = newModel;
|
||||
saveCards();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NReorderCheckboxes {
|
||||
Layout.fillWidth: true
|
||||
model: cardsModel
|
||||
disabledIds: Settings.data.location.weatherEnabled ? [] : ["weather-card"]
|
||||
onItemToggled: function (index, enabled) {
|
||||
var newModel = cardsModel.slice();
|
||||
newModel[index] = Object.assign({}, newModel[index], {
|
||||
"enabled": enabled
|
||||
});
|
||||
cardsModel = newModel;
|
||||
saveCards();
|
||||
}
|
||||
onItemsReordered: function (fromIndex, toIndex) {
|
||||
var newModel = cardsModel.slice();
|
||||
var item = newModel.splice(fromIndex, 1)[0];
|
||||
newModel.splice(toIndex, 0, item);
|
||||
cardsModel = newModel;
|
||||
saveCards();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Date & time section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
@@ -74,6 +74,10 @@ ColumnLayout {
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("options.launcher.position.bottom_right")
|
||||
},
|
||||
{
|
||||
"key": "bar",
|
||||
"name": I18n.tr("options.launcher.position.bar")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.notifications.location || "top_right"
|
||||
|
||||